• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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__anonce6a8a430111::bad_allocator24     template<class U> bad_allocator(const bad_allocator<U>& /*unused*/) { }
25 
26     template<class... Args>
construct__anonce6a8a430111::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__anonce6a8a430211::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__anonce6a8a430211::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__anonce6a8a430211::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__anonce6a8a430311::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__anonce6a8a430311::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