-
Notifications
You must be signed in to change notification settings - Fork 252
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix formats: destroy rapidjson::GenericValue manually
- Loading branch information
lfaktorovich
committed
Feb 21, 2023
1 parent
b9415c3
commit 1410c86
Showing
4 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
#include <string> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include <userver/engine/run_standalone.hpp> | ||
#include <userver/formats/json/serialize.hpp> | ||
|
||
USERVER_NAMESPACE_BEGIN | ||
|
||
namespace { | ||
|
||
std::string MakeStringOfDeepObject(std::size_t depth) { | ||
std::string str; | ||
str.reserve(depth * 6 + 1); | ||
for (std::size_t i = 0; i < depth; ++i) { | ||
str += R"({"a":)"; | ||
} | ||
str += "1"; | ||
for (std::size_t i = 0; i < depth; ++i) { | ||
str += "}"; | ||
} | ||
return str; | ||
} | ||
|
||
std::string MakeStringOfDeepArray(std::size_t depth) { | ||
std::string str; | ||
str.reserve(2 * depth + 1); | ||
for (std::size_t i = 0; i < depth; ++i) { | ||
str += "["; | ||
} | ||
str += "1"; | ||
for (std::size_t i = 0; i < depth; ++i) { | ||
str += "]"; | ||
} | ||
return str; | ||
} | ||
|
||
} // namespace | ||
|
||
TEST(FormatsJson, DeepObjectFromString) { | ||
constexpr std::size_t kWorkerThreads = 1; | ||
engine::TaskProcessorPoolsConfig config; | ||
config.coro_stack_size = 32 * 1024ULL; | ||
|
||
engine::RunStandalone(kWorkerThreads, config, [] { | ||
constexpr std::size_t kDepth = 16000; | ||
auto value = formats::json::FromString(MakeStringOfDeepObject(kDepth)); | ||
|
||
for (std::size_t i = 0; i < kDepth; ++i) { | ||
value = value["a"]; | ||
} | ||
EXPECT_EQ(value.As<int>(), 1); | ||
}); | ||
} | ||
|
||
TEST(FormatsJson, DeepArrayFromString) { | ||
constexpr std::size_t kWorkerThreads = 1; | ||
engine::TaskProcessorPoolsConfig config; | ||
config.coro_stack_size = 32 * 1024ULL; | ||
|
||
engine::RunStandalone(kWorkerThreads, config, [] { | ||
constexpr std::size_t kDepth = 16000; | ||
auto value = formats::json::FromString(MakeStringOfDeepArray(kDepth)); | ||
|
||
for (std::size_t i = 0; i < kDepth; ++i) { | ||
value = value[0]; | ||
} | ||
EXPECT_EQ(value.As<int>(), 1); | ||
}); | ||
} | ||
|
||
USERVER_NAMESPACE_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
#include <formats/json/impl/types_impl.hpp> | ||
|
||
#include <utility> | ||
|
||
#include <userver/utils/assert.hpp> | ||
#include <userver/utils/not_null.hpp> | ||
|
||
USERVER_NAMESPACE_BEGIN | ||
|
||
namespace formats::json::impl { | ||
|
||
namespace { | ||
|
||
bool IsComplex(const Value& value) { | ||
if (value.IsObject()) { | ||
return value.MemberBegin() != value.MemberEnd(); | ||
} | ||
if (value.IsArray()) { | ||
return value.Begin() != value.End(); | ||
} | ||
return false; | ||
} | ||
|
||
class JsonTreeDestroyer final { | ||
public: | ||
explicit JsonTreeDestroyer(Value&& root) | ||
: root_(std::move(root)), terminal_(&root_) { | ||
UpdateTerminal(); | ||
} | ||
|
||
void Destroy() && { | ||
if (&root_ == terminal_) { | ||
return; | ||
} | ||
|
||
do { | ||
MoveComplexChildrenToTerminal(); | ||
} while (NextDepth()); | ||
} | ||
|
||
private: | ||
void MoveComplexChildrenToTerminal() { | ||
UASSERT(IsComplex(root_)); | ||
|
||
if (root_.IsObject()) { | ||
for (auto it = root_.MemberBegin() + 1; it != root_.MemberEnd(); ++it) { | ||
if (IsComplex(it->value)) { | ||
MoveValueToTerminal(std::move(it->value)); | ||
} | ||
} | ||
} else { | ||
for (auto* it = root_.Begin() + 1; it != root_.End(); ++it) { | ||
if (IsComplex(*it)) { | ||
MoveValueToTerminal(std::move(*it)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
bool NextDepth() { | ||
UASSERT(IsComplex(root_)); | ||
|
||
Value& next_depth = | ||
root_.IsObject() ? root_.MemberBegin()->value : *root_.Begin(); | ||
|
||
const bool is_last_level = &next_depth == terminal_; | ||
|
||
Value field_to_destroy(std::move(next_depth)); | ||
root_.Swap(field_to_destroy); | ||
|
||
return !is_last_level; | ||
} | ||
|
||
void MoveValueToTerminal(Value&& value) { | ||
*terminal_ = std::move(value); | ||
UpdateTerminal(); | ||
} | ||
|
||
void UpdateTerminal() { | ||
while (IsComplex(*terminal_)) { | ||
if (terminal_->IsObject()) { | ||
terminal_ = terminal_->MemberBegin()->value; | ||
} else { | ||
terminal_ = *terminal_->Begin(); | ||
} | ||
} | ||
} | ||
|
||
Value root_; | ||
utils::NotNull<Value*> terminal_; | ||
}; | ||
|
||
// ~GenericValue destroys members with recursion, which causes | ||
// stackoverflow error with deep objects | ||
void DestroyMembersIteratively(Value&& native) noexcept { | ||
JsonTreeDestroyer(std::move(native)).Destroy(); | ||
} | ||
|
||
} // namespace | ||
|
||
VersionedValuePtr::Data::~Data() { | ||
DestroyMembersIteratively(std::move(native)); | ||
} | ||
|
||
} // namespace formats::json::impl | ||
|
||
USERVER_NAMESPACE_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters