Assert that write_bson_document() consumes every calc_bson_sizes() entry

calc_bson_sizes() and write_bson_document() are a hand-synchronized
pair of passes over the same object/array tree, introduced by #5553:
the size pass appends to nested_sizes in visiting order, and the write
pass consumes the table by position with nested_sizes[next_size++].
Nothing checked that the write pass consumed the whole table. If a
future change touched only one of the two passes - for example to skip
or reject an entry - every later size prefix in the document would be
silently wrong.

Add JSON_ASSERT(next_size == nested_sizes.size()) where
write_bson_document() returns, so such a future drift between the two
passes is caught immediately (JSON_ASSERT expands to nothing in
release builds using assert(), and the fuzzers/tests already build
with it enabled). The two passes agree today, so this changes nothing
observable; it only guards against the risk described in #5710 item 5.

Extracting a shared stepper for the two passes (the second half of the
proposed change) is left for a follow-up: it only saves ~30 lines and
the issue asks for it only if the result reads clearly, which needs
more room to get right than a mechanical cleanup pass allows.

#5710 item 5

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-30 18:26:26 +02:00
parent 1841b4f671
commit 70380a8de6
2 changed files with 16 additions and 0 deletions
@@ -1358,6 +1358,14 @@ class binary_writer
oa.write_character(to_char_type(0x00));
if (parents.empty())
{
// calc_bson_sizes() and write_bson_document() are two
// hand-synchronized passes over the same structure, linked
// only by nested_sizes' visiting order; this checks that the
// write pass consumed exactly the sizes the size pass
// produced, so a future change that desyncs them (skips or
// rejects an entry in only one pass) is caught immediately
// instead of silently writing wrong length prefixes.
JSON_ASSERT(next_size == nested_sizes.size());
return;
}
current = std::move(parents.back());