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__)