// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.11.2 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2022 Niels Lohmann // SPDX-License-Identifier: MIT #include "doctest_compatibility.h" #define JSON_TESTS_PRIVATE #include using nlohmann::json; namespace { // special test case to check if memory is leaked if constructor throws template struct bad_allocator : std::allocator { using std::allocator::allocator; bad_allocator() = default; template bad_allocator(const bad_allocator& /*unused*/) { } template void construct(T* /*unused*/, Args&& ... /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) { throw std::bad_alloc(); } template struct rebind { using other = bad_allocator; }; }; } // namespace TEST_CASE("bad_alloc") { SECTION("bad_alloc") { // create JSON type using the throwing allocator using bad_json = nlohmann::basic_json; // 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 struct my_allocator : std::allocator { using std::allocator::allocator; template void construct(T* p, Args&& ... args) { if (next_construct_fails) { next_construct_fails = false; throw std::bad_alloc(); } ::new (reinterpret_cast(p)) T(std::forward(args)...); } void deallocate(T* p, std::size_t n) { if (next_deallocate_fails) { next_deallocate_fails = false; throw std::bad_alloc(); } std::allocator::deallocate(p, n); } void destroy(T* p) { if (next_destroy_fails) { next_destroy_fails = false; throw std::bad_alloc(); } static_cast(p); // fix MSVC's C4100 warning p->~T(); } template struct rebind { using other = my_allocator; }; }; // allows deletion of raw pointer, usually hold by json_value template void my_allocator_clean_up(T* p) { assert(p != nullptr); my_allocator 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::basic_json; 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 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 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; } } } namespace { template struct allocator_no_forward : std::allocator { allocator_no_forward() = default; template allocator_no_forward(allocator_no_forward /*unused*/) {} template struct rebind { using other = allocator_no_forward; }; template void construct(T* p, const Args& ... args) noexcept(noexcept(::new (static_cast(p)) T(args...))) { // force copy even if move is available ::new (static_cast(p)) T(args...); } }; } // namespace TEST_CASE("bad my_allocator::construct") { SECTION("my_allocator::construct doesn't forward") { using bad_alloc_json = nlohmann::basic_json; bad_alloc_json j; j["test"] = bad_alloc_json::array_t(); j["test"].push_back("should not leak"); } } namespace { template struct NAlloc { // Aliases and inner types using value_type = T; using size_type = std::size_t; using difference_type = ptrdiff_t; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; template struct rebind { using other = NAlloc; }; NAlloc() : alloc() { }; virtual ~NAlloc() { } pointer allocate(std::size_t n) { return static_cast(alloc.allocate(n)); // get memory from pool } void deallocate(pointer p, [[maybe_unused]] std::size_t n) { alloc.deallocate(static_cast(p), 1); // return memory to pool } bool operator!=(const NAlloc& other) const { return !(*this == other); } bool operator==(const NAlloc& other) const { return alloc == other.alloc; } my_allocator alloc; }; using OstringStream = std::basic_ostringstream, NAlloc>; using IstringStream = std::basic_istringstream, NAlloc>; typedef std::basic_string, NAlloc> RtString; } TEST_CASE("controlled bad_alloc_rt_string") { // create JSON type using the throwing allocator using my_json = nlohmann::basic_json; 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_rt_string") { SECTION("basic_json(const CompatibleObjectType&)") { next_construct_fails = false; const std::map 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 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 RtString s("foo"); CHECK_NOTHROW(my_json(s)); next_construct_fails = true; CHECK_THROWS_AS(my_json(s), std::bad_alloc&); next_construct_fails = false; } } }