From 71020a41aa985f30ce8866024aadcce435570eb2 Mon Sep 17 00:00:00 2001 From: gistrec Date: Sat, 9 May 2026 19:00:55 +0200 Subject: [PATCH 1/2] Fix precondition violation in parse_key for malformed table headers parse_table_header() consumes the leading '[' (and the second '[' for array-of-tables headers) and then calls parse_key() unconditionally, checking only for a closing ']'. When the next character is neither a bare-key character nor a string delimiter (e.g. a third '[' as in the input '[[[1]]]'), parse_key() is entered with its documented precondition violated: TOML_ASSERT_ASSUME(is_bare_key_character(*cp) || is_string_delimiter(*cp)); In debug builds this aborts; under NDEBUG the macro expands to __builtin_assume, so the violation is undefined behaviour. Validate the key starter explicitly in parse_table_header() and raise the usual parse_error instead. Add regression tests for the malformed array-of-tables header cases and regenerate the single-header toml.hpp. --- include/toml++/impl/parser.inl | 10 ++++++++++ tests/parsing_tables.cpp | 8 ++++++++ toml.hpp | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/include/toml++/impl/parser.inl b/include/toml++/impl/parser.inl index 89682f28..3c190cfe 100644 --- a/include/toml++/impl/parser.inl +++ b/include/toml++/impl/parser.inl @@ -3157,6 +3157,16 @@ TOML_IMPL_NAMESPACE_START if (*cp == U']') set_error_and_return_default("tables with blank bare keys are explicitly prohibited"sv); + // the next character must be a valid key starter; otherwise parse_key() + // would be entered with an invalid precondition (e.g. for input like + // '[[[' the third '[' is neither a bare-key character nor a string + // delimiter). Reject explicitly here so we always raise a parse_error + // rather than tripping the precondition assertion in parse_key(). + if (!is_bare_key_character(*cp) && !is_string_delimiter(*cp)) + set_error_and_return_default("expected bare key starting character or string delimiter, saw '"sv, + to_sv(*cp), + "'"sv); + // get the actual key start_recording(); parse_key(); diff --git a/tests/parsing_tables.cpp b/tests/parsing_tables.cpp index 417427e5..d16c179b 100644 --- a/tests/parsing_tables.cpp +++ b/tests/parsing_tables.cpp @@ -20,6 +20,14 @@ TEST_CASE("parsing - tables") }); parsing_should_fail(FILE_LINE_ARGS, "[]"sv); + // Regression: '[[' followed by a non-key, non-']' character used to violate + // the precondition assertion in parse_key() (UB under NDEBUG, abort under + // debug). The parser must reject these as a normal parse error. + parsing_should_fail(FILE_LINE_ARGS, "[[[1]]]"sv); + parsing_should_fail(FILE_LINE_ARGS, "[[[a]]]"sv); + parsing_should_fail(FILE_LINE_ARGS, "[[[]]"sv); + parsing_should_fail(FILE_LINE_ARGS, "[[="sv); + // "Under that, and until the next header or EOF, are the key/values of that table. // Key/value pairs within tables are not guaranteed to be in any specific order." parsing_should_succeed(FILE_LINE_ARGS, diff --git a/toml.hpp b/toml.hpp index 91f99d9f..65c17cec 100644 --- a/toml.hpp +++ b/toml.hpp @@ -15741,6 +15741,16 @@ TOML_IMPL_NAMESPACE_START if (*cp == U']') set_error_and_return_default("tables with blank bare keys are explicitly prohibited"sv); + // the next character must be a valid key starter; otherwise parse_key() + // would be entered with an invalid precondition (e.g. for input like + // '[[[' the third '[' is neither a bare-key character nor a string + // delimiter). Reject explicitly here so we always raise a parse_error + // rather than tripping the precondition assertion in parse_key(). + if (!is_bare_key_character(*cp) && !is_string_delimiter(*cp)) + set_error_and_return_default("expected bare key starting character or string delimiter, saw '"sv, + to_sv(*cp), + "'"sv); + // get the actual key start_recording(); parse_key(); From 16febd515fac6988b7122436549a6eb78e764e13 Mon Sep 17 00:00:00 2001 From: Aleksandr Kovalko Date: Sun, 10 May 2026 14:34:32 +0200 Subject: [PATCH 2/2] Drop redundant comments from table header check --- include/toml++/impl/parser.inl | 5 ----- tests/parsing_tables.cpp | 3 --- toml.hpp | 5 ----- 3 files changed, 13 deletions(-) diff --git a/include/toml++/impl/parser.inl b/include/toml++/impl/parser.inl index 3c190cfe..f9b03c8e 100644 --- a/include/toml++/impl/parser.inl +++ b/include/toml++/impl/parser.inl @@ -3157,11 +3157,6 @@ TOML_IMPL_NAMESPACE_START if (*cp == U']') set_error_and_return_default("tables with blank bare keys are explicitly prohibited"sv); - // the next character must be a valid key starter; otherwise parse_key() - // would be entered with an invalid precondition (e.g. for input like - // '[[[' the third '[' is neither a bare-key character nor a string - // delimiter). Reject explicitly here so we always raise a parse_error - // rather than tripping the precondition assertion in parse_key(). if (!is_bare_key_character(*cp) && !is_string_delimiter(*cp)) set_error_and_return_default("expected bare key starting character or string delimiter, saw '"sv, to_sv(*cp), diff --git a/tests/parsing_tables.cpp b/tests/parsing_tables.cpp index d16c179b..fa21bf2e 100644 --- a/tests/parsing_tables.cpp +++ b/tests/parsing_tables.cpp @@ -20,9 +20,6 @@ TEST_CASE("parsing - tables") }); parsing_should_fail(FILE_LINE_ARGS, "[]"sv); - // Regression: '[[' followed by a non-key, non-']' character used to violate - // the precondition assertion in parse_key() (UB under NDEBUG, abort under - // debug). The parser must reject these as a normal parse error. parsing_should_fail(FILE_LINE_ARGS, "[[[1]]]"sv); parsing_should_fail(FILE_LINE_ARGS, "[[[a]]]"sv); parsing_should_fail(FILE_LINE_ARGS, "[[[]]"sv); diff --git a/toml.hpp b/toml.hpp index 65c17cec..fc12d078 100644 --- a/toml.hpp +++ b/toml.hpp @@ -15741,11 +15741,6 @@ TOML_IMPL_NAMESPACE_START if (*cp == U']') set_error_and_return_default("tables with blank bare keys are explicitly prohibited"sv); - // the next character must be a valid key starter; otherwise parse_key() - // would be entered with an invalid precondition (e.g. for input like - // '[[[' the third '[' is neither a bare-key character nor a string - // delimiter). Reject explicitly here so we always raise a parse_error - // rather than tripping the precondition assertion in parse_key(). if (!is_bare_key_character(*cp) && !is_string_delimiter(*cp)) set_error_and_return_default("expected bare key starting character or string delimiter, saw '"sv, to_sv(*cp),