/* -*- 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 <string>
#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<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"false";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"null";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"0";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"1";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"-1";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"1.75";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"9000000000";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"\"foo\
"";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"[]";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"[1, true]";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"{}";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"{\"key\
": 10}";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"{\"key\
": 10, \"prop\
": 20}";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"1 ";
CHECK(IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
// Invalid cases.
source =
"";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"1 1";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
".1";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"undefined";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"TRUE";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"'foo'";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"[";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"{";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(source),
strlen(source)));
source =
"/a/";
CHECK(!IsValidJSON(
reinterpret_cast<
const JS::Latin1Char*>(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<char16_t>::length(source)));
source = u
"false";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"null";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"0";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"1";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"-1";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"1.75";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"9000000000";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"\"foo\
"";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"[]";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"[1, true]";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"{}";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"{\"key\
": 10}";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"{\"key\
": 10, \"prop\
": 20}";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"1 ";
CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
// Invalid cases.
source = u
"";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"1 1";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
".1";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"undefined";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"TRUE";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"'foo'";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"[";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"{";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
source = u
"/a/";
CHECK(!IsValidJSON(source, std::char_traits<char16_t>::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<
char>::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<
char>::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<char16_t>::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<char16_t>::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<
char>::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<char16_t>::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;
}
{
const size_t failAt = 1;
MyHandler handler;
const char16_t* source;
#define IMMEDIATE_FAIL(json) \
handler.failAt.emplace(failAt); \
source = json; \
CHECK(!JS::ParseJSONWithHandler( \
source, std::char_traits<char16_t>::length(source), &handler)); \
CHECK(handler.events.length() == failAt); \
handler.events.clear();
IMMEDIATE_FAIL(u
"{");
IMMEDIATE_FAIL(u
"[");
IMMEDIATE_FAIL(u
"\"string\
"");
IMMEDIATE_FAIL(u
"1");
IMMEDIATE_FAIL(u
"true");
IMMEDIATE_FAIL(u
"null");
#undef IMMEDIATE_FAIL
}
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<Event, 0, js::SystemAllocPolicy> events;
mozilla::Maybe<size_t> 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)