From 0aa4721472c1c8c16714b8a52d39520f973bae5e Mon Sep 17 00:00:00 2001 From: Mart Slot Date: Tue, 24 Sep 2019 08:27:15 +0000 Subject: [PATCH] Compile-time error on too many arguments provided Adds a compile time error if the number of arguments provided to the format function is larger than the number of braces in the format string. Only works in automatic argument indexing mode. This check deliberately only works for compile-time format string checking, not at runtime. This is because we don't want existing code to have unexpected runtime errors after upgrades. There are also scenarios imaginable where either the arguments or the format string is generated at runtime, and the ability to have less braces in the format string can actually be a feature. At the same time, compile-time format calls are guaranteed to be constant. In other words, having too many arguments will always mean there's a bug in the code. The new feature works by adding a on_end_of_string() function to formatting handlers. This function is called after the whole format string has been parsed, and needs to be implemented by all parsing handlers. --- include/fmt/compile.h | 4 ++++ include/fmt/core.h | 3 +++ include/fmt/format.h | 12 +++++++++++- test/format-test.cc | 4 ++++ test/scan.h | 2 ++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 8617ef85..51f555a1 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -82,6 +82,8 @@ template struct part_counter { return begin; } + FMT_CONSTEXPR void on_end_of_string() {} + FMT_CONSTEXPR void on_error(const char*) {} }; @@ -148,6 +150,8 @@ class format_string_compiler : public error_handler { handler_(part); return it; } + + FMT_CONSTEXPR void on_end_of_string() {} }; // Compiles a format string and invokes handler(part) for each parsed part. diff --git a/include/fmt/core.h b/include/fmt/core.h index 0d7d15b5..e891d1e6 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -489,6 +489,9 @@ class basic_parse_context : private ErrorHandler { FMT_CONSTEXPR void check_arg_id(basic_string_view) {} + FMT_CONSTEXPR bool is_auto_arg_indexing() { return next_arg_id_ >= 0; } + FMT_CONSTEXPR int num_auto_args() { return next_arg_id_; } + FMT_CONSTEXPR void on_error(const char* message) { ErrorHandler::on_error(message); } diff --git a/include/fmt/format.h b/include/fmt/format.h index e198233b..7c12f782 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2477,8 +2477,10 @@ FMT_CONSTEXPR void parse_format_string(basic_string_view format_str, // Doing two passes with memchr (one for '{' and another for '}') is up to // 2.5x faster than the naive one-pass implementation on big format strings. const Char* p = begin; - if (*begin != '{' && !find(begin, end, '{', p)) + if (*begin != '{' && !find(begin, end, '{', p)) { + handler.on_end_of_string(); return write(begin, end); + } write(begin, p); ++p; if (p == end) return handler.on_error("invalid format string"); @@ -2502,6 +2504,7 @@ FMT_CONSTEXPR void parse_format_string(basic_string_view format_str, } begin = p + 1; } + handler.on_end_of_string(); } template @@ -2551,6 +2554,11 @@ class format_string_checker { return arg_id_ < num_args ? parse_funcs_[arg_id_](context_) : begin; } + FMT_CONSTEXPR void on_end_of_string() { + if (context_.is_auto_arg_indexing() && context_.num_auto_args() < num_args) + context_.on_error("number of arguments in format string is less than number of arguments provided"); + } + FMT_CONSTEXPR void on_error(const char* message) { context_.on_error(message); } @@ -3212,6 +3220,8 @@ struct format_handler : internal::error_handler { return begin; } + void on_end_of_string() {} + basic_parse_context parse_context; Context context; basic_format_arg arg; diff --git a/test/format-test.cc b/test/format-test.cc index f03a2bd6..5591ff14 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2377,6 +2377,8 @@ struct test_format_string_handler { return begin; } + FMT_CONSTEXPR void on_end_of_string() {} + FMT_CONSTEXPR void on_error(const char*) { error = true; } bool error = false; @@ -2496,6 +2498,8 @@ TEST(FormatTest, FormatStringErrors) { EXPECT_ERROR("{}{1}", "cannot switch from automatic to manual argument indexing", int, int); + EXPECT_ERROR("", "number of arguments in format string is less than number of arguments provided", int); + EXPECT_ERROR("{}", "number of arguments in format string is less than number of arguments provided", int, int); } TEST(FormatTest, VFormatTo) { diff --git a/test/scan.h b/test/scan.h index ace84f77..1062e45a 100644 --- a/test/scan.h +++ b/test/scan.h @@ -212,6 +212,8 @@ struct scan_handler : error_handler { arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_); return parse_ctx_.begin(); } + + void on_end_of_string() {} }; } // namespace internal