Skip to content

Commit 46a4599

Browse files
authored
Fix missing named-arg validation for compiled format strings (#4638)
1 parent cbd4fc1 commit 46a4599

File tree

2 files changed

+35
-21
lines changed

2 files changed

+35
-21
lines changed

include/fmt/compile.h

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,15 @@ constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
267267
return pos;
268268
}
269269

270-
template <typename Args, size_t POS, int ID, typename S>
270+
template <typename Args, size_t POS, int ID, bool DYNAMIC_NAMES, typename S>
271271
constexpr auto compile_format_string(S fmt);
272272

273-
template <typename Args, size_t POS, int ID, typename T, typename S>
273+
template <typename Args, size_t POS, int ID, bool DYNAMIC_NAMES, typename T,
274+
typename S>
274275
constexpr auto parse_tail(T head, S fmt) {
275276
if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
276-
constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
277+
constexpr auto tail =
278+
compile_format_string<Args, POS, ID, DYNAMIC_NAMES>(fmt);
277279
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
278280
unknown_format>())
279281
return tail;
@@ -347,13 +349,13 @@ struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
347349
};
348350

349351
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
350-
typename S>
352+
bool DYNAMIC_NAMES, typename S>
351353
constexpr auto parse_replacement_field_then_tail(S fmt) {
352354
using char_type = typename S::char_type;
353355
constexpr auto str = basic_string_view<char_type>(fmt);
354356
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
355357
if constexpr (c == '}') {
356-
return parse_tail<Args, END_POS + 1, NEXT_ID>(
358+
return parse_tail<Args, END_POS + 1, NEXT_ID, DYNAMIC_NAMES>(
357359
field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
358360
} else if constexpr (c != ':') {
359361
FMT_THROW(format_error("expected ':'"));
@@ -364,7 +366,8 @@ constexpr auto parse_replacement_field_then_tail(S fmt) {
364366
FMT_THROW(format_error("expected '}'"));
365367
return 0;
366368
} else {
367-
return parse_tail<Args, result.end + 1, result.next_arg_id>(
369+
return parse_tail<Args, result.end + 1, result.next_arg_id,
370+
DYNAMIC_NAMES>(
368371
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
369372
result.fmt},
370373
fmt);
@@ -374,22 +377,23 @@ constexpr auto parse_replacement_field_then_tail(S fmt) {
374377

375378
// Compiles a non-empty format string and returns the compiled representation
376379
// or unknown_format() on unrecognized input.
377-
template <typename Args, size_t POS, int ID, typename S>
380+
template <typename Args, size_t POS, int ID, bool DYNAMIC_NAMES, typename S>
378381
constexpr auto compile_format_string(S fmt) {
379382
using char_type = typename S::char_type;
380383
constexpr auto str = basic_string_view<char_type>(fmt);
381384
if constexpr (str[POS] == '{') {
382385
if constexpr (POS + 1 == str.size())
383386
FMT_THROW(format_error("unmatched '{' in format string"));
384387
if constexpr (str[POS + 1] == '{') {
385-
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
388+
return parse_tail<Args, POS + 2, ID, DYNAMIC_NAMES>(
389+
make_text(str, POS, 1), fmt);
386390
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
387391
static_assert(ID != manual_indexing_id,
388392
"cannot switch from manual to automatic argument indexing");
389393
constexpr auto next_id =
390394
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
391-
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
392-
POS + 1, ID, next_id>(fmt);
395+
return parse_replacement_field_then_tail<
396+
get_type<ID, Args>, Args, POS + 1, ID, next_id, DYNAMIC_NAMES>(fmt);
393397
} else {
394398
constexpr auto arg_id_result =
395399
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
@@ -402,21 +406,24 @@ constexpr auto compile_format_string(S fmt) {
402406
ID == manual_indexing_id || ID == 0,
403407
"cannot switch from automatic to manual argument indexing");
404408
constexpr auto arg_index = arg_id_result.arg_id.index;
405-
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
406-
Args, arg_id_end_pos,
407-
arg_index, manual_indexing_id>(
408-
fmt);
409+
return parse_replacement_field_then_tail<
410+
get_type<arg_index, Args>, Args, arg_id_end_pos, arg_index,
411+
manual_indexing_id, DYNAMIC_NAMES>(fmt);
409412
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
410413
constexpr auto arg_index =
411414
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
415+
416+
static_assert(arg_index >= 0 || DYNAMIC_NAMES,
417+
"named argument not found");
418+
412419
if constexpr (arg_index >= 0) {
413420
constexpr auto next_id =
414421
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
415422
return parse_replacement_field_then_tail<
416423
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
417-
arg_index, next_id>(fmt);
424+
arg_index, next_id, DYNAMIC_NAMES>(fmt);
418425
} else if constexpr (c == '}') {
419-
return parse_tail<Args, arg_id_end_pos + 1, ID>(
426+
return parse_tail<Args, arg_id_end_pos + 1, ID, DYNAMIC_NAMES>(
420427
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
421428
} else if constexpr (c == ':') {
422429
return unknown_format(); // no type info for specs parsing
@@ -426,13 +433,16 @@ constexpr auto compile_format_string(S fmt) {
426433
} else if constexpr (str[POS] == '}') {
427434
if constexpr (POS + 1 == str.size())
428435
FMT_THROW(format_error("unmatched '}' in format string"));
429-
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
436+
return parse_tail<Args, POS + 2, ID, DYNAMIC_NAMES>(make_text(str, POS, 1),
437+
fmt);
430438
} else {
431439
constexpr auto end = parse_text(str, POS + 1);
432440
if constexpr (end - POS > 1) {
433-
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
441+
return parse_tail<Args, end, ID, DYNAMIC_NAMES>(
442+
make_text(str, POS, end - POS), fmt);
434443
} else {
435-
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
444+
return parse_tail<Args, end, ID, DYNAMIC_NAMES>(
445+
code_unit<char_type>{str[POS]}, fmt);
436446
}
437447
}
438448
}
@@ -444,8 +454,11 @@ constexpr auto compile(S fmt) {
444454
if constexpr (str.size() == 0) {
445455
return detail::make_text(str, 0, 0);
446456
} else {
447-
constexpr auto result =
448-
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
457+
constexpr int num_static_named_args =
458+
detail::count_static_named_args<Args...>();
459+
constexpr auto result = detail::compile_format_string<
460+
detail::type_list<Args...>, 0, 0,
461+
num_static_named_args != detail::count_named_args<Args...>()>(fmt);
449462
return result;
450463
}
451464
}

test/compile-error-test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ set(fmt_headers "
77
#include <fmt/format.h>
88
#include <fmt/xchar.h>
99
#include <fmt/ostream.h>
10+
#include <fmt/compile.h>
1011
#include <iostream>
1112
")
1213

0 commit comments

Comments
 (0)