Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix ~basic_json causing std::terminate #4654

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 48 additions & 34 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
@@ -568,51 +568,65 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
if (t == value_t::array || t == value_t::object)
{
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json> stack;

// move the top-level items to stack
if (t == value_t::array)
{
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
try
{
stack.reserve(object->size());
for (auto&& it : *object)
{
stack.push_back(std::move(it.second));
}
}

while (!stack.empty())
{
// move the last item to local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
#endif
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json, allocator_type> stack;

// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
// move the top-level items to stack
if (t == value_t::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();
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else if (current_item.is_object())
else
{
for (auto&& it : *current_item.m_data.m_value.object)
stack.reserve(object->size());
for (auto&& it : *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
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));

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)
82 changes: 48 additions & 34 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
@@ -20567,51 +20567,65 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
if (t == value_t::array || t == value_t::object)
{
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json> stack;

// move the top-level items to stack
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));
}
}

while (!stack.empty())
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
try
{
// move the last item to local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
#endif
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json, allocator_type> stack;

// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
// move the top-level items to stack
if (t == value_t::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();
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else if (current_item.is_object())
else
{
for (auto&& it : *current_item.m_data.m_value.object)
stack.reserve(object->size());
for (auto&& it : *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
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));

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)
55 changes: 55 additions & 0 deletions tests/src/unit-allocator.cpp
Original file line number Diff line number Diff line change
@@ -261,3 +261,58 @@ TEST_CASE("bad my_allocator::construct")
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
Loading