diff options
Diffstat (limited to 'js/src/vm/JSONParser.cpp')
-rw-r--r-- | js/src/vm/JSONParser.cpp | 1481 |
1 files changed, 1481 insertions, 0 deletions
diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp new file mode 100644 index 0000000000..addd9aa263 --- /dev/null +++ b/js/src/vm/JSONParser.cpp @@ -0,0 +1,1481 @@ +/* -*- 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/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/StringBuffer.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 +#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, typename StringBuilderT> +void JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +bool JSONTokenizer<CharT, ParserT, + StringBuilderT>::consumeTrailingWhitespaces() { + for (; current < end; current++) { + if (!IsJSONWhitespace(*current)) { + return false; + } + } + return true; +} + +template <typename CharT, typename ParserT, typename StringBuilderT> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +JSONToken +JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +JSONToken +JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +JSONToken +JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +JSONToken +JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +template <JSONStringType ST> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +template <JSONStringType ST> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::stringToken( + StringBuilderT& builder) { + if (!parser->handler.template setStringValue<ST>(builder, getSource())) { + return JSONToken::OOM; + } + return JSONToken::String; +} + +template <typename CharT, typename ParserT, typename StringBuilderT> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::numberToken(double d) { + if (!parser->handler.setNumberValue(d, getSource())) { + return JSONToken::OOM; + } + return JSONToken::Number; +} + +template <typename CharT, typename ParserT, typename StringBuilderT> +template <JSONStringType ST> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::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. + */ + StringBuilderT 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, typename StringBuilderT> +JSONToken JSONTokenizer<CharT, ParserT, StringBuilderT>::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, typename StringBuilderT> +void JSONTokenizer<CharT, ParserT, StringBuilderT>::error(const char* msg) { + parser->error(msg); +} + +// 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(*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 void JSONFullParseHandlerAnyChar::finishObjectMember( + Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value, + PropertyVector** properties) { + *properties = &stack.back().properties(); + (*properties)->back().value = value; +} + +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; + } + JSObject* obj = NewPlainObjectWithMaybeDuplicateKeys( + cx, properties->begin(), properties->length(), 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(*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>::StringBuilder::append(char16_t c) { + return buffer.append(c); +} + +template <typename CharT> +bool JSONFullParseHandler<CharT>::StringBuilder::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 createJSONParseRecord(v, source); +} + +template <typename CharT> +template <JSONStringType ST> +inline bool JSONFullParseHandler<CharT>::setStringValue( + StringBuilder& 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 createJSONParseRecord(v, source); +} + +template <typename CharT> +inline bool JSONFullParseHandler<CharT>::setNumberValue( + double d, mozilla::Span<const CharT>&& source) { + v = JS::NumberValue(d); + return createJSONParseRecord(v, source); +} + +template <typename CharT> +inline bool JSONFullParseHandler<CharT>::setBooleanValue( + bool value, mozilla::Span<const CharT>&& source) { + return createJSONParseRecord(JS::BooleanValue(value), source); +} + +template <typename CharT> +inline bool JSONFullParseHandler<CharT>::setNullValue( + mozilla::Span<const CharT>&& source) { + return createJSONParseRecord(JS::NullValue(), source); +} + +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); + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_BAD_PARSE, + msg, lineString, columnString); +} + +template <typename CharT> +void JSONFullParseHandler<CharT>::trace(JSTracer* trc) { + Base::trace(trc); + parseRecord.trace(trc); +} + +template <typename CharT> +inline bool JSONFullParseHandler<CharT>::createJSONParseRecord( + const Value& value, mozilla::Span<const CharT>& source) { +#ifdef ENABLE_JSON_PARSE_WITH_SOURCE + if (cx->realm()->creationOptions().getJSONParseWithSource()) { + MOZ_ASSERT(!source.IsEmpty()); + Rooted<JSONParseNode*> parseNode(cx, + NewStringCopy<CanGC, CharT>(cx, source)); + if (!parseNode) { + return false; + } + parseRecord = ParseRecordObject(parseNode, value); + } +#endif + return true; +} + +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; + handler.finishObjectMember(stack, value, &properties); + + 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::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> +bool JSONParser<CharT>::parse(JS::MutableHandle<JS::Value> vp, + JS::MutableHandle<ParseRecordObject> pro) { + JS::Rooted<JS::Value> tempValue(this->handler.cx); + + vp.setUndefined(); + + bool result = this->parseImpl( + tempValue, [&](JS::Handle<JS::Value> value) { vp.set(value); }); + pro.get() = std::move(this->handler.parseRecord); + return result; +} + +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 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; +} + +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); +} + +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 StringBuilder { + public: + StringBuffer buffer; + + explicit StringBuilder(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(StringBuilder& 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) { + return true; + } + inline bool setNullValue(mozilla::Span<const CharT>&& source) { return true; } + + inline DummyValue numberValue() const { return DummyValue(); } + + inline DummyValue stringValue() const { return DummyValue(); } + + inline DummyValue booleanValue(bool value) { + if (hadHandlerError_) { + return DummyValue(); + } + + if (!handler_->booleanValue(value)) { + hadHandlerError_ = true; + } + return DummyValue(); + } + inline DummyValue nullValue() { + if (hadHandlerError_) { + return DummyValue(); + } + + if (!handler_->nullValue()) { + hadHandlerError_ = true; + } + 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 void finishObjectMember(Vector<StackEntry, 10>& stack, + DummyValue& value, + PropertyVector** properties) {} + 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); +} |