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