• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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