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