// Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #include "unittest.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/filereadstream.h" #include "rapidjson/encodedstream.h" #include "rapidjson/stringbuffer.h" #include #include #ifdef __clang__ RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(c++98-compat) RAPIDJSON_DIAG_OFF(missing-variable-declarations) #endif using namespace rapidjson; template void ParseCheck(DocumentType& doc) { typedef typename DocumentType::ValueType ValueType; EXPECT_FALSE(doc.HasParseError()); if (doc.HasParseError()) printf("Error: %d at %zu\n", static_cast(doc.GetParseError()), doc.GetErrorOffset()); EXPECT_TRUE(static_cast(doc)); EXPECT_TRUE(doc.IsObject()); EXPECT_TRUE(doc.HasMember("hello")); const ValueType& hello = doc["hello"]; EXPECT_TRUE(hello.IsString()); EXPECT_STREQ("world", hello.GetString()); EXPECT_TRUE(doc.HasMember("t")); const ValueType& t = doc["t"]; EXPECT_TRUE(t.IsTrue()); EXPECT_TRUE(doc.HasMember("f")); const ValueType& f = doc["f"]; EXPECT_TRUE(f.IsFalse()); EXPECT_TRUE(doc.HasMember("n")); const ValueType& n = doc["n"]; EXPECT_TRUE(n.IsNull()); EXPECT_TRUE(doc.HasMember("i")); const ValueType& i = doc["i"]; EXPECT_TRUE(i.IsNumber()); EXPECT_EQ(123, i.GetInt()); EXPECT_TRUE(doc.HasMember("pi")); const ValueType& pi = doc["pi"]; EXPECT_TRUE(pi.IsNumber()); EXPECT_DOUBLE_EQ(3.1416, pi.GetDouble()); EXPECT_TRUE(doc.HasMember("a")); const ValueType& a = doc["a"]; EXPECT_TRUE(a.IsArray()); EXPECT_EQ(4u, a.Size()); for (SizeType j = 0; j < 4; j++) EXPECT_EQ(j + 1, a[j].GetUint()); } template void ParseTest() { typedef GenericDocument, Allocator, StackAllocator> DocumentType; DocumentType doc; const char* json = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "; doc.Parse(json); ParseCheck(doc); doc.SetNull(); StringStream s(json); doc.template ParseStream<0>(s); ParseCheck(doc); doc.SetNull(); char *buffer = strdup(json); doc.ParseInsitu(buffer); ParseCheck(doc); free(buffer); // Parse(const Ch*, size_t) size_t length = strlen(json); buffer = reinterpret_cast(malloc(length * 2)); memcpy(buffer, json, length); memset(buffer + length, 'X', length); #if RAPIDJSON_HAS_STDSTRING std::string s2(buffer, length); // backup buffer #endif doc.SetNull(); doc.Parse(buffer, length); free(buffer); ParseCheck(doc); #if RAPIDJSON_HAS_STDSTRING // Parse(std::string) doc.SetNull(); doc.Parse(s2); ParseCheck(doc); #endif } TEST(Document, Parse) { ParseTest, CrtAllocator>(); ParseTest, MemoryPoolAllocator<> >(); ParseTest >(); ParseTest(); } TEST(Document, UnchangedOnParseError) { Document doc; doc.SetArray().PushBack(0, doc.GetAllocator()); ParseResult noError; EXPECT_TRUE(noError); ParseResult err = doc.Parse("{]"); EXPECT_TRUE(doc.HasParseError()); EXPECT_NE(err, noError); EXPECT_NE(err.Code(), noError); EXPECT_NE(noError, doc.GetParseError()); EXPECT_EQ(err.Code(), doc.GetParseError()); EXPECT_EQ(err.Offset(), doc.GetErrorOffset()); EXPECT_TRUE(doc.IsArray()); EXPECT_EQ(doc.Size(), 1u); err = doc.Parse("{}"); EXPECT_FALSE(doc.HasParseError()); EXPECT_FALSE(err.IsError()); EXPECT_TRUE(err); EXPECT_EQ(err, noError); EXPECT_EQ(err.Code(), noError); EXPECT_EQ(err.Code(), doc.GetParseError()); EXPECT_EQ(err.Offset(), doc.GetErrorOffset()); EXPECT_TRUE(doc.IsObject()); EXPECT_EQ(doc.MemberCount(), 0u); } static FILE* OpenEncodedFile(const char* filename) { const char *paths[] = { "encodings", "bin/encodings", "../bin/encodings", "../../bin/encodings", "../../../bin/encodings" }; char buffer[1024]; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { sprintf(buffer, "%s/%s", paths[i], filename); FILE *fp = fopen(buffer, "rb"); if (fp) return fp; } return 0; } TEST(Document, Parse_Encoding) { const char* json = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "; typedef GenericDocument > DocumentType; DocumentType doc; // Parse(const SourceEncoding::Ch*) // doc.Parse >(json); // EXPECT_FALSE(doc.HasParseError()); // EXPECT_EQ(0, StrCmp(doc[L"hello"].GetString(), L"world")); // Parse(const SourceEncoding::Ch*, size_t) size_t length = strlen(json); char* buffer = reinterpret_cast(malloc(length * 2)); memcpy(buffer, json, length); memset(buffer + length, 'X', length); #if RAPIDJSON_HAS_STDSTRING std::string s2(buffer, length); // backup buffer #endif doc.SetNull(); doc.Parse >(buffer, length); free(buffer); EXPECT_FALSE(doc.HasParseError()); if (doc.HasParseError()) printf("Error: %d at %zu\n", static_cast(doc.GetParseError()), doc.GetErrorOffset()); EXPECT_EQ(0, StrCmp(doc[L"hello"].GetString(), L"world")); #if RAPIDJSON_HAS_STDSTRING // Parse(std::string) doc.SetNull(); #if defined(_MSC_VER) && _MSC_VER < 1800 doc.Parse >(s2.c_str()); // VS2010 or below cannot handle templated function overloading. Use const char* instead. #else doc.Parse >(s2); #endif EXPECT_FALSE(doc.HasParseError()); EXPECT_EQ(0, StrCmp(doc[L"hello"].GetString(), L"world")); #endif } TEST(Document, ParseStream_EncodedInputStream) { // UTF8 -> UTF16 FILE* fp = OpenEncodedFile("utf8.json"); char buffer[256]; FileReadStream bis(fp, buffer, sizeof(buffer)); EncodedInputStream, FileReadStream> eis(bis); GenericDocument > d; d.ParseStream<0, UTF8<> >(eis); EXPECT_FALSE(d.HasParseError()); fclose(fp); wchar_t expected[] = L"I can eat glass and it doesn't hurt me."; GenericValue >& v = d[L"en"]; EXPECT_TRUE(v.IsString()); EXPECT_EQ(sizeof(expected) / sizeof(wchar_t) - 1, v.GetStringLength()); EXPECT_EQ(0, StrCmp(expected, v.GetString())); // UTF16 -> UTF8 in memory StringBuffer bos; typedef EncodedOutputStream, StringBuffer> OutputStream; OutputStream eos(bos, false); // Not writing BOM { Writer, UTF8<> > writer(eos); d.Accept(writer); } // Condense the original file and compare. fp = OpenEncodedFile("utf8.json"); FileReadStream is(fp, buffer, sizeof(buffer)); Reader reader; StringBuffer bos2; Writer writer2(bos2); reader.Parse(is, writer2); fclose(fp); EXPECT_EQ(bos.GetSize(), bos2.GetSize()); EXPECT_EQ(0, memcmp(bos.GetString(), bos2.GetString(), bos2.GetSize())); } TEST(Document, ParseStream_AutoUTFInputStream) { // Any -> UTF8 FILE* fp = OpenEncodedFile("utf32be.json"); char buffer[256]; FileReadStream bis(fp, buffer, sizeof(buffer)); AutoUTFInputStream eis(bis); Document d; d.ParseStream<0, AutoUTF >(eis); EXPECT_FALSE(d.HasParseError()); fclose(fp); char expected[] = "I can eat glass and it doesn't hurt me."; Value& v = d["en"]; EXPECT_TRUE(v.IsString()); EXPECT_EQ(sizeof(expected) - 1, v.GetStringLength()); EXPECT_EQ(0, StrCmp(expected, v.GetString())); // UTF8 -> UTF8 in memory StringBuffer bos; Writer writer(bos); d.Accept(writer); // Condense the original file and compare. fp = OpenEncodedFile("utf8.json"); FileReadStream is(fp, buffer, sizeof(buffer)); Reader reader; StringBuffer bos2; Writer writer2(bos2); reader.Parse(is, writer2); fclose(fp); EXPECT_EQ(bos.GetSize(), bos2.GetSize()); EXPECT_EQ(0, memcmp(bos.GetString(), bos2.GetString(), bos2.GetSize())); } TEST(Document, Swap) { Document d1; Document::AllocatorType& a = d1.GetAllocator(); d1.SetArray().PushBack(1, a).PushBack(2, a); Value o; o.SetObject().AddMember("a", 1, a); // Swap between Document and Value d1.Swap(o); EXPECT_TRUE(d1.IsObject()); EXPECT_TRUE(o.IsArray()); d1.Swap(o); EXPECT_TRUE(d1.IsArray()); EXPECT_TRUE(o.IsObject()); o.Swap(d1); EXPECT_TRUE(d1.IsObject()); EXPECT_TRUE(o.IsArray()); // Swap between Document and Document Document d2; d2.SetArray().PushBack(3, a); d1.Swap(d2); EXPECT_TRUE(d1.IsArray()); EXPECT_TRUE(d2.IsObject()); EXPECT_EQ(&d2.GetAllocator(), &a); // reset value Value().Swap(d1); EXPECT_TRUE(d1.IsNull()); // reset document, including allocator Document().Swap(d2); EXPECT_TRUE(d2.IsNull()); EXPECT_NE(&d2.GetAllocator(), &a); // testing std::swap compatibility d1.SetBool(true); using std::swap; swap(d1, d2); EXPECT_TRUE(d1.IsNull()); EXPECT_TRUE(d2.IsTrue()); swap(o, d2); EXPECT_TRUE(o.IsTrue()); EXPECT_TRUE(d2.IsArray()); } // This should be slow due to assignment in inner-loop. struct OutputStringStream : public std::ostringstream { typedef char Ch; virtual ~OutputStringStream(); void Put(char c) { put(c); } void Flush() {} }; OutputStringStream::~OutputStringStream() {} TEST(Document, AcceptWriter) { Document doc; doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); OutputStringStream os; Writer writer(os); doc.Accept(writer); EXPECT_EQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3,4]}", os.str()); } TEST(Document, UserBuffer) { typedef GenericDocument, MemoryPoolAllocator<>, MemoryPoolAllocator<> > DocumentType; char valueBuffer[4096]; char parseBuffer[1024]; MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer)); MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer)); DocumentType doc(&valueAllocator, sizeof(parseBuffer) / 2, &parseAllocator); doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); EXPECT_FALSE(doc.HasParseError()); EXPECT_LE(valueAllocator.Size(), sizeof(valueBuffer)); EXPECT_LE(parseAllocator.Size(), sizeof(parseBuffer)); // Cover MemoryPoolAllocator::Capacity() EXPECT_LE(valueAllocator.Size(), valueAllocator.Capacity()); EXPECT_LE(parseAllocator.Size(), parseAllocator.Capacity()); } // Issue 226: Value of string type should not point to NULL TEST(Document, AssertAcceptInvalidNameType) { Document doc; doc.SetObject(); doc.AddMember("a", 0, doc.GetAllocator()); doc.FindMember("a")->name.SetNull(); // Change name to non-string type. OutputStringStream os; Writer writer(os); ASSERT_THROW(doc.Accept(writer), AssertException); } // Issue 44: SetStringRaw doesn't work with wchar_t TEST(Document, UTF16_Document) { GenericDocument< UTF16<> > json; json.Parse(L"[{\"created_at\":\"Wed Oct 30 17:13:20 +0000 2012\"}]"); ASSERT_TRUE(json.IsArray()); GenericValue< UTF16<> >& v = json[0]; ASSERT_TRUE(v.IsObject()); GenericValue< UTF16<> >& s = v[L"created_at"]; ASSERT_TRUE(s.IsString()); EXPECT_EQ(0, memcmp(L"Wed Oct 30 17:13:20 +0000 2012", s.GetString(), (s.GetStringLength() + 1) * sizeof(wchar_t))); } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS #if 0 // Many old compiler does not support these. Turn it off temporaily. #include TEST(Document, Traits) { static_assert(std::is_constructible::value, ""); static_assert(std::is_default_constructible::value, ""); #ifndef _MSC_VER static_assert(!std::is_copy_constructible::value, ""); #endif static_assert(std::is_move_constructible::value, ""); static_assert(!std::is_nothrow_constructible::value, ""); static_assert(!std::is_nothrow_default_constructible::value, ""); #ifndef _MSC_VER static_assert(!std::is_nothrow_copy_constructible::value, ""); static_assert(std::is_nothrow_move_constructible::value, ""); #endif static_assert(std::is_assignable::value, ""); #ifndef _MSC_VER static_assert(!std::is_copy_assignable::value, ""); #endif static_assert(std::is_move_assignable::value, ""); #ifndef _MSC_VER static_assert(std::is_nothrow_assignable::value, ""); #endif static_assert(!std::is_nothrow_copy_assignable::value, ""); #ifndef _MSC_VER static_assert(std::is_nothrow_move_assignable::value, ""); #endif static_assert( std::is_destructible::value, ""); #ifndef _MSC_VER static_assert(std::is_nothrow_destructible::value, ""); #endif } #endif template struct DocumentMove: public ::testing::Test { }; typedef ::testing::Types< CrtAllocator, MemoryPoolAllocator<> > MoveAllocatorTypes; TYPED_TEST_CASE(DocumentMove, MoveAllocatorTypes); TYPED_TEST(DocumentMove, MoveConstructor) { typedef TypeParam Allocator; typedef GenericDocument, Allocator> D; Allocator allocator; D a(&allocator); a.Parse("[\"one\", \"two\", \"three\"]"); EXPECT_FALSE(a.HasParseError()); EXPECT_TRUE(a.IsArray()); EXPECT_EQ(3u, a.Size()); EXPECT_EQ(&a.GetAllocator(), &allocator); // Document b(a); // does not compile (!is_copy_constructible) D b(std::move(a)); EXPECT_TRUE(a.IsNull()); EXPECT_TRUE(b.IsArray()); EXPECT_EQ(3u, b.Size()); EXPECT_THROW(a.GetAllocator(), AssertException); EXPECT_EQ(&b.GetAllocator(), &allocator); b.Parse("{\"Foo\": \"Bar\", \"Baz\": 42}"); EXPECT_FALSE(b.HasParseError()); EXPECT_TRUE(b.IsObject()); EXPECT_EQ(2u, b.MemberCount()); // Document c = a; // does not compile (!is_copy_constructible) D c = std::move(b); EXPECT_TRUE(b.IsNull()); EXPECT_TRUE(c.IsObject()); EXPECT_EQ(2u, c.MemberCount()); EXPECT_THROW(b.GetAllocator(), AssertException); EXPECT_EQ(&c.GetAllocator(), &allocator); } TYPED_TEST(DocumentMove, MoveConstructorParseError) { typedef TypeParam Allocator; typedef GenericDocument, Allocator> D; ParseResult noError; D a; a.Parse("{ 4 = 4]"); ParseResult error(a.GetParseError(), a.GetErrorOffset()); EXPECT_TRUE(a.HasParseError()); EXPECT_NE(error, noError); EXPECT_NE(error.Code(), noError); EXPECT_NE(error.Code(), noError.Code()); EXPECT_NE(error.Offset(), noError.Offset()); D b(std::move(a)); EXPECT_FALSE(a.HasParseError()); EXPECT_TRUE(b.HasParseError()); EXPECT_EQ(a.GetParseError(), noError); EXPECT_EQ(a.GetParseError(), noError.Code()); EXPECT_EQ(a.GetErrorOffset(), noError.Offset()); EXPECT_EQ(b.GetParseError(), error); EXPECT_EQ(b.GetParseError(), error.Code()); EXPECT_EQ(b.GetErrorOffset(), error.Offset()); D c(std::move(b)); EXPECT_FALSE(b.HasParseError()); EXPECT_TRUE(c.HasParseError()); EXPECT_EQ(b.GetParseError(), noError.Code()); EXPECT_EQ(c.GetParseError(), error.Code()); EXPECT_EQ(b.GetErrorOffset(), noError.Offset()); EXPECT_EQ(c.GetErrorOffset(), error.Offset()); } // This test does not properly use parsing, just for testing. // It must call ClearStack() explicitly to prevent memory leak. // But here we cannot as ClearStack() is private. #if 0 TYPED_TEST(DocumentMove, MoveConstructorStack) { typedef TypeParam Allocator; typedef UTF8<> Encoding; typedef GenericDocument Document; Document a; size_t defaultCapacity = a.GetStackCapacity(); // Trick Document into getting GetStackCapacity() to return non-zero typedef GenericReader Reader; Reader reader(&a.GetAllocator()); GenericStringStream is("[\"one\", \"two\", \"three\"]"); reader.template Parse(is, a); size_t capacity = a.GetStackCapacity(); EXPECT_GT(capacity, 0u); Document b(std::move(a)); EXPECT_EQ(a.GetStackCapacity(), defaultCapacity); EXPECT_EQ(b.GetStackCapacity(), capacity); Document c = std::move(b); EXPECT_EQ(b.GetStackCapacity(), defaultCapacity); EXPECT_EQ(c.GetStackCapacity(), capacity); } #endif TYPED_TEST(DocumentMove, MoveAssignment) { typedef TypeParam Allocator; typedef GenericDocument, Allocator> D; Allocator allocator; D a(&allocator); a.Parse("[\"one\", \"two\", \"three\"]"); EXPECT_FALSE(a.HasParseError()); EXPECT_TRUE(a.IsArray()); EXPECT_EQ(3u, a.Size()); EXPECT_EQ(&a.GetAllocator(), &allocator); // Document b; b = a; // does not compile (!is_copy_assignable) D b; b = std::move(a); EXPECT_TRUE(a.IsNull()); EXPECT_TRUE(b.IsArray()); EXPECT_EQ(3u, b.Size()); EXPECT_THROW(a.GetAllocator(), AssertException); EXPECT_EQ(&b.GetAllocator(), &allocator); b.Parse("{\"Foo\": \"Bar\", \"Baz\": 42}"); EXPECT_FALSE(b.HasParseError()); EXPECT_TRUE(b.IsObject()); EXPECT_EQ(2u, b.MemberCount()); // Document c; c = a; // does not compile (see static_assert) D c; c = std::move(b); EXPECT_TRUE(b.IsNull()); EXPECT_TRUE(c.IsObject()); EXPECT_EQ(2u, c.MemberCount()); EXPECT_THROW(b.GetAllocator(), AssertException); EXPECT_EQ(&c.GetAllocator(), &allocator); } TYPED_TEST(DocumentMove, MoveAssignmentParseError) { typedef TypeParam Allocator; typedef GenericDocument, Allocator> D; ParseResult noError; D a; a.Parse("{ 4 = 4]"); ParseResult error(a.GetParseError(), a.GetErrorOffset()); EXPECT_TRUE(a.HasParseError()); EXPECT_NE(error.Code(), noError.Code()); EXPECT_NE(error.Offset(), noError.Offset()); D b; b = std::move(a); EXPECT_FALSE(a.HasParseError()); EXPECT_TRUE(b.HasParseError()); EXPECT_EQ(a.GetParseError(), noError.Code()); EXPECT_EQ(b.GetParseError(), error.Code()); EXPECT_EQ(a.GetErrorOffset(), noError.Offset()); EXPECT_EQ(b.GetErrorOffset(), error.Offset()); D c; c = std::move(b); EXPECT_FALSE(b.HasParseError()); EXPECT_TRUE(c.HasParseError()); EXPECT_EQ(b.GetParseError(), noError.Code()); EXPECT_EQ(c.GetParseError(), error.Code()); EXPECT_EQ(b.GetErrorOffset(), noError.Offset()); EXPECT_EQ(c.GetErrorOffset(), error.Offset()); } // This test does not properly use parsing, just for testing. // It must call ClearStack() explicitly to prevent memory leak. // But here we cannot as ClearStack() is private. #if 0 TYPED_TEST(DocumentMove, MoveAssignmentStack) { typedef TypeParam Allocator; typedef UTF8<> Encoding; typedef GenericDocument D; D a; size_t defaultCapacity = a.GetStackCapacity(); // Trick Document into getting GetStackCapacity() to return non-zero typedef GenericReader Reader; Reader reader(&a.GetAllocator()); GenericStringStream is("[\"one\", \"two\", \"three\"]"); reader.template Parse(is, a); size_t capacity = a.GetStackCapacity(); EXPECT_GT(capacity, 0u); D b; b = std::move(a); EXPECT_EQ(a.GetStackCapacity(), defaultCapacity); EXPECT_EQ(b.GetStackCapacity(), capacity); D c; c = std::move(b); EXPECT_EQ(b.GetStackCapacity(), defaultCapacity); EXPECT_EQ(c.GetStackCapacity(), capacity); } #endif #endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS // Issue 22: Memory corruption via operator= // Fixed by making unimplemented assignment operator private. //TEST(Document, Assignment) { // Document d1; // Document d2; // d1 = d2; //} #ifdef __clang__ RAPIDJSON_DIAG_POP #endif