From 2038838eea7eadcf44b35cb7659044958c921993 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 27 Sep 2026 14:00:04 +0200 Subject: [PATCH] Copy the diff frame's members instead of holding a reference to it The loop in diff_iteratively held a reference to the top frame, which enter() invalidates when it pushes and the end of the loop invalidates when it pops. Nothing used it afterwards, but a later change could. As in the other iterative walks, the members the loop reads are now copied out as constants and the ones it advances are changed through stack.back(). The frame as a whole is not copied: it holds the common keys and the "add" operations of an object. Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 42 +++++++++++++++++--------------- single_include/nlohmann/json.hpp | 42 +++++++++++++++++--------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 8a4c4b167..f63ea6108 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6391,23 +6391,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec enter(source, target); while (!stack.empty()) { - // invalidated when enter() pushes a frame and by the pop_back() - // at the end, so not used after either - diff_frame& frame = stack.back(); - const std::size_t path_length = frame.path_length; + // the frame is copied out member by member and changed through + // stack.back(): enter() may push a frame and the end of the loop + // pops it, either of which would invalidate a reference to it + const basic_json* const s = stack.back().source; + const basic_json* const t = stack.back().target; + const std::size_t path_length = stack.back().path_length; const std::size_t depth = stack.size(); - if (frame.source->is_array()) + if (s->is_array()) { - const auto& source_array = *frame.source->m_data.m_value.array; - const auto& target_array = *frame.target->m_data.m_value.array; + const auto& source_array = *s->m_data.m_value.array; + const auto& target_array = *t->m_data.m_value.array; // first pass: traverse common elements - if (frame.index < source_array.size() && frame.index < target_array.size()) + const std::size_t i = stack.back().index; + if (i < source_array.size() && i < target_array.size()) { - const std::size_t i = frame.index++; + ++stack.back().index; detail::concat_into(current_path, '/', detail::to_string(i)); - enter(source_array[i], target_array[i]); // may push, which invalidates `frame` + enter(source_array[i], target_array[i]); if (stack.size() == depth) { current_path.resize(path_length); @@ -6417,20 +6420,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // We now reached the end of at least one array // in a second pass, traverse the remaining elements - diff_array_tails(result, *frame.source, *frame.target, current_path, frame.index); + diff_array_tails(result, *s, *t, current_path, i); } else { - if (frame.member != frame.source->cend()) + const const_iterator it = stack.back().member; + if (it != s->cend()) { - const const_iterator it = frame.member; - ++frame.member; - if (frame.next_common < frame.common_keys.size() && it.key() == frame.common_keys[frame.next_common]) + ++stack.back().member; + const std::size_t next_common = stack.back().next_common; + if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common]) { - ++frame.next_common; - const basic_json& target_value = (*frame.target)[it.key()]; + ++stack.back().next_common; + const basic_json& target_value = (*t)[it.key()]; detail::concat_into(current_path, '/', detail::escape(it.key())); - enter(it.value(), target_value); // may push, which invalidates `frame` + enter(it.value(), target_value); if (stack.size() == depth) { current_path.resize(path_length); @@ -6446,7 +6450,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // append the "add" ops for brand-new keys collected when the // object was entered - result.insert(result.end(), frame.added_ops.begin(), frame.added_ops.end()); + result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end()); } // this array or object is done: continue with the one it is in diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 163504e5c..0f85bc61c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -31349,23 +31349,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec enter(source, target); while (!stack.empty()) { - // invalidated when enter() pushes a frame and by the pop_back() - // at the end, so not used after either - diff_frame& frame = stack.back(); - const std::size_t path_length = frame.path_length; + // the frame is copied out member by member and changed through + // stack.back(): enter() may push a frame and the end of the loop + // pops it, either of which would invalidate a reference to it + const basic_json* const s = stack.back().source; + const basic_json* const t = stack.back().target; + const std::size_t path_length = stack.back().path_length; const std::size_t depth = stack.size(); - if (frame.source->is_array()) + if (s->is_array()) { - const auto& source_array = *frame.source->m_data.m_value.array; - const auto& target_array = *frame.target->m_data.m_value.array; + const auto& source_array = *s->m_data.m_value.array; + const auto& target_array = *t->m_data.m_value.array; // first pass: traverse common elements - if (frame.index < source_array.size() && frame.index < target_array.size()) + const std::size_t i = stack.back().index; + if (i < source_array.size() && i < target_array.size()) { - const std::size_t i = frame.index++; + ++stack.back().index; detail::concat_into(current_path, '/', detail::to_string(i)); - enter(source_array[i], target_array[i]); // may push, which invalidates `frame` + enter(source_array[i], target_array[i]); if (stack.size() == depth) { current_path.resize(path_length); @@ -31375,20 +31378,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // We now reached the end of at least one array // in a second pass, traverse the remaining elements - diff_array_tails(result, *frame.source, *frame.target, current_path, frame.index); + diff_array_tails(result, *s, *t, current_path, i); } else { - if (frame.member != frame.source->cend()) + const const_iterator it = stack.back().member; + if (it != s->cend()) { - const const_iterator it = frame.member; - ++frame.member; - if (frame.next_common < frame.common_keys.size() && it.key() == frame.common_keys[frame.next_common]) + ++stack.back().member; + const std::size_t next_common = stack.back().next_common; + if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common]) { - ++frame.next_common; - const basic_json& target_value = (*frame.target)[it.key()]; + ++stack.back().next_common; + const basic_json& target_value = (*t)[it.key()]; detail::concat_into(current_path, '/', detail::escape(it.key())); - enter(it.value(), target_value); // may push, which invalidates `frame` + enter(it.value(), target_value); if (stack.size() == depth) { current_path.resize(path_length); @@ -31404,7 +31408,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // append the "add" ops for brand-new keys collected when the // object was entered - result.insert(result.end(), frame.added_ops.begin(), frame.added_ops.end()); + result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end()); } // this array or object is done: continue with the one it is in