Deduplicate the null-to-container conversion into convert_null_to()

Nine sites wrote out the same "turn a null value into an empty array
or object" logic with two different idioms: operator[](size_type),
operator[](key_type), operator[](KeyType&&) and update() set m_type
then assigned m_value.array/object directly via create<T>(), while the
three push_back() overloads, emplace_back() and emplace() set m_type
then assigned m_value = value_t::array/object (going through
json_value's converting constructor and a temporary). Both idioms end
up calling create<T>() and produce the same state, just via a
different path; both also share a latent exception-safety bug, since
m_type is written before the (possibly throwing) allocation, so a
throwing allocator leaves m_type == array/object with a null pointer
behind it, violating the class invariant and crashing on the next
access to, or destruction of, the value.

Add a private convert_null_to(value_t) helper and call it from all
nine sites. Unlike the idioms it replaces, it allocates the container
first and only then writes m_type, so a throwing allocation leaves the
value as a valid null instead of a mistyped, half-constructed one;
verified with a throwing allocator (see unit-allocator.cpp's
bad_allocator) that j["x"] = ... on a null j now stays null, and no
longer trips assert_invariant()/crashes, when create<object_t>()
throws. Same allocator usage and assert_invariant() call as before,
otherwise.

Overlaps #5585, which reorders these same nine blocks for exception
safety; whichever of this commit and that PR lands second will need a
small rebase.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-30 19:05:05 +02:00
parent 03d53596f9
commit 51233dc17d
2 changed files with 58 additions and 54 deletions
+29 -27
View File
@@ -2741,6 +2741,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return (*j.m_data.m_value.array)[idx];
}
/// @brief convert a null value to an empty container of type @a t (array or object)
void convert_null_to(value_t t)
{
JSON_ASSERT(is_null());
JSON_ASSERT(t == value_t::array || t == value_t::object);
// create the container before touching the type, so a throwing
// allocation leaves this value as a valid null rather than a type
// tag with a dangling/null pointer behind it
if (t == value_t::array)
{
m_data.m_value.array = create<array_t>();
}
else
{
m_data.m_value.object = create<object_t>();
}
m_data.m_type = t;
assert_invariant();
}
public:
////////////////////
// element access //
@@ -2803,9 +2823,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>();
assert_invariant();
convert_null_to(value_t::array);
}
// operator[] only works for arrays
@@ -2863,9 +2881,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// 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();
convert_null_to(value_t::object);
}
// operator[] only works for objects
@@ -2916,9 +2932,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// 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();
convert_null_to(value_t::object);
}
// operator[] only works for objects
@@ -3790,9 +3804,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
assert_invariant();
convert_null_to(value_t::array);
}
// add the element to the array (move semantics)
@@ -3823,9 +3835,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
assert_invariant();
convert_null_to(value_t::array);
}
// add the element to the array
@@ -3855,9 +3865,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
assert_invariant();
convert_null_to(value_t::object);
}
// add the element to the object
@@ -3911,9 +3919,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
assert_invariant();
convert_null_to(value_t::array);
}
// add the element to the array (perfect forwarding)
@@ -3936,9 +3942,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
assert_invariant();
convert_null_to(value_t::object);
}
// add the element to the array (perfect forwarding)
@@ -4133,9 +4137,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// 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();
convert_null_to(value_t::object);
}
if (JSON_HEDLEY_UNLIKELY(!is_object()))
+29 -27
View File
@@ -28821,6 +28821,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return (*j.m_data.m_value.array)[idx];
}
/// @brief convert a null value to an empty container of type @a t (array or object)
void convert_null_to(value_t t)
{
JSON_ASSERT(is_null());
JSON_ASSERT(t == value_t::array || t == value_t::object);
// create the container before touching the type, so a throwing
// allocation leaves this value as a valid null rather than a type
// tag with a dangling/null pointer behind it
if (t == value_t::array)
{
m_data.m_value.array = create<array_t>();
}
else
{
m_data.m_value.object = create<object_t>();
}
m_data.m_type = t;
assert_invariant();
}
public:
////////////////////
// element access //
@@ -28883,9 +28903,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>();
assert_invariant();
convert_null_to(value_t::array);
}
// operator[] only works for arrays
@@ -28943,9 +28961,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// 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();
convert_null_to(value_t::object);
}
// operator[] only works for objects
@@ -28996,9 +29012,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// 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();
convert_null_to(value_t::object);
}
// operator[] only works for objects
@@ -29870,9 +29884,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
assert_invariant();
convert_null_to(value_t::array);
}
// add the element to the array (move semantics)
@@ -29903,9 +29915,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
assert_invariant();
convert_null_to(value_t::array);
}
// add the element to the array
@@ -29935,9 +29945,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
assert_invariant();
convert_null_to(value_t::object);
}
// add the element to the object
@@ -29991,9 +29999,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
assert_invariant();
convert_null_to(value_t::array);
}
// add the element to the array (perfect forwarding)
@@ -30016,9 +30022,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
assert_invariant();
convert_null_to(value_t::object);
}
// add the element to the array (perfect forwarding)
@@ -30213,9 +30217,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// 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();
convert_null_to(value_t::object);
}
if (JSON_HEDLEY_UNLIKELY(!is_object()))