Add JSON_RECURSION_DEPTH_LIMIT to set how deep value walks recurse (#6081)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-11-11 22:32:05 +01:00
1 parent 0660d78b9c
commit 1dae5a66b1
30 files changed
+1340 -95

No files matched your search

+10
View File
@@ -198,6 +198,16 @@ this is planned to become the default in version 4.0.0.
See [full documentation of `JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md).
## `JSON_RECURSION_DEPTH_LIMIT`
Sets the number of nesting levels that the operations walking a value (copying, comparing, serializing, hashing,
merging, diffing, converting, destroying, and the binary writers) recurse into on the call stack before they continue
with an explicit stack on the heap. The default is `128`; allowed are the integers from `0` to `254`. Lower it for
threads with small stacks, raise it to trade stack for speed. Results are the same for every setting; the value must be
the same in all translation units of a program.
See [full documentation of `JSON_RECURSION_DEPTH_LIMIT`](../api/macros/json_recursion_depth_limit.md).
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
When defined, the library will not create a compiler warning when a different version of the library was already
@@ -54,10 +54,32 @@ Once a value has been parsed, operations that walk it recursively — serializin
[`dump`](../../api/basic_json/dump.md), hashing it, copying it, comparing two values with `#!cpp ==`, `#!cpp <`, or (in
C++20) `#!cpp <=>`, merging with [`update`](../../api/basic_json/update.md), applying a
[`merge_patch`](../../api/basic_json/merge_patch.md), creating a patch with [`diff`](../../api/basic_json/diff.md) or
[`merge_diff`](../../api/basic_json/merge_diff.md), [`flatten`](../../api/basic_json/flatten.md), and the binary
writers (`to_cbor`, `to_msgpack`, ...) — descend at most 128 levels on the call stack and continue
below that with an explicit stack instead, so none of them can exhaust the stack either, however deeply the value is
nested. Destroying a value (its destructor) never recurses at all, regardless of nesting depth, for the same reason.
[`merge_diff`](../../api/basic_json/merge_diff.md), converting it to another `basic_json` type, destroying it, and the
binary writers (`to_cbor`, `to_msgpack`, ...) — descend at most
[`JSON_RECURSION_DEPTH_LIMIT`](../../api/macros/json_recursion_depth_limit.md) levels on the call stack, 128 by default,
and continue below that with an explicit stack on the heap. So the call stack does not grow with the nesting depth of the
value, however deeply it is nested. ([`flatten`](../../api/basic_json/flatten.md),
[`unflatten`](../../api/basic_json/unflatten.md), and [`to_bson`](../../api/basic_json/to_bson.md) never use the call
stack for the nesting.)
The call stack still needs room for the levels that are recursed into, and the default limit assumes a stack of ordinary
size. The stack that an operation needs is roughly proportional to the limit; with the default of 128 levels, the most
demanding operation (`dump` in an unoptimized build) needed up to 288 KiB, and an optimized build needed 48 KiB or less,
in measurements on macOS with arm64. The most demanding operation needed this much stack at the limits `0`, `32`,
`128`, and `254`:
| Stack needed, in KiB | limit 0 | limit 32 | limit 128 (default) | limit 254 |
|---|---:|---:|---:|---:|
| unoptimized build (`-O0`) | 16 or less | 80 | 288 | 576 |
| optimized build (`-O2`) | 16 or less | 16 or less | 48 | 112 |
A thread with a smaller stack (a task of a real-time operating system, a fiber, or a thread of the musl C library, whose
default stack is 128 KiB) can still overflow its stack on deeply nested input, which is why the limit can be lowered:
define `JSON_RECURSION_DEPTH_LIMIT` to a smaller value, down to `0`, before including the library. Such a limit changes
only the stack that is needed and the speed (values nested more deeply than the limit are walked more slowly), never a
result. See the [documentation of the macro](../../api/macros/json_recursion_depth_limit.md) for the complete tables,
the speed, and the rules for choosing a value. Destroying a value (its destructor) recurses over the same number of
levels and then continues without the call stack, and needs 32 KiB or less at every limit.
## Input size