diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3b3da50fff..0430f7beeb 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -998,6 +998,11 @@ class basic_json { case value_t::object: { + if (!this->object->empty()) + { + destroy_subelements_iterative(t); + } + AllocatorType alloc; std::allocator_traits::destroy(alloc, object); std::allocator_traits::deallocate(alloc, object, 1); @@ -1006,6 +1011,11 @@ class basic_json case value_t::array: { + if (!this->array->empty()) + { + destroy_subelements_iterative(t); + } + AllocatorType alloc; std::allocator_traits::destroy(alloc, array); std::allocator_traits::deallocate(alloc, array, 1); @@ -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> 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>& 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>& 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(); + } + }; /*! diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 70b6c8a855..d51494d940 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -13466,6 +13466,11 @@ class basic_json { case value_t::object: { + if (!this->object->empty()) + { + destroy_subelements_iterative(t); + } + AllocatorType alloc; std::allocator_traits::destroy(alloc, object); std::allocator_traits::deallocate(alloc, object, 1); @@ -13474,6 +13479,11 @@ class basic_json case value_t::array: { + if (!this->array->empty()) + { + destroy_subelements_iterative(t); + } + AllocatorType alloc; std::allocator_traits::destroy(alloc, array); std::allocator_traits::deallocate(alloc, array, 1); @@ -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> 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>& 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>& 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(); + } + }; /*!