mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 03:30:31 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9802542843 |
@@ -19,17 +19,11 @@ namespace detail
|
||||
/*!
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (copying, comparing, serializing, hashing, merging,
|
||||
...) recurse once
|
||||
Operations that walk a value (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.
|
||||
|
||||
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.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
|
||||
@@ -898,8 +898,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
#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");
|
||||
/// the number of levels an operation descends into before it finishes the
|
||||
/// value below it without the call stack
|
||||
static constexpr std::uint8_t nesting_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
@@ -941,7 +945,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
static_cast<void>(may_descend);
|
||||
return true;
|
||||
#else
|
||||
return !may_descend || nesting_depth() >= detail::recursion_depth_limit();
|
||||
return !may_descend || nesting_depth() >= nesting_depth_limit();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -965,7 +969,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#ifdef JSON_NO_THREAD_LOCAL
|
||||
: m_okay(false)
|
||||
#else
|
||||
: m_okay(nesting_depth() < detail::recursion_depth_limit())
|
||||
: m_okay(nesting_depth() < nesting_depth_limit())
|
||||
#endif
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
@@ -1169,7 +1173,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
The values whose copy has not been created yet are kept on an explicit
|
||||
worklist rather than on the call stack. This is only reached for values
|
||||
nested deeper than @ref detail::recursion_depth_limit levels, which is why it copies
|
||||
nested deeper than @ref nesting_depth_limit levels, which is why it copies
|
||||
every container by hand instead of letting the container do it: the fast
|
||||
ways of doing so would descend into the elements and defeat the purpose.
|
||||
*/
|
||||
@@ -1235,7 +1239,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
Copying a container copies its elements, so a value nested deeply enough
|
||||
used to exhaust the call stack. The descent is bounded here: the first
|
||||
@ref detail::recursion_depth_limit levels are copied by the containers themselves, just
|
||||
@ref nesting_depth_limit levels are copied by the containers themselves, just
|
||||
as they always were, and anything below that is copied without the call
|
||||
stack by @ref copy_iteratively. Copying a value can therefore no longer
|
||||
exhaust the stack, however deeply it is nested, just like destroying one
|
||||
@@ -1373,7 +1377,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/*!
|
||||
@brief compare @a lhs and @a rhs without descending into them
|
||||
|
||||
Reached once a comparison has descended @ref detail::recursion_depth_limit levels, so
|
||||
Reached once a comparison has descended @ref nesting_depth_limit levels, so
|
||||
that comparing values cannot exhaust the call stack however deeply they are
|
||||
nested. The two values are walked in lockstep on an explicit stack and
|
||||
compared lexicographically, element by element in the order the containers
|
||||
@@ -6183,7 +6187,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
|
||||
// Only an object type that keeps its members in insertion
|
||||
// order, such as nlohmann::ordered_map, can need reordering:
|
||||
// patch() appends a new member at the end of such an object.
|
||||
// Any other object type places its members itself - std::map
|
||||
// in key order, a hash map in an order its operator== ignores -
|
||||
// so a member-by-member diff always reproduces target there.
|
||||
if (!detail::is_ordered_map<object_t>::value
|
||||
|| (common_keys_source_order == common_keys_target_order && new_keys_form_suffix))
|
||||
{
|
||||
// fast path: order of common keys already matches (or the
|
||||
// object_t's iteration order does not depend on
|
||||
|
||||
@@ -7281,17 +7281,11 @@ namespace detail
|
||||
/*!
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (copying, comparing, serializing, hashing, merging,
|
||||
...) recurse once
|
||||
Operations that walk a value (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.
|
||||
|
||||
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.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
@@ -26985,8 +26979,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
#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");
|
||||
/// the number of levels an operation descends into before it finishes the
|
||||
/// value below it without the call stack
|
||||
static constexpr std::uint8_t nesting_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
@@ -27028,7 +27026,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
static_cast<void>(may_descend);
|
||||
return true;
|
||||
#else
|
||||
return !may_descend || nesting_depth() >= detail::recursion_depth_limit();
|
||||
return !may_descend || nesting_depth() >= nesting_depth_limit();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -27052,7 +27050,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#ifdef JSON_NO_THREAD_LOCAL
|
||||
: m_okay(false)
|
||||
#else
|
||||
: m_okay(nesting_depth() < detail::recursion_depth_limit())
|
||||
: m_okay(nesting_depth() < nesting_depth_limit())
|
||||
#endif
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
@@ -27256,7 +27254,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
The values whose copy has not been created yet are kept on an explicit
|
||||
worklist rather than on the call stack. This is only reached for values
|
||||
nested deeper than @ref detail::recursion_depth_limit levels, which is why it copies
|
||||
nested deeper than @ref nesting_depth_limit levels, which is why it copies
|
||||
every container by hand instead of letting the container do it: the fast
|
||||
ways of doing so would descend into the elements and defeat the purpose.
|
||||
*/
|
||||
@@ -27322,7 +27320,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
Copying a container copies its elements, so a value nested deeply enough
|
||||
used to exhaust the call stack. The descent is bounded here: the first
|
||||
@ref detail::recursion_depth_limit levels are copied by the containers themselves, just
|
||||
@ref nesting_depth_limit levels are copied by the containers themselves, just
|
||||
as they always were, and anything below that is copied without the call
|
||||
stack by @ref copy_iteratively. Copying a value can therefore no longer
|
||||
exhaust the stack, however deeply it is nested, just like destroying one
|
||||
@@ -27460,7 +27458,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/*!
|
||||
@brief compare @a lhs and @a rhs without descending into them
|
||||
|
||||
Reached once a comparison has descended @ref detail::recursion_depth_limit levels, so
|
||||
Reached once a comparison has descended @ref nesting_depth_limit levels, so
|
||||
that comparing values cannot exhaust the call stack however deeply they are
|
||||
nested. The two values are walked in lockstep on an explicit stack and
|
||||
compared lexicographically, element by element in the order the containers
|
||||
@@ -32270,7 +32268,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
|
||||
// Only an object type that keeps its members in insertion
|
||||
// order, such as nlohmann::ordered_map, can need reordering:
|
||||
// patch() appends a new member at the end of such an object.
|
||||
// Any other object type places its members itself - std::map
|
||||
// in key order, a hash map in an order its operator== ignores -
|
||||
// so a member-by-member diff always reproduces target there.
|
||||
if (!detail::is_ordered_map<object_t>::value
|
||||
|| (common_keys_source_order == common_keys_target_order && new_keys_form_suffix))
|
||||
{
|
||||
// fast path: order of common keys already matches (or the
|
||||
// object_t's iteration order does not depend on
|
||||
|
||||
@@ -1752,6 +1752,58 @@ TEST_CASE("JSON patch - diff emits array removals in descending index order")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - diff() takes the fast path for non-reorderable object types (regression #5639)")
|
||||
{
|
||||
// #5465 added an order check to diff()'s object handling so a
|
||||
// member-by-member diff is only used when it would also reproduce
|
||||
// target's member *order* -- needed for ordered_json, whose object_t
|
||||
// keeps insertion order and whose patch() "add" op appends a new
|
||||
// member at the end. For json's default object_t (std::map, which
|
||||
// orders members by key regardless of insertion history), that check
|
||||
// could still fail: a new key that sorts before an existing common key
|
||||
// makes target's iteration interleave the new key between common keys,
|
||||
// even though nothing else about the object changed. That sent the
|
||||
// whole object through the slow (remove-every-member,
|
||||
// re-add-every-member) path instead of the minimal one.
|
||||
SECTION("json: added key sorts before an existing common key")
|
||||
{
|
||||
const json source = {{"a", 1}, {"c", {{"x", 1}, {"y", 2}}}};
|
||||
const json target = {{"a", 1}, {"b", 0}, {"c", {{"x", 1}, {"y", 2}}}};
|
||||
|
||||
const json patch = json::diff(source, target);
|
||||
|
||||
// only the new key is added; "a" and "c" are left alone instead of
|
||||
// being removed and re-added
|
||||
const json expected = R"([{"op": "add", "path": "/b", "value": 0}])"_json;
|
||||
CHECK(patch == expected);
|
||||
CHECK(source.patch(patch) == target);
|
||||
}
|
||||
|
||||
SECTION("ordered_json: reordering behavior from #5465 is unchanged")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
// same key/value shape as the json case above, but for ordered_json
|
||||
// the *target*'s member order must be reproduced, so the slow path
|
||||
// is still required here.
|
||||
ordered_json source;
|
||||
source["a"] = 1;
|
||||
source["c"] = ordered_json{{"x", 1}, {"y", 2}};
|
||||
|
||||
ordered_json target;
|
||||
target["a"] = 1;
|
||||
target["b"] = 0;
|
||||
target["c"] = ordered_json{{"x", 1}, {"y", 2}};
|
||||
|
||||
const ordered_json patch = ordered_json::diff(source, target);
|
||||
|
||||
// unlike the json case: every member is still removed and re-added
|
||||
// so the result ends up in target's order (2 removes + 3 adds)
|
||||
CHECK(patch.size() == 5);
|
||||
CHECK(source.patch(patch) == target);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - every operation on ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
Reference in New Issue
Block a user