diff --git a/include/fmt/core.h b/include/fmt/core.h index a543f096..566da7e3 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -568,7 +568,7 @@ class basic_format_parse_context : private ErrorHandler { using iterator = typename basic_string_view::iterator; explicit FMT_CONSTEXPR basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = ErrorHandler()) + basic_string_view format_str, ErrorHandler eh = {}) : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} /** diff --git a/include/fmt/format.h b/include/fmt/format.h index d11a714b..80b464ea 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2626,25 +2626,51 @@ FMT_CONSTEXPR const typename ParseContext::char_type* parse_format_specs( return f.parse(ctx); } +// A parse context with extra argument id checks. It is only used at compile +// time because adding checks at runtime would introduce substantial overhead +// and would be redundant since argument ids are checked when arguments are +// retrieved anyway. +template +class compile_parse_context + : public basic_format_parse_context { + private: + int num_args_; + using base = basic_format_parse_context; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view format_str, int num_args = max_value(), + ErrorHandler eh = {}) + : base(format_str, eh), num_args_(num_args) {} + + FMT_CONSTEXPR int next_arg_id() { + int id = base::next_arg_id(); + if (id >= num_args_) this->on_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) this->on_error("argument not found"); + } + using base::check_arg_id; +}; + template class format_string_checker { public: explicit FMT_CONSTEXPR format_string_checker( basic_string_view format_str, ErrorHandler eh) : arg_id_(-1), - context_(format_str, eh), + context_(format_str, num_args, eh), parse_funcs_{&parse_format_specs...} {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_arg_id() { - arg_id_ = context_.next_arg_id(); - check_arg_id(); - } + FMT_CONSTEXPR void on_arg_id() { arg_id_ = context_.next_arg_id(); } FMT_CONSTEXPR void on_arg_id(int id) { arg_id_ = id; context_.check_arg_id(id); - check_arg_id(); } FMT_CONSTEXPR void on_arg_id(basic_string_view) { on_error("compile-time checks don't support named arguments"); @@ -2662,13 +2688,9 @@ class format_string_checker { } private: - using parse_context_type = basic_format_parse_context; + using parse_context_type = compile_parse_context; enum { num_args = sizeof...(Args) }; - FMT_CONSTEXPR void check_arg_id() { - if (arg_id_ >= num_args) context_.on_error("argument not found"); - } - // Format specifier parsing function. using parse_func = const Char* (*)(parse_context_type&); diff --git a/test/format-test.cc b/test/format-test.cc index fa3574bc..f9e75d20 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2493,6 +2493,8 @@ TEST(FormatTest, FormatStringErrors) { EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned); EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned); EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned); + EXPECT_ERROR("{:{}}", "argument not found", int); + EXPECT_ERROR("{:.{}}", "argument not found", double); EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int); EXPECT_ERROR("{:s}", "invalid type specifier", int); EXPECT_ERROR("{:s}", "invalid type specifier", bool);