[c++] Use RapidJSON's iterative parser
Use RapidJSON's iterative parser to handle deeply nested JSON data without causing a stack overflow. Added a unit test with a deeply nested JSON array to test this.
This commit is contained in:
Родитель
04a5cb5662
Коммит
e2f74ee413
|
@ -31,6 +31,8 @@ different versioning scheme, following the Haskell community's
|
|||
used as a template" have been fixed.
|
||||
[Issue #538](https://github.com/Microsoft/bond/issues/538)
|
||||
* Guard against overflows in OutputMemoryStream, blob, and SimpleArray.
|
||||
* Use RapidJSON's iterative parser to handle deeply nested JSON data without
|
||||
causing a stack overflow.
|
||||
|
||||
### C# ###
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include "encoding.h"
|
||||
#include "detail/rapidjson_helper.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/reader.h"
|
||||
#include <boost/call_traits.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
|
||||
|
@ -21,7 +23,7 @@ class SimpleJsonReader
|
|||
{
|
||||
public:
|
||||
typedef BufferT Buffer;
|
||||
typedef DOMParser<SimpleJsonReader&> Parser;
|
||||
typedef DOMParser<SimpleJsonReader&> Parser;
|
||||
typedef SimpleJsonWriter<Buffer> Writer;
|
||||
typedef rapidjson::Value Field;
|
||||
|
||||
|
@ -49,7 +51,7 @@ public:
|
|||
_document(that._document),
|
||||
_value(that._value)
|
||||
{}
|
||||
|
||||
|
||||
bool ReadVersion()
|
||||
{
|
||||
return false;
|
||||
|
@ -60,24 +62,27 @@ public:
|
|||
// Don't need to reparse for nested fields
|
||||
if (!_value || _value == _document.get())
|
||||
{
|
||||
_document->ParseStream<rapidjson::kParseStopWhenDoneFlag>(_stream);
|
||||
const unsigned parseFlags = rapidjson::kParseIterativeFlag | rapidjson::kParseStopWhenDoneFlag;
|
||||
|
||||
_document->ParseStream<parseFlags>(_stream);
|
||||
BOOST_ASSERT(!_document->HasParseError());
|
||||
_value = _document.get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const Field* FindField(uint16_t id, const Metadata& metadata, BondDataType type)
|
||||
{
|
||||
// BT_INT32 may be an enum. This allows us to decode symbolic enum values
|
||||
// when parsing using runtime schema. The assumption is that runtime schema
|
||||
// matches JSON payload. If it doesn't, nothing horrible will happen, but
|
||||
// we might not indicate a required field missing for an int32 field if we
|
||||
// we might not indicate a required field missing for an int32 field if we
|
||||
// mistake a string member with matching name for it.
|
||||
return FindField(id, metadata, type, type == BT_INT32);
|
||||
}
|
||||
|
||||
const Field* FindField(uint16_t id, const Metadata& metadata, BondDataType type, bool is_enum);
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
void Read(T& var)
|
||||
{
|
||||
|
@ -103,7 +108,7 @@ public:
|
|||
void Skip(const T&)
|
||||
{}
|
||||
|
||||
|
||||
|
||||
bool operator==(const SimpleJsonReader& rhs) const
|
||||
{
|
||||
return _value == rhs._value;
|
||||
|
@ -122,7 +127,7 @@ public:
|
|||
{
|
||||
return _input;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
rapidjson::Value::ConstMemberIterator MemberBegin() const
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "precompiled.h"
|
||||
#include "json_tests.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <locale>
|
||||
#include <stdarg.h>
|
||||
|
||||
|
@ -187,10 +189,8 @@ TEST_CASE_END
|
|||
|
||||
|
||||
template <uint16_t N, typename Reader, typename Writer>
|
||||
void StringTests(const char* name)
|
||||
void StringTests(UnitTestSuite& suite)
|
||||
{
|
||||
UnitTestSuite suite(name);
|
||||
|
||||
AddTestCase<TEST_ID(N),
|
||||
StringRoundtripTest, Reader, Writer>(suite, "Roundtrip string/wstring");
|
||||
|
||||
|
@ -199,14 +199,38 @@ void StringTests(const char* name)
|
|||
}
|
||||
|
||||
|
||||
TEST_CASE_BEGIN(DeepNesting)
|
||||
{
|
||||
const size_t nestingDepth = 10000;
|
||||
|
||||
std::string listOpens(nestingDepth, '[');
|
||||
std::string listCloses(nestingDepth, ']');
|
||||
|
||||
std::string deeplyNestedList = boost::str(
|
||||
boost::format("{\"deeplyNestedList\": %strue%s}") % listOpens % listCloses);
|
||||
|
||||
bond::SimpleJsonReader<const char*> json_reader(deeplyNestedList.c_str());
|
||||
|
||||
// The type here doesn't really matter. We need something with no
|
||||
// required fields, as we're really just testing that we can parse a
|
||||
// deeply nested JSON array without crashing.
|
||||
SimpleStruct to;
|
||||
bond::Deserialize(json_reader, to);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
void JSONTest::Initialize()
|
||||
{
|
||||
UnitTestSuite suite("Simple JSON test");
|
||||
|
||||
TEST_SIMPLE_JSON_PROTOCOL(
|
||||
StringTests<
|
||||
0x1c04,
|
||||
bond::SimpleJsonReader<bond::InputBuffer>,
|
||||
bond::SimpleJsonWriter<bond::OutputBuffer> >("Simple JSON test");
|
||||
bond::SimpleJsonWriter<bond::OutputBuffer> >(suite);
|
||||
);
|
||||
|
||||
AddTestCase<TEST_ID(0x1c05), DeepNesting>(suite, "Deeply nested JSON struct");
|
||||
}
|
||||
|
||||
bool init_unit_test()
|
||||
|
|
Загрузка…
Ссылка в новой задаче