diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index cadb12163d..543c4cf9bb 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -53,6 +53,7 @@ add_library(common Flag.h FloatUtils.cpp FloatUtils.h + FormatUtil.h FPURoundMode.h GekkoDisassembler.cpp GekkoDisassembler.h diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index c45edb1b59..07e040b626 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -58,6 +58,7 @@ + @@ -283,4 +284,4 @@ - \ No newline at end of file + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 0db5b561d0..ec4dc86c9d 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -48,6 +48,7 @@ + @@ -376,4 +377,4 @@ - \ No newline at end of file + diff --git a/Source/Core/Common/FormatUtil.h b/Source/Core/Common/FormatUtil.h new file mode 100644 index 0000000000..3c0bc3f262 --- /dev/null +++ b/Source/Core/Common/FormatUtil.h @@ -0,0 +1,41 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Common +{ +constexpr std::size_t CountFmtReplacementFields(std::string_view s) +{ + std::size_t count = 0; + for (std::size_t i = 0; i < s.size(); ++i) + { + if (s[i] != '{') + continue; + + // If the opening brace is followed by another brace, what we have is + // an escaped brace, not a replacement field. + if (i + 1 < s.size() && s[i + 1] == '{') + { + // Skip the second brace. + // This ensures that e.g. {{{}}} is counted correctly: when the first brace character + // is read and detected as being part of an '{{' escape sequence, the second character + // is skipped so the most inner brace (the third character) is not detected + // as the end of an '{{' pair. + ++i; + continue; + } + + ++count; + } + return count; +} + +static_assert(CountFmtReplacementFields("") == 0); +static_assert(CountFmtReplacementFields("{} test {:x}") == 2); +static_assert(CountFmtReplacementFields("{} {{}} test {{{}}}") == 2); +} // namespace Common diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h index 926648760a..7d762b504c 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -4,8 +4,10 @@ #pragma once +#include #include #include +#include "Common/FormatUtil.h" namespace Common::Log { @@ -77,13 +79,17 @@ enum LOG_LEVELS static const char LOG_LEVEL_TO_CHAR[7] = "-NEWID"; void GenericLogFmtImpl(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, - std::string_view format, const fmt::format_args& args); + fmt::string_view format, const fmt::format_args& args); -template -void GenericLogFmt(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, - std::string_view format, const Args&... args) +template +void GenericLogFmt(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, const S& format, + const Args&... args) { - GenericLogFmtImpl(level, type, file, line, format, fmt::make_format_args(args...)); + static_assert(NumFields == sizeof...(args), + "Unexpected number of replacement fields in format string; did you pass too few or " + "too many arguments?"); + GenericLogFmtImpl(level, type, file, line, format, + fmt::make_args_checked(format, args...)); } void GenericLog(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, const char* fmt, ...) @@ -137,11 +143,16 @@ void GenericLog(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, con // fmtlib capable API -#define GENERIC_LOG_FMT(t, v, ...) \ +#define GENERIC_LOG_FMT(t, v, format, ...) \ do \ { \ if (v <= MAX_LOGLEVEL) \ - Common::Log::GenericLogFmt(v, t, __FILE__, __LINE__, __VA_ARGS__); \ + { \ + /* Use a macro-like name to avoid shadowing warnings */ \ + constexpr auto GENERIC_LOG_FMT_N = Common::CountFmtReplacementFields(format); \ + Common::Log::GenericLogFmt(v, t, __FILE__, __LINE__, FMT_STRING(format), \ + ##__VA_ARGS__); \ + } \ } while (0) #define ERROR_LOG_FMT(t, ...) \ diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp index 6485db8eca..d882093adf 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -81,7 +81,7 @@ void GenericLog(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, con } void GenericLogFmtImpl(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, - std::string_view format, const fmt::format_args& args) + fmt::string_view format, const fmt::format_args& args) { auto* instance = LogManager::GetInstance(); if (instance == nullptr) diff --git a/Source/Core/Common/MsgHandler.h b/Source/Core/Common/MsgHandler.h index 0cf13e0856..b06d2e63a9 100644 --- a/Source/Core/Common/MsgHandler.h +++ b/Source/Core/Common/MsgHandler.h @@ -4,11 +4,14 @@ #pragma once +#include #include #include #include +#include "Common/FormatUtil.h" + namespace Common { // Message alerts @@ -37,9 +40,12 @@ bool MsgAlert(bool yes_no, MsgType style, const char* format, ...) bool MsgAlertFmtImpl(bool yes_no, MsgType style, fmt::string_view format, const fmt::format_args& args); -template -bool MsgAlertFmt(bool yes_no, MsgType style, fmt::string_view format, const Args&... args) +template +bool MsgAlertFmt(bool yes_no, MsgType style, const S& format, const Args&... args) { + static_assert(NumFields == sizeof...(args), + "Unexpected number of replacement fields in format string; did you pass too few or " + "too many arguments?"); return MsgAlertFmtImpl(yes_no, style, format, fmt::make_args_checked(format, args...)); } @@ -88,33 +94,41 @@ std::string FmtFormatT(const char* string, Args&&... args) // Fmt-capable variants of the macros +#define GenericAlertFmt(yes_no, style, format, ...) \ + [&] { \ + /* Use a macro-like name to avoid shadowing warnings */ \ + constexpr auto GENERIC_ALERT_FMT_N = Common::CountFmtReplacementFields(format); \ + return Common::MsgAlertFmt(yes_no, style, FMT_STRING(format), \ + ##__VA_ARGS__); \ + }() + #define SuccessAlertFmt(format, ...) \ - Common::MsgAlertFmt(false, Common::MsgType::Information, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(false, Common::MsgType::Information, format, ##__VA_ARGS__) #define PanicAlertFmt(format, ...) \ - Common::MsgAlertFmt(false, Common::MsgType::Warning, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(false, Common::MsgType::Warning, format, ##__VA_ARGS__) #define PanicYesNoFmt(format, ...) \ - Common::MsgAlertFmt(true, Common::MsgType::Warning, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(true, Common::MsgType::Warning, format, ##__VA_ARGS__) #define AskYesNoFmt(format, ...) \ - Common::MsgAlertFmt(true, Common::MsgType::Question, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(true, Common::MsgType::Question, format, ##__VA_ARGS__) #define CriticalAlertFmt(format, ...) \ - Common::MsgAlertFmt(false, Common::MsgType::Critical, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(false, Common::MsgType::Critical, format, ##__VA_ARGS__) // Use these macros (that do the same thing) if the message should be translated. #define SuccessAlertFmtT(format, ...) \ - Common::MsgAlertFmt(false, Common::MsgType::Information, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(false, Common::MsgType::Information, format, ##__VA_ARGS__) #define PanicAlertFmtT(format, ...) \ - Common::MsgAlertFmt(false, Common::MsgType::Warning, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(false, Common::MsgType::Warning, format, ##__VA_ARGS__) #define PanicYesNoFmtT(format, ...) \ - Common::MsgAlertFmt(true, Common::MsgType::Warning, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(true, Common::MsgType::Warning, format, ##__VA_ARGS__) #define AskYesNoFmtT(format, ...) \ - Common::MsgAlertFmt(true, Common::MsgType::Question, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(true, Common::MsgType::Question, format, ##__VA_ARGS__) #define CriticalAlertFmtT(format, ...) \ - Common::MsgAlertFmt(false, Common::MsgType::Critical, FMT_STRING(format), ##__VA_ARGS__) + GenericAlertFmt(false, Common::MsgType::Critical, format, ##__VA_ARGS__)