mirror of
https://github.com/nlohmann/json.git
synced 2026-11-12 07:00:46 +00:00
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:
1 parent
0660d78b9c
commit
1dae5a66b1
30 files changed
+1340
-95
No files matched your search
@@ -224,6 +224,12 @@
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#endif
|
||||
|
||||
// JSON_RECURSION_DEPTH_LIMIT (the number of nesting levels the walks over a
|
||||
// value recurse into before they continue with an explicit stack; 128 by
|
||||
// default, 0 to 254 allowed) is defined and checked where it is used, in
|
||||
// detail/recursion_depth_limit.hpp. Like JSON_NO_THREAD_LOCAL it is neither part
|
||||
// of the ABI tag nor undefined at the end of json.hpp.
|
||||
|
||||
// allow disabling exceptions
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
#define JSON_THROW(exception) throw exception
|
||||
|
||||
@@ -9,9 +9,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <type_traits> // false_type, is_integral, remove_cv, true_type
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
// JSON_RECURSION_DEPTH_LIMIT: the number of nesting levels the operations that
|
||||
// walk a value recurse into on the call stack before they continue with an
|
||||
// explicit stack (see detail::recursion_depth_limit() below).
|
||||
//
|
||||
// May be defined by the user before the first include of the library; it has
|
||||
// to be the same in every translation unit of a program (it is not part of the
|
||||
// ABI tag, see https://json.nlohmann.me/api/macros/json_recursion_depth_limit/).
|
||||
// Allowed: an integer constant expression from 0 to 254. The default is 128.
|
||||
#ifndef JSON_RECURSION_DEPTH_LIMIT
|
||||
#define JSON_RECURSION_DEPTH_LIMIT 128
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -20,21 +33,71 @@ namespace detail
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (copying, comparing, serializing, hashing, merging,
|
||||
...) recurse once
|
||||
per nesting level, which is fastest, but a value nested deeply enough would
|
||||
exhaust the call stack. So they recurse only this many levels deep and finish
|
||||
whatever lies below with an explicit stack. All of them share this limit.
|
||||
diffing, converting, destroying, and writing the binary formats CBOR,
|
||||
MessagePack, UBJSON, BJData, and BON8) recurse once per nesting level, which is
|
||||
fastest, but a value nested deeply enough would exhaust the call stack. So they
|
||||
recurse only this many levels deep and finish whatever lies below with an
|
||||
explicit stack that lives on the heap. All of them share this limit and none has
|
||||
a constant of its own. It is the value of the macro JSON_RECURSION_DEPTH_LIMIT,
|
||||
128 by default. (Parsing, the binary readers, flatten(), unflatten(), and
|
||||
to_bson() always use an explicit stack and do not read it, and the lockstep
|
||||
comparison of json_view documents has a fixed bound of its own, 48 levels.)
|
||||
|
||||
Most of them pass the depth down as an argument. The copy constructor and the
|
||||
comparison operators cannot, as their signatures are fixed, so they count it
|
||||
in basic_json::nesting_depth() instead, a byte per thread; the limit must
|
||||
therefore stay below 255.
|
||||
Most of the operations pass the depth down as an argument. The copy constructor
|
||||
and the comparison operators cannot, as their signatures are fixed, so they
|
||||
count it in basic_json::nesting_depth() instead, a byte per thread. Only the
|
||||
levels that are descended into are counted, so the count never exceeds the limit.
|
||||
The limit must fit in the byte with some headroom, so that the count cannot wrap
|
||||
around: it must be below 255, and 254 is the largest value allowed. (More levels
|
||||
would hardly be useful: 254 levels need up to 576 KiB of stack in an unoptimized
|
||||
build already, twice the stack of the default.)
|
||||
|
||||
The smallest value is 0: every operation then uses its explicit stack from the
|
||||
first level on and no operation recurses over the nesting of a value at all.
|
||||
That is slower, but needs a constant amount of call stack. It works because
|
||||
every use of the limit compares a depth with it (`depth >= limit`, or `<` for
|
||||
the opposite), which holds for 0 as for any other value; nothing computes
|
||||
`limit - 1` or `limit + 1`, so nothing wraps around. Each operation's explicit
|
||||
stack handles any nesting level, including the first, so none of them relies on
|
||||
having recursed first.
|
||||
|
||||
The call stack that an operation needs is roughly a few KiB plus a cost per level
|
||||
times the limit. At the default of 128 levels, an unoptimized build needs up to
|
||||
about 290 KiB (`dump()` with Clang at -O0: about 2.3 KiB per level) and an
|
||||
optimized build up to about 48 KiB; with 254 levels it is up to 576 KiB and
|
||||
112 KiB, and with 0 it is 16 KiB or less. Lower the limit for threads with small
|
||||
stacks (RTOS tasks, fibers, musl threads), raise it to trade stack for speed on
|
||||
deeply nested values. All of this, with the measurements, is in the
|
||||
documentation of the macro.
|
||||
|
||||
@sa https://json.nlohmann.me/api/macros/json_recursion_depth_limit/
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
/// the type of the value of JSON_RECURSION_DEPTH_LIMIT is bool or a character type
|
||||
template<typename T> struct recursion_depth_limit_is_char_or_bool : std::false_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<bool> : std::true_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<char> : std::true_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<signed char> : std::true_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<unsigned char> : std::true_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<wchar_t> : std::true_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<char16_t> : std::true_type {};
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<char32_t> : std::true_type {};
|
||||
#if defined(__cpp_char8_t)
|
||||
template<> struct recursion_depth_limit_is_char_or_bool<char8_t> : std::true_type {};
|
||||
#endif
|
||||
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
{
|
||||
return 128;
|
||||
// an integer: not a floating-point number, whose range check would pass for 1.5 or 254.0
|
||||
// (bool and the character types are integer types, too, but true, false or 'a' are
|
||||
// no way to say a limit; JSON_UBJSON_VALUELESS_ELEMENTS_PER_BYTE refuses them as well)
|
||||
static_assert(std::is_integral<decltype(JSON_RECURSION_DEPTH_LIMIT)>::value
|
||||
&& !recursion_depth_limit_is_char_or_bool<typename std::remove_cv<decltype(JSON_RECURSION_DEPTH_LIMIT)>::type>::value,
|
||||
"JSON_RECURSION_DEPTH_LIMIT must be an integer constant (not bool or a character type)");
|
||||
static_assert(JSON_RECURSION_DEPTH_LIMIT >= 0 && JSON_RECURSION_DEPTH_LIMIT <= 254,
|
||||
"JSON_RECURSION_DEPTH_LIMIT must be between 0 and 254: the nesting depth is counted in a byte per thread "
|
||||
"and may exceed the limit by one (see https://json.nlohmann.me/api/macros/json_recursion_depth_limit/)");
|
||||
return static_cast<std::size_t>(JSON_RECURSION_DEPTH_LIMIT);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -1256,8 +1256,10 @@ public:
|
||||
}
|
||||
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
// nesting_depth() is a byte and may exceed the limit by one level
|
||||
static_assert(detail::recursion_depth_limit() < 255, "the nesting depth count must fit in a byte");
|
||||
// nesting_depth() is a byte that never exceeds the limit (JSON_RECURSION_DEPTH_LIMIT),
|
||||
// which is at most 254: the largest value stays one below the byte's maximum, so
|
||||
// that the count cannot wrap around to 0 and let a descent go on without a bound.
|
||||
// recursion_depth_limit() checks this range with a static_assert.
|
||||
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
@@ -1268,8 +1270,15 @@ public:
|
||||
user code nests them anyway, sharing the count only ends a descent sooner
|
||||
than it had to, which costs a little speed and is never wrong.
|
||||
|
||||
A byte is enough: the count never exceeds the limit by more than the single
|
||||
level that notices the limit has been reached.
|
||||
A byte is enough: only the levels that are descended into are counted
|
||||
(@ref nesting_depth_guard), so the count never exceeds the limit, and the
|
||||
limit is at most 254.
|
||||
|
||||
The count is per thread and not per value or per library configuration: all
|
||||
translation units of a program share it, even if they were compiled with
|
||||
different values of JSON_RECURSION_DEPTH_LIMIT (which is not supported, see
|
||||
the documentation of the macro); each of them then compares the shared count
|
||||
with its own limit.
|
||||
*/
|
||||
static std::uint8_t& nesting_depth() noexcept
|
||||
{
|
||||
@@ -1327,14 +1336,25 @@ public:
|
||||
#endif
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
++nesting_depth();
|
||||
// Only a level that is entered is counted, so that the count never
|
||||
// exceeds the limit. A level that is not entered is finished without
|
||||
// descending, and whatever runs while it is (a copy made by a custom
|
||||
// base class, say) sees the same count as the level itself did and
|
||||
// is not entered either.
|
||||
if (m_okay)
|
||||
{
|
||||
++nesting_depth();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
~nesting_depth_guard()
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
--nesting_depth();
|
||||
if (m_okay)
|
||||
{
|
||||
--nesting_depth();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5246,7 +5266,10 @@ public:
|
||||
*/
|
||||
void update_members(const iterator& first, const iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
// without merge_objects the loop below never descends, so there is no
|
||||
// call stack to save (and the explicit stack always merges); the
|
||||
// distinction matters for a limit of 0, which reaches this at depth 0
|
||||
if (JSON_HEDLEY_UNLIKELY(merge_objects && depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
update_members_iteratively(first, last);
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user