Lexer, parser, stream adapter, SAX: remove checks that invariants make dead

Branch coverage of the test suite and of 24k mutated inputs showed
these conditions as never taken; each is implied by the code around it:

- lexer: `next_unget` in get_codepoint_bulk() and scan_literal_bulk().
  unget() is only called at the end of scan_number(), in scan_comment()
  and in skip_bom(); every token scan starts with a plain get() in
  skip_whitespace() that consumes a pending unget, and nothing between
  that get() and the bulk helpers ungets (now asserts, as in
  get_after_string_run())
- lexer: `number_type == value_integer` in convert_integer(): both
  callers exclude value_float and the type has three values
- parser: `!context.empty()` in exception_message(): all callers pass a
  literal
- input_stream_adapter: `refill` in get_character_slow(): the function
  is only called after get_character() found the get area exhausted,
  and the variable recomputed that comparison
- json_sax_dom_callback_parser: `ref_stack.back()` in key() (the null
  case returned a few lines earlier, and the callback in between cannot
  reach ref_stack), `is_structured()` in end_object() (non-null entries
  are only pushed for objects and arrays), `!key_stack.empty()` in
  current_key() (a kept object pushed its key before any value)

No change in behaviour: a build that aborts on each removed side passes
the test suite, the fuzz corpora and 40k random documents through every
input adapter in four macro configurations.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-11-02 09:41:00 +01:00
1 parent 2301f01284
commit f093573828
5 files changed
+42 -54

No files matched your search

+13 -8
View File
@@ -300,14 +300,16 @@ class lexer : public lexer_base<BasicJsonType>
/// calls would (including the position and token bookkeeping of a
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
/// the 4 digits, just as the last such get() would; the codepoint is
/// written to @a out. Makes no state change and returns false for a
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
/// being a hex digit, so the caller falls back unchanged to the
/// per-character loop, which then reports the same diagnostic (stopping
/// at the first invalid digit) as before this optimization.
/// written to @a out. Makes no state change and returns false for fewer
/// than 4 remaining bytes or any of the 4 bytes not being a hex digit, so
/// the caller falls back unchanged to the per-character loop, which then
/// reports the same diagnostic (stopping at the first invalid digit) as
/// before this optimization.
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
{
if (next_unget || ia.bulk_remaining() < 4)
// no unget is pending inside a string (see get_after_string_run())
JSON_ASSERT(!next_unget);
if (ia.bulk_remaining() < 4)
{
return false;
}
@@ -1409,7 +1411,7 @@ scan_number_done:
return token_type::value_unsigned;
}
}
else if (number_type == token_type::value_integer)
else // value_integer (the callers handle value_float themselves)
{
if (parse_integer_signed(first, last, value_integer))
{
@@ -1780,7 +1782,10 @@ scan_number_done:
/// the error at the right character
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
{
if (next_unget || ia.bulk_remaining() < length
// called right after scan_after_whitespace() read current (see
// get_after_string_run()), so no unget is pending
JSON_ASSERT(!next_unget);
if (ia.bulk_remaining() < length
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
{
return false;