Report an empty array for UBJSON [$N#<n> instead of n elements

A no-op (N) is not a value, so an optimized array of type N holds no
element whatever count it declares. The reader skipped the elements but
still passed the declared count to the SAX parser: a handler was told
start_array(n) and no element followed, and the DOM parser reserved
min(n, 16384) values (256 KB) for an array that stays empty. Nine bytes
(`[$N#l 0x7FFFFFFF`) therefore committed 256 KB: 2000 of them in one
input, 18 KB, committed 512 MB of heap (capacity 163,840,000 values for
the 10000 arrays of the original probe: 2.6 GB for 70 KB of input).
Untouched pages keep the resident size small, but the commit charge and
a 32-bit address space are consumed.

The reader now announces 0 elements for an optimized array of type N.
The loop that skips the elements is unchanged (it never ran). A count
that does not fit into std::size_t is still rejected by the count
reader; a count that is too large for the array type of the DOM (before:
out_of_range.408 "excessive array size" from the DOM handler) is
accepted now, because the array is empty.

Behaviour change: sax_parse reports start_array(0) for `[$N#n` (before:
n); from_ubjson no longer throws out_of_range.408 for an N-typed array
whose count exceeds the maximum size of the array type.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-11-11 10:46:41 +01:00
1 parent 71ec3b4306
commit 44b1e7b2b9
6 files changed
+116 -26

No files matched your search

@@ -3861,18 +3861,15 @@ class binary_reader
}
}
if (JSON_HEDLEY_UNLIKELY(!enter_array(size_and_type.first, size_and_type.second)))
// a no-op is not a value, so an array of them is empty whatever
// count it declares: the SAX parser is told so, instead of being
// made to reserve storage for a count that no input backs
const std::size_t announced = size_and_type.second == 'N' ? 0 : size_and_type.first;
if (JSON_HEDLEY_UNLIKELY(!enter_array(announced, size_and_type.second)))
{
return false;
}
if (size_and_type.second == 'N')
{
// a no-op is not a value, so a container of them holds none;
// the declared size has already been passed to the SAX parser
container_stack.back().remaining = 0;
}
return size_and_type.second == 0
|| parse_ubjson_typed_run(container_stack.back().remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {});
}