1// __ _____ _____ _____ 2// __| | __| | | | JSON for Modern C++ (supporting code) 3// | | |__ | | | | | | version 3.11.2 4// |_____|_____|_____|_|___| https://github.com/nlohmann/json 5// 6// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me> 7// SPDX-License-Identifier: MIT 8 9#include "doctest_compatibility.h" 10 11#define JSON_TESTS_PRIVATE 12#include <nlohmann/json.hpp> 13using nlohmann::json; 14 15namespace 16{ 17// special test case to check if memory is leaked if constructor throws 18template<class T> 19struct bad_allocator : std::allocator<T> 20{ 21 using std::allocator<T>::allocator; 22 23 template<class... Args> 24 void construct(T* /*unused*/, Args&& ... /*unused*/) 25 { 26 throw std::bad_alloc(); 27 } 28}; 29} // namespace 30 31TEST_CASE("bad_alloc") 32{ 33 SECTION("bad_alloc") 34 { 35 // create JSON type using the throwing allocator 36 using bad_json = nlohmann::basic_json<std::map, 37 std::vector, 38 std::string, 39 bool, 40 std::int64_t, 41 std::uint64_t, 42 double, 43 bad_allocator>; 44 45 // creating an object should throw 46 CHECK_THROWS_AS(bad_json(bad_json::value_t::object), std::bad_alloc&); 47 } 48} 49 50namespace 51{ 52bool next_construct_fails = false; 53bool next_destroy_fails = false; 54bool next_deallocate_fails = false; 55 56template<class T> 57struct my_allocator : std::allocator<T> 58{ 59 using std::allocator<T>::allocator; 60 61 template<class... Args> 62 void construct(T* p, Args&& ... args) 63 { 64 if (next_construct_fails) 65 { 66 next_construct_fails = false; 67 throw std::bad_alloc(); 68 } 69 70 ::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...); 71 } 72 73 void deallocate(T* p, std::size_t n) 74 { 75 if (next_deallocate_fails) 76 { 77 next_deallocate_fails = false; 78 throw std::bad_alloc(); 79 } 80 81 std::allocator<T>::deallocate(p, n); 82 } 83 84 void destroy(T* p) 85 { 86 if (next_destroy_fails) 87 { 88 next_destroy_fails = false; 89 throw std::bad_alloc(); 90 } 91 92 static_cast<void>(p); // fix MSVC's C4100 warning 93 p->~T(); 94 } 95 96 template <class U> 97 struct rebind 98 { 99 using other = my_allocator<U>; 100 }; 101}; 102 103// allows deletion of raw pointer, usually hold by json_value 104template<class T> 105void my_allocator_clean_up(T* p) 106{ 107 assert(p != nullptr); 108 my_allocator<T> alloc; 109 alloc.destroy(p); 110 alloc.deallocate(p, 1); 111} 112} // namespace 113 114TEST_CASE("controlled bad_alloc") 115{ 116 // create JSON type using the throwing allocator 117 using my_json = nlohmann::basic_json<std::map, 118 std::vector, 119 std::string, 120 bool, 121 std::int64_t, 122 std::uint64_t, 123 double, 124 my_allocator>; 125 126 SECTION("class json_value") 127 { 128 SECTION("json_value(value_t)") 129 { 130 SECTION("object") 131 { 132 next_construct_fails = false; 133 auto t = my_json::value_t::object; 134 CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).object)); 135 next_construct_fails = true; 136 CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&); 137 next_construct_fails = false; 138 } 139 SECTION("array") 140 { 141 next_construct_fails = false; 142 auto t = my_json::value_t::array; 143 CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).array)); 144 next_construct_fails = true; 145 CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&); 146 next_construct_fails = false; 147 } 148 SECTION("string") 149 { 150 next_construct_fails = false; 151 auto t = my_json::value_t::string; 152 CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).string)); 153 next_construct_fails = true; 154 CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&); 155 next_construct_fails = false; 156 } 157 } 158 159 SECTION("json_value(const string_t&)") 160 { 161 next_construct_fails = false; 162 my_json::string_t v("foo"); 163 CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(v).string)); 164 next_construct_fails = true; 165 CHECK_THROWS_AS(my_json::json_value(v), std::bad_alloc&); 166 next_construct_fails = false; 167 } 168 } 169 170 SECTION("class basic_json") 171 { 172 SECTION("basic_json(const CompatibleObjectType&)") 173 { 174 next_construct_fails = false; 175 std::map<std::string, std::string> v {{"foo", "bar"}}; 176 CHECK_NOTHROW(my_json(v)); 177 next_construct_fails = true; 178 CHECK_THROWS_AS(my_json(v), std::bad_alloc&); 179 next_construct_fails = false; 180 } 181 182 SECTION("basic_json(const CompatibleArrayType&)") 183 { 184 next_construct_fails = false; 185 std::vector<std::string> v {"foo", "bar", "baz"}; 186 CHECK_NOTHROW(my_json(v)); 187 next_construct_fails = true; 188 CHECK_THROWS_AS(my_json(v), std::bad_alloc&); 189 next_construct_fails = false; 190 } 191 192 SECTION("basic_json(const typename string_t::value_type*)") 193 { 194 next_construct_fails = false; 195 CHECK_NOTHROW(my_json("foo")); 196 next_construct_fails = true; 197 CHECK_THROWS_AS(my_json("foo"), std::bad_alloc&); 198 next_construct_fails = false; 199 } 200 201 SECTION("basic_json(const typename string_t::value_type*)") 202 { 203 next_construct_fails = false; 204 std::string s("foo"); 205 CHECK_NOTHROW(my_json(s)); 206 next_construct_fails = true; 207 CHECK_THROWS_AS(my_json(s), std::bad_alloc&); 208 next_construct_fails = false; 209 } 210 } 211} 212 213namespace 214{ 215template<class T> 216struct allocator_no_forward : std::allocator<T> 217{ 218 allocator_no_forward() = default; 219 template <class U> 220 allocator_no_forward(allocator_no_forward<U> /*unused*/) {} 221 222 template <class U> 223 struct rebind 224 { 225 using other = allocator_no_forward<U>; 226 }; 227 228 template <class... Args> 229 void construct(T* p, const Args& ... args) noexcept(noexcept(::new (static_cast<void*>(p)) T(args...))) 230 { 231 // force copy even if move is available 232 ::new (static_cast<void*>(p)) T(args...); 233 } 234}; 235} // namespace 236 237TEST_CASE("bad my_allocator::construct") 238{ 239 SECTION("my_allocator::construct doesn't forward") 240 { 241 using bad_alloc_json = nlohmann::basic_json<std::map, 242 std::vector, 243 std::string, 244 bool, 245 std::int64_t, 246 std::uint64_t, 247 double, 248 allocator_no_forward>; 249 250 bad_alloc_json j; 251 j["test"] = bad_alloc_json::array_t(); 252 j["test"].push_back("should not leak"); 253 } 254} 255