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>
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 template<class... Args>
construct__anon20ff38c40111::bad_allocator24 void construct(T* /*unused*/, Args&& ... /*unused*/)
25 {
26 throw std::bad_alloc();
27 }
28 };
29 } // namespace
30
31 TEST_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
50 namespace
51 {
52 bool next_construct_fails = false;
53 bool next_destroy_fails = false;
54 bool next_deallocate_fails = false;
55
56 template<class T>
57 struct my_allocator : std::allocator<T>
58 {
59 using std::allocator<T>::allocator;
60
61 template<class... Args>
construct__anon20ff38c40211::my_allocator62 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
deallocate__anon20ff38c40211::my_allocator73 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
destroy__anon20ff38c40211::my_allocator84 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
104 template<class T>
my_allocator_clean_up(T * p)105 void 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
114 TEST_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
213 namespace
214 {
215 template<class T>
216 struct allocator_no_forward : std::allocator<T>
217 {
218 allocator_no_forward() = default;
219 template <class U>
allocator_no_forward__anon20ff38c40311::allocator_no_forward220 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>
construct__anon20ff38c40311::allocator_no_forward229 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
237 TEST_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