/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Maybe.h" // mozilla::Maybe #include #include "js/AllocPolicy.h" // js::SystemAllocPolicy #include "js/JSON.h" #include "js/Vector.h" // js::Vector #include "jsapi-tests/tests.h" using namespace JS; BEGIN_FRONTEND_TEST(testIsValidJSONLatin1) { const char* source; source = "true"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "false"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "null"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "0"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "1"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "-1"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "1.75"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "9000000000"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "\"foo\""; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "[]"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "[1, true]"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "{}"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "{\"key\": 10}"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "{\"key\": 10, \"prop\": 20}"; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); source = "1 "; CHECK(IsValidJSON(reinterpret_cast(source), strlen(source))); // Invalid cases. source = ""; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "1 1"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = ".1"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "undefined"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "TRUE"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "'foo'"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "["; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "{"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); source = "/a/"; CHECK(!IsValidJSON(reinterpret_cast(source), strlen(source))); return true; } END_TEST(testIsValidJSONLatin1) BEGIN_FRONTEND_TEST(testIsValidJSONTwoBytes) { const char16_t* source; source = u"true"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"false"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"null"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"0"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"1"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"-1"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"1.75"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"9000000000"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"\"foo\""; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"[]"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"[1, true]"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"{}"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"{\"key\": 10}"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"{\"key\": 10, \"prop\": 20}"; CHECK(IsValidJSON(source, std::char_traits::length(source))); source = u"1 "; CHECK(IsValidJSON(source, std::char_traits::length(source))); // Invalid cases. source = u""; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"1 1"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u".1"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"undefined"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"TRUE"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"'foo'"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"["; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"{"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); source = u"/a/"; CHECK(!IsValidJSON(source, std::char_traits::length(source))); return true; } END_TEST(testIsValidJSONTwoBytes) BEGIN_FRONTEND_TEST(testParseJSONWithHandler) { { MyHandler handler; const char* source = "{ \"prop1\": 10.5, \"prop\\uff12\": [true, false, null, \"Ascii\", " "\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }"; CHECK(JS::ParseJSONWithHandler((const JS::Latin1Char*)source, std::char_traits::length(source), &handler)); size_t i = 0; CHECK(handler.events[i++] == MyHandler::Event::StartObject); // Non-escaped ASCII property name in Latin1 input should be passed with // Latin1. CHECK(handler.events[i++] == MyHandler::Event::Latin1Prop1); CHECK(handler.events[i++] == MyHandler::Event::Number); // Escaped non-Latin1 property name in Latin1 input should be passed with // TwoBytes. CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2); CHECK(handler.events[i++] == MyHandler::Event::StartArray); CHECK(handler.events[i++] == MyHandler::Event::True); CHECK(handler.events[i++] == MyHandler::Event::False); CHECK(handler.events[i++] == MyHandler::Event::Null); // Non-escaped ASCII string in Latin1 input should be passed with Latin1. CHECK(handler.events[i++] == MyHandler::Event::Latin1Str1); // Escaped non-Latin1 string in Latin1 input should be passed with TwoBytes. CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2); // Escaped ASCII-range string in Latin1 input should be passed with Latin1. CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3); // Escaped Latin1-range string in Latin1 input should be passed with Latin1. CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4); CHECK(handler.events[i++] == MyHandler::Event::EndArray); CHECK(handler.events[i++] == MyHandler::Event::EndObject); CHECK(handler.events.length() == i); } { MyHandler handler; const char* source = "{"; CHECK(!JS::ParseJSONWithHandler((const JS::Latin1Char*)source, std::char_traits::length(source), &handler)); size_t i = 0; CHECK(handler.events[i++] == MyHandler::Event::StartObject); CHECK(handler.events[i++] == MyHandler::Event::Error); CHECK(handler.events.length() == i); } { MyHandler handler; const char16_t* source = u"{ \"prop1\": 10.5, \"prop\uff12\": [true, false, null, \"Ascii\", " u"\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }"; CHECK(JS::ParseJSONWithHandler( source, std::char_traits::length(source), &handler)); size_t i = 0; CHECK(handler.events[i++] == MyHandler::Event::StartObject); // Non-escaped ASCII property name in TwoBytes input should be passed with // TwoBytes. CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp1); CHECK(handler.events[i++] == MyHandler::Event::Number); // Escaped non-Latin1 property name in TwoBytes input should be passed with // TwoBytes. CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2); CHECK(handler.events[i++] == MyHandler::Event::StartArray); CHECK(handler.events[i++] == MyHandler::Event::True); CHECK(handler.events[i++] == MyHandler::Event::False); CHECK(handler.events[i++] == MyHandler::Event::Null); // Non-escaped ASCII string in TwoBytes input should be passed with // TwoBytes. CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr1); // Escaped non-Latin1 string in TwoBytes input should be passed with // TwoBytes. CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2); // Escaped ASCII-range string in TwoBytes input should be passed with // Latin1. CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3); // Escaped Latin1-range string in TwoBytes input should be passed with // Latin1. CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4); CHECK(handler.events[i++] == MyHandler::Event::EndArray); CHECK(handler.events[i++] == MyHandler::Event::EndObject); CHECK(handler.events.length() == i); } { MyHandler handler; const char16_t* source = u"{"; CHECK(!JS::ParseJSONWithHandler( source, std::char_traits::length(source), &handler)); size_t i = 0; CHECK(handler.events[i++] == MyHandler::Event::StartObject); CHECK(handler.events[i++] == MyHandler::Event::Error); CHECK(handler.events.length() == i); } // Verify the failure case is handled properly and no methods are called // after the failure. bool checkedLast = false; for (size_t failAt = 1; !checkedLast; failAt++) { MyHandler handler; handler.failAt.emplace(failAt); const char* source = "{ \"prop1\": 10.5, \"prop\\uff12\": [true, false, null, \"Ascii\", " "\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }"; CHECK(!JS::ParseJSONWithHandler((const JS::Latin1Char*)source, std::char_traits::length(source), &handler)); CHECK(handler.events.length() == failAt); size_t i = 0; CHECK(handler.events[i++] == MyHandler::Event::StartObject); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Latin1Prop1); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Number); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::StartArray); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::True); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::False); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Null); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Latin1Str1); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::EndArray); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::EndObject); checkedLast = true; } checkedLast = false; for (size_t failAt = 1; !checkedLast; failAt++) { MyHandler handler; handler.failAt.emplace(failAt); const char16_t* source = u"{ \"prop1\": 10.5, \"prop\uff12\": [true, false, null, \"Ascii\", " u"\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }"; CHECK(!JS::ParseJSONWithHandler( source, std::char_traits::length(source), &handler)); CHECK(handler.events.length() == failAt); size_t i = 0; CHECK(handler.events[i++] == MyHandler::Event::StartObject); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp1); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Number); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::StartArray); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::True); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::False); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Null); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr1); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::EndArray); if (i >= failAt) { continue; } CHECK(handler.events[i++] == MyHandler::Event::EndObject); checkedLast = true; } return true; } class MyHandler : public JS::JSONParseHandler { public: enum class Event { Uninitialized = 0, StartObject, Latin1Prop1, TwoBytesProp1, Number, TwoBytesProp2, StartArray, True, False, Null, Latin1Str1, TwoBytesStr1, TwoBytesStr2, Latin1Str3, Latin1Str4, EndArray, EndObject, Error, UnexpectedNumber, UnexpectedLatin1Prop, UnexpectedTwoBytesProp, UnexpectedLatin1String, UnexpectedTwoBytesString, }; js::Vector events; mozilla::Maybe failAt; MyHandler() {} virtual ~MyHandler() {} bool startObject() override { MOZ_ALWAYS_TRUE(events.append(Event::StartObject)); if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool propertyName(const JS::Latin1Char* name, size_t length) override { if (length == 5 && name[0] == 'p' && name[1] == 'r' && name[2] == 'o' && name[3] == 'p' && name[4] == '1') { MOZ_ALWAYS_TRUE(events.append(Event::Latin1Prop1)); } else { MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedLatin1Prop)); } if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool propertyName(const char16_t* name, size_t length) override { if (length == 5 && name[0] == 'p' && name[1] == 'r' && name[2] == 'o' && name[3] == 'p' && name[4] == '1') { MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesProp1)); } else if (length == 5 && name[0] == 'p' && name[1] == 'r' && name[2] == 'o' && name[3] == 'p' && name[4] == 0xff12) { MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesProp2)); } else { MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedTwoBytesProp)); } if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool endObject() override { MOZ_ALWAYS_TRUE(events.append(Event::EndObject)); if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool startArray() override { MOZ_ALWAYS_TRUE(events.append(Event::StartArray)); if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool endArray() override { MOZ_ALWAYS_TRUE(events.append(Event::EndArray)); if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool stringValue(const JS::Latin1Char* name, size_t length) override { if (length == 5 && name[0] == 'A' && name[1] == 's' && name[2] == 'c' && name[3] == 'i' && name[4] == 'i') { MOZ_ALWAYS_TRUE(events.append(Event::Latin1Str1)); } else if (length == 1 && name[0] == ' ') { MOZ_ALWAYS_TRUE(events.append(Event::Latin1Str3)); } else if (length == 1 && name[0] == 0x80) { MOZ_ALWAYS_TRUE(events.append(Event::Latin1Str4)); } else { MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedLatin1String)); } if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool stringValue(const char16_t* name, size_t length) override { if (length == 5 && name[0] == 'A' && name[1] == 's' && name[2] == 'c' && name[3] == 'i' && name[4] == 'i') { MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesStr1)); } else if (length == 3 && name[0] == 0x3042 && name[1] == 0x3044 && name[2] == 0x3046) { MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesStr2)); } else { MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedTwoBytesString)); } if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool numberValue(double d) override { if (d == 10.5) { MOZ_ALWAYS_TRUE(events.append(Event::Number)); } else { MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedNumber)); } if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool booleanValue(bool v) override { if (v) { MOZ_ALWAYS_TRUE(events.append(Event::True)); } else { MOZ_ALWAYS_TRUE(events.append(Event::False)); } if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } bool nullValue() override { MOZ_ALWAYS_TRUE(events.append(Event::Null)); if (failAt.isSome() && events.length() == *failAt) { failAt.reset(); return false; } return true; } void error(const char* msg, uint32_t line, uint32_t column) override { MOZ_ALWAYS_TRUE(events.append(Event::Error)); } }; END_TEST(testParseJSONWithHandler)