diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index c55bdf54b..a337c1c69 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1304,12 +1304,25 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } - reference set_parent(reference j) + reference set_parent(reference j, std::size_t old_capacity = std::size_t(-1)) { #if JSON_DIAGNOSTICS + if (old_capacity != std::size_t(-1)) + { + // see https://github.com/nlohmann/json/issues/2838 + JSON_ASSERT(type() == value_t::array); + if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity)) + { + // capacity has changed: update all parents + set_parents(); + return j; + } + } + j.m_parent = this; #else static_cast(j); + static_cast(old_capacity); #endif return j; } @@ -5371,8 +5384,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // add element to array (move semantics) + const auto old_capacity = m_value.array->capacity(); m_value.array->push_back(std::move(val)); - set_parent(m_value.array->back()); + set_parent(m_value.array->back(), old_capacity); // if val is moved from, basic_json move constructor marks it null so we do not call the destructor } @@ -5407,8 +5421,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // add element to array + const auto old_capacity = m_value.array->capacity(); m_value.array->push_back(val); - set_parent(m_value.array->back()); + set_parent(m_value.array->back(), old_capacity); } /*! @@ -5562,12 +5577,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // add element to array (perfect forwarding) -#ifdef JSON_HAS_CPP_17 - return set_parent(m_value.array->emplace_back(std::forward(args)...)); -#else + const auto old_capacity = m_value.array->capacity(); m_value.array->emplace_back(std::forward(args)...); - return set_parent(m_value.array->back()); -#endif + return set_parent(m_value.array->back(), old_capacity); } /*! @@ -5643,6 +5655,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + set_parents(); return result; } @@ -5680,7 +5693,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, val), static_cast(1)); + return insert_iterator(pos, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); @@ -5731,7 +5744,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, cnt, val), static_cast(cnt)); + return insert_iterator(pos, cnt, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); @@ -5793,7 +5806,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator), std::distance(first, last)); + return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); } /*! @@ -5835,7 +5848,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, ilist.begin(), ilist.end()), static_cast(ilist.size())); + return insert_iterator(pos, ilist.begin(), ilist.end()); } /*! diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index cbe69ef47..429964dd7 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18339,12 +18339,25 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } - reference set_parent(reference j) + reference set_parent(reference j, std::size_t old_capacity = std::size_t(-1)) { #if JSON_DIAGNOSTICS + if (old_capacity != std::size_t(-1)) + { + // see https://github.com/nlohmann/json/issues/2838 + JSON_ASSERT(type() == value_t::array); + if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity)) + { + // capacity has changed: update all parents + set_parents(); + return j; + } + } + j.m_parent = this; #else static_cast(j); + static_cast(old_capacity); #endif return j; } @@ -22406,8 +22419,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // add element to array (move semantics) + const auto old_capacity = m_value.array->capacity(); m_value.array->push_back(std::move(val)); - set_parent(m_value.array->back()); + set_parent(m_value.array->back(), old_capacity); // if val is moved from, basic_json move constructor marks it null so we do not call the destructor } @@ -22442,8 +22456,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // add element to array + const auto old_capacity = m_value.array->capacity(); m_value.array->push_back(val); - set_parent(m_value.array->back()); + set_parent(m_value.array->back(), old_capacity); } /*! @@ -22597,12 +22612,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // add element to array (perfect forwarding) -#ifdef JSON_HAS_CPP_17 - return set_parent(m_value.array->emplace_back(std::forward(args)...)); -#else + const auto old_capacity = m_value.array->capacity(); m_value.array->emplace_back(std::forward(args)...); - return set_parent(m_value.array->back()); -#endif + return set_parent(m_value.array->back(), old_capacity); } /*! @@ -22678,6 +22690,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + set_parents(); return result; } @@ -22715,7 +22728,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, val), static_cast(1)); + return insert_iterator(pos, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); @@ -22766,7 +22779,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, cnt, val), static_cast(cnt)); + return insert_iterator(pos, cnt, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); @@ -22828,7 +22841,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator), std::distance(first, last)); + return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); } /*! @@ -22870,7 +22883,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // insert to array and return iterator - return set_parents(insert_iterator(pos, ilist.begin(), ilist.end()), static_cast(ilist.size())); + return insert_iterator(pos, ilist.begin(), ilist.end()); } /*! diff --git a/test/src/unit-diagnostics.cpp b/test/src/unit-diagnostics.cpp index 21ced33b1..ebbe64f38 100644 --- a/test/src/unit-diagnostics.cpp +++ b/test/src/unit-diagnostics.cpp @@ -109,4 +109,69 @@ TEST_CASE("Better diagnostics") j["/foo"] = {1, 2, 3}; CHECK_THROWS_WITH_AS(j.unflatten(), "[json.exception.type_error.315] (/~1foo) values in object must be primitive", json::type_error); } + + SECTION("Regression test for https://github.com/nlohmann/json/issues/2838") + { + // void push_back(basic_json&& val) + { + json j_arr = json::array(); + j_arr.push_back(json::object()); + j_arr.push_back(json::object()); + j_arr.push_back(json::object()); + j_arr.push_back(json::object()); + json j_obj = json::object(); + j_obj["key"] = j_arr; + } + + // void push_back(const basic_json& val) + { + json j_arr = json::array(); + auto object = json::object(); + j_arr.push_back(object); + j_arr.push_back(object); + j_arr.push_back(object); + j_arr.push_back(object); + json j_obj = json::object(); + j_obj["key"] = j_arr; + } + + // reference emplace_back(Args&& ... args) + { + json j_arr = json::array(); + j_arr.emplace_back(json::object()); + j_arr.emplace_back(json::object()); + j_arr.emplace_back(json::object()); + j_arr.emplace_back(json::object()); + json j_obj = json::object(); + j_obj["key"] = j_arr; + } + + // iterator insert(const_iterator pos, const basic_json& val) + { + json j_arr = json::array(); + j_arr.insert(j_arr.begin(), json::object()); + j_arr.insert(j_arr.begin(), json::object()); + j_arr.insert(j_arr.begin(), json::object()); + j_arr.insert(j_arr.begin(), json::object()); + json j_obj = json::object(); + j_obj["key"] = j_arr; + } + + // iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + json j_arr = json::array(); + j_arr.insert(j_arr.begin(), 2, json::object()); + json j_obj = json::object(); + j_obj["key"] = j_arr; + } + + // iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + json j_arr = json::array(); + json j_objects = {json::object(), json::object()}; + j_arr.insert(j_arr.begin(), j_objects.begin(), j_objects.end()); + json j_obj = json::object(); + j_obj["key"] = j_arr; + } + } }