Compare commits

..
Author SHA1 Message Date
Niels Lohmann 056d187986 Merge branch 'develop' into claude/fix-allocation-exception-safety
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:58:25 +02:00
Niels Lohmann 90078597c7 Test the remaining to_json overloads with a failing allocation
The test only reached 5 of the 14 external_constructor overloads that
were reordered: reverting the fix in any of the other 9 (const string_t&,
compatible strings, binary_t&&, array_t const&/&&, std::valarray, ranges
views, object_t const&/&&) left it green. Each of them is now called with
a failing allocation, and reverting any single hunk of the fix makes the
test fail under ASan.

binary_t&& is only reached by to_json for a binary container type other
than std::vector<std::uint8_t>, so the test calls the constructor
directly.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 18:29:37 +02:00
Niels Lohmann fa28fff7d6 Skip the vector<bool> failed-allocation check for VS 2015 with iterator debugging
AppVeyor's VS 2015 Debug build terminated in "converting into an
existing value": to_json for std::vector<bool> default-constructs an
array_t first, and VS 2015's std::vector default constructor is noexcept
but, with iterator debugging, constructs its proxy through the test's
allocator, which throws.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 13:30:57 +02:00
Niels Lohmann e9c84befa1 Keep the created pointer rather than an uninitialized json_value
clang-tidy reported the json_value unions declared in the string, array,
and object constructors of to_json.hpp as uninitialized
(cppcoreguidelines-pro-type-member-init). Assign the created pointer to
the member directly once the old value is destroyed.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-26 10:07:22 +02:00
Niels Lohmann 8a26f2dc8f Skip the failed-allocation test when exceptions are disabled
The no-exceptions CI job runs doctest with --no-throw, which skips every
CHECK_THROWS_AS. The test then left next_construct_fails set, and the
next allocation outside a check threw std::bad_alloc.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-26 07:41:42 +02:00
Niels Lohmann 2c108d0b56 Create a value before giving it its type
Several functions set the type of a value before creating the string,
array, object, or binary value it stands for. When that creation threw -
std::bad_alloc from the allocator, say - the value was left behind with
the new type but nothing behind it:

- json::binary() returned no value, but its destructor asserted that a
  binary value has a binary array, aborting debug builds.
- operator[], push_back, emplace_back, emplace, and update turned a null
  value into an array or object that did not exist; any later access
  dereferenced a null pointer.
- The to_json conversions destroyed the old value first. A failed
  creation of the new one left a pointer to the destroyed old value,
  which was then used and freed again (heap-use-after-free).

The value is now created first and the type set after it, and to_json
destroys the old value only once the new one exists, so a failed
allocation leaves the value unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 22:19:39 +02:00
7 changed files with 309 additions and 171 deletions
@@ -128,13 +128,10 @@ Strong exception safety: if an exception occurs, the original value stays intact
When the JSON pointer traverses intermediate levels that don't exist at all yet (not just a missing
leaf), each missing level is created as an array or an object depending on whether the corresponding
pointer token is a valid array index: the token `0`, a sequence of digits that does not begin with `0`,
or the token `-` creates an array, and every other token creates an object. For example, on an
initially `#!json null` value, `/foo/0/0/0` creates nested arrays, while `/foo/one/one/one` creates
nested objects. Tokens such as `01` or the empty token cannot be array indices (cf. RFC 6901, Sect. 4)
and therefore create objects, just as they would if the level already existed as an object. This is not
specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also
[JSON Pointer](../../features/json_pointer.md).
pointer token parses as a non-negative integer: a numeric token creates an array, a non-numeric token
creates an object. For example, on an initially `#!json null` value, `/foo/0/0/0` creates nested arrays,
while `/foo/one/one/one` creates nested objects. This is not specified by the JSON Pointer RFC; it is
this library's own, intentional disambiguation rule. See also [JSON Pointer](../../features/json_pointer.md).
## Examples
+45 -25
View File
@@ -42,6 +42,10 @@ namespace detail
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
* allocated value (e.g., a string). See bug issue
* https://github.com/nlohmann/json/issues/2865 for more information.
*
* A value that has to be allocated is created before the old one is destroyed:
* were it the other way around, an exception while creating the new value would
* leave j with the type of the new value, but the pointer to the destroyed old one.
*/
template<value_t> struct external_constructor;
@@ -65,18 +69,20 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
{
const typename BasicJsonType::json_value value(s);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value = s;
j.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
const typename BasicJsonType::json_value value(std::move(s));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value = std::move(s);
j.m_data.m_value = value;
j.assert_invariant();
}
@@ -85,9 +91,10 @@ struct external_constructor<value_t::string>
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str)
{
auto* created = j.template create<typename BasicJsonType::string_t>(str);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
j.m_data.m_value.string = created;
j.assert_invariant();
}
};
@@ -98,18 +105,20 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
{
const typename BasicJsonType::json_value value(b);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary;
j.m_data.m_value = typename BasicJsonType::binary_t(b);
j.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
{
const typename BasicJsonType::json_value value(std::move(b));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary;
j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));
j.m_data.m_value = value;
j.assert_invariant();
}
};
@@ -159,9 +168,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
{
const typename BasicJsonType::json_value value(arr);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = arr;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -169,9 +179,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
const typename BasicJsonType::json_value value(std::move(arr));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = std::move(arr);
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -187,9 +198,10 @@ struct external_constructor<value_t::array>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.m_data.m_value.array = created;
j.set_parents();
j.assert_invariant();
}
@@ -197,15 +209,17 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->reserve(arr.size());
typename BasicJsonType::array_t elements;
elements.reserve(arr.size());
for (const bool x : arr)
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -213,11 +227,12 @@ struct external_constructor<value_t::array>
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
{
typename BasicJsonType::array_t elements(arr.size());
std::copy(std::begin(arr), std::end(arr), elements.begin());
const typename BasicJsonType::json_value value(std::move(elements));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->resize(arr.size());
std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -229,14 +244,16 @@ struct external_constructor<value_t::array>
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
typename BasicJsonType::array_t elements;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
#endif
@@ -248,9 +265,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
{
const typename BasicJsonType::json_value value(obj);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value = obj;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -258,9 +276,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
const typename BasicJsonType::json_value value(std::move(obj));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value = std::move(obj);
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -272,9 +291,10 @@ struct external_constructor<value_t::object>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.m_data.m_value.object = created;
j.set_parents();
j.assert_invariant();
}
+5 -9
View File
@@ -416,19 +416,15 @@ class json_pointer
// convert null values to arrays or objects before continuing
if (ptr->is_null())
{
// check if the reference token is a valid array index, that is
// a nonempty sequence of digits without a leading '0'
// (cf. RFC 6901, Sect. 4); tokens that could never be a valid
// array index (such as "01" or "") are treated as object keys
const bool nums = !reference_token.empty()
&& (reference_token.size() == 1 || reference_token[0] != '0')
&& std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
// check if the reference token is a number
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
{
return std::isdigit(x);
});
// change value to an array for array indices or "-" or to object otherwise
// change value to an array for numbers or "-" or to object otherwise
*ptr = (nums || reference_token == "-")
? detail::value_t::array
: detail::value_t::object;
+15 -15
View File
@@ -1702,8 +1702,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_an_object)
{
// the initializer list is a list of pairs -> create an object
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
for (auto& element_ref : init)
{
@@ -1725,8 +1725,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
#endif
// the initializer list describes an array -> create an array
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>(init.begin(), init.end());
m_data.m_type = value_t::array;
}
set_parents();
@@ -1739,8 +1739,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = init;
res.m_data.m_type = value_t::binary;
return res;
}
@@ -1750,8 +1750,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(init, subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -1761,8 +1761,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = std::move(init);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -1772,8 +1772,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(std::move(init), subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -2819,8 +2819,8 @@ 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>();
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -2879,8 +2879,8 @@ 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>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -2932,8 +2932,8 @@ 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>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -3868,8 +3868,8 @@ 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;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -3901,8 +3901,8 @@ 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;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -3933,8 +3933,8 @@ 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;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -3989,8 +3989,8 @@ 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;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -4014,8 +4014,8 @@ 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;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -4196,8 +4196,8 @@ 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>();
m_data.m_type = value_t::object;
assert_invariant();
}
+65 -49
View File
@@ -6571,6 +6571,10 @@ namespace detail
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
* allocated value (e.g., a string). See bug issue
* https://github.com/nlohmann/json/issues/2865 for more information.
*
* A value that has to be allocated is created before the old one is destroyed:
* were it the other way around, an exception while creating the new value would
* leave j with the type of the new value, but the pointer to the destroyed old one.
*/
template<value_t> struct external_constructor;
@@ -6594,18 +6598,20 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
{
const typename BasicJsonType::json_value value(s);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value = s;
j.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
const typename BasicJsonType::json_value value(std::move(s));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value = std::move(s);
j.m_data.m_value = value;
j.assert_invariant();
}
@@ -6614,9 +6620,10 @@ struct external_constructor<value_t::string>
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str)
{
auto* created = j.template create<typename BasicJsonType::string_t>(str);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
j.m_data.m_value.string = created;
j.assert_invariant();
}
};
@@ -6627,18 +6634,20 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
{
const typename BasicJsonType::json_value value(b);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary;
j.m_data.m_value = typename BasicJsonType::binary_t(b);
j.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
{
const typename BasicJsonType::json_value value(std::move(b));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary;
j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));
j.m_data.m_value = value;
j.assert_invariant();
}
};
@@ -6688,9 +6697,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
{
const typename BasicJsonType::json_value value(arr);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = arr;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6698,9 +6708,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
const typename BasicJsonType::json_value value(std::move(arr));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = std::move(arr);
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6716,9 +6727,10 @@ struct external_constructor<value_t::array>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.m_data.m_value.array = created;
j.set_parents();
j.assert_invariant();
}
@@ -6726,15 +6738,17 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->reserve(arr.size());
typename BasicJsonType::array_t elements;
elements.reserve(arr.size());
for (const bool x : arr)
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6742,11 +6756,12 @@ struct external_constructor<value_t::array>
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
{
typename BasicJsonType::array_t elements(arr.size());
std::copy(std::begin(arr), std::end(arr), elements.begin());
const typename BasicJsonType::json_value value(std::move(elements));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->resize(arr.size());
std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6758,14 +6773,16 @@ struct external_constructor<value_t::array>
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
typename BasicJsonType::array_t elements;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
#endif
@@ -6777,9 +6794,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
{
const typename BasicJsonType::json_value value(obj);
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value = obj;
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6787,9 +6805,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
const typename BasicJsonType::json_value value(std::move(obj));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value = std::move(obj);
j.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6801,9 +6820,10 @@ struct external_constructor<value_t::object>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.m_data.m_value.object = created;
j.set_parents();
j.assert_invariant();
}
@@ -19060,19 +19080,15 @@ class json_pointer
// convert null values to arrays or objects before continuing
if (ptr->is_null())
{
// check if the reference token is a valid array index, that is
// a nonempty sequence of digits without a leading '0'
// (cf. RFC 6901, Sect. 4); tokens that could never be a valid
// array index (such as "01" or "") are treated as object keys
const bool nums = !reference_token.empty()
&& (reference_token.size() == 1 || reference_token[0] != '0')
&& std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
// check if the reference token is a number
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
{
return std::isdigit(x);
});
// change value to an array for array indices or "-" or to object otherwise
// change value to an array for numbers or "-" or to object otherwise
*ptr = (nums || reference_token == "-")
? detail::value_t::array
: detail::value_t::object;
@@ -27589,8 +27605,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_an_object)
{
// the initializer list is a list of pairs -> create an object
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
for (auto& element_ref : init)
{
@@ -27612,8 +27628,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
#endif
// the initializer list describes an array -> create an array
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>(init.begin(), init.end());
m_data.m_type = value_t::array;
}
set_parents();
@@ -27626,8 +27642,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = init;
res.m_data.m_type = value_t::binary;
return res;
}
@@ -27637,8 +27653,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(init, subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -27648,8 +27664,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = std::move(init);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -27659,8 +27675,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(std::move(init), subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -28706,8 +28722,8 @@ 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>();
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -28766,8 +28782,8 @@ 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>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -28819,8 +28835,8 @@ 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>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -29755,8 +29771,8 @@ 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;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -29788,8 +29804,8 @@ 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;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -29820,8 +29836,8 @@ 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;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -29876,8 +29892,8 @@ 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;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -29901,8 +29917,8 @@ 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;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -30083,8 +30099,8 @@ 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>();
m_data.m_type = value_t::object;
assert_invariant();
}
+175
View File
@@ -12,6 +12,11 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <valarray>
#if JSON_HAS_RANGES
#include <ranges>
#endif
namespace
{
// special test case to check if memory is leaked if constructor throws
@@ -394,3 +399,173 @@ TEST_CASE("bad my_allocator::construct")
j["test"].push_back("should not leak");
}
}
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
// next_construct_fails set for the next allocation outside a check
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("a failed allocation leaves the value unchanged")
{
// create JSON type using the throwing allocator
using my_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
my_allocator>;
// Each of these creates a string, array, object, or binary value. The
// value must be created before the type is changed: otherwise, a failed
// creation left a value of the new type without anything behind it (an
// assertion in its destructor, a null pointer everywhere else) or, when
// an old value was destroyed first, with a pointer to that destroyed one.
SECTION("creating a binary value")
{
const std::vector<std::uint8_t> bytes = {1, 2, 3};
my_json _;
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(bytes), std::bad_alloc&);
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(bytes, 42), std::bad_alloc&);
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes)), std::bad_alloc&);
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes), 42), std::bad_alloc&);
next_construct_fails = false;
}
SECTION("turning a null value into an array or object")
{
my_json j;
next_construct_fails = true;
CHECK_THROWS_AS(j[0], std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
CHECK(j.is_null());
#ifdef JSON_HAS_CPP_17
next_construct_fails = true;
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
CHECK(j.is_null());
#endif
next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
CHECK(j.is_null());
const my_json one = 1;
next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(one), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(my_json::object_t::value_type("key", 1)), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j.emplace_back(1), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j.emplace("key", 1), std::bad_alloc&);
CHECK(j.is_null());
const my_json object = {{"key", 1}};
next_construct_fails = true;
CHECK_THROWS_AS(j.update(object), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = false;
}
SECTION("converting into an existing value")
{
// to_json replaces the value it is given; the old one must survive a
// failed creation of the new one
my_json j = "old";
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::string("new")), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<int> {1, 2}), std::bad_alloc&);
CHECK(j == "old");
// with iterator debugging, the default constructor of VS 2015's
// std::vector is noexcept but constructs a proxy with the allocator,
// which terminates when this allocator throws
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<bool> {true, false}), std::bad_alloc&);
CHECK(j == "old");
#endif
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::map<std::string, int> {{"a", 1}}), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, my_json::binary_t({1, 2})), std::bad_alloc&);
CHECK(j == "old");
// the overloads for lvalues of the value types, for the value types
// themselves, and for the remaining compatible types
const std::string string = "new";
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, string), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, "new"), std::bad_alloc&);
CHECK(j == "old");
// to_json only moves a binary value that it converted from another
// container type, which my_json's std::vector<std::uint8_t> is not
using binary_constructor = nlohmann::detail::external_constructor<nlohmann::detail::value_t::binary>;
next_construct_fails = true;
CHECK_THROWS_AS(binary_constructor::construct(j, my_json::binary_t({1, 2})), std::bad_alloc&);
CHECK(j == "old");
my_json::array_t array = {1, 2};
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, array), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(array)), std::bad_alloc&);
CHECK(j == "old");
my_json::object_t object = {{"a", 1}};
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, object), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(object)), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
CHECK(j == "old");
#if JSON_HAS_RANGES && !defined(__MINGW32__)
const std::vector<int> numbers = {1, 2};
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
{
return true;
})), std::bad_alloc&);
CHECK(j == "old");
#endif
next_construct_fails = false;
nlohmann::to_json(j, std::vector<int> {1, 2});
CHECK(j == my_json({1, 2}));
}
}
#endif
-66
View File
@@ -438,72 +438,6 @@ TEST_CASE("JSON pointers")
}
}
SECTION("creating intermediate levels")
{
SECTION("tokens that are valid array indices create arrays")
{
json j;
j["/0"_json_pointer] = 1;
CHECK(j == json({1}));
json j2;
j2["/2"_json_pointer] = 1;
CHECK(j2 == json({nullptr, nullptr, 1}));
json j3;
j3["/-"_json_pointer] = 1;
CHECK(j3 == json({1}));
json j4;
j4["/foo/0/0"_json_pointer] = 1;
CHECK(j4 == json({{"foo", {{1}}}}));
}
SECTION("tokens that are no valid array indices create objects")
{
json j;
j["/one"_json_pointer] = 1;
CHECK(j == json({{"one", 1}}));
// leading '0' can never be a valid array index (RFC 6901, Sect. 4)
json j2;
j2["/01"_json_pointer] = 1;
CHECK(j2 == json({{"01", 1}}));
// the empty token is a valid object key, but no valid array index
json j3;
j3["/"_json_pointer] = 1;
CHECK(j3 == json({{"", 1}}));
}
SECTION("creating a level yields the same result as reusing it (#5357)")
{
json j;
j["/a/b/01/d"_json_pointer] = "value";
json j_init = json::object();
j_init["/a/b"_json_pointer] = json::object();
j_init["/a/b/01/d"_json_pointer] = "value";
const json expected = json::parse(R"({"a":{"b":{"01":{"d":"value"}}}})");
CHECK(j == expected);
CHECK(j_init == expected);
// unflatten uses the same key
const json flat = {{"/a/b/01/d", "value"}};
CHECK(flat.unflatten() == expected);
}
SECTION("existing arrays still reject invalid indices")
{
json j = {1, 2, 3};
CHECK_THROWS_WITH_AS(j["/01"_json_pointer],
"[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer),
"[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
}
}
SECTION("flatten")
{
json j =