From 2c108d0b56747f184a998b5ed713f7efc1580398 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 25 Sep 2026 22:19:39 +0200 Subject: [PATCH 1/7] 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 --- .../nlohmann/detail/conversions/to_json.hpp | 73 +++++++---- include/nlohmann/json.hpp | 30 ++--- single_include/nlohmann/json.hpp | 103 +++++++++------- tests/src/unit-allocator.cpp | 113 ++++++++++++++++++ 4 files changed, 239 insertions(+), 80 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 491bb9873..aa8d35a0f 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -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 struct external_constructor; @@ -65,18 +69,20 @@ struct external_constructor template 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 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,11 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { + typename BasicJsonType::json_value value; + value.string = j.template create(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(str); + j.m_data.m_value = value; j.assert_invariant(); } }; @@ -98,18 +106,20 @@ struct external_constructor template 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 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 +169,10 @@ struct external_constructor template 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 +180,10 @@ struct external_constructor template 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 +199,11 @@ struct external_constructor using std::begin; using std::end; + typename BasicJsonType::json_value value; + value.array = j.template create(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(begin(arr), end(arr)); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -197,15 +211,17 @@ struct external_constructor template static void construct(BasicJsonType& j, const std::vector& 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 +229,12 @@ struct external_constructor enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& 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 +246,16 @@ struct external_constructor enable_if_t>::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(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 +267,10 @@ struct external_constructor template 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 +278,10 @@ struct external_constructor template 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 +293,11 @@ struct external_constructor using std::begin; using std::end; + typename BasicJsonType::json_value value; + value.object = j.template create(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(begin(obj), end(obj)); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index c578fd11a..1bbe396af 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1687,8 +1687,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) { @@ -1710,8 +1710,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(init.begin(), init.end()); + m_data.m_type = value_t::array; } set_parents(); @@ -1724,8 +1724,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; } @@ -1735,8 +1735,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; } @@ -1746,8 +1746,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; } @@ -1757,8 +1757,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; } @@ -2815,8 +2815,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(); + m_data.m_type = value_t::array; assert_invariant(); } @@ -2875,8 +2875,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(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -2928,8 +2928,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(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -3864,8 +3864,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(); } @@ -3897,8 +3897,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(); } @@ -3929,8 +3929,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(); } @@ -3985,8 +3985,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(); } @@ -4010,8 +4010,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(); } @@ -4192,8 +4192,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(); + m_data.m_type = value_t::object; assert_invariant(); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4981c3b02..3f74e166f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -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 struct external_constructor; @@ -6594,18 +6598,20 @@ struct external_constructor template 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 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,11 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { + typename BasicJsonType::json_value value; + value.string = j.template create(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(str); + j.m_data.m_value = value; j.assert_invariant(); } }; @@ -6627,18 +6635,20 @@ struct external_constructor template 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 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 +6698,10 @@ struct external_constructor template 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 +6709,10 @@ struct external_constructor template 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 +6728,11 @@ struct external_constructor using std::begin; using std::end; + typename BasicJsonType::json_value value; + value.array = j.template create(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(begin(arr), end(arr)); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -6726,15 +6740,17 @@ struct external_constructor template static void construct(BasicJsonType& j, const std::vector& 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 +6758,12 @@ struct external_constructor enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& 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 +6775,16 @@ struct external_constructor enable_if_t>::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(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 +6796,10 @@ struct external_constructor template 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 +6807,10 @@ struct external_constructor template 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 +6822,11 @@ struct external_constructor using std::begin; using std::end; + typename BasicJsonType::json_value value; + value.object = j.template create(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(begin(obj), end(obj)); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -26645,8 +26668,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) { @@ -26668,8 +26691,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(init.begin(), init.end()); + m_data.m_type = value_t::array; } set_parents(); @@ -26682,8 +26705,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; } @@ -26693,8 +26716,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; } @@ -26704,8 +26727,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; } @@ -26715,8 +26738,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; } @@ -27773,8 +27796,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(); + m_data.m_type = value_t::array; assert_invariant(); } @@ -27833,8 +27856,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(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -27886,8 +27909,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(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -28822,8 +28845,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(); } @@ -28855,8 +28878,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(); } @@ -28887,8 +28910,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(); } @@ -28943,8 +28966,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(); } @@ -28968,8 +28991,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(); } @@ -29150,8 +29173,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(); + m_data.m_type = value_t::object; assert_invariant(); } diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index cdc532e3a..7c32944c1 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -388,3 +388,116 @@ TEST_CASE("bad my_allocator::construct") j["test"].push_back("should not leak"); } } + +TEST_CASE("a failed allocation leaves the value unchanged") +{ + // create JSON type using the throwing allocator + using my_json = nlohmann::basic_json; + + // 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 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(bytes)), std::bad_alloc&); + next_construct_fails = true; + CHECK_THROWS_AS(_ = my_json::binary(std::vector(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 {1, 2}), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::vector {true, false}), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::map {{"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"); + + next_construct_fails = false; + nlohmann::to_json(j, std::vector {1, 2}); + CHECK(j == my_json({1, 2})); + } +} From 8a26f2dc8f6309c05406d5912fe9fdb7d5ba6388 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 26 Sep 2026 07:41:42 +0200 Subject: [PATCH 2/7] 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 --- tests/src/unit-allocator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 7c32944c1..4e505e9f4 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -389,6 +389,9 @@ TEST_CASE("bad my_allocator::construct") } } +// 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 @@ -501,3 +504,4 @@ TEST_CASE("a failed allocation leaves the value unchanged") CHECK(j == my_json({1, 2})); } } +#endif From e9c84befa15d35339b2b7f326fcb8921c1c4c907 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 26 Sep 2026 10:07:22 +0200 Subject: [PATCH 3/7] 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 --- include/nlohmann/detail/conversions/to_json.hpp | 15 ++++++--------- single_include/nlohmann/json.hpp | 15 ++++++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index aa8d35a0f..7ba3ab71f 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -91,11 +91,10 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { - typename BasicJsonType::json_value value; - value.string = j.template create(str); + auto* created = j.template create(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 = value; + j.m_data.m_value.string = created; j.assert_invariant(); } }; @@ -199,11 +198,10 @@ struct external_constructor using std::begin; using std::end; - typename BasicJsonType::json_value value; - value.array = j.template create(begin(arr), end(arr)); + auto* created = j.template create(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 = value; + j.m_data.m_value.array = created; j.set_parents(); j.assert_invariant(); } @@ -293,11 +291,10 @@ struct external_constructor using std::begin; using std::end; - typename BasicJsonType::json_value value; - value.object = j.template create(begin(obj), end(obj)); + auto* created = j.template create(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 = value; + j.m_data.m_value.object = created; j.set_parents(); j.assert_invariant(); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3f74e166f..118f1cefe 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6620,11 +6620,10 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { - typename BasicJsonType::json_value value; - value.string = j.template create(str); + auto* created = j.template create(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 = value; + j.m_data.m_value.string = created; j.assert_invariant(); } }; @@ -6728,11 +6727,10 @@ struct external_constructor using std::begin; using std::end; - typename BasicJsonType::json_value value; - value.array = j.template create(begin(arr), end(arr)); + auto* created = j.template create(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 = value; + j.m_data.m_value.array = created; j.set_parents(); j.assert_invariant(); } @@ -6822,11 +6820,10 @@ struct external_constructor using std::begin; using std::end; - typename BasicJsonType::json_value value; - value.object = j.template create(begin(obj), end(obj)); + auto* created = j.template create(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 = value; + j.m_data.m_value.object = created; j.set_parents(); j.assert_invariant(); } From fa28fff7d618712a604eb59932293c2f5552a74f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 27 Sep 2026 13:30:57 +0200 Subject: [PATCH 4/7] Skip the vector 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 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 --- tests/src/unit-allocator.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 4e505e9f4..9405cfd38 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -487,9 +487,14 @@ TEST_CASE("a failed allocation leaves the value unchanged") CHECK_THROWS_AS(nlohmann::to_json(j, std::vector {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 {true, false}), std::bad_alloc&); CHECK(j == "old"); +#endif next_construct_fails = true; CHECK_THROWS_AS(nlohmann::to_json(j, std::map {{"a", 1}}), std::bad_alloc&); From 90078597c7399e6fd516daf12ab7d6314a5ac086 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 27 Sep 2026 18:29:37 +0200 Subject: [PATCH 5/7] 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, so the test calls the constructor directly. Signed-off-by: Niels Lohmann --- tests/src/unit-allocator.cpp | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 9405cfd38..1c0d4102c 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -12,6 +12,11 @@ #include using nlohmann::json; +#include +#if JSON_HAS_RANGES + #include +#endif + namespace { // special test case to check if memory is leaked if constructor throws @@ -504,6 +509,54 @@ TEST_CASE("a failed allocation leaves the value unchanged") 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 is not + using binary_constructor = nlohmann::detail::external_constructor; + 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 {1, 2}), std::bad_alloc&); + CHECK(j == "old"); + +#if JSON_HAS_RANGES && !defined(__MINGW32__) + const std::vector 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 {1, 2}); CHECK(j == my_json({1, 2})); From ba6bf32f6d81b7fdd7fc13fdf1d7e4533ea5ad75 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 30 Sep 2026 18:12:52 +0200 Subject: [PATCH 6/7] 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 check; skip the whole section on that toolchain instead. The Release builds and all other compilers still run it. Signed-off-by: Niels Lohmann --- tests/src/unit-allocator.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 8de2022d3..c3e170d9c 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -484,6 +484,10 @@ TEST_CASE("a failed allocation leaves the value unchanged") 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 @@ -498,14 +502,9 @@ TEST_CASE("a failed allocation leaves the value unchanged") CHECK_THROWS_AS(nlohmann::to_json(j, std::vector {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 {true, false}), std::bad_alloc&); CHECK(j == "old"); -#endif next_construct_fails = true; CHECK_THROWS_AS(nlohmann::to_json(j, std::map {{"a", 1}}), std::bad_alloc&); @@ -567,5 +566,6 @@ TEST_CASE("a failed allocation leaves the value unchanged") nlohmann::to_json(j, std::vector {1, 2}); CHECK(j == my_json({1, 2})); } +#endif } #endif From e40078053380913d0bc6aa2f7a01cc6ec84779a6 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 1 Oct 2026 08:09:34 +0200 Subject: [PATCH 7/7] Re-amalgamate single_include (#5745) * Re-amalgamate single_include #5737 changed 13 headers under include/ but merged without the matching single_include/nlohmann/json.hpp update, so the amalgamated header still had, among others, the GCC C++20 -Wignored-attributes pragma block and the clang -Wdocumentation push/pop that #5737 removed, the forwarding from_json tuple/array helpers it replaced with const references, and lacked the output_adapter char_traits changes it added. Regenerated with `make amalgamate` (astyle 3.4.13). The diff is exactly `git diff b54ed188e e5a89d671 -- include/` (164+/95-); json_fwd.hpp and json_literals.hpp were already up to date. Signed-off-by: Niels Lohmann * Run the amalgamation check on pushes to develop #5737 was merged 39 seconds after its last push, while its own check_amalgamation run was still queued (earlier runs had been cancelled by the concurrency group), so the stale single_include reached develop without any failing check. Also run the check on pushes to develop, without cancelling in-progress develop runs. The "save" job (PR number/author for the comment workflow) only runs for pull requests, the checkout falls back to github.sha, and comment_check_amalgamation.yml only comments for PR-triggered runs, since push runs have no PR and no "pr" artifact. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .github/workflows/check_amalgamation.yml | 13 +- .../workflows/comment_check_amalgamation.yml | 3 +- single_include/nlohmann/json.hpp | 259 +++++++++++------- 3 files changed, 176 insertions(+), 99 deletions(-) diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index d81a80b18..2f234d8be 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -2,16 +2,23 @@ name: "Check amalgamation" on: pull_request: + # also check develop itself: a PR can be merged before its own run of this + # workflow completes (e.g. while it is still queued), leaving single_include + # stale on develop without any failing check + push: + branches: + - develop concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read jobs: save: + if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - name: Harden Runner @@ -43,11 +50,11 @@ jobs: with: egress-policy: audit - - name: Checkout pull request + - name: Checkout pull request or pushed commit uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: main - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false - name: Checkout tools diff --git a/.github/workflows/comment_check_amalgamation.yml b/.github/workflows/comment_check_amalgamation.yml index 4667329d2..d5bd23fc8 100644 --- a/.github/workflows/comment_check_amalgamation.yml +++ b/.github/workflows/comment_check_amalgamation.yml @@ -10,7 +10,8 @@ permissions: jobs: comment: - if: ${{ github.event.workflow_run.conclusion == 'failure' }} + # push runs on develop have no PR to comment on (and no "pr" artifact) + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} runs-on: ubuntu-latest permissions: contents: read diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f1de82e0a..0e394cb10 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18,16 +18,6 @@ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ -// Workaround for GCC template redefinition errors in C++ modules -// When nlohmann/json.hpp is included in a C++20 module preamble after -// other module imports, GCC may report spurious redefinition errors for -// STL templates. These pragmas suppress those false positives. -// See: https://github.com/nlohmann/json/issues/5103 -#if defined(__GNUC__) && !defined(__clang__) && __cplusplus >= 202002L - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wignored-attributes" -#endif - #include // all_of, find, for_each, none_of #include // isnan #include // nullptr_t, ptrdiff_t, size_t @@ -2617,13 +2607,6 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_NO_THREAD_LOCAL 1 #endif -// disable documentation warnings on clang -#if defined(__clang__) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdocumentation" - #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -#endif - // allow disabling exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception @@ -2682,7 +2665,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ @@ -2696,7 +2679,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ @@ -2735,7 +2718,7 @@ void templated_json_throw(ExceptionType exception) { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ @@ -2750,7 +2733,7 @@ void templated_json_throw(ExceptionType exception) { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ @@ -3578,8 +3561,7 @@ inline StringType escape(const StringType& s) /*! * @brief string unescaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to unescape - * @return unescaped string + * @param[in,out] s string to unescape in place * * Note the order of escaping "~1" to "/" and "~0" to "~" is important. * @@ -5871,22 +5853,22 @@ void()) } template < typename BasicJsonType, typename T, std::size_t... Idx > -std::array from_json_inplace_array_impl(BasicJsonType&& j, +std::array from_json_inplace_array_impl(const BasicJsonType& j, identity_tag> /*unused*/, index_sequence /*unused*/) { - return { { std::forward(j).at(Idx).template get()... } }; + return { { j.at(Idx).template get()... } }; } template < typename BasicJsonType, typename T, std::size_t N > -auto from_json(BasicJsonType&& j, identity_tag> tag) --> decltype(from_json_inplace_array_impl(std::forward(j), tag, make_index_sequence {})) +auto from_json(const BasicJsonType& j, identity_tag> tag) +-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence {})) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); } - return from_json_inplace_array_impl(std::forward(j), tag, make_index_sequence {}); + return from_json_inplace_array_impl(j, tag, make_index_sequence {}); } template @@ -6021,54 +6003,54 @@ template using tuple_type = std::tuple < decltype(from_json_tuple_get_impl(std::declval(), detail::identity_tag {}, detail::priority_tag {}))... >; template -tuple_type from_json_tuple_impl_base(BasicJsonType&& j, index_sequence /*unused*/) +tuple_type from_json_tuple_impl_base(const BasicJsonType& j, index_sequence /*unused*/) { - return tuple_type(from_json_tuple_get_impl(std::forward(j).at(Idx), detail::identity_tag {}, detail::priority_tag {})...); + return tuple_type(from_json_tuple_get_impl(j.at(Idx), detail::identity_tag {}, detail::priority_tag {})...); } template -std::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/) +std::tuple<> from_json_tuple_impl_base(const BasicJsonType& /*unused*/, index_sequence<> /*unused*/) { return {}; } template < typename BasicJsonType, class A1, class A2 > -std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) +std::pair from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) { - return {std::forward(j).at(0).template get(), - std::forward(j).at(1).template get()}; + return {j.at(0).template get(), + j.at(1).template get()}; } template -inline void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) +inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, priority_tag<1> /*unused*/) { - p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); + p = from_json_tuple_impl(j, identity_tag> {}, priority_tag<0> {}); } template -std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) +std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(cxpr_and>, is_compatible_reference_type>...>::value, + static_assert(cxpr_and>, is_compatible_reference_type>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); - return from_json_tuple_impl_base<1, Args...>(std::forward(j), index_sequence_for {}); + return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } template -inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) +inline void from_json_tuple_impl(const BasicJsonType& j, std::tuple& t, priority_tag<3> /*unused*/) { - t = from_json_tuple_impl_base<2, Args...>(std::forward(j), index_sequence_for {}); + t = from_json_tuple_impl_base<2, Args...>(j, index_sequence_for {}); } template -auto from_json(BasicJsonType&& j, TupleRelated&& t) --> decltype(from_json_tuple_impl(std::forward(j), std::forward(t), priority_tag<3> {})) +auto from_json(const BasicJsonType& j, TupleRelated&& t) +-> decltype(from_json_tuple_impl(j, std::forward(t), priority_tag<3> {})) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); } - return from_json_tuple_impl(std::forward(j), std::forward(t), priority_tag<3> {}); + return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); } template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, @@ -6153,7 +6135,7 @@ struct from_json_fn /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html -namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces,misc-anonymous-namespace-in-header) { #endif JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers) @@ -6629,11 +6611,11 @@ namespace std JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags") #endif template -class tuple_size<::nlohmann::detail::iteration_proxy_value> // NOLINT(cert-dcl58-cpp) +class tuple_size<::nlohmann::detail::iteration_proxy_value> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification) : public std::integral_constant {}; template -class tuple_element> // NOLINT(cert-dcl58-cpp) +class tuple_element> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification) { public: using type = decltype( @@ -7178,7 +7160,7 @@ struct to_json_fn /// namespace to hold default `to_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html -namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces,misc-anonymous-namespace-in-header) { #endif JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers) @@ -8399,6 +8381,9 @@ struct container_input_adapter_factory< ContainerType, static adapter_type create(ContainerType&& container) { + // container is forwarded twice on purpose: the resulting begin/end + // iterator types must match adapter_type, computed the same way + // NOLINTNEXTLINE(bugprone-use-after-move) return input_adapter(begin(std::forward(container)), end(std::forward(container))); } }; @@ -10298,9 +10283,9 @@ class lexer : public lexer_base ///////////////////// /*! - @brief get codepoint from 4 hex characters following `\u` + @brief get codepoint from 4 hex characters following `\\u` - For input "\u c1 c2 c3 c4" the codepoint is: + For input "\\u c1 c2 c3 c4" the codepoint is: (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) @@ -13949,7 +13934,7 @@ class binary_reader @brief Parses a C-style string from the BSON input. @param[in,out] result A reference to the string variable where the read string is to be stored. - @return `true` if the \x00-byte indicating the end of the string was + @return `true` if the \\x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ bool get_bson_cstr(string_t& result) @@ -13979,7 +13964,7 @@ class binary_reader @brief read a C-style string from contiguous input in one step @param[in,out] result the string to append to - @return whether the string was read; if the input has no \x00-byte, nothing + @return whether the string was read; if the input has no \\x00-byte, nothing is read, and @ref get_bson_cstr reports the end of the input */ bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/) @@ -18636,7 +18621,7 @@ This class implements a both iterators (iterator and const_iterator) for the been set (e.g., by a constructor or a copy assignment). If the iterator is default-constructed, it is *uninitialized* and most methods are undefined. **The library uses assertions to detect calls on uninitialized iterators.** -@requirement REQ-JSON-01 The class satisfies the following concept requirements: +This class satisfies the following concept requirements (REQ-JSON-01): - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. @@ -19400,7 +19385,7 @@ namespace detail iterator (to create @ref reverse_iterator) and @ref const_iterator (to create @ref const_reverse_iterator). -@requirement REQ-JSON-02 The class satisfies the following concept requirements: +This class satisfies the following concept requirements (REQ-JSON-02): - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. @@ -19843,11 +19828,11 @@ class json_pointer JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); } - // only triggered on special platforms (like 32bit), see also - // https://github.com/nlohmann/json/pull/2203 + // the index does not fit into size_type; on 64-bit platforms this is + // only SIZE_MAX itself (see #2203 and #5395) if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) { - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE + JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); } return static_cast(res); @@ -19881,7 +19866,7 @@ class json_pointer /*! @brief create and return a reference to the pointed to value - @complexity Linear in the number of reference tokens. + Complexity: Linear in the number of reference tokens. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if array index is not a number @@ -19968,7 +19953,7 @@ class json_pointer @return reference to the JSON value pointed to by the JSON pointer - @complexity Linear in the length of the JSON pointer. + Complexity: Linear in the length of the JSON pointer. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @@ -20880,6 +20865,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // size_t #include // shared_ptr, make_shared #include // basic_string +#include // conditional, integral_constant, is_same #include // move #include // vector @@ -20988,11 +20974,13 @@ class output_stream_adapter : public output_adapter_protocol : stream(s) {} + // NOLINTNEXTLINE(portability-template-virtual-member-function) void write_character(CharType c) override { stream.put(c); } + // NOLINTNEXTLINE(portability-template-virtual-member-function) void write_characters(const CharType* s, std::size_t length) override { stream.write(s, static_cast(length)); @@ -21059,7 +21047,82 @@ class output_adapter_sink output_adapter_t oa; }; -template> +/// @brief whether std::basic_string has a non-deprecated std::char_traits +/// specialization, and is therefore usable as output_adapter's default StringType +/// +/// std::char_traits is only guaranteed (and, on some standard libraries, only +/// implemented without a deprecation warning) for the character types listed +/// below; std::char_traits for any other T (e.g. std::uint8_t, as used by the +/// binary writers) is a non-standard extension some standard libraries deprecate. +/// See https://github.com/nlohmann/json/issues/5725 item 2. +template +struct is_output_adapter_string_char_type : std::integral_constant < bool, + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value +#if defined(__cpp_lib_char8_t) && (__cpp_lib_char8_t >= 201907L) + || std::is_same::value +#endif + > {}; + +/// @brief placeholder type for output_adapter's StringType and (with JSON_NO_IO +/// undefined) its std::basic_ostream constructor parameter, for CharType +/// with no non-deprecated std::char_traits specialization +/// +/// Never actually used: the StringType- and std::basic_ostream-based +/// output_adapter constructors are neither documented nor tested for such +/// CharType (only the std::vector-based constructor is used for them, by the +/// binary writers). Naming std::basic_string or +/// std::basic_ostream anywhere such a constructor would otherwise be +/// declared - even as an unused default template argument or an unused, +/// never-called overload - instantiates std::char_traits merely to +/// name the type, which is exactly what triggers the deprecation warning this +/// placeholder avoids. +template +struct output_adapter_no_string_type {}; + +// Select output_adapter's default StringType (and, below, its ostream +// constructor's parameter type) via partial specialization, not +// std::conditional: std::conditional requires both T and F to be named +// as template arguments up front, which would still instantiate (and thus name) +// std::basic_string / std::basic_ostream for every CharType, +// defeating the point. A bool non-type parameter with two specializations only +// ever names the type that is actually selected. +template::value> +struct output_adapter_default_string_type +{ + using type = output_adapter_no_string_type; +}; + +template +struct output_adapter_default_string_type +{ + using type = std::basic_string; +}; + +#ifndef JSON_NO_IO +/// distinct from output_adapter_no_string_type, so the placeholder overloads of +/// output_adapter's constructor (used when CharType is not a character type) +/// stay distinct overloads instead of colliding into a single redeclaration +template +struct output_adapter_no_ostream_type {}; + +template::value> +struct output_adapter_ostream_type +{ + using type = output_adapter_no_ostream_type; +}; + +template +struct output_adapter_ostream_type +{ + using type = std::basic_ostream; +}; +#endif // JSON_NO_IO + +template < typename CharType, typename StringType = + typename output_adapter_default_string_type::type > class output_adapter { public: @@ -21068,7 +21131,7 @@ class output_adapter : oa(std::make_shared>(vec)) {} #ifndef JSON_NO_IO - output_adapter(std::basic_ostream& s) + output_adapter(typename output_adapter_ostream_type::type& s) : oa(std::make_shared>(s)) {} #endif // JSON_NO_IO @@ -24727,7 +24790,7 @@ class serializer @param[in] ichar indentation character to use @param[in] pretty_print_ whether the output shall be pretty-printed @param[in] ensure_ascii_ If @a ensure_ascii_ is true, all non-ASCII - characters in the output are escaped with `\uXXXX` sequences, and the + characters in the output are escaped with `\\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] indent_step_ the indent level @param[in] error_handler_ how to react on decoding errors @@ -25352,7 +25415,7 @@ class serializer @param[in] s the string to escape - @complexity Linear in the length of string @a s. + Complexity: Linear in the length of string @a s. */ void dump_escaped(const string_t& s) { @@ -25856,7 +25919,7 @@ class serializer } /*! - * @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer + * @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer * * Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the * string escaping hot path. It writes exactly six characters ('\\', 'u' and @@ -26206,7 +26269,7 @@ class serializer /// whether to pretty-print the output const bool pretty_print; - /// whether to escape non-ASCII characters with \uXXXX sequences + /// whether to escape non-ASCII characters with \\uXXXX sequences const bool ensure_ascii; /// the indent level @@ -29156,12 +29219,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @throw what @ref json_serializer `from_json()` method throws - @liveexample{The example below shows several conversions from JSON values + The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get__ValueType_const} + be converted to integers, (2) A JSON array can be converted to a standard + `std::vector`, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`. @since version 2.1.0 */ @@ -29228,7 +29291,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @return a copy of *this, converted into @a BasicJsonType - @complexity Depending on the implementation of the called `from_json()` + Complexity: Depending on the implementation of the called `from_json()` method. @since version 3.2.0 @@ -29252,7 +29315,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @return a copy of *this - @complexity Constant. + Complexity: Constant. @since version 2.1.0 */ @@ -29298,7 +29361,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @tparam ValueTypeCV the provided value type @tparam ValueType the returned value type - @return copy of the JSON value, converted to @tparam ValueType if necessary + @return copy of the JSON value, converted to @a ValueType if necessary @throw what @ref json_serializer `from_json()` method throws if conversion is required @@ -29336,12 +29399,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - @complexity Constant. + Complexity: Constant. - @liveexample{The example below shows how pointers to internal values of a + The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `nullptr` is returned if the value and the requested pointer type does not - match.,get__PointerType} + match. @sa see @ref get_ptr() for explicit pointer-member access @@ -29435,14 +29498,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec to the JSON value type (e.g., the JSON value is of type boolean, but a string is requested); see example below - @complexity Linear in the size of the JSON value. + Complexity: Linear in the size of the JSON value. - @liveexample{The example below shows several conversions from JSON values + The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,operator__ValueType} + be converted to integers, (2) A JSON array can be converted to a standard + `std::vector`, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`. @since version 1.0.0 */ @@ -31897,6 +31960,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @deprecated This function is deprecated since 3.8.0 and will be removed in /// version 4.0.0 of the library. Please use /// sax_parse(ptr, ptr + len) instead. + // + // Clang reports "declaration is marked with '@deprecated' command but does + // not have a deprecation attribute" for this overload even though + // JSON_HEDLEY_DEPRECATED_FOR below does expand to __attribute__((deprecated)); + // isolated reproductions of this exact declaration shape (doc comment, + // template<>, two stacked __attribute__ lines, an overload set of the same + // name) do not reproduce it, so this looks like a Clang comment/declaration + // association quirk specific to this overload within basic_json, not a + // genuine documentation bug. See #5725 item 2. +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation-deprecated-sync" +#endif template JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) JSON_HEDLEY_NON_NULL(2) @@ -31913,6 +31989,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict); } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif #ifndef JSON_NO_IO /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ @@ -33575,7 +33654,7 @@ namespace std // NOLINT(cert-dcl58-cpp) /// @brief hash value for JSON objects /// @sa https://json.nlohmann.me/api/basic_json/std_hash/ NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct hash // NOLINT(cert-dcl58-cpp) +struct hash // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification) { std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const { @@ -33608,7 +33687,7 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', /// @brief exchanges the values of two JSON objects /// @sa https://json.nlohmann.me/api/basic_json/std_swap/ NLOHMANN_BASIC_JSON_TPL_DECLARATION -inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp) +inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp,bugprone-std-namespace-modification) is_nothrow_move_constructible::value&& // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) is_nothrow_move_assignable::value) { @@ -33622,7 +33701,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC /// @brief std::formatter specialization for JSON values /// @sa https://json.nlohmann.me/api/basic_json/std_formatter/ NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct formatter // NOLINT(cert-dcl58-cpp) +struct formatter // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification) { // -1 means compact output (dump()); any value >= 0 means pretty-printed // output with that many spaces (or indent_char) per level (dump(indent, indent_char)). @@ -33711,11 +33790,6 @@ struct formatter // NOLINT(cert-dcl58-c -// restore clang diagnostic settings -#if defined(__clang__) - #pragma clang diagnostic pop -#endif - // clean up #undef JSON_ASSERT #undef JSON_INTERNAL_CATCH @@ -33916,11 +33990,6 @@ struct formatter // NOLINT(cert-dcl58-c // IWYU pragma: keep -// End of GCC diagnostic pragmas for C++ modules support -#if defined(__GNUC__) && !defined(__clang__) && __cplusplus >= 202002L - #pragma GCC diagnostic pop -#endif - // The user-defined string literals are in a separate header, because their // bodies instantiate the parser in every translation unit that includes them. // Define JSON_NO_AUTOMATIC_UDLS to include only