Compare commits

...
Author SHA1 Message Date
Niels Lohmann dd2dd45abd Fix false GCC -Warray-bounds error with JSON_DIAGNOSTICS at -O3 (#5744)
* Re-amalgamate single_include after #5737

#5737 changed sources under include/ without regenerating single_include,
so the single header on develop is out of date. This commit only runs
make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Suppress GCC's false -Warray-bounds error in set_parents() with JSON_DIAGNOSTICS

With JSON_DIAGNOSTICS, GCC 13 to 15 report a false -Warray-bounds error
at -O3 when set_parents() is inlined right after a non-container value
(e.g., a string) was created: the std::map access in the object branch
is checked against the string allocation although m_type rules that
branch out. GCC 15 fixed the pattern from #4819 but not this one.

Suppress -Warray-bounds around set_parents() only, and add a regression
test that is compiled with -O3 -Werror=array-bounds on GCC.

Fixes #5742

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the false -Warray-bounds error by setting the type after the value

The pragma around set_parents() is no longer needed: the base branch
(#5585) creates a value before setting its type. Before, the type of a new
string was stored first, so GCC had to assume that operator new could
change it again, kept the object branch of the inlined set_parents() alive,
and checked it against the string's allocation. Rewriting set_parents()
itself (if/else instead of switch) does not help.

Verified with Docker GCC 12.5, 13, 14, 15.3 and 16.2: the regression test
builds and passes without the pragma, and fails to build on develop.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:24:10 +02:00
Niels Lohmann 362a902170 Merge branch 'develop' into claude/fix-allocation-exception-safety
include/nlohmann/json.hpp conflicted in update(const_iterator, const_iterator, bool):
kept develop's prepare_update() helper, which already creates the object
before setting its type (the same fix this branch makes elsewhere).
single_include/nlohmann/json.hpp was regenerated via `make amalgamate`
instead of being hand-resolved.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 08:53:14 +02:00
Niels Lohmann ba6bf32f6d Skip the to_json allocation-failure section on VS 2015 Debug
AppVeyor's VS 2015 Debug x86 job crashed (SIGSEGV) in "a failed
allocation leaves the value unchanged / converting into an existing
value". With iterator debugging, VS 2015's containers construct a proxy
with the allocator in constructors that cannot report its failure, so
the throwing test allocator crashes there. The existing guard covered
only the std::vector<bool> check; skip the whole section on that
toolchain instead. The Release builds and all other compilers still run
it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 18:12:52 +02:00
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
6 changed files with 378 additions and 78 deletions
+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();
}
+14 -14
View File
@@ -1894,8 +1894,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)
{
@@ -1917,8 +1917,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();
@@ -1931,8 +1931,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;
}
@@ -1942,8 +1942,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;
}
@@ -1953,8 +1953,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;
}
@@ -1964,8 +1964,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;
}
@@ -3011,8 +3011,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();
}
@@ -3078,8 +3078,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();
}
@@ -3131,8 +3131,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();
}
@@ -4082,8 +4082,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();
}
@@ -4115,8 +4115,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();
}
@@ -4147,8 +4147,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();
}
@@ -4203,8 +4203,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();
}
@@ -4228,8 +4228,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();
}
+59 -39
View File
@@ -6655,6 +6655,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;
@@ -6678,18 +6682,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();
}
@@ -6698,9 +6704,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();
}
};
@@ -6711,18 +6718,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();
}
};
@@ -6772,9 +6781,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();
}
@@ -6782,9 +6792,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();
}
@@ -6800,9 +6811,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();
}
@@ -6810,15 +6822,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();
}
@@ -6826,11 +6840,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();
}
@@ -6842,14 +6857,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
@@ -6861,9 +6878,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();
}
@@ -6871,9 +6889,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();
}
@@ -6885,9 +6904,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();
}
@@ -28572,8 +28592,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)
{
@@ -28595,8 +28615,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();
@@ -28609,8 +28629,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;
}
@@ -28620,8 +28640,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;
}
@@ -28631,8 +28651,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;
}
@@ -28642,8 +28662,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;
}
@@ -29689,8 +29709,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();
}
@@ -29756,8 +29776,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();
}
@@ -29809,8 +29829,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();
}
@@ -30760,8 +30780,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();
}
@@ -30793,8 +30813,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();
}
@@ -30825,8 +30845,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();
}
@@ -30881,8 +30901,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();
}
@@ -30906,8 +30926,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();
}
+5
View File
@@ -139,6 +139,11 @@ json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
# only the #972 regression test needs thirdparty/fifo_map on its include path
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742)
json_test_set_test_options(test-diagnostics-optimized
COMPILE_OPTIONS $<$<CXX_COMPILER_ID:GNU>:-O3 -Werror=array-bounds>
)
#############################################################################
# add unit tests
#############################################################################
+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
@@ -592,3 +597,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;
}
// With iterator debugging, VS 2015's containers construct a proxy with the
// allocator in constructors that cannot report its failure, so a failing
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
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");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<bool> {true, false}), std::bad_alloc&);
CHECK(j == "old");
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
}
#endif
+80
View File
@@ -0,0 +1,80 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// Regression test for https://github.com/nlohmann/json/issues/5742: with
// JSON_DIAGNOSTICS, GCC (12 to at least 16) reported a false -Warray-bounds
// error in the inlined set_parents() at -O3. The type of a new string was set
// before the string was allocated, so GCC had to assume that operator new
// could change it again and checked the object branch of set_parents()
// against the string's allocation. Setting the type after creating the value
// avoids this. The warning depends on GCC's inlining decisions, so the
// sections cover two patterns that trigger it on different GCC versions
// (#4819 and #5742).
// On GCC, this file is compiled with -O3 -Werror=array-bounds (see
// tests/CMakeLists.txt), so the test fails to build if the warning returns.
#include "doctest_compatibility.h"
#ifdef JSON_DIAGNOSTICS
#undef JSON_DIAGNOSTICS
#endif
#define JSON_DIAGNOSTICS 1
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <algorithm>
#include <iterator>
#include <utility>
#include <vector>
namespace
{
enum class diag_color
{
red,
green,
blue
};
void to_json(json& j, const diag_color& c)
{
static const std::pair<diag_color, json> m[] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
{diag_color::red, "r"},
{diag_color::green, "g"},
{diag_color::blue, "b"},
};
const auto* it = std::find_if(std::begin(m), std::end(m), [c](const std::pair<diag_color, json>& p)
{
return p.first == c;
});
j = it->second;
}
} // namespace
TEST_CASE("diagnostics with optimization")
{
SECTION("issue #4819 - object in vector")
{
std::vector<json> jsons{};
jsons.emplace_back(json({{"key", "value"}}));
CHECK(jsons.back()["key"] == "value");
}
SECTION("issue #5742 - string values from a static table")
{
json j = json::array();
j.push_back(diag_color::red);
j.push_back(diag_color::green);
j.push_back(diag_color::blue);
CHECK(j.dump() == R"(["r","g","b"])");
CHECK_THROWS_WITH_AS(j[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
}
}