Compare commits

..
Author SHA1 Message Date
Niels Lohmann 994b83cd17 Merge branch 'develop' into claude/fix-allocation-exception-safety
- json.hpp: develop's convert_null_to() already creates the container
  before setting the type, so the "null becomes array/object" sites
  follow develop
- to_json.hpp: keep this branch's create-then-assign order in the range
  constructor together with develop's single set_parents() call

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:34:51 +02:00
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
9 changed files with 363 additions and 129 deletions
@@ -50,11 +50,9 @@ The default value is `0` (disabled — existing behavior is preserved).
``` ```
Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one
element, and a single `[string, value]` pair *written as a braced list*, such as `{{"key", "value"}}`, which still element, and a single `[string, value]` pair such as `{{"key", "value"}}`, which still creates an object, are not
creates an object, are not affected. This exception is based on how the pair is written, not on the shape of its affected. The library's own conversions are not affected either: for example, `std::tuple<int>{5}` still becomes
value: an existing JSON value that happens to be a two-element array with a string as its first element, such as `[5]`.
`json arr = {"key", 42};`, is still copied by `json j{arr};` rather than turned into an object. The library's own
conversions are not affected either: for example, `std::tuple<int>{5}` still becomes `[5]`.
!!! note "ABI compatibility" !!! note "ABI compatibility"
+44 -24
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 * 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 * allocated value (e.g., a string). See bug issue
* https://github.com/nlohmann/json/issues/2865 for more information. * 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; template<value_t> struct external_constructor;
@@ -65,18 +69,20 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string; j.m_data.m_type = value_t::string;
j.m_data.m_value = s; j.m_data.m_value = value;
j.assert_invariant(); j.assert_invariant();
} }
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string; 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(); j.assert_invariant();
} }
@@ -85,9 +91,10 @@ struct external_constructor<value_t::string>
int > = 0 > int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string; 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(); j.assert_invariant();
} }
}; };
@@ -98,18 +105,20 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary; 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(); j.assert_invariant();
} }
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary; 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(); j.assert_invariant();
} }
}; };
@@ -159,9 +168,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; j.m_data.m_type = value_t::array;
j.m_data.m_value = arr; j.m_data.m_value = value;
j.set_parents(); j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -169,9 +179,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -187,9 +198,10 @@ struct external_constructor<value_t::array>
using std::begin; using std::begin;
using std::end; 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -197,15 +209,17 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr) static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{ {
j.m_data.m_value.destroy(j.m_data.m_type); typename BasicJsonType::array_t elements;
j.m_data.m_type = value_t::array; elements.reserve(arr.size());
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->reserve(arr.size());
for (const bool x : arr) for (const bool x : arr)
{ {
j.m_data.m_value.array->push_back(x); elements.push_back(x);
j.set_parent(j.m_data.m_value.array->back());
} }
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(); j.assert_invariant();
} }
@@ -213,11 +227,12 @@ struct external_constructor<value_t::array>
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array; j.m_data.m_value = value;
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.set_parents(); j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -227,13 +242,15 @@ struct external_constructor<value_t::array>
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0> enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr) static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{ {
j.m_data.m_value.destroy(j.m_data.m_type); typename BasicJsonType::array_t elements;
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
for (auto&& x : std::forward<CompatibleArrayType>(arr)) for (auto&& x : std::forward<CompatibleArrayType>(arr))
{ {
j.m_data.m_value.array->push_back(x); 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;
// set the parents only once all elements are in place: a push_back // set the parents only once all elements are in place: a push_back
// that reallocates moves the earlier elements, which does not keep // that reallocates moves the earlier elements, which does not keep
// their parent pointers // their parent pointers
@@ -249,9 +266,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object; j.m_data.m_type = value_t::object;
j.m_data.m_value = obj; j.m_data.m_value = value;
j.set_parents(); j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -259,9 +277,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -273,9 +292,10 @@ struct external_constructor<value_t::object>
using std::begin; using std::begin;
using std::end; 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
-9
View File
@@ -34,7 +34,6 @@ class json_ref
json_ref(std::initializer_list<json_ref> init) json_ref(std::initializer_list<json_ref> init)
: owned_value(init) : owned_value(init)
, braced_list(true)
{} {}
template < template <
@@ -70,17 +69,9 @@ class json_ref
return &** this; return &** this;
} }
/// whether the value was written as a braced list, such as {"key", 1},
/// rather than given as a value
bool is_braced_list() const noexcept
{
return braced_list;
}
private: private:
mutable value_type owned_value = nullptr; mutable value_type owned_value = nullptr;
value_type const* value_ref = nullptr; value_type const* value_ref = nullptr;
bool braced_list = false;
}; };
} // namespace detail } // namespace detail
+6 -18
View File
@@ -2026,18 +2026,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
bool type_deduction = true, bool type_deduction = true,
value_t manual_type = value_t::array) value_t manual_type = value_t::array)
{ {
#if JSON_BRACE_INIT_COPY_SEMANTICS
// a single element that is a value rather than a braced list is
// copied or moved as is, whatever its content looks like
if (type_deduction && init.size() == 1 && !init.begin()->is_braced_list())
{
*this = init.begin()->moved_or_copied();
set_parents();
assert_invariant();
return;
}
#endif
// check if each element is an array with two elements whose first // check if each element is an array with two elements whose first
// element is a string // element is a string
bool is_an_object = std::all_of(init.begin(), init.end(), bool is_an_object = std::all_of(init.begin(), init.end(),
@@ -2068,8 +2056,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_an_object) if (is_an_object)
{ {
// the initializer list is a list of pairs -> create 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_value = value_t::object;
m_data.m_type = value_t::object;
for (auto& element_ref : init) for (auto& element_ref : init)
{ {
@@ -2091,8 +2079,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
} }
#endif #endif
// the initializer list describes an array -> create an array // 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_value.array = create<array_t>(init.begin(), init.end());
m_data.m_type = value_t::array;
} }
set_parents(); set_parents();
@@ -2105,8 +2093,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init) static basic_json binary(const typename binary_t::container_type& init)
{ {
auto res = basic_json(); auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = init; res.m_data.m_value = init;
res.m_data.m_type = value_t::binary;
return res; return res;
} }
@@ -2116,8 +2104,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) static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{ {
auto res = basic_json(); 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_value = binary_t(init, subtype);
res.m_data.m_type = value_t::binary;
return res; return res;
} }
@@ -2127,8 +2115,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init) static basic_json binary(typename binary_t::container_type&& init)
{ {
auto res = basic_json(); auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = std::move(init); res.m_data.m_value = std::move(init);
res.m_data.m_type = value_t::binary;
return res; return res;
} }
@@ -2138,8 +2126,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) static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{ {
auto res = basic_json(); 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_value = binary_t(std::move(init), subtype);
res.m_data.m_type = value_t::binary;
return res; return res;
} }
+50 -51
View File
@@ -6747,6 +6747,10 @@ namespace detail
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an * 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 * allocated value (e.g., a string). See bug issue
* https://github.com/nlohmann/json/issues/2865 for more information. * 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; template<value_t> struct external_constructor;
@@ -6770,18 +6774,20 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string; j.m_data.m_type = value_t::string;
j.m_data.m_value = s; j.m_data.m_value = value;
j.assert_invariant(); j.assert_invariant();
} }
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string; 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(); j.assert_invariant();
} }
@@ -6790,9 +6796,10 @@ struct external_constructor<value_t::string>
int > = 0 > int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string; 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(); j.assert_invariant();
} }
}; };
@@ -6803,18 +6810,20 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary; 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(); j.assert_invariant();
} }
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary; 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(); j.assert_invariant();
} }
}; };
@@ -6864,9 +6873,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; j.m_data.m_type = value_t::array;
j.m_data.m_value = arr; j.m_data.m_value = value;
j.set_parents(); j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -6874,9 +6884,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -6892,9 +6903,10 @@ struct external_constructor<value_t::array>
using std::begin; using std::begin;
using std::end; 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -6902,15 +6914,17 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr) static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{ {
j.m_data.m_value.destroy(j.m_data.m_type); typename BasicJsonType::array_t elements;
j.m_data.m_type = value_t::array; elements.reserve(arr.size());
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->reserve(arr.size());
for (const bool x : arr) for (const bool x : arr)
{ {
j.m_data.m_value.array->push_back(x); elements.push_back(x);
j.set_parent(j.m_data.m_value.array->back());
} }
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(); j.assert_invariant();
} }
@@ -6918,11 +6932,12 @@ struct external_constructor<value_t::array>
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array; j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array; j.m_data.m_value = value;
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.set_parents(); j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -6932,13 +6947,15 @@ struct external_constructor<value_t::array>
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0> enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr) static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{ {
j.m_data.m_value.destroy(j.m_data.m_type); typename BasicJsonType::array_t elements;
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
for (auto&& x : std::forward<CompatibleArrayType>(arr)) for (auto&& x : std::forward<CompatibleArrayType>(arr))
{ {
j.m_data.m_value.array->push_back(x); 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;
// set the parents only once all elements are in place: a push_back // set the parents only once all elements are in place: a push_back
// that reallocates moves the earlier elements, which does not keep // that reallocates moves the earlier elements, which does not keep
// their parent pointers // their parent pointers
@@ -6954,9 +6971,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object; j.m_data.m_type = value_t::object;
j.m_data.m_value = obj; j.m_data.m_value = value;
j.set_parents(); j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -6964,9 +6982,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -6978,9 +6997,10 @@ struct external_constructor<value_t::object>
using std::begin; using std::begin;
using std::end; 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_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object; 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.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
@@ -20865,7 +20885,6 @@ class json_ref
json_ref(std::initializer_list<json_ref> init) json_ref(std::initializer_list<json_ref> init)
: owned_value(init) : owned_value(init)
, braced_list(true)
{} {}
template < template <
@@ -20901,17 +20920,9 @@ class json_ref
return &** this; return &** this;
} }
/// whether the value was written as a braced list, such as {"key", 1},
/// rather than given as a value
bool is_braced_list() const noexcept
{
return braced_list;
}
private: private:
mutable value_type owned_value = nullptr; mutable value_type owned_value = nullptr;
value_type const* value_ref = nullptr; value_type const* value_ref = nullptr;
bool braced_list = false;
}; };
} // namespace detail } // namespace detail
@@ -28984,18 +28995,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
bool type_deduction = true, bool type_deduction = true,
value_t manual_type = value_t::array) value_t manual_type = value_t::array)
{ {
#if JSON_BRACE_INIT_COPY_SEMANTICS
// a single element that is a value rather than a braced list is
// copied or moved as is, whatever its content looks like
if (type_deduction && init.size() == 1 && !init.begin()->is_braced_list())
{
*this = init.begin()->moved_or_copied();
set_parents();
assert_invariant();
return;
}
#endif
// check if each element is an array with two elements whose first // check if each element is an array with two elements whose first
// element is a string // element is a string
bool is_an_object = std::all_of(init.begin(), init.end(), bool is_an_object = std::all_of(init.begin(), init.end(),
@@ -29026,8 +29025,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_an_object) if (is_an_object)
{ {
// the initializer list is a list of pairs -> create 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_value = value_t::object;
m_data.m_type = value_t::object;
for (auto& element_ref : init) for (auto& element_ref : init)
{ {
@@ -29049,8 +29048,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
} }
#endif #endif
// the initializer list describes an array -> create an array // 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_value.array = create<array_t>(init.begin(), init.end());
m_data.m_type = value_t::array;
} }
set_parents(); set_parents();
@@ -29063,8 +29062,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init) static basic_json binary(const typename binary_t::container_type& init)
{ {
auto res = basic_json(); auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = init; res.m_data.m_value = init;
res.m_data.m_type = value_t::binary;
return res; return res;
} }
@@ -29074,8 +29073,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) static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{ {
auto res = basic_json(); 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_value = binary_t(init, subtype);
res.m_data.m_type = value_t::binary;
return res; return res;
} }
@@ -29085,8 +29084,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init) static basic_json binary(typename binary_t::container_type&& init)
{ {
auto res = basic_json(); auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = std::move(init); res.m_data.m_value = std::move(init);
res.m_data.m_type = value_t::binary;
return res; return res;
} }
@@ -29096,8 +29095,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) static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{ {
auto res = basic_json(); 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_value = binary_t(std::move(init), subtype);
res.m_data.m_type = value_t::binary;
return res; return res;
} }
+5
View File
@@ -140,6 +140,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 # 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) 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 # add unit tests
############################################################################# #############################################################################
+175
View File
@@ -12,6 +12,11 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
#include <valarray>
#if JSON_HAS_RANGES
#include <ranges>
#endif
namespace namespace
{ {
// special test case to check if memory is leaked if constructor throws // special test case to check if memory is leaked if constructor throws
@@ -602,3 +607,173 @@ TEST_CASE("bad my_allocator::construct")
j["test"].push_back("should not leak"); 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
@@ -72,28 +72,6 @@ TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS")
CHECK(j7 == json::array({1, 2})); CHECK(j7 == json::array({1, 2}));
} }
SECTION("single-element brace initialization copies a pair-shaped array value (#5662)")
{
// a JSON value that happens to be a 2-element array whose first
// element is a string must still be copied, not turned into an
// object; only a braced list written in the source, such as the
// inner {"key", "value"} of {{"key", "value"}}, describes an object
json const pair_shaped = json::array({"key", 42});
json const j1{pair_shaped};
CHECK(j1.is_array());
CHECK(j1 == pair_shaped);
json const j2 = {pair_shaped};
CHECK(j2.is_array());
CHECK(j2 == pair_shaped);
// the same holds for an rvalue of the same shape
json const j3{json::array({"key", 42})};
CHECK(j3.is_array());
CHECK(j3 == pair_shaped);
}
SECTION("what the macro does not change") SECTION("what the macro does not change")
{ {
// lists with more than one element are unaffected // lists with more than one element are unaffected
+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);
}
}