Fix rollback of pointer operator[] for unusual object and string types
Follow-up to the strong exception guarantee of the non-const operator[](json_pointer): - get_unchecked_create assumed that emplace() always inserts the key that the walk did not find. For object types whose lookup and emplace() disagree (e.g. a copied fifo_map), nothing was inserted, the rollback erased the existing member, and the walk continued with a value of the wrong type (assertion, type_error.305, or a crash). It now checks the result of emplace(), arms the rollback only if a key was inserted, and otherwise continues the walk with the member found, as basic_json's operator[] does: such pointers throw out_of_range.404 and leave the value unchanged, as before the strong guarantee. - The rollback erased the inserted key by key, which converts string_t to object_t::key_type and may allocate inside a noexcept function (std::terminate on bad_alloc with custom key types). It now erases by the iterator emplace() returned, and shrinks a grown array with erase() at the end instead of resize(). The documentation states the remaining requirement for custom template parameters. - Creating values no longer calls string_t::begin(), so the non-const operator[](json_pointer) compiles with string types that lack it (as the alternative string type of the tests). - Correct the @throw lists of get_unchecked and get_unchecked_create. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
d1566fe9e8
commit
d2e05c8b45
5 files changed
+325
-73
No files matched your search
@@ -373,6 +373,23 @@ TEST_CASE("alternative string type")
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/bar")));
|
||||
}
|
||||
|
||||
SECTION("non-const operator[](json_pointer)")
|
||||
{
|
||||
// creating values must compile without string_t::begin()
|
||||
alt_json j = alt_json::parse(R"({"a":{"b":[1]},"c":null})");
|
||||
const alt_json before = j;
|
||||
j[alt_json::json_pointer("/a/b/3/x/-/y")] = 1; // pad, append, create
|
||||
j[alt_json::json_pointer("/a/z")] = 2; // insert a key
|
||||
j[alt_json::json_pointer("/c/0/q")] = 3; // convert null
|
||||
j[alt_json::json_pointer("/a/b/-")] = 4; // append
|
||||
CHECK(j.dump() == R"({"a":{"b":[1,null,null,{"x":[{"y":1}]},4],"z":2},"c":[{"q":3}]})");
|
||||
|
||||
// a failing access leaves the value unchanged
|
||||
alt_json k = before;
|
||||
CHECK_THROWS_AS(k[alt_json::json_pointer("/a/n/99999999999999999999999")], alt_json::out_of_range&);
|
||||
CHECK(k == before);
|
||||
}
|
||||
|
||||
SECTION("operator/(std::size_t)")
|
||||
{
|
||||
// json_pointer::operator/=(std::size_t) must compile without string_t
|
||||
|
||||
@@ -1499,6 +1499,23 @@ TEST_CASE("regression tests 1")
|
||||
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2},"new_key":3})");
|
||||
}
|
||||
|
||||
SECTION("operator[](json_pointer) when emplace() finds a key the lookup does not")
|
||||
{
|
||||
// a copied fifo_map restarts its timestamps, so a new key "k" gets
|
||||
// the timestamp of "x": the lookup does not find "k", but emplace()
|
||||
// finds "x" and inserts nothing; the pointer then continues with the
|
||||
// member of "x", as basic_json::operator[] does
|
||||
const my_json orig = my_json::parse(R"({"x":1})");
|
||||
const std::array<const char*, 3> pointers = {{"/k/0", "/k/a/b", "/k/-"}};
|
||||
for (const char* pointer : pointers)
|
||||
{
|
||||
CAPTURE(pointer)
|
||||
my_json j = orig;
|
||||
CHECK_THROWS_AS(j[my_json::json_pointer(pointer)], my_json::out_of_range&);
|
||||
CHECK(j.dump() == R"({"x":1})");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("issue #977 - Assigning between different json types")
|
||||
{
|
||||
foo_json lj = ns::foo{3};
|
||||
|
||||
Reference in new issue
Block a user