From 2c108d0b56747f184a998b5ed713f7efc1580398 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 25 Sep 2026 22:19:39 +0200 Subject: [PATCH] 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})); + } +}