| // __ _____ _____ _____ | |
| // __| | __| | | | JSON for Modern C++ (supporting code) | |
| // | | |__ | | | | | | version 3.12.0 | |
| // |_____|_____|_____|_|___| https://github.com/nlohmann/json | |
| // | |
| // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> | |
| // SPDX-License-Identifier: MIT | |
| #include "doctest_compatibility.h" | |
| #define JSON_TESTS_PRIVATE | |
| #include <nlohmann/json.hpp> | |
| using nlohmann::json; | |
| #include <valarray> | |
| #if JSON_HAS_RANGES | |
| #include <ranges> | |
| #endif | |
| namespace | |
| { | |
| // special test case to check if memory is leaked if constructor throws | |
| template<class T> | |
| struct bad_allocator : std::allocator<T> | |
| { | |
| using std::allocator<T>::allocator; | |
| bad_allocator() = default; | |
| template<class U> bad_allocator(const bad_allocator<U>& /*unused*/) { } | |
| template<class... Args> | |
| [[noreturn]] void construct(T* /*unused*/, Args&& ... /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) | |
| { | |
| throw std::bad_alloc(); | |
| } | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = bad_allocator<U>; | |
| }; | |
| }; | |
| } // namespace | |
| TEST_CASE("get_allocator") | |
| { | |
| const auto alloc = nlohmann::json::get_allocator(); | |
| CHECK(alloc == std::allocator<nlohmann::json>()); | |
| } | |
| TEST_CASE("bad_alloc") | |
| { | |
| SECTION("bad_alloc") | |
| { | |
| // create JSON type using the throwing allocator | |
| using bad_json = nlohmann::json::with_allocator_t<bad_allocator>; | |
| // creating an object should throw | |
| CHECK_THROWS_AS(bad_json(bad_json::value_t::object), std::bad_alloc&); | |
| } | |
| } | |
| namespace | |
| { | |
| bool next_construct_fails = false; | |
| bool next_destroy_fails = false; | |
| bool next_deallocate_fails = false; | |
| template<class T> | |
| struct my_allocator : std::allocator<T> | |
| { | |
| using std::allocator<T>::allocator; | |
| template<class... Args> | |
| void construct(T* p, Args&& ... args) | |
| { | |
| if (next_construct_fails) | |
| { | |
| next_construct_fails = false; | |
| throw std::bad_alloc(); | |
| } | |
| ::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...); | |
| } | |
| void deallocate(T* p, std::size_t n) | |
| { | |
| if (next_deallocate_fails) | |
| { | |
| next_deallocate_fails = false; | |
| throw std::bad_alloc(); | |
| } | |
| std::allocator<T>::deallocate(p, n); | |
| } | |
| void destroy(T* p) | |
| { | |
| if (next_destroy_fails) | |
| { | |
| next_destroy_fails = false; | |
| throw std::bad_alloc(); | |
| } | |
| static_cast<void>(p); // fix MSVC's C4100 warning | |
| p->~T(); | |
| } | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = my_allocator<U>; | |
| }; | |
| }; | |
| // allows deletion of raw pointer, usually hold by json_value | |
| template<class T> | |
| void my_allocator_clean_up(T* p) | |
| { | |
| assert(p != nullptr); | |
| my_allocator<T> alloc; | |
| alloc.destroy(p); | |
| alloc.deallocate(p, 1); | |
| } | |
| } // namespace | |
| TEST_CASE("controlled bad_alloc") | |
| { | |
| // create JSON type using the throwing allocator | |
| using my_json = nlohmann::json::with_allocator_t<my_allocator>; | |
| SECTION("class json_value") | |
| { | |
| SECTION("json_value(value_t)") | |
| { | |
| SECTION("object") | |
| { | |
| next_construct_fails = false; | |
| auto t = my_json::value_t::object; | |
| CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).object)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("array") | |
| { | |
| next_construct_fails = false; | |
| auto t = my_json::value_t::array; | |
| CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).array)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("string") | |
| { | |
| next_construct_fails = false; | |
| auto t = my_json::value_t::string; | |
| CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).string)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| } | |
| SECTION("json_value(const string_t&)") | |
| { | |
| next_construct_fails = false; | |
| const my_json::string_t v("foo"); | |
| CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(v).string)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json::json_value(v), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| } | |
| SECTION("class basic_json") | |
| { | |
| SECTION("basic_json(const CompatibleObjectType&)") | |
| { | |
| next_construct_fails = false; | |
| const std::map<std::string, std::string> v {{"foo", "bar"}}; | |
| CHECK_NOTHROW(my_json(v)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json(v), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("basic_json(const CompatibleArrayType&)") | |
| { | |
| next_construct_fails = false; | |
| const std::vector<std::string> v {"foo", "bar", "baz"}; | |
| CHECK_NOTHROW(my_json(v)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json(v), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("basic_json(const typename string_t::value_type*)") | |
| { | |
| next_construct_fails = false; | |
| CHECK_NOTHROW(my_json("foo")); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json("foo"), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("basic_json(const typename string_t::value_type*)") | |
| { | |
| next_construct_fails = false; | |
| const std::string s("foo"); | |
| CHECK_NOTHROW(my_json(s)); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(my_json(s), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("basic_json(const basic_json&) of a deeply nested value (#5387)") | |
| { | |
| // Copying a value nested deeper than the descent bound builds the | |
| // copy from the top down: every value whose own copy has not been | |
| // made yet stays a null value until it is. Failing an allocation | |
| // part-way through is what proves such a half-built copy can still | |
| // be destroyed. | |
| // | |
| // Which path the failure lands in depends on the build: the first | |
| // allocation of a copy belongs to the outermost level, so here it | |
| // is the descending one. Built with JSON_NO_THREAD_LOCAL - as the | |
| // ci_test_no_thread_local target builds the whole suite - no | |
| // descent is made at all and the very same failure lands in the | |
| // iterative path instead, part-way through its worklist. | |
| const auto check_deep_copy = [](bool objects) | |
| { | |
| CAPTURE(objects) | |
| next_construct_fails = false; | |
| // deeper than the 128 levels the copy constructor descends into | |
| const std::size_t depth = 300; | |
| my_json j = 1; | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| if (objects) | |
| { | |
| my_json wrapper = my_json::object(); | |
| wrapper["a"] = std::move(j); | |
| j = std::move(wrapper); | |
| } | |
| else | |
| { | |
| j = my_json::array({std::move(j)}); | |
| } | |
| } | |
| // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested | |
| CHECK_NOTHROW(my_json(j)); | |
| next_construct_fails = true; | |
| // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested | |
| CHECK_THROWS_AS(my_json(j), std::bad_alloc&); | |
| next_construct_fails = false; | |
| }; | |
| check_deep_copy(false); | |
| check_deep_copy(true); | |
| } | |
| } | |
| } | |
| namespace | |
| { | |
| // counts every allocation made on behalf of a basic_json value (of its own | |
| // object_t/array_t/string_t/binary_t or of its own type), and can be told to | |
| // fail one of them: the n-th call to allocate() throws std::bad_alloc instead | |
| // of allocating, whichever type it is allocating for | |
| std::size_t alloc_call_count = 0; | |
| long fail_at_alloc_call = -1; // -1: never fail | |
| template<class T> | |
| struct nth_alloc_fails_allocator : std::allocator<T> | |
| { | |
| using std::allocator<T>::allocator; | |
| T* allocate(std::size_t n) | |
| { | |
| const auto index = alloc_call_count++; | |
| if (fail_at_alloc_call >= 0 && index == static_cast<std::size_t>(fail_at_alloc_call)) | |
| { | |
| throw std::bad_alloc(); | |
| } | |
| return std::allocator<T>::allocate(n); | |
| } | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = nth_alloc_fails_allocator<U>; | |
| }; | |
| }; | |
| // builds a value nested more than 128 levels deep - the bound the copy | |
| // constructor descends into before it continues without the call stack - and | |
| // checks that a copy survives any single allocation of it failing: every | |
| // attempt either throws std::bad_alloc, without crashing or leaving the | |
| // source altered, or completes the copy | |
| template<class BasicJsonType> | |
| void check_deep_copy_survives_failing_allocation(bool nest_objects) | |
| { | |
| CAPTURE(nest_objects) | |
| fail_at_alloc_call = -1; | |
| // [[[ ... [1] ... ]]], or the same nesting with objects, 130 levels deep | |
| BasicJsonType src = 1; | |
| for (std::size_t i = 0; i < 130; ++i) | |
| { | |
| if (nest_objects) | |
| { | |
| BasicJsonType wrapper = BasicJsonType::object(); | |
| wrapper["a"] = std::move(src); | |
| src = std::move(wrapper); | |
| } | |
| else | |
| { | |
| src = BasicJsonType::array({std::move(src)}); | |
| } | |
| } | |
| const std::string original_dump = src.dump(); | |
| // first measure how many allocations an unhindered copy takes | |
| alloc_call_count = 0; | |
| { | |
| // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is measured | |
| const BasicJsonType measure(src); | |
| } | |
| const std::size_t total_allocations = alloc_call_count; | |
| REQUIRE(total_allocations > 0); | |
| REQUIRE(src.dump() == original_dump); | |
| // let the 0th, 1st, 2nd, ... allocation of the copy fail in turn; every | |
| // such copy must throw std::bad_alloc rather than crash, and the source | |
| // must come out exactly as it went in | |
| for (std::size_t n = 0; n < total_allocations; ++n) | |
| { | |
| CAPTURE(n) | |
| alloc_call_count = 0; | |
| fail_at_alloc_call = static_cast<long>(n); | |
| CHECK_THROWS_AS(BasicJsonType(src), std::bad_alloc&); | |
| fail_at_alloc_call = -1; | |
| CHECK(src.dump() == original_dump); | |
| } | |
| // once no allocation is made to fail, the copy itself must succeed | |
| fail_at_alloc_call = -1; | |
| const BasicJsonType copy(src); | |
| CHECK(copy.dump() == original_dump); | |
| CHECK(src.dump() == original_dump); | |
| } | |
| } // namespace | |
| TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") | |
| { | |
| // With iterator debugging (MSVC STL debug builds, also used by clang-cl), | |
| // containers allocate a debug proxy through the allocator inside their | |
| // noexcept move constructors, so failing that allocation terminates the | |
| // program instead of throwing std::bad_alloc. Nothing to check there. | |
| #if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) | |
| SECTION("std::map-backed object_t") | |
| { | |
| using bad_alloc_json = nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>; | |
| check_deep_copy_survives_failing_allocation<bad_alloc_json>(false); | |
| check_deep_copy_survives_failing_allocation<bad_alloc_json>(true); | |
| } | |
| SECTION("ordered_map-backed object_t") | |
| { | |
| using bad_alloc_ordered_json = nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>; | |
| check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(false); | |
| check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(true); | |
| } | |
| #endif | |
| } | |
| namespace | |
| { | |
| // counts the allocations of pairs with a non-const first member: the object | |
| // types store std::pair<const Key, T>, so only the scratch space of the | |
| // iterative deep copy allocates std::pair<Key, T> | |
| std::size_t scratch_pair_allocations = 0; | |
| template<class T> | |
| struct is_scratch_pair : std::false_type {}; | |
| template<class K, class V> | |
| struct is_scratch_pair<std::pair<K, V>> : std::integral_constant < bool, !std::is_const<K>::value > {}; | |
| template<class T> | |
| struct scratch_counting_allocator : std::allocator<T> | |
| { | |
| using std::allocator<T>::allocator; | |
| T* allocate(std::size_t n) | |
| { | |
| if (is_scratch_pair<T>::value) | |
| { | |
| ++scratch_pair_allocations; | |
| } | |
| return std::allocator<T>::allocate(n); | |
| } | |
| #ifdef __cpp_lib_allocate_at_least | |
| // std::allocator<T>::allocate_at_least would bypass the counting, and | |
| // libc++'s containers prefer it over allocate from C++23 on | |
| auto allocate_at_least(std::size_t n) | |
| { | |
| if (is_scratch_pair<T>::value) | |
| { | |
| ++scratch_pair_allocations; | |
| } | |
| return std::allocator<T>::allocate_at_least(n); | |
| } | |
| #endif | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = scratch_counting_allocator<U>; | |
| }; | |
| }; | |
| } // namespace | |
| TEST_CASE("deep copy uses the provided allocator") | |
| { | |
| using counting_json = nlohmann::json::with_allocator_t<scratch_counting_allocator>; | |
| // deeper than the 128 levels the copy constructor descends into, so the | |
| // innermost objects are copied by the iterative deep copy | |
| counting_json j = 1; | |
| for (std::size_t i = 0; i < 300; ++i) | |
| { | |
| counting_json wrapper = counting_json::object(); | |
| wrapper["a"] = std::move(j); | |
| j = std::move(wrapper); | |
| } | |
| scratch_pair_allocations = 0; | |
| // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested | |
| const counting_json copy(j); | |
| CHECK(scratch_pair_allocations > 0); | |
| CHECK(copy == j); | |
| } | |
| namespace | |
| { | |
| // the number of constructions countdown_allocator lets happen, including the | |
| // one that fails; 0 means none ever fails | |
| std::size_t constructions_until_failure = 0; | |
| template<class T> | |
| struct countdown_allocator : std::allocator<T> | |
| { | |
| using std::allocator<T>::allocator; | |
| template<class U, class... Args> | |
| void construct(U* p, Args&& ... args) | |
| { | |
| if (constructions_until_failure != 0) | |
| { | |
| --constructions_until_failure; | |
| if (constructions_until_failure == 0) | |
| { | |
| throw std::bad_alloc(); | |
| } | |
| } | |
| ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...); | |
| } | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = countdown_allocator<U>; | |
| }; | |
| }; | |
| } // namespace | |
| TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)") | |
| { | |
| // MSVC 2015's debug STL constructs the containers' debug proxies through | |
| // the allocator in noexcept constructors, so a failing construction crashes | |
| // the program there instead of throwing std::bad_alloc. Nothing to check. | |
| #if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) | |
| using countdown_json = nlohmann::json::with_allocator_t<countdown_allocator>; | |
| // deeper than the 128 levels the converting constructor descends into, so | |
| // that failures land on both sides of the bound - or, built with | |
| // JSON_NO_THREAD_LOCAL, all in the iterative conversion | |
| json j = {1, "two", {{"three", 3}}}; | |
| for (std::size_t i = 0; i < 150; ++i) | |
| { | |
| j = json{{"a", json::array({j, "sibling"})}}; | |
| } | |
| // Fail every construction in turn. Each failure has to reach the caller, | |
| // and everything built until then has to be destroyed cleanly. | |
| std::size_t failures = 0; | |
| for (std::size_t n = 1;; ++n) | |
| { | |
| constructions_until_failure = n; | |
| try | |
| { | |
| const countdown_json converted = j; | |
| constructions_until_failure = 0; | |
| CHECK(converted.dump() == j.dump()); | |
| break; | |
| } | |
| catch (const std::bad_alloc&) | |
| { | |
| ++failures; | |
| } | |
| } | |
| CHECK(failures > 0); | |
| #endif | |
| } | |
| namespace | |
| { | |
| template<class T> | |
| struct allocator_no_forward : std::allocator<T> | |
| { | |
| allocator_no_forward() = default; | |
| template <class U> | |
| allocator_no_forward(const allocator_no_forward<U>& /*unused*/) {} | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = allocator_no_forward<U>; | |
| }; | |
| template <class... Args> | |
| void construct(T* p, const Args& ... args) noexcept(noexcept(::new (static_cast<void*>(p)) T(args...))) | |
| { | |
| // force copy even if move is available | |
| ::new (static_cast<void*>(p)) T(args...); | |
| } | |
| }; | |
| } // namespace | |
| TEST_CASE("bad my_allocator::construct") | |
| { | |
| SECTION("my_allocator::construct doesn't forward") | |
| { | |
| using bad_alloc_json = nlohmann::json::with_allocator_t<allocator_no_forward>; | |
| bad_alloc_json j; | |
| j["test"] = bad_alloc_json::array_t(); | |
| j["test"].push_back("should not leak"); | |
| } | |
| } | |
| namespace | |
| { | |
| std::size_t counting_allocator_allocations = 0; | |
| std::size_t counting_allocator_deallocations = 0; | |
| template<class T> | |
| struct counting_allocator : std::allocator<T> | |
| { | |
| using std::allocator<T>::allocator; | |
| T* allocate(std::size_t n) | |
| { | |
| ++counting_allocator_allocations; | |
| return std::allocator<T>::allocate(n); | |
| } | |
| void deallocate(T* p, std::size_t n) | |
| { | |
| ++counting_allocator_deallocations; | |
| std::allocator<T>::deallocate(p, n); | |
| } | |
| template <class U> | |
| struct rebind | |
| { | |
| using other = counting_allocator<U>; | |
| }; | |
| }; | |
| } // namespace | |
| TEST_CASE("destructor performs no allocation, only deallocation") | |
| { | |
| // see https://github.com/nlohmann/json/issues/4842 and | |
| // https://github.com/nlohmann/json/issues/5135: destroying nested | |
| // arrays/objects used to allocate a temporary stack (first with | |
| // std::allocator, later - after #4842 - with the provided allocator). | |
| // Since that stack could itself throw bad_alloc from inside the | |
| // noexcept destructor (#5135), destroy() no longer allocates anything: | |
| // it only ever frees what is already there. | |
| using counting_json = nlohmann::json::with_allocator_t<counting_allocator>; | |
| SECTION("array") | |
| { | |
| auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory) | |
| const auto allocations_before = counting_allocator_allocations; | |
| const auto deallocations_before = counting_allocator_deallocations; | |
| delete j; // NOLINT(cppcoreguidelines-owning-memory) | |
| CHECK(counting_allocator_allocations == allocations_before); | |
| CHECK(counting_allocator_deallocations > deallocations_before); | |
| } | |
| SECTION("object") | |
| { | |
| auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory) | |
| const auto allocations_before = counting_allocator_allocations; | |
| const auto deallocations_before = counting_allocator_deallocations; | |
| delete j; // NOLINT(cppcoreguidelines-owning-memory) | |
| CHECK(counting_allocator_allocations == allocations_before); | |
| CHECK(counting_allocator_deallocations > deallocations_before); | |
| } | |
| SECTION("mixed tree of empty/non-empty arrays and objects") | |
| { | |
| auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory) | |
| { | |
| {"empty_obj", counting_json::object()}, | |
| {"empty_arr", counting_json::array()}, | |
| {"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}}, | |
| {"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})} | |
| }); | |
| const auto allocations_before = counting_allocator_allocations; | |
| const auto deallocations_before = counting_allocator_deallocations; | |
| delete j; // NOLINT(cppcoreguidelines-owning-memory) | |
| CHECK(counting_allocator_allocations == allocations_before); | |
| CHECK(counting_allocator_deallocations > deallocations_before); | |
| } | |
| } | |
| // the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave | |
| // next_construct_fails set for the next allocation outside a check | |
| #if !defined(JSON_NOEXCEPTION) | |
| TEST_CASE("a failed allocation leaves the value unchanged") | |
| { | |
| // create JSON type using the throwing allocator | |
| using my_json = nlohmann::json::with_allocator_t<my_allocator>; | |
| // Each of these creates a string, array, object, or binary value. The | |
| // value must be created before the type is changed: otherwise, a failed | |
| // creation left a value of the new type without anything behind it (an | |
| // assertion in its destructor, a null pointer everywhere else) or, when | |
| // an old value was destroyed first, with a pointer to that destroyed one. | |
| SECTION("creating a binary value") | |
| { | |
| const std::vector<std::uint8_t> bytes = {1, 2, 3}; | |
| my_json _; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(_ = my_json::binary(bytes), std::bad_alloc&); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(_ = my_json::binary(bytes, 42), std::bad_alloc&); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes)), std::bad_alloc&); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes), 42), std::bad_alloc&); | |
| next_construct_fails = false; | |
| } | |
| SECTION("turning a null value into an array or object") | |
| { | |
| my_json j; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j[0], std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j["key"], std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| #ifdef JSON_HAS_CPP_17 | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| #endif | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| const my_json one = 1; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j.push_back(one), std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j.push_back(my_json::object_t::value_type("key", 1)), std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j.emplace_back(1), std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j.emplace("key", 1), std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| const my_json object = {{"key", 1}}; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(j.update(object), std::bad_alloc&); | |
| CHECK(j.is_null()); | |
| next_construct_fails = false; | |
| } | |
| // With iterator debugging, VS 2015's containers construct a proxy with the | |
| // allocator in constructors that cannot report its failure, so a failing | |
| // allocator crashes this section there (SIGSEGV with VS 2015 Debug x86). | |
| #if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) | |
| SECTION("converting into an existing value") | |
| { | |
| // to_json replaces the value it is given; the old one must survive a | |
| // failed creation of the new one | |
| my_json j = "old"; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::string("new")), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<int> {1, 2}), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<bool> {true, false}), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::map<std::string, int> {{"a", 1}}), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, my_json::binary_t({1, 2})), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| // the overloads for lvalues of the value types, for the value types | |
| // themselves, and for the remaining compatible types | |
| const std::string string = "new"; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, string), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, "new"), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| // to_json only moves a binary value that it converted from another | |
| // container type, which my_json's std::vector<std::uint8_t> is not | |
| using binary_constructor = nlohmann::detail::external_constructor<nlohmann::detail::value_t::binary>; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(binary_constructor::construct(j, my_json::binary_t({1, 2})), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| my_json::array_t array = {1, 2}; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, array), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::move(array)), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| my_json::object_t object = {{"a", 1}}; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, object), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::move(object)), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| #if JSON_HAS_RANGES && !defined(__MINGW32__) | |
| const std::vector<int> numbers = {1, 2}; | |
| next_construct_fails = true; | |
| CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/) | |
| { | |
| return true; | |
| })), std::bad_alloc&); | |
| CHECK(j == "old"); | |
| #endif | |
| next_construct_fails = false; | |
| nlohmann::to_json(j, std::vector<int> {1, 2}); | |
| CHECK(j == my_json({1, 2})); | |
| } | |
| #endif | |
| } | |
| #endif |