insert(): strong guarantee for single elements, basic for the rest
Rework of the previous two commits, following the discussion in #5996: the standard containers give the strong guarantee only for a single element (std::vector: at the end, or if the exception does not come from copying T; std::map: single-element insert), and copying a basic_json allocates. Rebuilding a rollback for the count, range and object-range overloads on top of array_t/object_t made the code verbose and slower. - insert(pos, const basic_json&): at end() forward as before (the standard covers it); elsewhere copy first and insert by move, as libstdc++ does internally (libc++ shifts the tail before copying). - insert_iterator() and object insert(first, last) set the parents in a scope guard, so a failed insertion leaves a valid value with JSON_DIAGNOSTICS too (the next assert_invariant() aborted before). - Docs: strong guarantee for (1), basic guarantee for (2)-(5); the argument checks throw before anything is changed. - Test: allocation-failure sweep checks the strong guarantee for (1) and validity, type and parents for the other overloads. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
4 files changed
+157
-648
No files matched your search
@@ -844,13 +844,21 @@ bool children_name_their_parent(const BasicJsonType& j)
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class guarantee
|
||||
{
|
||||
basic, // the value stays valid and keeps its type
|
||||
strong // the value is unchanged
|
||||
};
|
||||
|
||||
// Runs op on a fresh value make() with the 0th, 1st, 2nd, ... allocation made
|
||||
// by the library failing in turn, until op completes. Every failed attempt
|
||||
// must have thrown std::bad_alloc and left the value exactly as it was, with
|
||||
// all parent pointers intact (copying a value asserts the invariant).
|
||||
// must have thrown std::bad_alloc and left the value valid, with all parent
|
||||
// pointers intact (copying a value asserts the invariant), and for the strong
|
||||
// guarantee exactly as it was.
|
||||
template<class BasicJsonType>
|
||||
void check_strong_guarantee(const std::function<BasicJsonType()>& make,
|
||||
const std::function<void(BasicJsonType&)>& op)
|
||||
void check_exception_guarantee(guarantee g,
|
||||
const std::function<BasicJsonType()>& make,
|
||||
const std::function<void(BasicJsonType&)>& op)
|
||||
{
|
||||
fail_at_alloc_call = -1;
|
||||
|
||||
@@ -863,6 +871,7 @@ void check_strong_guarantee(const std::function<BasicJsonType()>& make,
|
||||
|
||||
BasicJsonType j = make();
|
||||
const std::string before = j.dump();
|
||||
const auto type_before = j.type();
|
||||
|
||||
alloc_call_count = 0;
|
||||
fail_at_alloc_call = static_cast<long>(n);
|
||||
@@ -879,10 +888,14 @@ void check_strong_guarantee(const std::function<BasicJsonType()>& make,
|
||||
|
||||
if (!completed)
|
||||
{
|
||||
CHECK(j.dump() == before);
|
||||
CHECK((j.type() == type_before));
|
||||
CHECK(children_name_their_parent(j));
|
||||
const BasicJsonType copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is the point, it allocates
|
||||
CHECK(copy.dump() == before);
|
||||
CHECK(copy.dump() == j.dump());
|
||||
if (g == guarantee::strong)
|
||||
{
|
||||
CHECK(j.dump() == before);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -891,7 +904,7 @@ void check_strong_guarantee(const std::function<BasicJsonType()>& make,
|
||||
}
|
||||
|
||||
template<class BasicJsonType>
|
||||
void check_insert_strong_guarantee()
|
||||
void check_insert_exception_guarantees()
|
||||
{
|
||||
using json_t = BasicJsonType;
|
||||
using make_t = std::function<json_t()>;
|
||||
@@ -929,7 +942,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("insert(pos, const basic_json&)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, val);
|
||||
});
|
||||
@@ -937,7 +950,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("insert(pos, basic_json&&)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, json_t(val));
|
||||
});
|
||||
@@ -945,7 +958,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("insert(pos, size_type, const basic_json&)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, 3, val);
|
||||
});
|
||||
@@ -953,7 +966,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("insert(pos, const_iterator, const_iterator)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, source.begin(), source.end());
|
||||
});
|
||||
@@ -961,7 +974,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("insert(pos, initializer_list_t)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, {val, val});
|
||||
});
|
||||
@@ -975,7 +988,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("no member exists yet")
|
||||
{
|
||||
check_strong_guarantee<json_t>([]()
|
||||
check_exception_guarantee<json_t>(guarantee::basic, []()
|
||||
{
|
||||
return json_t({{"a", 1}});
|
||||
}, [&](json_t& j)
|
||||
@@ -986,7 +999,7 @@ void check_insert_strong_guarantee()
|
||||
|
||||
SECTION("some members exist already")
|
||||
{
|
||||
check_strong_guarantee<json_t>([]()
|
||||
check_exception_guarantee<json_t>(guarantee::basic, []()
|
||||
{
|
||||
return json_t({{"a", 1}, {"n", std::string(64, 'x')}});
|
||||
}, [&](json_t& j)
|
||||
@@ -998,16 +1011,16 @@ void check_insert_strong_guarantee()
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("insert() provides the strong exception guarantee (#5986)")
|
||||
TEST_CASE("insert() provides its documented exception guarantees (#5986)")
|
||||
{
|
||||
SECTION("std::map-backed object_t")
|
||||
{
|
||||
check_insert_strong_guarantee<nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
check_insert_exception_guarantees<nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
}
|
||||
|
||||
SECTION("ordered_map-backed object_t")
|
||||
{
|
||||
check_insert_strong_guarantee<nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
check_insert_exception_guarantees<nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user