mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 04:00:31 +00:00
Fix update() and merge_patch() when the argument is *this or one of its members (#5678)
This commit is contained in:
@@ -37,6 +37,12 @@ Thereby, `Target` is the current object; that is, the patch is applied to the cu
|
||||
|
||||
Linear in the lengths of `apply_patch`.
|
||||
|
||||
## Notes
|
||||
|
||||
`apply_patch` may be `#!cpp *this` itself or refer to a value contained in `#!cpp *this` (for example, a subobject
|
||||
returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was called, before any modification of
|
||||
`#!cpp *this`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -61,3 +67,5 @@ Linear in the lengths of `apply_patch`.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.0.0.
|
||||
- Fixed use of freed or relocated memory when `apply_patch` is `#!cpp *this` or refers to a value contained in
|
||||
`#!cpp *this`, in version 3.13.0.
|
||||
|
||||
@@ -59,6 +59,12 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value
|
||||
1. O(N*log(size() + N)), where N is the number of elements to insert.
|
||||
2. O(N*log(size() + N)), where N is the number of elements to insert.
|
||||
|
||||
## Notes
|
||||
|
||||
The argument `j` (or, for overload (2), the range `[first, last)`) may be `#!cpp *this` itself or refer to a value
|
||||
contained in `#!cpp *this` (for example, a subobject returned by `#!cpp (*this)[key]`); it is read as it was when
|
||||
`update()` was called, before any modification of `#!cpp *this`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -155,3 +161,5 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value
|
||||
|
||||
- Added in version 3.0.0.
|
||||
- Added `merge_objects` parameter in 3.10.5.
|
||||
- Fixed use of freed or relocated memory when the argument is `#!cpp *this` or refers to a value contained in
|
||||
`#!cpp *this`, in version 3.13.0.
|
||||
|
||||
+68
-33
@@ -4415,25 +4415,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/update/
|
||||
void update(const_reference j, bool merge_objects = false)
|
||||
{
|
||||
update(j.begin(), j.end(), merge_objects);
|
||||
prepare_update();
|
||||
|
||||
// passed value must be an object (checked here so a type_error names
|
||||
// j, not the copy made below)
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j));
|
||||
}
|
||||
|
||||
// copy first: j may be *this or one of its descendants, and is
|
||||
// iterated (and moved from) below to update *this
|
||||
basic_json source = j;
|
||||
update_from(source, merge_objects);
|
||||
}
|
||||
|
||||
/// @brief updates a JSON object from another object, overwriting existing keys
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/update/
|
||||
void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// implicitly convert a null value to an empty object
|
||||
if (is_null())
|
||||
{
|
||||
m_data.m_type = value_t::object;
|
||||
m_data.m_value.object = create<object_t>();
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this));
|
||||
}
|
||||
prepare_update();
|
||||
|
||||
// check if range iterators belong to the same JSON object
|
||||
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
|
||||
@@ -4447,7 +4448,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object));
|
||||
}
|
||||
|
||||
update_members(first, last, merge_objects, 0);
|
||||
// copy first: the range may belong to *this or one of its
|
||||
// descendants, and is iterated (and moved from) below to update
|
||||
// *this
|
||||
basic_json source(first, last);
|
||||
update_from(source, merge_objects);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -4455,15 +4460,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// merge_patch_iteratively is merging into, and the members still to merge
|
||||
struct merge_frame
|
||||
{
|
||||
merge_frame(basic_json* target_, const_iterator position_, const_iterator last_) noexcept
|
||||
merge_frame(basic_json* target_, iterator position_, iterator last_) noexcept
|
||||
: target(target_), position(std::move(position_)), last(std::move(last_))
|
||||
{}
|
||||
|
||||
basic_json* target;
|
||||
const_iterator position;
|
||||
const_iterator last;
|
||||
iterator position;
|
||||
iterator last;
|
||||
};
|
||||
|
||||
/// @brief converts a null value to an empty object and checks that this
|
||||
/// value is an object; called first by both @ref update overloads
|
||||
void prepare_update()
|
||||
{
|
||||
// implicitly convert a null value to an empty object; create the
|
||||
// object before setting the type, so a throwing allocation leaves
|
||||
// this value null
|
||||
if (is_null())
|
||||
{
|
||||
m_data.m_value.object = create<object_t>();
|
||||
m_data.m_type = value_t::object;
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief starts the @ref update_members loop over an already-copied @a
|
||||
/// source; called by both @ref update overloads
|
||||
void update_from(basic_json& source, const bool merge_objects)
|
||||
{
|
||||
update_members(source.begin(), source.end(), merge_objects, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the members loop of @ref update, for this object and range
|
||||
|
||||
@@ -4476,7 +4508,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
@param[in] depth nesting level of this object, counted from the object
|
||||
@ref update was called on
|
||||
*/
|
||||
void update_members(const const_iterator& first, const const_iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
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()))
|
||||
{
|
||||
@@ -4494,13 +4526,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// are overwritten as usual" behavior (see #5402).
|
||||
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
||||
it2->second.update_members(it.value().begin(), it.value().end(), true, depth + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = it.value());
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4514,7 +4546,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
version. Only reached for values nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void update_members_iteratively(const_iterator first, const_iterator last)
|
||||
void update_members_iteratively(iterator first, iterator last)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
@@ -4541,18 +4573,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
const auto it2 = target->m_data.m_value.object->find(first.key());
|
||||
if (it2 != target->m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
const basic_json& source = first.value();
|
||||
basic_json& source = first.value();
|
||||
++first;
|
||||
stack.emplace_back(target, first, last);
|
||||
target = &it2->second;
|
||||
first = source.cbegin();
|
||||
last = source.cend();
|
||||
first = source.begin();
|
||||
last = source.end();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = first.value());
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
|
||||
++first;
|
||||
}
|
||||
}
|
||||
@@ -6561,7 +6593,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_patch/
|
||||
void merge_patch(const basic_json& apply_patch)
|
||||
{
|
||||
apply_merge_patch(apply_patch, 0);
|
||||
// copy first: apply_patch may be *this or one of its descendants,
|
||||
// and is iterated (and moved from) below to patch *this
|
||||
basic_json patch = apply_patch;
|
||||
apply_merge_patch(patch, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -6574,7 +6609,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_patch_iteratively applies what is left without the call stack.
|
||||
*/
|
||||
void apply_merge_patch(const basic_json& apply_patch, const std::size_t depth)
|
||||
void apply_merge_patch(basic_json& apply_patch, const std::size_t depth)
|
||||
{
|
||||
if (apply_patch.is_object())
|
||||
{
|
||||
@@ -6602,7 +6637,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
else
|
||||
{
|
||||
*this = apply_patch;
|
||||
*this = std::move(apply_patch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6615,12 +6650,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
recursive version. Only reached for patches nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void merge_patch_iteratively(const basic_json& apply_patch)
|
||||
void merge_patch_iteratively(basic_json& apply_patch)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
// patch `target` with `patch`, or start patching it member by member
|
||||
const auto apply = [&stack](basic_json & target, const basic_json & patch)
|
||||
const auto apply = [&stack](basic_json & target, basic_json & patch)
|
||||
{
|
||||
if (patch.is_object())
|
||||
{
|
||||
@@ -6628,11 +6663,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{
|
||||
target = basic_json::object();
|
||||
}
|
||||
stack.emplace_back(&target, patch.cbegin(), patch.cend());
|
||||
stack.emplace_back(&target, patch.begin(), patch.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
target = patch;
|
||||
target = std::move(patch);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6648,7 +6683,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
continue;
|
||||
}
|
||||
|
||||
const const_iterator member = frame.position;
|
||||
const iterator member = frame.position;
|
||||
++stack.back().position;
|
||||
if (member.value().is_null())
|
||||
{
|
||||
|
||||
@@ -31162,25 +31162,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/update/
|
||||
void update(const_reference j, bool merge_objects = false)
|
||||
{
|
||||
update(j.begin(), j.end(), merge_objects);
|
||||
prepare_update();
|
||||
|
||||
// passed value must be an object (checked here so a type_error names
|
||||
// j, not the copy made below)
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j));
|
||||
}
|
||||
|
||||
// copy first: j may be *this or one of its descendants, and is
|
||||
// iterated (and moved from) below to update *this
|
||||
basic_json source = j;
|
||||
update_from(source, merge_objects);
|
||||
}
|
||||
|
||||
/// @brief updates a JSON object from another object, overwriting existing keys
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/update/
|
||||
void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// implicitly convert a null value to an empty object
|
||||
if (is_null())
|
||||
{
|
||||
m_data.m_type = value_t::object;
|
||||
m_data.m_value.object = create<object_t>();
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this));
|
||||
}
|
||||
prepare_update();
|
||||
|
||||
// check if range iterators belong to the same JSON object
|
||||
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
|
||||
@@ -31194,7 +31195,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object));
|
||||
}
|
||||
|
||||
update_members(first, last, merge_objects, 0);
|
||||
// copy first: the range may belong to *this or one of its
|
||||
// descendants, and is iterated (and moved from) below to update
|
||||
// *this
|
||||
basic_json source(first, last);
|
||||
update_from(source, merge_objects);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -31202,15 +31207,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// merge_patch_iteratively is merging into, and the members still to merge
|
||||
struct merge_frame
|
||||
{
|
||||
merge_frame(basic_json* target_, const_iterator position_, const_iterator last_) noexcept
|
||||
merge_frame(basic_json* target_, iterator position_, iterator last_) noexcept
|
||||
: target(target_), position(std::move(position_)), last(std::move(last_))
|
||||
{}
|
||||
|
||||
basic_json* target;
|
||||
const_iterator position;
|
||||
const_iterator last;
|
||||
iterator position;
|
||||
iterator last;
|
||||
};
|
||||
|
||||
/// @brief converts a null value to an empty object and checks that this
|
||||
/// value is an object; called first by both @ref update overloads
|
||||
void prepare_update()
|
||||
{
|
||||
// implicitly convert a null value to an empty object; create the
|
||||
// object before setting the type, so a throwing allocation leaves
|
||||
// this value null
|
||||
if (is_null())
|
||||
{
|
||||
m_data.m_value.object = create<object_t>();
|
||||
m_data.m_type = value_t::object;
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief starts the @ref update_members loop over an already-copied @a
|
||||
/// source; called by both @ref update overloads
|
||||
void update_from(basic_json& source, const bool merge_objects)
|
||||
{
|
||||
update_members(source.begin(), source.end(), merge_objects, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the members loop of @ref update, for this object and range
|
||||
|
||||
@@ -31223,7 +31255,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
@param[in] depth nesting level of this object, counted from the object
|
||||
@ref update was called on
|
||||
*/
|
||||
void update_members(const const_iterator& first, const const_iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
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()))
|
||||
{
|
||||
@@ -31241,13 +31273,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// are overwritten as usual" behavior (see #5402).
|
||||
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
||||
it2->second.update_members(it.value().begin(), it.value().end(), true, depth + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = it.value());
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31261,7 +31293,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
version. Only reached for values nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void update_members_iteratively(const_iterator first, const_iterator last)
|
||||
void update_members_iteratively(iterator first, iterator last)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
@@ -31288,18 +31320,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
const auto it2 = target->m_data.m_value.object->find(first.key());
|
||||
if (it2 != target->m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
const basic_json& source = first.value();
|
||||
basic_json& source = first.value();
|
||||
++first;
|
||||
stack.emplace_back(target, first, last);
|
||||
target = &it2->second;
|
||||
first = source.cbegin();
|
||||
last = source.cend();
|
||||
first = source.begin();
|
||||
last = source.end();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = first.value());
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
|
||||
++first;
|
||||
}
|
||||
}
|
||||
@@ -33308,7 +33340,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_patch/
|
||||
void merge_patch(const basic_json& apply_patch)
|
||||
{
|
||||
apply_merge_patch(apply_patch, 0);
|
||||
// copy first: apply_patch may be *this or one of its descendants,
|
||||
// and is iterated (and moved from) below to patch *this
|
||||
basic_json patch = apply_patch;
|
||||
apply_merge_patch(patch, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -33321,7 +33356,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_patch_iteratively applies what is left without the call stack.
|
||||
*/
|
||||
void apply_merge_patch(const basic_json& apply_patch, const std::size_t depth)
|
||||
void apply_merge_patch(basic_json& apply_patch, const std::size_t depth)
|
||||
{
|
||||
if (apply_patch.is_object())
|
||||
{
|
||||
@@ -33349,7 +33384,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
else
|
||||
{
|
||||
*this = apply_patch;
|
||||
*this = std::move(apply_patch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33362,12 +33397,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
recursive version. Only reached for patches nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void merge_patch_iteratively(const basic_json& apply_patch)
|
||||
void merge_patch_iteratively(basic_json& apply_patch)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
// patch `target` with `patch`, or start patching it member by member
|
||||
const auto apply = [&stack](basic_json & target, const basic_json & patch)
|
||||
const auto apply = [&stack](basic_json & target, basic_json & patch)
|
||||
{
|
||||
if (patch.is_object())
|
||||
{
|
||||
@@ -33375,11 +33410,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{
|
||||
target = basic_json::object();
|
||||
}
|
||||
stack.emplace_back(&target, patch.cbegin(), patch.cend());
|
||||
stack.emplace_back(&target, patch.begin(), patch.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
target = patch;
|
||||
target = std::move(patch);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33395,7 +33430,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
continue;
|
||||
}
|
||||
|
||||
const const_iterator member = frame.position;
|
||||
const iterator member = frame.position;
|
||||
++stack.back().position;
|
||||
if (member.value().is_null())
|
||||
{
|
||||
|
||||
@@ -236,6 +236,31 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5641 - parent pointers after update()/merge_patch() with an aliasing argument")
|
||||
{
|
||||
// update()'s and merge_patch()'s argument may be *this or one of its
|
||||
// descendants; the values moved out of the (temporary) copy must end
|
||||
// up with their parent pointing at their new location in *this
|
||||
{
|
||||
json j = {{"a", {{"a", 1}, {"b", 2}}}};
|
||||
j.update(j["a"]);
|
||||
CHECK(j == json({{"a", 1}, {"b", 2}}));
|
||||
|
||||
// Must call operator[] on const element, otherwise m_parent gets updated.
|
||||
auto const& constJ = j;
|
||||
CHECK_THROWS_WITH_AS(constJ["a"].at(0), "[json.exception.type_error.304] (/a) cannot use at() with number", json::type_error);
|
||||
}
|
||||
|
||||
{
|
||||
json j = {{"a", {{"a", nullptr}, {"b", 2}}}};
|
||||
j.merge_patch(j["a"]);
|
||||
CHECK(j == json({{"b", 2}}));
|
||||
|
||||
auto const& constJ = j;
|
||||
CHECK_THROWS_WITH_AS(constJ["b"].at(0), "[json.exception.type_error.304] (/b) cannot use at() with number", json::type_error);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #3032 - Yet another assertion failure when inserting into arrays with JSON_DIAGNOSTICS set")
|
||||
{
|
||||
// reference operator[](size_type idx)
|
||||
|
||||
@@ -374,3 +374,52 @@ TEST_CASE("JSON Merge Patch and update on ordered_json")
|
||||
CHECK(target == ordered_json::parse(R"({"a": 1, "e": {"y": 5}, "g": 6})"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
|
||||
{
|
||||
SECTION("j.merge_patch(j): erasing a member destroys the node the loop's iterator points to")
|
||||
{
|
||||
// reproduces issue #5641, case 1
|
||||
json j = {{"a", nullptr}, {"b", 1}};
|
||||
j.merge_patch(j);
|
||||
CHECK(j == json({{"b", 1}}));
|
||||
}
|
||||
|
||||
SECTION("j.merge_patch(j[\"a\"]): removing \"a\" destroys the patch while it is iterated")
|
||||
{
|
||||
// reproduces issue #5641, case 2
|
||||
json j = {{"a", {{"a", nullptr}, {"b", 2}}}};
|
||||
j.merge_patch(j["a"]);
|
||||
CHECK(j == json({{"b", 2}}));
|
||||
}
|
||||
|
||||
SECTION("a patch nested past the iterative descent bound aliases *this")
|
||||
{
|
||||
// every depth on either side of where the iterative version takes
|
||||
// over (detail::recursion_depth_limit(), 128); patching *this with
|
||||
// itself is idempotent, aliased or not
|
||||
for (const std::size_t depth :
|
||||
{
|
||||
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
|
||||
})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
json j = json::parse(nested_objects(depth, 0));
|
||||
const json expected = j;
|
||||
j.merge_patch(j);
|
||||
CHECK(j == expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
SECTION("merge_patch with a member of *this")
|
||||
{
|
||||
ordered_json j = {{"a", {{"a", nullptr}, {"b", 2}}}};
|
||||
j.merge_patch(j["a"]);
|
||||
CHECK(j == ordered_json({{"b", 2}}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1116,3 +1116,72 @@ TEST_CASE("update() on deeply nested values")
|
||||
CHECK(p->at("y") == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update() with an argument that aliases *this (#5641)")
|
||||
{
|
||||
SECTION("the target is checked before the argument, as before the copy")
|
||||
{
|
||||
json j = 1;
|
||||
CHECK_THROWS_WITH_AS(j.update(json::array()), "[json.exception.type_error.312] cannot use update() with number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j.update(j.cbegin(), j.cend()), "[json.exception.type_error.312] cannot use update() with number", json::type_error&);
|
||||
|
||||
json k;
|
||||
CHECK_THROWS_WITH_AS(k.update(json::array()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
|
||||
CHECK(k == json::object());
|
||||
}
|
||||
|
||||
SECTION("const reference")
|
||||
{
|
||||
SECTION("j.update(j[\"a\"]): assigning into the argument's parent destroys it mid-iteration")
|
||||
{
|
||||
// reproduces issue #5641, case 3
|
||||
json j = {{"a", {{"a", 1}, {"b", 2}}}};
|
||||
j.update(j["a"]);
|
||||
CHECK(j == json({{"a", 1}, {"b", 2}}));
|
||||
}
|
||||
|
||||
SECTION("merge_objects with an argument that is a member of *this")
|
||||
{
|
||||
json j = {{"defaults", {{"opts", {{"a", 1}}}}}, {"opts", {{"b", 2}}}};
|
||||
j.update(j["defaults"], true);
|
||||
CHECK(j == json({{"defaults", {{"opts", {{"a", 1}}}}}, {"opts", {{"a", 1}, {"b", 2}}}}));
|
||||
}
|
||||
|
||||
SECTION("ordered_json: inserting a new key relocates the vector behind the argument")
|
||||
{
|
||||
// reproduces issue #5641, case 4
|
||||
using nlohmann::ordered_json;
|
||||
ordered_json j = {{"a", {{"x", 1}, {"y", 2}, {"z", 3}}}};
|
||||
j.update(j["a"]);
|
||||
CHECK(j == ordered_json({{"a", {{"x", 1}, {"y", 2}, {"z", 3}}}, {"x", 1}, {"y", 2}, {"z", 3}}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("iterator range")
|
||||
{
|
||||
SECTION("range that is a member of *this")
|
||||
{
|
||||
json j = {{"a", {{"a", 1}, {"b", 2}}}};
|
||||
j.update(j["a"].begin(), j["a"].end());
|
||||
CHECK(j == json({{"a", 1}, {"b", 2}}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("nested past the iterative descent bound aliases *this")
|
||||
{
|
||||
// every depth on either side of where the iterative version takes
|
||||
// over (detail::recursion_depth_limit(), 128); merging *this into
|
||||
// itself is idempotent, aliased or not
|
||||
for (const std::size_t depth :
|
||||
{
|
||||
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
|
||||
})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
json j = json::parse(nested_objects(depth, 0));
|
||||
const json expected = j;
|
||||
j.update(j, true);
|
||||
CHECK(j == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user