This commit is contained in:
Gareth Andrew Lloyd 2025-02-28 21:05:18 +01:00 committed by GitHub
commit fe0942314a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 153 additions and 70 deletions

View File

@ -568,51 +568,65 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
} }
if (t == value_t::array || t == value_t::object) if (t == value_t::array || t == value_t::object)
{ {
// flatten the current json_value to a heap-allocated stack #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
std::vector<basic_json> stack; try
{
#endif
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json, allocator_type> stack;
// move the top-level items to stack // move the top-level items to stack
if (t == value_t::array) if (t == value_t::array)
{
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else
{
stack.reserve(object->size());
for (auto&& it : *object)
{ {
stack.push_back(std::move(it.second)); stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
} }
} else
while (!stack.empty())
{
// move the last item to local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
{ {
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack)); stack.reserve(object->size());
for (auto&& it : *object)
current_item.m_data.m_value.array->clear();
}
else if (current_item.is_object())
{
for (auto&& it : *current_item.m_data.m_value.object)
{ {
stack.push_back(std::move(it.second)); stack.push_back(std::move(it.second));
} }
current_item.m_data.m_value.object->clear();
} }
// it's now safe that current_item get destructed while (!stack.empty())
// since it doesn't have any children {
// move the last item to local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
{
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
current_item.m_data.m_value.array->clear();
}
else if (current_item.is_object())
{
for (auto&& it : *current_item.m_data.m_value.object)
{
stack.push_back(std::move(it.second));
}
current_item.m_data.m_value.object->clear();
}
// it's now safe that current_item get destructed
// since it doesn't have any children
}
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
} }
catch (...) // NOLINT(bugprone-empty-catch)
{
// Recursion avoidance has issue allocating temporary space. This may have been `std::bad_alloc`
// or any other exception thrown by a custom allocator.
// RAII will correctly clean up anything moved into `stack`.
// Then we continue with regular recursion based destroy, which will not heap allocate.
}
#endif
} }
switch (t) switch (t)

View File

@ -20567,51 +20567,65 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
} }
if (t == value_t::array || t == value_t::object) if (t == value_t::array || t == value_t::object)
{ {
// flatten the current json_value to a heap-allocated stack #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
std::vector<basic_json> stack; try
{
#endif
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json, allocator_type> stack;
// move the top-level items to stack // move the top-level items to stack
if (t == value_t::array) if (t == value_t::array)
{
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else
{
stack.reserve(object->size());
for (auto&& it : *object)
{ {
stack.push_back(std::move(it.second)); stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
} }
} else
while (!stack.empty())
{
// move the last item to local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
{ {
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack)); stack.reserve(object->size());
for (auto&& it : *object)
current_item.m_data.m_value.array->clear();
}
else if (current_item.is_object())
{
for (auto&& it : *current_item.m_data.m_value.object)
{ {
stack.push_back(std::move(it.second)); stack.push_back(std::move(it.second));
} }
current_item.m_data.m_value.object->clear();
} }
// it's now safe that current_item get destructed while (!stack.empty())
// since it doesn't have any children {
// move the last item to local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
{
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
current_item.m_data.m_value.array->clear();
}
else if (current_item.is_object())
{
for (auto&& it : *current_item.m_data.m_value.object)
{
stack.push_back(std::move(it.second));
}
current_item.m_data.m_value.object->clear();
}
// it's now safe that current_item get destructed
// since it doesn't have any children
}
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
} }
catch (...) // NOLINT(bugprone-empty-catch)
{
// Recursion avoidance has issue allocating temporary space. This may have been `std::bad_alloc`
// or any other exception thrown by a custom allocator.
// RAII will correctly clean up anything moved into `stack`.
// Then we continue with regular recursion based destroy, which will not heap allocate.
}
#endif
} }
switch (t) switch (t)

View File

@ -261,3 +261,58 @@ TEST_CASE("bad my_allocator::construct")
j["test"].push_back("should not leak"); j["test"].push_back("should not leak");
} }
} }
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
namespace
{
thread_local bool should_throw = false;
struct QuotaReached : std::exception {};
template<class T>
struct allocator_controlled_throw : std::allocator<T>
{
allocator_controlled_throw() = default;
template <class U>
allocator_controlled_throw(allocator_controlled_throw<U> /*unused*/) {}
template <class U>
struct rebind
{
using other = allocator_controlled_throw<U>;
};
T* allocate(size_t n)
{
if (should_throw)
{
throw QuotaReached{};
}
return std::allocator<T>::allocate(n);
}
};
} // namespace
TEST_CASE("controlled my_allocator::allocate")
{
SECTION("~basic_json tolerant of internal exceptions")
{
using my_alloc_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
allocator_controlled_throw>;
{
auto j = my_alloc_json{1, 2, 3, 4};
should_throw = true;
// `j` is destroyed, ~basic_json is noexcept
// if allocation attempted, exception thrown
// exception should be internally handled
} // should not std::terminate
}
}
#endif