1635 lines
47 KiB
C++
1635 lines
47 KiB
C++
/* -*- 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 "vm/JSONParser.h"
|
|
|
|
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
|
#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
|
|
#include "mozilla/Range.h" // mozilla::Range
|
|
#include "mozilla/RangedPtr.h" // mozilla::RangedPtr
|
|
|
|
#include "mozilla/Sprintf.h" // SprintfLiteral
|
|
#include "mozilla/TextUtils.h" // mozilla::AsciiAlphanumericToNumber, mozilla::IsAsciiDigit, mozilla::IsAsciiHexDigit
|
|
|
|
#include <stddef.h> // size_t
|
|
#include <stdint.h> // uint32_t
|
|
#include <utility> // std::move
|
|
|
|
#include "jsnum.h" // ParseDecimalNumber, GetFullInteger, FullStringToDouble
|
|
|
|
#include "builtin/Array.h" // NewDenseCopiedArray
|
|
#include "builtin/ParseRecordObject.h" // js::ParseRecordObject
|
|
#include "ds/IdValuePair.h" // IdValuePair
|
|
#include "gc/GCEnum.h" // CanGC
|
|
#include "gc/Tracer.h" // JS::TraceRoot
|
|
#include "js/AllocPolicy.h" // ReportOutOfMemory
|
|
#include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ
|
|
#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
|
|
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
|
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
|
#include "js/GCVector.h" // JS::GCVector
|
|
#include "js/Id.h" // jsid
|
|
#include "js/JSON.h" // JS::IsValidJSON
|
|
#include "js/PropertyAndElement.h" // JS_SetPropertyById
|
|
#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle, MutableWrappedPtrOperations
|
|
#include "js/TypeDecls.h" // Latin1Char
|
|
#include "js/Utility.h" // js_delete
|
|
#include "js/Value.h" // JS::Value, JS::BooleanValue, JS::NullValue, JS::NumberValue, JS::StringValue
|
|
#include "js/Vector.h" // Vector
|
|
#include "util/StringBuilder.h" // JSStringBuilder
|
|
#include "vm/ArrayObject.h" // ArrayObject
|
|
#include "vm/ErrorReporting.h" // ReportCompileErrorLatin1, ErrorMetadata
|
|
#include "vm/JSAtomUtils.h" // AtomizeChars
|
|
#include "vm/JSContext.h" // JSContext
|
|
#include "vm/PlainObject.h" // NewPlainObjectWithMaybeDuplicateKeys, NewPlainObjectWithProto
|
|
#include "vm/Realm.h" // JS::Realm
|
|
#include "vm/StringType.h" // JSString, JSAtom, JSLinearString, NewStringCopyN, NameToId
|
|
|
|
#include "vm/JSAtomUtils-inl.h" // AtomToId
|
|
|
|
using namespace js;
|
|
|
|
using mozilla::AsciiAlphanumericToNumber;
|
|
using mozilla::IsAsciiDigit;
|
|
using mozilla::IsAsciiHexDigit;
|
|
using mozilla::RangedPtr;
|
|
|
|
template <typename CharT, typename ParserT>
|
|
void JSONTokenizer<CharT, ParserT>::getTextPosition(uint32_t* column,
|
|
uint32_t* line) {
|
|
CharPtr ptr = begin;
|
|
uint32_t col = 1;
|
|
uint32_t row = 1;
|
|
for (; ptr < current; ptr++) {
|
|
if (*ptr == '\n' || *ptr == '\r') {
|
|
++row;
|
|
col = 1;
|
|
// \r\n is treated as a single newline.
|
|
if (ptr + 1 < current && *ptr == '\r' && *(ptr + 1) == '\n') {
|
|
++ptr;
|
|
}
|
|
} else {
|
|
++col;
|
|
}
|
|
}
|
|
*column = col;
|
|
*line = row;
|
|
}
|
|
|
|
static inline bool IsJSONWhitespace(char16_t c) {
|
|
return c == '\t' || c == '\r' || c == '\n' || c == ' ';
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
bool JSONTokenizer<CharT, ParserT>::consumeTrailingWhitespaces() {
|
|
for (; current < end; current++) {
|
|
if (!IsJSONWhitespace(*current)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::advance() {
|
|
while (current < end && IsJSONWhitespace(*current)) {
|
|
current++;
|
|
}
|
|
if (current >= end) {
|
|
error("unexpected end of data");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
sourceStart = current;
|
|
switch (*current) {
|
|
case '"':
|
|
return readString<JSONStringType::LiteralValue>();
|
|
|
|
case '-':
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
return readNumber();
|
|
|
|
case 't':
|
|
if (end - current < 4 || current[1] != 'r' || current[2] != 'u' ||
|
|
current[3] != 'e') {
|
|
error("unexpected keyword");
|
|
return token(JSONToken::Error);
|
|
}
|
|
current += 4;
|
|
if (!parser->handler.setBooleanValue(true, getSource())) {
|
|
return token(JSONToken::OOM);
|
|
}
|
|
return token(JSONToken::True);
|
|
|
|
case 'f':
|
|
if (end - current < 5 || current[1] != 'a' || current[2] != 'l' ||
|
|
current[3] != 's' || current[4] != 'e') {
|
|
error("unexpected keyword");
|
|
return token(JSONToken::Error);
|
|
}
|
|
current += 5;
|
|
if (!parser->handler.setBooleanValue(false, getSource())) {
|
|
return token(JSONToken::OOM);
|
|
}
|
|
return token(JSONToken::False);
|
|
|
|
case 'n':
|
|
if (end - current < 4 || current[1] != 'u' || current[2] != 'l' ||
|
|
current[3] != 'l') {
|
|
error("unexpected keyword");
|
|
return token(JSONToken::Error);
|
|
}
|
|
current += 4;
|
|
if (!parser->handler.setNullValue(getSource())) {
|
|
return token(JSONToken::OOM);
|
|
}
|
|
return token(JSONToken::Null);
|
|
|
|
case '[':
|
|
current++;
|
|
return token(JSONToken::ArrayOpen);
|
|
case ']':
|
|
current++;
|
|
return token(JSONToken::ArrayClose);
|
|
|
|
case '{':
|
|
current++;
|
|
return token(JSONToken::ObjectOpen);
|
|
case '}':
|
|
current++;
|
|
return token(JSONToken::ObjectClose);
|
|
|
|
case ',':
|
|
current++;
|
|
return token(JSONToken::Comma);
|
|
|
|
case ':':
|
|
current++;
|
|
return token(JSONToken::Colon);
|
|
|
|
default:
|
|
error("unexpected character");
|
|
return token(JSONToken::Error);
|
|
}
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::advancePropertyName() {
|
|
MOZ_ASSERT(current[-1] == ',');
|
|
|
|
while (current < end && IsJSONWhitespace(*current)) {
|
|
current++;
|
|
}
|
|
if (current >= end) {
|
|
error("end of data when property name was expected");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
if (*current == '"') {
|
|
return readString<JSONStringType::PropertyName>();
|
|
}
|
|
|
|
error("expected double-quoted property name");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::advancePropertyColon() {
|
|
MOZ_ASSERT(current[-1] == '"');
|
|
|
|
while (current < end && IsJSONWhitespace(*current)) {
|
|
current++;
|
|
}
|
|
if (current >= end) {
|
|
error("end of data after property name when ':' was expected");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
if (*current == ':') {
|
|
current++;
|
|
return token(JSONToken::Colon);
|
|
}
|
|
|
|
error("expected ':' after property name in object");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
template <typename CharT>
|
|
static inline void AssertPastValue(const RangedPtr<const CharT> current) {
|
|
/*
|
|
* We're past an arbitrary JSON value, so the previous character is
|
|
* *somewhat* constrained, even if this assertion is pretty broad. Don't
|
|
* knock it till you tried it: this assertion *did* catch a bug once.
|
|
*/
|
|
MOZ_ASSERT((current[-1] == 'l' && current[-2] == 'l' && current[-3] == 'u' &&
|
|
current[-4] == 'n') ||
|
|
(current[-1] == 'e' && current[-2] == 'u' && current[-3] == 'r' &&
|
|
current[-4] == 't') ||
|
|
(current[-1] == 'e' && current[-2] == 's' && current[-3] == 'l' &&
|
|
current[-4] == 'a' && current[-5] == 'f') ||
|
|
current[-1] == '}' || current[-1] == ']' || current[-1] == '"' ||
|
|
IsAsciiDigit(current[-1]));
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::advanceAfterProperty() {
|
|
AssertPastValue(current);
|
|
|
|
while (current < end && IsJSONWhitespace(*current)) {
|
|
current++;
|
|
}
|
|
if (current >= end) {
|
|
error("end of data after property value in object");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
if (*current == ',') {
|
|
current++;
|
|
return token(JSONToken::Comma);
|
|
}
|
|
|
|
if (*current == '}') {
|
|
current++;
|
|
return token(JSONToken::ObjectClose);
|
|
}
|
|
|
|
error("expected ',' or '}' after property value in object");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::advanceAfterObjectOpen() {
|
|
MOZ_ASSERT(current[-1] == '{');
|
|
|
|
while (current < end && IsJSONWhitespace(*current)) {
|
|
current++;
|
|
}
|
|
if (current >= end) {
|
|
error("end of data while reading object contents");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
if (*current == '"') {
|
|
return readString<JSONStringType::PropertyName>();
|
|
}
|
|
|
|
if (*current == '}') {
|
|
current++;
|
|
return token(JSONToken::ObjectClose);
|
|
}
|
|
|
|
error("expected property name or '}'");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::advanceAfterArrayElement() {
|
|
AssertPastValue(current);
|
|
|
|
while (current < end && IsJSONWhitespace(*current)) {
|
|
current++;
|
|
}
|
|
if (current >= end) {
|
|
error("end of data when ',' or ']' was expected");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
if (*current == ',') {
|
|
current++;
|
|
return token(JSONToken::Comma);
|
|
}
|
|
|
|
if (*current == ']') {
|
|
current++;
|
|
return token(JSONToken::ArrayClose);
|
|
}
|
|
|
|
error("expected ',' or ']' after array element");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
template <JSONStringType ST>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::stringToken(const CharPtr start,
|
|
size_t length) {
|
|
if (!parser->handler.template setStringValue<ST>(start, length,
|
|
getSource())) {
|
|
return JSONToken::OOM;
|
|
}
|
|
return JSONToken::String;
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
template <JSONStringType ST>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::stringToken(
|
|
JSONStringBuilder& builder) {
|
|
if (!parser->handler.template setStringValue<ST>(builder, getSource())) {
|
|
return JSONToken::OOM;
|
|
}
|
|
return JSONToken::String;
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::numberToken(double d) {
|
|
if (!parser->handler.setNumberValue(d, getSource())) {
|
|
return JSONToken::OOM;
|
|
}
|
|
return JSONToken::Number;
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
template <JSONStringType ST>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::readString() {
|
|
MOZ_ASSERT(current < end);
|
|
MOZ_ASSERT(*current == '"');
|
|
|
|
/*
|
|
* JSONString:
|
|
* /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
|
|
*/
|
|
|
|
if (++current == end) {
|
|
error("unterminated string literal");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
/*
|
|
* Optimization: if the source contains no escaped characters, create the
|
|
* string directly from the source text.
|
|
*/
|
|
CharPtr start = current;
|
|
for (; current < end; current++) {
|
|
if (*current == '"') {
|
|
size_t length = current - start;
|
|
current++;
|
|
return stringToken<ST>(start, length);
|
|
}
|
|
|
|
if (*current == '\\') {
|
|
break;
|
|
}
|
|
|
|
if (*current <= 0x001F) {
|
|
error("bad control character in string literal");
|
|
return token(JSONToken::Error);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Slow case: string contains escaped characters. Copy a maximal sequence
|
|
* of unescaped characters into a temporary buffer, then an escaped
|
|
* character, and repeat until the entire string is consumed.
|
|
*/
|
|
JSONStringBuilder builder(parser->handler.context());
|
|
do {
|
|
if (start < current && !builder.append(start.get(), current.get())) {
|
|
return token(JSONToken::OOM);
|
|
}
|
|
|
|
if (current >= end) {
|
|
break;
|
|
}
|
|
|
|
char16_t c = *current++;
|
|
if (c == '"') {
|
|
return stringToken<ST>(builder);
|
|
}
|
|
|
|
if (c != '\\') {
|
|
--current;
|
|
error("bad character in string literal");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
if (current >= end) {
|
|
break;
|
|
}
|
|
|
|
switch (*current++) {
|
|
case '"':
|
|
c = '"';
|
|
break;
|
|
case '/':
|
|
c = '/';
|
|
break;
|
|
case '\\':
|
|
c = '\\';
|
|
break;
|
|
case 'b':
|
|
c = '\b';
|
|
break;
|
|
case 'f':
|
|
c = '\f';
|
|
break;
|
|
case 'n':
|
|
c = '\n';
|
|
break;
|
|
case 'r':
|
|
c = '\r';
|
|
break;
|
|
case 't':
|
|
c = '\t';
|
|
break;
|
|
|
|
case 'u':
|
|
if (end - current < 4 ||
|
|
!(IsAsciiHexDigit(current[0]) && IsAsciiHexDigit(current[1]) &&
|
|
IsAsciiHexDigit(current[2]) && IsAsciiHexDigit(current[3]))) {
|
|
// Point to the first non-hexadecimal character (which may be
|
|
// missing).
|
|
if (current == end || !IsAsciiHexDigit(current[0])) {
|
|
; // already at correct location
|
|
} else if (current + 1 == end || !IsAsciiHexDigit(current[1])) {
|
|
current += 1;
|
|
} else if (current + 2 == end || !IsAsciiHexDigit(current[2])) {
|
|
current += 2;
|
|
} else if (current + 3 == end || !IsAsciiHexDigit(current[3])) {
|
|
current += 3;
|
|
} else {
|
|
MOZ_CRASH("logic error determining first erroneous character");
|
|
}
|
|
|
|
error("bad Unicode escape");
|
|
return token(JSONToken::Error);
|
|
}
|
|
c = (AsciiAlphanumericToNumber(current[0]) << 12) |
|
|
(AsciiAlphanumericToNumber(current[1]) << 8) |
|
|
(AsciiAlphanumericToNumber(current[2]) << 4) |
|
|
(AsciiAlphanumericToNumber(current[3]));
|
|
current += 4;
|
|
break;
|
|
|
|
default:
|
|
current--;
|
|
error("bad escaped character");
|
|
return token(JSONToken::Error);
|
|
}
|
|
if (!builder.append(c)) {
|
|
return token(JSONToken::OOM);
|
|
}
|
|
|
|
start = current;
|
|
for (; current < end; current++) {
|
|
if (*current == '"' || *current == '\\' || *current <= 0x001F) {
|
|
break;
|
|
}
|
|
}
|
|
} while (current < end);
|
|
|
|
error("unterminated string");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
JSONToken JSONTokenizer<CharT, ParserT>::readNumber() {
|
|
MOZ_ASSERT(current < end);
|
|
MOZ_ASSERT(IsAsciiDigit(*current) || *current == '-');
|
|
|
|
/*
|
|
* JSONNumber:
|
|
* /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
|
|
*/
|
|
|
|
bool negative = *current == '-';
|
|
|
|
/* -? */
|
|
if (negative && ++current == end) {
|
|
error("no number after minus sign");
|
|
return token(JSONToken::Error);
|
|
}
|
|
|
|
const CharPtr digitStart = current;
|
|
|
|
/* 0|[1-9][0-9]+ */
|
|
if (!IsAsciiDigit(*current)) {
|
|
error("unexpected non-digit");
|
|
return token(JSONToken::Error);
|
|
}
|
|
if (*current++ != '0') {
|
|
for (; current < end; current++) {
|
|
if (!IsAsciiDigit(*current)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fast path: no fractional or exponent part. */
|
|
if (current == end ||
|
|
(*current != '.' && *current != 'e' && *current != 'E')) {
|
|
mozilla::Range<const CharT> chars(digitStart.get(), current - digitStart);
|
|
if (chars.length() < strlen("9007199254740992")) {
|
|
// If the decimal number is shorter than the length of 2**53, (the
|
|
// largest number a double can represent with integral precision),
|
|
// parse it using a decimal-only parser. This comparison is
|
|
// conservative but faster than a fully-precise check.
|
|
double d = ParseDecimalNumber(chars);
|
|
return numberToken(negative ? -d : d);
|
|
}
|
|
|
|
double d;
|
|
if (!GetFullInteger(digitStart.get(), current.get(), 10,
|
|
IntegerSeparatorHandling::None, &d)) {
|
|
parser->outOfMemory();
|
|
return token(JSONToken::OOM);
|
|
}
|
|
return numberToken(negative ? -d : d);
|
|
}
|
|
|
|
/* (\.[0-9]+)? */
|
|
if (current < end && *current == '.') {
|
|
if (++current == end) {
|
|
error("missing digits after decimal point");
|
|
return token(JSONToken::Error);
|
|
}
|
|
if (!IsAsciiDigit(*current)) {
|
|
error("unterminated fractional number");
|
|
return token(JSONToken::Error);
|
|
}
|
|
while (++current < end) {
|
|
if (!IsAsciiDigit(*current)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ([eE][\+\-]?[0-9]+)? */
|
|
if (current < end && (*current == 'e' || *current == 'E')) {
|
|
if (++current == end) {
|
|
error("missing digits after exponent indicator");
|
|
return token(JSONToken::Error);
|
|
}
|
|
if (*current == '+' || *current == '-') {
|
|
if (++current == end) {
|
|
error("missing digits after exponent sign");
|
|
return token(JSONToken::Error);
|
|
}
|
|
}
|
|
if (!IsAsciiDigit(*current)) {
|
|
error("exponent part is missing a number");
|
|
return token(JSONToken::Error);
|
|
}
|
|
while (++current < end) {
|
|
if (!IsAsciiDigit(*current)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
double d = FullStringToDouble(digitStart.get(), current.get());
|
|
return numberToken(negative ? -d : d);
|
|
}
|
|
|
|
template <typename CharT, typename ParserT>
|
|
void JSONTokenizer<CharT, ParserT>::error(const char* msg) {
|
|
parser->error(msg);
|
|
}
|
|
|
|
static void ReportJSONSyntaxError(FrontendContext* fc, ErrorMetadata&& metadata,
|
|
unsigned errorNumber, ...) {
|
|
va_list args;
|
|
va_start(args, errorNumber);
|
|
|
|
js::ReportCompileErrorLatin1VA(fc, std::move(metadata), nullptr, errorNumber,
|
|
&args);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
// JSONFullParseHandlerAnyChar uses an AutoSelectGCHeap to switch to allocating
|
|
// in the tenured heap if we trigger more than one nursery collection.
|
|
//
|
|
// JSON parsing allocates from the leaves of the tree upwards (unlike
|
|
// structured clone deserialization which works from the root
|
|
// downwards). Because of this it doesn't necessarily make sense to stop
|
|
// nursery allocation after the first collection as this doesn't doom the
|
|
// whole data structure to being tenured. We don't know ahead of time how
|
|
// big the resulting data structure will be but after two nursery
|
|
// collections then at least half of it will end up tenured.
|
|
|
|
JSONFullParseHandlerAnyChar::JSONFullParseHandlerAnyChar(JSContext* cx)
|
|
: cx(cx), gcHeap(cx, 1), freeElements(cx), freeProperties(cx) {}
|
|
|
|
JSONFullParseHandlerAnyChar::JSONFullParseHandlerAnyChar(
|
|
JSONFullParseHandlerAnyChar&& other) noexcept
|
|
: cx(other.cx),
|
|
v(other.v),
|
|
parseType(other.parseType),
|
|
gcHeap(cx, 1),
|
|
freeElements(std::move(other.freeElements)),
|
|
freeProperties(std::move(other.freeProperties)) {}
|
|
|
|
JSONFullParseHandlerAnyChar::~JSONFullParseHandlerAnyChar() {
|
|
for (size_t i = 0; i < freeElements.length(); i++) {
|
|
js_delete(freeElements[i]);
|
|
}
|
|
|
|
for (size_t i = 0; i < freeProperties.length(); i++) {
|
|
js_delete(freeProperties[i]);
|
|
}
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::objectOpen(
|
|
Vector<StackEntry, 10>& stack, PropertyVector** properties) {
|
|
if (!freeProperties.empty()) {
|
|
*properties = freeProperties.popCopy();
|
|
(*properties)->clear();
|
|
} else {
|
|
(*properties) = cx->new_<PropertyVector>(cx);
|
|
if (!*properties) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!stack.append(StackEntry(cx, *properties))) {
|
|
js_delete(*properties);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::objectPropertyName(
|
|
Vector<StackEntry, 10>& stack, bool* isProtoInEval) {
|
|
*isProtoInEval = false;
|
|
jsid id = AtomToId(atomValue());
|
|
if (parseType == ParseType::AttemptForEval) {
|
|
// In |JSON.parse|, "__proto__" is a property like any other and may
|
|
// appear multiple times. In object literal syntax, "__proto__" is
|
|
// prototype mutation and can appear at most once. |JSONParser| only
|
|
// supports the former semantics, so if this parse attempt is for
|
|
// |eval|, return true (without reporting an error) to indicate the
|
|
// JSON parse attempt was unsuccessful.
|
|
if (id == NameToId(cx->names().proto_)) {
|
|
*isProtoInEval = true;
|
|
return true;
|
|
}
|
|
}
|
|
PropertyVector& properties = stack.back().properties();
|
|
if (!properties.emplaceBack(id)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::finishObjectMember(
|
|
Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value,
|
|
PropertyVector** properties) {
|
|
*properties = &stack.back().properties();
|
|
(*properties)->back().value = value;
|
|
return true;
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::finishObject(
|
|
Vector<StackEntry, 10>& stack, JS::MutableHandle<JS::Value> vp,
|
|
PropertyVector* properties) {
|
|
MOZ_ASSERT(properties == &stack.back().properties());
|
|
|
|
NewObjectKind newKind = GenericObject;
|
|
if (gcHeap == gc::Heap::Tenured) {
|
|
newKind = TenuredObject;
|
|
}
|
|
// properties is traced in the parser; see JSONParser<CharT>::trace()
|
|
JSObject* obj = NewPlainObjectWithMaybeDuplicateKeys(
|
|
cx, Handle<IdValueVector>::fromMarkedLocation(properties), newKind);
|
|
if (!obj) {
|
|
return false;
|
|
}
|
|
|
|
vp.setObject(*obj);
|
|
if (!freeProperties.append(properties)) {
|
|
return false;
|
|
}
|
|
stack.popBack();
|
|
return true;
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::arrayOpen(
|
|
Vector<StackEntry, 10>& stack, ElementVector** elements) {
|
|
if (!freeElements.empty()) {
|
|
*elements = freeElements.popCopy();
|
|
(*elements)->clear();
|
|
} else {
|
|
(*elements) = cx->new_<ElementVector>(cx);
|
|
if (!*elements) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!stack.append(StackEntry(cx, *elements))) {
|
|
js_delete(*elements);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::arrayElement(
|
|
Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value,
|
|
ElementVector** elements) {
|
|
*elements = &stack.back().elements();
|
|
return (*elements)->append(value.get());
|
|
}
|
|
|
|
inline bool JSONFullParseHandlerAnyChar::finishArray(
|
|
Vector<StackEntry, 10>& stack, JS::MutableHandle<JS::Value> vp,
|
|
ElementVector* elements) {
|
|
MOZ_ASSERT(elements == &stack.back().elements());
|
|
|
|
NewObjectKind newKind = GenericObject;
|
|
if (gcHeap == gc::Heap::Tenured) {
|
|
newKind = TenuredObject;
|
|
}
|
|
ArrayObject* obj =
|
|
NewDenseCopiedArray(cx, elements->length(), elements->begin(), newKind);
|
|
if (!obj) {
|
|
return false;
|
|
}
|
|
|
|
vp.setObject(*obj);
|
|
if (!freeElements.append(elements)) {
|
|
return false;
|
|
}
|
|
stack.popBack();
|
|
return true;
|
|
}
|
|
|
|
inline void JSONFullParseHandlerAnyChar::freeStackEntry(StackEntry& entry) {
|
|
if (entry.state == JSONParserState::FinishArrayElement) {
|
|
js_delete(&entry.elements());
|
|
} else {
|
|
js_delete(&entry.properties());
|
|
}
|
|
}
|
|
|
|
void JSONFullParseHandlerAnyChar::trace(JSTracer* trc) {
|
|
JS::TraceRoot(trc, &v, "JSONFullParseHandlerAnyChar current value");
|
|
}
|
|
|
|
template <typename CharT>
|
|
bool JSONFullParseHandler<CharT>::JSONStringBuilder::append(char16_t c) {
|
|
return buffer.append(c);
|
|
}
|
|
|
|
template <typename CharT>
|
|
bool JSONFullParseHandler<CharT>::JSONStringBuilder::append(const CharT* begin,
|
|
const CharT* end) {
|
|
return buffer.append(begin, end);
|
|
}
|
|
|
|
template <typename CharT>
|
|
template <JSONStringType ST>
|
|
inline bool JSONFullParseHandler<CharT>::setStringValue(
|
|
CharPtr start, size_t length, mozilla::Span<const CharT>&& source) {
|
|
JSString* str;
|
|
if constexpr (ST == JSONStringType::PropertyName) {
|
|
str = AtomizeChars(cx, start.get(), length);
|
|
} else {
|
|
str = NewStringCopyN<CanGC>(cx, start.get(), length, gcHeap);
|
|
}
|
|
|
|
if (!str) {
|
|
return false;
|
|
}
|
|
v = JS::StringValue(str);
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
template <JSONStringType ST>
|
|
inline bool JSONFullParseHandler<CharT>::setStringValue(
|
|
JSONStringBuilder& builder, mozilla::Span<const CharT>&& source) {
|
|
JSString* str;
|
|
if constexpr (ST == JSONStringType::PropertyName) {
|
|
str = builder.buffer.finishAtom();
|
|
} else {
|
|
str = builder.buffer.finishString(gcHeap);
|
|
}
|
|
|
|
if (!str) {
|
|
return false;
|
|
}
|
|
v = JS::StringValue(str);
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONFullParseHandler<CharT>::setNumberValue(
|
|
double d, mozilla::Span<const CharT>&& source) {
|
|
v = JS::NumberValue(d);
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONFullParseHandler<CharT>::setBooleanValue(
|
|
bool value, mozilla::Span<const CharT>&& source) {
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONFullParseHandler<CharT>::setNullValue(
|
|
mozilla::Span<const CharT>&& source) {
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
void JSONFullParseHandler<CharT>::reportError(const char* msg, uint32_t line,
|
|
uint32_t column) {
|
|
const size_t MaxWidth = sizeof("4294967295");
|
|
char columnString[MaxWidth];
|
|
SprintfLiteral(columnString, "%" PRIu32, column);
|
|
char lineString[MaxWidth];
|
|
SprintfLiteral(lineString, "%" PRIu32, line);
|
|
|
|
if (reportLineNumbersFromParsedData) {
|
|
AutoReportFrontendContext fc(cx);
|
|
|
|
ErrorMetadata metadata;
|
|
metadata.isMuted = false;
|
|
metadata.filename = filename.valueOr(JS::ConstUTF8CharsZ(""));
|
|
metadata.lineNumber = line;
|
|
metadata.columnNumber = JS::ColumnNumberOneOrigin(column);
|
|
|
|
ReportJSONSyntaxError(&fc, std::move(metadata), JSMSG_JSON_BAD_PARSE, msg,
|
|
lineString, columnString);
|
|
} else {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
JSMSG_JSON_BAD_PARSE, msg, lineString,
|
|
columnString);
|
|
}
|
|
}
|
|
|
|
template <typename CharT, typename HandlerT>
|
|
JSONPerHandlerParser<CharT, HandlerT>::~JSONPerHandlerParser() {
|
|
for (size_t i = 0; i < stack.length(); i++) {
|
|
handler.freeStackEntry(stack[i]);
|
|
}
|
|
}
|
|
|
|
template <typename CharT, typename HandlerT>
|
|
template <typename TempValueT, typename ResultSetter>
|
|
bool JSONPerHandlerParser<CharT, HandlerT>::parseImpl(TempValueT& value,
|
|
ResultSetter setResult) {
|
|
MOZ_ASSERT(stack.empty());
|
|
|
|
JSONToken token;
|
|
JSONParserState state = JSONParserState::JSONValue;
|
|
while (true) {
|
|
switch (state) {
|
|
case JSONParserState::FinishObjectMember: {
|
|
typename HandlerT::PropertyVector* properties;
|
|
if (!handler.finishObjectMember(stack, value, &properties)) {
|
|
return false;
|
|
}
|
|
|
|
token = tokenizer.advanceAfterProperty();
|
|
if (token == JSONToken::ObjectClose) {
|
|
if (!handler.finishObject(stack, &value, properties)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
if (token != JSONToken::Comma) {
|
|
if (token == JSONToken::OOM) {
|
|
return false;
|
|
}
|
|
if (token != JSONToken::Error) {
|
|
error(
|
|
"expected ',' or '}' after property-value pair in object "
|
|
"literal");
|
|
}
|
|
return handler.errorReturn();
|
|
}
|
|
token = tokenizer.advancePropertyName();
|
|
/* FALL THROUGH */
|
|
}
|
|
|
|
JSONMember:
|
|
if (token == JSONToken::String) {
|
|
bool isProtoInEval;
|
|
if (!handler.objectPropertyName(stack, &isProtoInEval)) {
|
|
return false;
|
|
}
|
|
if (isProtoInEval) {
|
|
// See JSONFullParseHandlerAnyChar::objectPropertyName.
|
|
return true;
|
|
}
|
|
token = tokenizer.advancePropertyColon();
|
|
if (token != JSONToken::Colon) {
|
|
MOZ_ASSERT(token == JSONToken::Error);
|
|
return handler.errorReturn();
|
|
}
|
|
goto JSONValue;
|
|
}
|
|
if (token == JSONToken::OOM) {
|
|
return false;
|
|
}
|
|
if (token != JSONToken::Error) {
|
|
error("property names must be double-quoted strings");
|
|
}
|
|
return handler.errorReturn();
|
|
|
|
case JSONParserState::FinishArrayElement: {
|
|
typename HandlerT::ElementVector* elements;
|
|
if (!handler.arrayElement(stack, value, &elements)) {
|
|
return false;
|
|
}
|
|
token = tokenizer.advanceAfterArrayElement();
|
|
if (token == JSONToken::Comma) {
|
|
goto JSONValue;
|
|
}
|
|
if (token == JSONToken::ArrayClose) {
|
|
if (!handler.finishArray(stack, &value, elements)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
MOZ_ASSERT(token == JSONToken::Error);
|
|
return handler.errorReturn();
|
|
}
|
|
|
|
JSONValue:
|
|
case JSONParserState::JSONValue:
|
|
token = tokenizer.advance();
|
|
JSONValueSwitch:
|
|
switch (token) {
|
|
case JSONToken::String:
|
|
value = handler.stringValue();
|
|
break;
|
|
case JSONToken::Number:
|
|
value = handler.numberValue();
|
|
break;
|
|
case JSONToken::True:
|
|
value = handler.booleanValue(true);
|
|
break;
|
|
case JSONToken::False:
|
|
value = handler.booleanValue(false);
|
|
break;
|
|
case JSONToken::Null:
|
|
value = handler.nullValue();
|
|
break;
|
|
|
|
case JSONToken::ArrayOpen: {
|
|
typename HandlerT::ElementVector* elements;
|
|
if (!handler.arrayOpen(stack, &elements)) {
|
|
return false;
|
|
}
|
|
|
|
token = tokenizer.advance();
|
|
if (token == JSONToken::ArrayClose) {
|
|
if (!handler.finishArray(stack, &value, elements)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
goto JSONValueSwitch;
|
|
}
|
|
|
|
case JSONToken::ObjectOpen: {
|
|
typename HandlerT::PropertyVector* properties;
|
|
if (!handler.objectOpen(stack, &properties)) {
|
|
return false;
|
|
}
|
|
|
|
token = tokenizer.advanceAfterObjectOpen();
|
|
if (token == JSONToken::ObjectClose) {
|
|
if (!handler.finishObject(stack, &value, properties)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
goto JSONMember;
|
|
}
|
|
|
|
case JSONToken::ArrayClose:
|
|
case JSONToken::ObjectClose:
|
|
case JSONToken::Colon:
|
|
case JSONToken::Comma:
|
|
// Move the current pointer backwards so that the position
|
|
// reported in the error message is correct.
|
|
tokenizer.unget();
|
|
error("unexpected character");
|
|
return handler.errorReturn();
|
|
|
|
case JSONToken::OOM:
|
|
return false;
|
|
|
|
case JSONToken::Error:
|
|
return handler.errorReturn();
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (stack.empty()) {
|
|
break;
|
|
}
|
|
state = stack.back().state;
|
|
}
|
|
|
|
if (!tokenizer.consumeTrailingWhitespaces()) {
|
|
error("unexpected non-whitespace character after JSON data");
|
|
return handler.errorReturn();
|
|
}
|
|
|
|
MOZ_ASSERT(tokenizer.finished());
|
|
MOZ_ASSERT(stack.empty());
|
|
|
|
setResult(value);
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT, typename HandlerT>
|
|
void JSONPerHandlerParser<CharT, HandlerT>::outOfMemory() {
|
|
ReportOutOfMemory(handler.context());
|
|
}
|
|
|
|
template <typename CharT, typename HandlerT>
|
|
void JSONPerHandlerParser<CharT, HandlerT>::error(const char* msg) {
|
|
if (handler.ignoreError()) {
|
|
return;
|
|
}
|
|
|
|
uint32_t column = 1, line = 1;
|
|
tokenizer.getTextPosition(&column, &line);
|
|
|
|
handler.reportError(msg, line, column);
|
|
}
|
|
|
|
template class js::JSONPerHandlerParser<Latin1Char,
|
|
js::JSONFullParseHandler<Latin1Char>>;
|
|
template class js::JSONPerHandlerParser<char16_t,
|
|
js::JSONFullParseHandler<char16_t>>;
|
|
|
|
template class js::JSONPerHandlerParser<Latin1Char,
|
|
js::JSONReviveHandler<Latin1Char>>;
|
|
template class js::JSONPerHandlerParser<char16_t,
|
|
js::JSONReviveHandler<char16_t>>;
|
|
|
|
template class js::JSONPerHandlerParser<Latin1Char,
|
|
js::JSONSyntaxParseHandler<Latin1Char>>;
|
|
template class js::JSONPerHandlerParser<char16_t,
|
|
js::JSONSyntaxParseHandler<char16_t>>;
|
|
|
|
template <typename CharT>
|
|
bool JSONParser<CharT>::parse(JS::MutableHandle<JS::Value> vp) {
|
|
JS::Rooted<JS::Value> tempValue(this->handler.cx);
|
|
|
|
vp.setUndefined();
|
|
|
|
return this->parseImpl(tempValue,
|
|
[&](JS::Handle<JS::Value> value) { vp.set(value); });
|
|
}
|
|
|
|
template <typename CharT>
|
|
void JSONParser<CharT>::trace(JSTracer* trc) {
|
|
this->handler.trace(trc);
|
|
|
|
for (auto& elem : this->stack) {
|
|
if (elem.state == JSONParserState::FinishArrayElement) {
|
|
elem.elements().trace(trc);
|
|
} else {
|
|
elem.properties().trace(trc);
|
|
}
|
|
}
|
|
}
|
|
|
|
template class js::JSONParser<Latin1Char>;
|
|
template class js::JSONParser<char16_t>;
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::objectOpen(Vector<StackEntry, 10>& stack,
|
|
PropertyVector** properties) {
|
|
ParseRecordObject::EntryMap* newParseEntry =
|
|
NewPlainObjectWithProto(context(), nullptr);
|
|
if (!newParseEntry) {
|
|
return false;
|
|
}
|
|
if (!parseRecordStack.append(newParseEntry)) {
|
|
return false;
|
|
}
|
|
|
|
return Base::objectOpen(stack, properties);
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::finishObjectMember(
|
|
Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value,
|
|
PropertyVector** properties) {
|
|
if (!Base::finishObjectMember(stack, value, properties)) {
|
|
return false;
|
|
}
|
|
parseRecord->setValue(value);
|
|
Rooted<JS::PropertyKey> key(context(), (*properties)->back().id);
|
|
Rooted<ParseRecordObject::EntryMap*> parseRecordBack(context(),
|
|
parseRecordStack.back());
|
|
return finishMemberParseRecord(key, parseRecordBack);
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::finishObject(
|
|
Vector<StackEntry, 10>& stack, JS::MutableHandle<JS::Value> vp,
|
|
PropertyVector* properties) {
|
|
if (!Base::finishObject(stack, vp, properties)) {
|
|
return false;
|
|
}
|
|
Rooted<ParseRecordObject::EntryMap*> parseRecordBack(context(),
|
|
parseRecordStack.back());
|
|
if (!finishCompoundParseRecord(vp, parseRecordBack)) {
|
|
return false;
|
|
}
|
|
parseRecordStack.popBack();
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::arrayOpen(Vector<StackEntry, 10>& stack,
|
|
ElementVector** elements) {
|
|
ParseRecordObject::EntryMap* newParseEntry =
|
|
NewPlainObjectWithProto(context(), nullptr);
|
|
if (!newParseEntry) {
|
|
return false;
|
|
}
|
|
if (!parseRecordStack.append(newParseEntry)) {
|
|
return false;
|
|
}
|
|
|
|
return Base::arrayOpen(stack, elements);
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::arrayElement(
|
|
Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value,
|
|
ElementVector** elements) {
|
|
if (!Base::arrayElement(stack, value, elements)) {
|
|
return false;
|
|
}
|
|
size_t index = (*elements)->length() - 1;
|
|
// The JSON string is limited to JS::MaxStringLength, so there should be no
|
|
// way to get more than IntMax elements
|
|
MOZ_ASSERT(index <= js::PropertyKey::IntMax);
|
|
Rooted<JS::PropertyKey> key(context(), js::PropertyKey::Int(int32_t(index)));
|
|
Rooted<ParseRecordObject::EntryMap*> parseRecordBack(context(),
|
|
parseRecordStack.back());
|
|
return finishMemberParseRecord(key, parseRecordBack);
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::finishArray(
|
|
Vector<StackEntry, 10>& stack, JS::MutableHandle<JS::Value> vp,
|
|
ElementVector* elements) {
|
|
if (!Base::finishArray(stack, vp, elements)) {
|
|
return false;
|
|
}
|
|
Rooted<ParseRecordObject::EntryMap*> parseRecordBack(context(),
|
|
parseRecordStack.back());
|
|
if (!finishCompoundParseRecord(vp, parseRecordBack)) {
|
|
return false;
|
|
}
|
|
parseRecordStack.popBack();
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::finishMemberParseRecord(
|
|
Handle<JS::PropertyKey> key,
|
|
Handle<ParseRecordObject::EntryMap*> parseEntry) {
|
|
parseRecord->setKey(context(), key.get());
|
|
Rooted<Value> pro(context(), ObjectValue(*parseRecord));
|
|
parseRecord = nullptr;
|
|
return JS_SetPropertyById(context(), parseEntry, key, pro);
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::finishCompoundParseRecord(
|
|
const Value& value, Handle<ParseRecordObject::EntryMap*> parseEntry) {
|
|
parseRecord = ParseRecordObject::create(context(), value);
|
|
if (!parseRecord) {
|
|
return false;
|
|
}
|
|
parseRecord->setEntries(context(), parseEntry);
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONReviveHandler<CharT>::finishPrimitiveParseRecord(
|
|
const Value& value, SourceT source) {
|
|
MOZ_ASSERT(!source.IsEmpty()); // Empty source is for objects and arrays
|
|
Rooted<JSONParseNode*> parseNode(
|
|
context(), NewStringCopy<CanGC, CharT>(context(), source));
|
|
if (!parseNode) {
|
|
return false;
|
|
}
|
|
parseRecord = ParseRecordObject::create(context(), parseNode, value);
|
|
return !!parseRecord;
|
|
}
|
|
|
|
template <typename CharT>
|
|
void JSONReviveHandler<CharT>::trace(JSTracer* trc) {
|
|
Base::trace(trc);
|
|
if (parseRecord) {
|
|
TraceRoot(trc, &parseRecord, "parse record");
|
|
}
|
|
this->parseRecordStack.trace(trc);
|
|
}
|
|
|
|
template <typename CharT>
|
|
bool JSONReviveParser<CharT>::parse(JS::MutableHandle<JS::Value> vp,
|
|
JS::MutableHandle<ParseRecordObject*> pro) {
|
|
JS::Rooted<JS::Value> tempValue(this->handler.cx);
|
|
|
|
vp.setUndefined();
|
|
|
|
if (!this->parseImpl(tempValue,
|
|
[&](JS::Handle<JS::Value> value) { vp.set(value); })) {
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(this->handler.parseRecord);
|
|
pro.set(this->handler.parseRecord);
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
void JSONReviveParser<CharT>::trace(JSTracer* trc) {
|
|
this->handler.trace(trc);
|
|
|
|
for (auto& elem : this->stack) {
|
|
if (elem.state == JSONParserState::FinishArrayElement) {
|
|
elem.elements().trace(trc);
|
|
} else {
|
|
elem.properties().trace(trc);
|
|
}
|
|
}
|
|
}
|
|
|
|
template class js::JSONReviveParser<Latin1Char>;
|
|
template class js::JSONReviveParser<char16_t>;
|
|
|
|
template <typename CharT>
|
|
inline bool JSONSyntaxParseHandler<CharT>::objectOpen(
|
|
Vector<StackEntry, 10>& stack, PropertyVector** properties) {
|
|
StackEntry entry{JSONParserState::FinishObjectMember};
|
|
if (!stack.append(entry)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONSyntaxParseHandler<CharT>::finishObject(
|
|
Vector<StackEntry, 10>& stack, DummyValue* vp, PropertyVector* properties) {
|
|
stack.popBack();
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONSyntaxParseHandler<CharT>::arrayOpen(
|
|
Vector<StackEntry, 10>& stack, ElementVector** elements) {
|
|
StackEntry entry{JSONParserState::FinishArrayElement};
|
|
if (!stack.append(entry)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
inline bool JSONSyntaxParseHandler<CharT>::finishArray(
|
|
Vector<StackEntry, 10>& stack, DummyValue* vp, ElementVector* elements) {
|
|
stack.popBack();
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT>
|
|
void JSONSyntaxParseHandler<CharT>::reportError(const char* msg, uint32_t line,
|
|
uint32_t column) {
|
|
const size_t MaxWidth = sizeof("4294967295");
|
|
char columnString[MaxWidth];
|
|
SprintfLiteral(columnString, "%" PRIu32, column);
|
|
char lineString[MaxWidth];
|
|
SprintfLiteral(lineString, "%" PRIu32, line);
|
|
|
|
ErrorMetadata metadata;
|
|
metadata.isMuted = false;
|
|
metadata.filename = JS::ConstUTF8CharsZ("");
|
|
metadata.lineNumber = 0;
|
|
metadata.columnNumber = JS::ColumnNumberOneOrigin();
|
|
|
|
ReportJSONSyntaxError(fc, std::move(metadata), JSMSG_JSON_BAD_PARSE, msg,
|
|
lineString, columnString);
|
|
}
|
|
|
|
template class js::JSONSyntaxParseHandler<Latin1Char>;
|
|
template class js::JSONSyntaxParseHandler<char16_t>;
|
|
|
|
template <typename CharT>
|
|
bool JSONSyntaxParser<CharT>::parse() {
|
|
typename HandlerT::DummyValue unused;
|
|
|
|
if (!this->parseImpl(unused,
|
|
[&](const typename HandlerT::DummyValue& unused) {})) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template class js::JSONSyntaxParser<Latin1Char>;
|
|
template class js::JSONSyntaxParser<char16_t>;
|
|
|
|
template <typename CharT>
|
|
static bool IsValidJSONImpl(const CharT* chars, uint32_t len) {
|
|
FrontendContext fc;
|
|
// NOTE: We don't set stack quota here because JSON parser doesn't use it.
|
|
|
|
JSONSyntaxParser<CharT> parser(&fc, mozilla::Range(chars, len));
|
|
if (!parser.parse()) {
|
|
MOZ_ASSERT(fc.hadErrors());
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(!fc.hadErrors());
|
|
|
|
return true;
|
|
}
|
|
|
|
JS_PUBLIC_API bool JS::IsValidJSON(const JS::Latin1Char* chars, uint32_t len) {
|
|
return IsValidJSONImpl(chars, len);
|
|
}
|
|
|
|
JS_PUBLIC_API bool JS::IsValidJSON(const char16_t* chars, uint32_t len) {
|
|
return IsValidJSONImpl(chars, len);
|
|
}
|
|
|
|
template <typename CharT>
|
|
class MOZ_STACK_CLASS DelegateHandler {
|
|
private:
|
|
using CharPtr = mozilla::RangedPtr<const CharT>;
|
|
|
|
public:
|
|
using ContextT = FrontendContext;
|
|
|
|
class DummyValue {};
|
|
|
|
struct ElementVector {};
|
|
struct PropertyVector {};
|
|
|
|
class JSONStringBuilder {
|
|
public:
|
|
StringBuilder buffer;
|
|
|
|
explicit JSONStringBuilder(FrontendContext* fc) : buffer(fc) {}
|
|
|
|
bool append(char16_t c) { return buffer.append(c); }
|
|
bool append(const CharT* begin, const CharT* end) {
|
|
return buffer.append(begin, end);
|
|
}
|
|
};
|
|
|
|
struct StackEntry {
|
|
JSONParserState state;
|
|
};
|
|
|
|
public:
|
|
FrontendContext* fc;
|
|
|
|
explicit DelegateHandler(FrontendContext* fc) : fc(fc) {}
|
|
|
|
DelegateHandler(DelegateHandler&& other) noexcept
|
|
: fc(other.fc), handler_(other.handler_) {}
|
|
|
|
DelegateHandler(const DelegateHandler& other) = delete;
|
|
void operator=(const DelegateHandler& other) = delete;
|
|
|
|
FrontendContext* context() { return fc; }
|
|
|
|
template <JSONStringType ST>
|
|
inline bool setStringValue(CharPtr start, size_t length,
|
|
mozilla::Span<const CharT>&& source) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
if constexpr (ST == JSONStringType::PropertyName) {
|
|
return handler_->propertyName(start.get(), length);
|
|
}
|
|
|
|
return handler_->stringValue(start.get(), length);
|
|
}
|
|
|
|
template <JSONStringType ST>
|
|
inline bool setStringValue(JSONStringBuilder& builder,
|
|
mozilla::Span<const CharT>&& source) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
if constexpr (ST == JSONStringType::PropertyName) {
|
|
if (builder.buffer.isUnderlyingBufferLatin1()) {
|
|
return handler_->propertyName(builder.buffer.rawLatin1Begin(),
|
|
builder.buffer.length());
|
|
}
|
|
|
|
return handler_->propertyName(builder.buffer.rawTwoByteBegin(),
|
|
builder.buffer.length());
|
|
}
|
|
|
|
if (builder.buffer.isUnderlyingBufferLatin1()) {
|
|
return handler_->stringValue(builder.buffer.rawLatin1Begin(),
|
|
builder.buffer.length());
|
|
}
|
|
|
|
return handler_->stringValue(builder.buffer.rawTwoByteBegin(),
|
|
builder.buffer.length());
|
|
}
|
|
|
|
inline bool setNumberValue(double d, mozilla::Span<const CharT>&& source) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
if (!handler_->numberValue(d)) {
|
|
hadHandlerError_ = true;
|
|
}
|
|
return !hadHandlerError_;
|
|
}
|
|
|
|
inline bool setBooleanValue(bool value, mozilla::Span<const CharT>&& source) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
if (!handler_->booleanValue(value)) {
|
|
hadHandlerError_ = true;
|
|
}
|
|
return !hadHandlerError_;
|
|
}
|
|
inline bool setNullValue(mozilla::Span<const CharT>&& source) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
if (!handler_->nullValue()) {
|
|
hadHandlerError_ = true;
|
|
}
|
|
return !hadHandlerError_;
|
|
}
|
|
|
|
inline DummyValue numberValue() const { return DummyValue(); }
|
|
|
|
inline DummyValue stringValue() const { return DummyValue(); }
|
|
|
|
inline DummyValue booleanValue(bool value) { return DummyValue(); }
|
|
|
|
inline DummyValue nullValue() { return DummyValue(); }
|
|
|
|
inline bool objectOpen(Vector<StackEntry, 10>& stack,
|
|
PropertyVector** properties) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
StackEntry entry{JSONParserState::FinishObjectMember};
|
|
if (!stack.append(entry)) {
|
|
return false;
|
|
}
|
|
|
|
return handler_->startObject();
|
|
}
|
|
inline bool objectPropertyName(Vector<StackEntry, 10>& stack,
|
|
bool* isProtoInEval) {
|
|
*isProtoInEval = false;
|
|
return true;
|
|
}
|
|
inline bool finishObjectMember(Vector<StackEntry, 10>& stack,
|
|
DummyValue& value,
|
|
PropertyVector** properties) {
|
|
return true;
|
|
}
|
|
inline bool finishObject(Vector<StackEntry, 10>& stack, DummyValue* vp,
|
|
PropertyVector* properties) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
stack.popBack();
|
|
|
|
return handler_->endObject();
|
|
}
|
|
|
|
inline bool arrayOpen(Vector<StackEntry, 10>& stack,
|
|
ElementVector** elements) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
StackEntry entry{JSONParserState::FinishArrayElement};
|
|
if (!stack.append(entry)) {
|
|
return false;
|
|
}
|
|
|
|
return handler_->startArray();
|
|
}
|
|
inline bool arrayElement(Vector<StackEntry, 10>& stack, DummyValue& value,
|
|
ElementVector** elements) {
|
|
return true;
|
|
}
|
|
inline bool finishArray(Vector<StackEntry, 10>& stack, DummyValue* vp,
|
|
ElementVector* elements) {
|
|
if (hadHandlerError_) {
|
|
return false;
|
|
}
|
|
|
|
stack.popBack();
|
|
|
|
return handler_->endArray();
|
|
}
|
|
|
|
inline bool errorReturn() const { return false; }
|
|
|
|
inline bool ignoreError() const { return false; }
|
|
|
|
inline void freeStackEntry(StackEntry& entry) {}
|
|
|
|
void reportError(const char* msg, uint32_t line, uint32_t column) {
|
|
handler_->error(msg, line, column);
|
|
}
|
|
|
|
void setDelegateHandler(JS::JSONParseHandler* handler) { handler_ = handler; }
|
|
|
|
private:
|
|
JS::JSONParseHandler* handler_ = nullptr;
|
|
bool hadHandlerError_ = false;
|
|
};
|
|
|
|
template class DelegateHandler<Latin1Char>;
|
|
template class DelegateHandler<char16_t>;
|
|
|
|
template <typename CharT>
|
|
class MOZ_STACK_CLASS DelegateParser
|
|
: JSONPerHandlerParser<CharT, DelegateHandler<CharT>> {
|
|
using HandlerT = DelegateHandler<CharT>;
|
|
using Base = JSONPerHandlerParser<CharT, HandlerT>;
|
|
|
|
public:
|
|
DelegateParser(FrontendContext* fc, mozilla::Range<const CharT> data,
|
|
JS::JSONParseHandler* handler)
|
|
: Base(fc, data) {
|
|
this->handler.setDelegateHandler(handler);
|
|
}
|
|
|
|
DelegateParser(DelegateParser<CharT>&& other) noexcept
|
|
: Base(std::move(other)) {}
|
|
|
|
DelegateParser(const DelegateParser& other) = delete;
|
|
void operator=(const DelegateParser& other) = delete;
|
|
|
|
bool parse() {
|
|
typename HandlerT::DummyValue unused;
|
|
|
|
if (!this->parseImpl(unused,
|
|
[&](const typename HandlerT::DummyValue& unused) {})) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template class DelegateParser<Latin1Char>;
|
|
template class DelegateParser<char16_t>;
|
|
|
|
template <typename CharT>
|
|
static bool ParseJSONWithHandlerImpl(const CharT* chars, uint32_t len,
|
|
JS::JSONParseHandler* handler) {
|
|
FrontendContext fc;
|
|
// NOTE: We don't set stack quota here because JSON parser doesn't use it.
|
|
|
|
DelegateParser<CharT> parser(&fc, mozilla::Range(chars, len), handler);
|
|
if (!parser.parse()) {
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(!fc.hadErrors());
|
|
|
|
return true;
|
|
}
|
|
|
|
JS_PUBLIC_API bool JS::ParseJSONWithHandler(const JS::Latin1Char* chars,
|
|
uint32_t len,
|
|
JS::JSONParseHandler* handler) {
|
|
return ParseJSONWithHandlerImpl(chars, len, handler);
|
|
}
|
|
|
|
JS_PUBLIC_API bool JS::ParseJSONWithHandler(const char16_t* chars, uint32_t len,
|
|
JS::JSONParseHandler* handler) {
|
|
return ParseJSONWithHandlerImpl(chars, len, handler);
|
|
}
|