Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Iterative destruction of JSON containers (objects and arrays) #1431

Closed
Show file tree
Hide file tree
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: 82 additions & 0 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,11 @@ class basic_json
{
case value_t::object:
{
if (!this->object->empty())
{
destroy_subelements_iterative(t);
}

AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
Expand All @@ -1006,6 +1011,11 @@ class basic_json

case value_t::array:
{
if (!this->array->empty())
{
destroy_subelements_iterative(t);
}

AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
Expand All @@ -1026,6 +1036,78 @@ class basic_json
}
}
}

private:

/// destroys all children in an iterative depth-first traversal using an explicit stack
/// to avoid stack overflows in a recursive destruction of deeply nested hierarchies
void destroy_subelements_iterative(value_t t) noexcept
{
std::vector<std::pair<json_value*, value_t>> stack;
stack.emplace_back(this, t);
while (!stack.empty())
{
json_value* value;
value_t type;
std::tie(value, type) = stack.back();
switch (type)
{
case value_t::object:
destroy_object_subelements(stack, value);
break;
case value_t::array:
destroy_array_subelements(stack, value);
break;
default:
// this should never happen
break;
}
}
}

void destroy_object_subelements(std::vector<std::pair<json_value*, value_t>>& stack, json_value* value) noexcept
{
while (!value->object->empty())
{
basic_json& inner_value = value->object->begin()->second;
value_t inner_type = inner_value.type();
if ((inner_type == value_t::object || inner_type == value_t::array) && !inner_value.empty())
{
// the current inner value has children, push it onto the stack and return to continue the depth-first
// traversal with the children, this limits the maximal stack size exactly to the maximal hierarchy depth
stack.emplace_back(&inner_value.m_value, inner_type);
return;
}
// the current inner value does not have any children (anymore), remove it from this object
value->object->erase(value->object->begin());
}
// the object does not have any children anymore, remove it from the stack
// the object itself will be destroyed later
stack.pop_back();
}

void destroy_array_subelements(std::vector<std::pair<json_value*, value_t>>& stack, json_value* value) noexcept
{
while (!value->array->empty())
{
// start removing children from the back of the array, to guarantee O(1) removal complexity
basic_json& inner_value = value->array->back();
value_t inner_type = inner_value.type();
if ((inner_type == value_t::object || inner_type == value_t::array) && !inner_value.empty())
{
// the current inner value has children, push it onto the stack and return to continue the depth-first
// traversal with the children, this limits the maximal stack size exactly to the maximal hierarchy depth
stack.emplace_back(&inner_value.m_value, inner_type);
return;
}
// the current inner value does not have any children (anymore), remove it from this array
value->array->pop_back();
}
// the array does not have any children anymore, remove it from the stack
// the array itself will be destroyed later
stack.pop_back();
}

};

/*!
Expand Down
82 changes: 82 additions & 0 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13466,6 +13466,11 @@ class basic_json
{
case value_t::object:
{
if (!this->object->empty())
{
destroy_subelements_iterative(t);
}

AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
Expand All @@ -13474,6 +13479,11 @@ class basic_json

case value_t::array:
{
if (!this->array->empty())
{
destroy_subelements_iterative(t);
}

AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
Expand All @@ -13494,6 +13504,78 @@ class basic_json
}
}
}

private:

/// destroys all children in an iterative depth-first traversal using an explicit stack
/// to avoid stack overflows in a recursive destruction of deeply nested hierarchies
void destroy_subelements_iterative(value_t t) noexcept
{
std::vector<std::pair<json_value*, value_t>> stack;
stack.emplace_back(this, t);
while (!stack.empty())
{
json_value* value;
value_t type;
std::tie(value, type) = stack.back();
switch (type)
{
case value_t::object:
destroy_object_subelements(stack, value);
break;
case value_t::array:
destroy_array_subelements(stack, value);
break;
default:
// this should never happen
break;
}
}
}

void destroy_object_subelements(std::vector<std::pair<json_value*, value_t>>& stack, json_value* value) noexcept
{
while (!value->object->empty())
{
basic_json& inner_value = value->object->begin()->second;
value_t inner_type = inner_value.type();
if ((inner_type == value_t::object || inner_type == value_t::array) && !inner_value.empty())
{
// the current inner value has children, push it onto the stack and return to continue the depth-first
// traversal with the children, this limits the maximal stack size exactly to the maximal hierarchy depth
stack.emplace_back(&inner_value.m_value, inner_type);
return;
}
// the current inner value does not have any children (anymore), remove it from this object
value->object->erase(value->object->begin());
}
// the object does not have any children anymore, remove it from the stack
// the object itself will be destroyed later
stack.pop_back();
}

void destroy_array_subelements(std::vector<std::pair<json_value*, value_t>>& stack, json_value* value) noexcept
{
while (!value->array->empty())
{
// start removing children from the back of the array, to guarantee O(1) removal complexity
basic_json& inner_value = value->array->back();
value_t inner_type = inner_value.type();
if ((inner_type == value_t::object || inner_type == value_t::array) && !inner_value.empty())
{
// the current inner value has children, push it onto the stack and return to continue the depth-first
// traversal with the children, this limits the maximal stack size exactly to the maximal hierarchy depth
stack.emplace_back(&inner_value.m_value, inner_type);
return;
}
// the current inner value does not have any children (anymore), remove it from this array
value->array->pop_back();
}
// the array does not have any children anymore, remove it from the stack
// the array itself will be destroyed later
stack.pop_back();
}

};

/*!
Expand Down