Common: Validate the number of {} fields in format strings

Unfortunately, {fmt} allows passing too many arguments to a format call
without raising any runtime or compile-time error [1].

As this is a common source of bugs since we started migrating to {fmt},
this commit adds some custom logic to validate the number of
replacement fields in format strings in addition to {fmt}'s own checks.

[1] https://github.com/fmtlib/fmt/issues/492
This commit is contained in:
Léo Lam
2020-11-19 02:51:56 +01:00
parent 47c91696ee
commit 62eeb05519
5 changed files with 58 additions and 4 deletions

View File

@ -4,8 +4,10 @@
#pragma once
#include <cstddef>
#include <fmt/format.h>
#include <string_view>
#include "Common/FormatUtil.h"
namespace Common::Log
{
@ -78,10 +80,13 @@ static const char LOG_LEVEL_TO_CHAR[7] = "-NEWID";
void GenericLogFmtImpl(LOG_LEVELS level, LOG_TYPE type, const char* file, int line,
fmt::string_view format, const fmt::format_args& args);
template <typename S, typename... Args>
template <std::size_t NumFields, typename S, typename... Args>
void GenericLogFmt(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, 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?");
GenericLogFmtImpl(level, type, file, line, format,
fmt::make_args_checked<Args...>(format, args...));
}
@ -141,7 +146,12 @@ void GenericLog(LOG_LEVELS level, LOG_TYPE type, const char* file, int line, con
do \
{ \
if (v <= MAX_LOGLEVEL) \
Common::Log::GenericLogFmt(v, t, __FILE__, __LINE__, FMT_STRING(format), ##__VA_ARGS__); \
{ \
/* Use a macro-like name to avoid shadowing warnings */ \
constexpr auto GENERIC_LOG_FMT_N = Common::CountFmtReplacementFields(format); \
Common::Log::GenericLogFmt<GENERIC_LOG_FMT_N>(v, t, __FILE__, __LINE__, FMT_STRING(format), \
##__VA_ARGS__); \
} \
} while (0)
#define ERROR_LOG_FMT(t, ...) \