From 70380a8de66a2bd333c38fd945646ef6694bfd51 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 30 Sep 2026 18:26:26 +0200 Subject: [PATCH] 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 --- include/nlohmann/detail/output/binary_writer.hpp | 8 ++++++++ single_include/nlohmann/json.hpp | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 4b481e61d..6e5592839 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -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()); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index cc1a08824..8024c58b5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -21651,6 +21651,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());