mirror of
https://github.com/nlohmann/json.git
synced 2026-08-22 00:53:18 +00:00
Use the shared descent bookkeeping rather than a second set
Comparing kept a thread_local count, a limit and a guard of its own beside the ones copying already had, all three the same thing under a different name. They are gone; the shared count, limit and guard do the work. The guard grows a second constructor here, because the comparison operators are written as a macro and a macro cannot use the preprocessor: it cannot look the count up behind an #ifdef the way copy_structured does, so the guard looks it up for it. nesting_depth_exhausted() arrives for the same reason - whether an operator descends at all is a constant at every call site, and testing it there is what MSVC reports as C4127. Also say in compare_leaves what happens to a pair that is an array on one side and an object on the other, since the answer is not obvious from the code: an operator only descends into two values of the same type, so such a pair is told apart by its types alone - unequal, and ordered the way the types are - exactly as it is above the bound. And record what the explicit stack costs: the comparison operators are noexcept and the container comparison this replaces allocated nothing, so running out of memory here ends the process instead of throwing. It takes a value nested past the bound and an exhausted heap to reach, and the same comparison used to exhaust the call stack, but it is a new way to fail. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -22200,35 +22200,61 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
static thread_local std::size_t depth = 0; // NOLINT(misc-use-internal-linkage)
|
||||
return depth;
|
||||
}
|
||||
|
||||
/// @brief how many levels the comparison going on in this thread has descended into
|
||||
static std::size_t& compare_depth() noexcept
|
||||
{
|
||||
static thread_local std::size_t depth = 0; // NOLINT(misc-use-internal-linkage)
|
||||
return depth;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief whether a descent must stop here and finish without the call stack
|
||||
|
||||
@a may_descend says whether the operator descends at all; it is a constant
|
||||
at every call site, and is passed rather than tested by the caller so that
|
||||
the test does not become a constant condition there, which MSVC reports as
|
||||
C4127.
|
||||
*/
|
||||
static bool nesting_depth_exhausted(bool may_descend = true) noexcept
|
||||
{
|
||||
#ifdef JSON_NO_THREAD_LOCAL
|
||||
// without a count of its own per thread, a descent cannot be bounded
|
||||
// without racing another one, so none is made
|
||||
static_cast<void>(may_descend);
|
||||
return true;
|
||||
#else
|
||||
return !may_descend || nesting_depth() >= nesting_depth_limit();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief counts one level of a bounded descent for as long as it runs
|
||||
|
||||
The count is taken rather than looked up here, because the caller has looked
|
||||
it up already to test it against the limit: reaching thread-local storage is
|
||||
not free, and the path that is taken almost every time should reach it once
|
||||
rather than twice.
|
||||
The constructor taking the count is for callers that have looked it up
|
||||
already to test it: reaching thread-local storage is not free, and the path
|
||||
that is taken almost every time should reach it once rather than twice. The
|
||||
other is for callers that cannot look it up - the comparison operators are
|
||||
written as a macro, and a macro cannot use the preprocessor.
|
||||
*/
|
||||
class nesting_depth_guard
|
||||
{
|
||||
public:
|
||||
explicit nesting_depth_guard(std::size_t& depth) noexcept
|
||||
: m_depth(depth)
|
||||
: m_depth(&depth)
|
||||
{
|
||||
++m_depth;
|
||||
++*m_depth;
|
||||
}
|
||||
|
||||
nesting_depth_guard() noexcept
|
||||
: m_depth(countable())
|
||||
{
|
||||
if (m_depth != nullptr)
|
||||
{
|
||||
++*m_depth;
|
||||
}
|
||||
}
|
||||
|
||||
~nesting_depth_guard()
|
||||
{
|
||||
--m_depth;
|
||||
if (m_depth != nullptr)
|
||||
{
|
||||
--*m_depth;
|
||||
}
|
||||
}
|
||||
|
||||
nesting_depth_guard(const nesting_depth_guard&) = delete;
|
||||
@@ -22237,7 +22263,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
nesting_depth_guard& operator=(nesting_depth_guard&&) = delete;
|
||||
|
||||
private:
|
||||
std::size_t& m_depth;
|
||||
/// @brief the count to keep, or nullptr where there is none to keep
|
||||
static std::size_t* countable() noexcept
|
||||
{
|
||||
#ifdef JSON_NO_THREAD_LOCAL
|
||||
return nullptr;
|
||||
#else
|
||||
return &nesting_depth();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t* m_depth;
|
||||
};
|
||||
|
||||
/// an entry of the iterative deep copy's worklist: a structured value and
|
||||
@@ -22516,53 +22552,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// ordered at all, such as a discarded value or a NaN
|
||||
enum class compare_result { less, equal, greater, unordered };
|
||||
|
||||
/*!
|
||||
@brief counts one level of a comparison for as long as it runs
|
||||
|
||||
Does nothing without thread_local storage, where no descent is made at all.
|
||||
*/
|
||||
class compare_depth_guard
|
||||
{
|
||||
public:
|
||||
compare_depth_guard() noexcept
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
++compare_depth();
|
||||
#endif
|
||||
}
|
||||
|
||||
~compare_depth_guard() noexcept
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
--compare_depth();
|
||||
#endif
|
||||
}
|
||||
|
||||
compare_depth_guard(const compare_depth_guard&) = delete;
|
||||
compare_depth_guard& operator=(const compare_depth_guard&) = delete;
|
||||
compare_depth_guard(compare_depth_guard&&) = delete;
|
||||
compare_depth_guard& operator=(compare_depth_guard&&) = delete;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief whether a comparison must stop descending and finish iteratively
|
||||
|
||||
@a may_descend says whether the operator descends at all; it is constant at
|
||||
every call site, and is passed rather than tested by the caller so that the
|
||||
test does not become a constant condition there, which MSVC reports (C4127).
|
||||
*/
|
||||
static bool compare_descent_exhausted(bool may_descend) noexcept
|
||||
{
|
||||
#ifdef JSON_NO_THREAD_LOCAL
|
||||
// without a counter of its own per thread, the descent cannot be
|
||||
// bounded without racing another one, so none is made
|
||||
static_cast<void>(may_descend);
|
||||
return true;
|
||||
#else
|
||||
return !may_descend || compare_depth() >= compare_depth_limit();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
/// @brief the ordering that @a result stands for
|
||||
static std::partial_ordering to_partial_ordering(compare_result result) noexcept // *NOPAD*
|
||||
@@ -22582,18 +22571,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the number of levels a comparison descends into before it compares what
|
||||
/// is left without the call stack
|
||||
static constexpr std::size_t compare_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief compare two values that are not both an array or both an object
|
||||
|
||||
Such a pair is compared by the operators themselves, which cannot descend
|
||||
into it and therefore cannot recurse.
|
||||
|
||||
That holds for a pair whose types differ as much as for a pair of leaves: an
|
||||
array and an object are told apart by their types alone, because an operator
|
||||
only ever descends into two values of the same type. So `==` reports them as
|
||||
unequal without looking inside either, and an ordering falls back to the
|
||||
order of the types - an object sorts before an array - exactly as it does
|
||||
for a value that is not nested deeply enough to get here.
|
||||
*/
|
||||
template<bool Ordered>
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs) noexcept
|
||||
@@ -22667,7 +22656,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 compare_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
|
||||
@@ -22676,6 +22665,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
nlohmann::ordered_map in insertion order. An object type that enumerates its
|
||||
entries in an unspecified order, such as std::unordered_map, compares them
|
||||
pairwise instead; the difference could only ever show below the bound.
|
||||
|
||||
Note that the stack this walks with is allocated, while the comparison
|
||||
operators are noexcept and the container comparison this replaces allocated
|
||||
nothing. Failing that allocation therefore ends the process rather than
|
||||
throwing. It only arises for values nested past the bound, and only when
|
||||
memory has run out - where the same comparison used to exhaust the call
|
||||
stack instead - but it is a way to fail that the operators did not have.
|
||||
*/
|
||||
template<bool Ordered>
|
||||
static compare_result compare_iteratively(const_reference lhs, const_reference rhs,
|
||||
@@ -25601,21 +25597,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{ \
|
||||
case value_t::array: \
|
||||
{ \
|
||||
if (JSON_HEDLEY_UNLIKELY(compare_descent_exhausted(may_descend))) \
|
||||
if (JSON_HEDLEY_UNLIKELY(nesting_depth_exhausted(may_descend))) \
|
||||
{ \
|
||||
return (deep_result); \
|
||||
} \
|
||||
const compare_depth_guard guard; \
|
||||
const nesting_depth_guard guard; \
|
||||
return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array); \
|
||||
} \
|
||||
\
|
||||
case value_t::object: \
|
||||
{ \
|
||||
if (JSON_HEDLEY_UNLIKELY(compare_descent_exhausted(may_descend))) \
|
||||
if (JSON_HEDLEY_UNLIKELY(nesting_depth_exhausted(may_descend))) \
|
||||
{ \
|
||||
return (deep_result); \
|
||||
} \
|
||||
const compare_depth_guard guard; \
|
||||
const nesting_depth_guard guard; \
|
||||
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
||||
} \
|
||||
\
|
||||
|
||||
Reference in New Issue
Block a user