const MAX_VALUE_NODES: usize = _; // 4_194_304usizeExpand description
Sanity cap on the number of Value nodes one top-level decode may
materialize across every array and map in the datum. Arrays and maps apply
it per block (a fast reject for an absurd single-block count) and against the
shared cumulative budget threaded through the whole decode (see
charge_value_nodes / DECODE_NODES).
This bounds memory, not element count: each element is weighted by
min_value_nodes, a lower bound on the Value nodes it decodes into. An
element-count cap alone is not enough, because a zero-width element — null,
or a record of only null/empty-record fields — occupies no input yet still
allocates a Value (a Vec slot, plus a record’s own Vec and field-name
Strings). The min_encoded_len byte-floor check below bounds a block by
the remaining input only when each element occupies at least one wire byte,
so a multi-million-element block of zero-width elements would otherwise
amplify a handful of bytes into gigabytes. Weighting the count and capping
the product bounds that amplification (as well as the analogous case of a
huge block of wide, positive-floor records read from a large input).
The budget is shared across the whole datum rather than reset per collection
so the bound composes through nesting: a per-collection budget would hand
every array/map a fresh ceiling, letting a schema like
array<record{array<record{null}>}> amplify a few wire bytes into roughly
this cap raised to the nesting depth. Sharing one budget keeps the worst case
flat regardless of nesting.
Without any cap, a malicious or corrupt file can claim up to i64::MAX items
and the generic array/map decode loop runs until it OOMs or hits Vec
capacity-overflow.
At 1 << 22 nodes the worst case (decoding zero-width records right up to the
cap) peaks around 750 MiB — including the transient doubling of the element
Vec mid-push — leaving comfortable headroom under the fuzzer’s 2 GiB RSS
limit, while still admitting any realistically-sized array/map.