summaryrefslogtreecommitdiffstats
path: root/js/src/vm/JSONParser.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/vm/JSONParser.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/vm/JSONParser.cpp')
-rw-r--r--js/src/vm/JSONParser.cpp801
1 files changed, 801 insertions, 0 deletions
diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp
new file mode 100644
index 000000000..01883bb15
--- /dev/null
+++ b/js/src/vm/JSONParser.cpp
@@ -0,0 +1,801 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/Range.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/Sprintf.h"
+
+#include <ctype.h>
+
+#include "jsarray.h"
+#include "jscompartment.h"
+#include "jsnum.h"
+#include "jsprf.h"
+
+#include "vm/StringBuffer.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::RangedPtr;
+
+JSONParserBase::~JSONParserBase()
+{
+ for (size_t i = 0; i < stack.length(); i++) {
+ if (stack[i].state == FinishArrayElement)
+ js_delete(&stack[i].elements());
+ else
+ js_delete(&stack[i].properties());
+ }
+
+ 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]);
+}
+
+void
+JSONParserBase::trace(JSTracer* trc)
+{
+ for (size_t i = 0; i < stack.length(); i++) {
+ if (stack[i].state == FinishArrayElement) {
+ ElementVector& elements = stack[i].elements();
+ for (size_t j = 0; j < elements.length(); j++)
+ TraceRoot(trc, &elements[j], "JSONParser element");
+ } else {
+ PropertyVector& properties = stack[i].properties();
+ for (size_t j = 0; j < properties.length(); j++) {
+ TraceRoot(trc, &properties[j].value, "JSONParser property value");
+ TraceRoot(trc, &properties[j].id, "JSONParser property id");
+ }
+ }
+ }
+}
+
+template <typename CharT>
+void
+JSONParser<CharT>::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;
+}
+
+template <typename CharT>
+void
+JSONParser<CharT>::error(const char* msg)
+{
+ if (errorHandling == RaiseError) {
+ uint32_t column = 1, line = 1;
+ getTextPosition(&column, &line);
+
+ const size_t MaxWidth = sizeof("4294967295");
+ char columnNumber[MaxWidth];
+ SprintfLiteral(columnNumber, "%" PRIu32, column);
+ char lineNumber[MaxWidth];
+ SprintfLiteral(lineNumber, "%" PRIu32, line);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_BAD_PARSE,
+ msg, lineNumber, columnNumber);
+ }
+}
+
+bool
+JSONParserBase::errorReturn()
+{
+ return errorHandling == NoError;
+}
+
+template <typename CharT>
+template <JSONParserBase::StringType ST>
+JSONParserBase::Token
+JSONParser<CharT>::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(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++;
+ JSFlatString* str = (ST == JSONParser::PropertyName)
+ ? AtomizeChars(cx, start.get(), length)
+ : NewStringCopyN<CanGC>(cx, start.get(), length);
+ if (!str)
+ return token(OOM);
+ return stringToken(str);
+ }
+
+ if (*current == '\\')
+ break;
+
+ if (*current <= 0x001F) {
+ error("bad control character in string literal");
+ return token(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.
+ */
+ StringBuffer buffer(cx);
+ do {
+ if (start < current && !buffer.append(start.get(), current.get()))
+ return token(OOM);
+
+ if (current >= end)
+ break;
+
+ char16_t c = *current++;
+ if (c == '"') {
+ JSFlatString* str = (ST == JSONParser::PropertyName)
+ ? buffer.finishAtom()
+ : buffer.finishString();
+ if (!str)
+ return token(OOM);
+ return stringToken(str);
+ }
+
+ if (c != '\\') {
+ --current;
+ error("bad character in string literal");
+ return token(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 ||
+ !(JS7_ISHEX(current[0]) &&
+ JS7_ISHEX(current[1]) &&
+ JS7_ISHEX(current[2]) &&
+ JS7_ISHEX(current[3])))
+ {
+ // Point to the first non-hexadecimal character (which may be
+ // missing).
+ if (current == end || !JS7_ISHEX(current[0]))
+ ; // already at correct location
+ else if (current + 1 == end || !JS7_ISHEX(current[1]))
+ current += 1;
+ else if (current + 2 == end || !JS7_ISHEX(current[2]))
+ current += 2;
+ else if (current + 3 == end || !JS7_ISHEX(current[3]))
+ current += 3;
+ else
+ MOZ_CRASH("logic error determining first erroneous character");
+
+ error("bad Unicode escape");
+ return token(Error);
+ }
+ c = (JS7_UNHEX(current[0]) << 12)
+ | (JS7_UNHEX(current[1]) << 8)
+ | (JS7_UNHEX(current[2]) << 4)
+ | (JS7_UNHEX(current[3]));
+ current += 4;
+ break;
+
+ default:
+ current--;
+ error("bad escaped character");
+ return token(Error);
+ }
+ if (!buffer.append(c))
+ return token(OOM);
+
+ start = current;
+ for (; current < end; current++) {
+ if (*current == '"' || *current == '\\' || *current <= 0x001F)
+ break;
+ }
+ } while (current < end);
+
+ error("unterminated string");
+ return token(Error);
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::readNumber()
+{
+ MOZ_ASSERT(current < end);
+ MOZ_ASSERT(JS7_ISDEC(*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(Error);
+ }
+
+ const CharPtr digitStart = current;
+
+ /* 0|[1-9][0-9]+ */
+ if (!JS7_ISDEC(*current)) {
+ error("unexpected non-digit");
+ return token(Error);
+ }
+ if (*current++ != '0') {
+ for (; current < end; current++) {
+ if (!JS7_ISDEC(*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;
+ const CharT* dummy;
+ if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
+ return token(OOM);
+ MOZ_ASSERT(current == dummy);
+ return numberToken(negative ? -d : d);
+ }
+
+ /* (\.[0-9]+)? */
+ if (current < end && *current == '.') {
+ if (++current == end) {
+ error("missing digits after decimal point");
+ return token(Error);
+ }
+ if (!JS7_ISDEC(*current)) {
+ error("unterminated fractional number");
+ return token(Error);
+ }
+ while (++current < end) {
+ if (!JS7_ISDEC(*current))
+ break;
+ }
+ }
+
+ /* ([eE][\+\-]?[0-9]+)? */
+ if (current < end && (*current == 'e' || *current == 'E')) {
+ if (++current == end) {
+ error("missing digits after exponent indicator");
+ return token(Error);
+ }
+ if (*current == '+' || *current == '-') {
+ if (++current == end) {
+ error("missing digits after exponent sign");
+ return token(Error);
+ }
+ }
+ if (!JS7_ISDEC(*current)) {
+ error("exponent part is missing a number");
+ return token(Error);
+ }
+ while (++current < end) {
+ if (!JS7_ISDEC(*current))
+ break;
+ }
+ }
+
+ double d;
+ const CharT* finish;
+ if (!js_strtod(cx, digitStart.get(), current.get(), &finish, &d))
+ return token(OOM);
+ MOZ_ASSERT(current == finish);
+ return numberToken(negative ? -d : d);
+}
+
+static inline bool
+IsJSONWhitespace(char16_t c)
+{
+ return c == '\t' || c == '\r' || c == '\n' || c == ' ';
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::advance()
+{
+ while (current < end && IsJSONWhitespace(*current))
+ current++;
+ if (current >= end) {
+ error("unexpected end of data");
+ return token(Error);
+ }
+
+ switch (*current) {
+ case '"':
+ return readString<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(Error);
+ }
+ current += 4;
+ return token(True);
+
+ case 'f':
+ if (end - current < 5 ||
+ current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e')
+ {
+ error("unexpected keyword");
+ return token(Error);
+ }
+ current += 5;
+ return token(False);
+
+ case 'n':
+ if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') {
+ error("unexpected keyword");
+ return token(Error);
+ }
+ current += 4;
+ return token(Null);
+
+ case '[':
+ current++;
+ return token(ArrayOpen);
+ case ']':
+ current++;
+ return token(ArrayClose);
+
+ case '{':
+ current++;
+ return token(ObjectOpen);
+ case '}':
+ current++;
+ return token(ObjectClose);
+
+ case ',':
+ current++;
+ return token(Comma);
+
+ case ':':
+ current++;
+ return token(Colon);
+
+ default:
+ error("unexpected character");
+ return token(Error);
+ }
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::advanceAfterObjectOpen()
+{
+ MOZ_ASSERT(current[-1] == '{');
+
+ while (current < end && IsJSONWhitespace(*current))
+ current++;
+ if (current >= end) {
+ error("end of data while reading object contents");
+ return token(Error);
+ }
+
+ if (*current == '"')
+ return readString<PropertyName>();
+
+ if (*current == '}') {
+ current++;
+ return token(ObjectClose);
+ }
+
+ error("expected property name or '}'");
+ return token(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] == '"' ||
+ JS7_ISDEC(current[-1]));
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::advanceAfterArrayElement()
+{
+ AssertPastValue(current);
+
+ while (current < end && IsJSONWhitespace(*current))
+ current++;
+ if (current >= end) {
+ error("end of data when ',' or ']' was expected");
+ return token(Error);
+ }
+
+ if (*current == ',') {
+ current++;
+ return token(Comma);
+ }
+
+ if (*current == ']') {
+ current++;
+ return token(ArrayClose);
+ }
+
+ error("expected ',' or ']' after array element");
+ return token(Error);
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::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(Error);
+ }
+
+ if (*current == '"')
+ return readString<PropertyName>();
+
+ error("expected double-quoted property name");
+ return token(Error);
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::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(Error);
+ }
+
+ if (*current == ':') {
+ current++;
+ return token(Colon);
+ }
+
+ error("expected ':' after property name in object");
+ return token(Error);
+}
+
+template <typename CharT>
+JSONParserBase::Token
+JSONParser<CharT>::advanceAfterProperty()
+{
+ AssertPastValue(current);
+
+ while (current < end && IsJSONWhitespace(*current))
+ current++;
+ if (current >= end) {
+ error("end of data after property value in object");
+ return token(Error);
+ }
+
+ if (*current == ',') {
+ current++;
+ return token(Comma);
+ }
+
+ if (*current == '}') {
+ current++;
+ return token(ObjectClose);
+ }
+
+ error("expected ',' or '}' after property value in object");
+ return token(Error);
+}
+
+inline bool
+JSONParserBase::finishObject(MutableHandleValue vp, PropertyVector& properties)
+{
+ MOZ_ASSERT(&properties == &stack.back().properties());
+
+ JSObject* obj = ObjectGroup::newPlainObject(cx, properties.begin(), properties.length(), GenericObject);
+ if (!obj)
+ return false;
+
+ vp.setObject(*obj);
+ if (!freeProperties.append(&properties))
+ return false;
+ stack.popBack();
+
+ if (!stack.empty() && stack.back().state == FinishArrayElement) {
+ const ElementVector& elements = stack.back().elements();
+ if (!CombinePlainObjectPropertyTypes(cx, obj, elements.begin(), elements.length()))
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+JSONParserBase::finishArray(MutableHandleValue vp, ElementVector& elements)
+{
+ MOZ_ASSERT(&elements == &stack.back().elements());
+
+ JSObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
+ GenericObject);
+ if (!obj)
+ return false;
+
+ vp.setObject(*obj);
+ if (!freeElements.append(&elements))
+ return false;
+ stack.popBack();
+
+ if (!stack.empty() && stack.back().state == FinishArrayElement) {
+ const ElementVector& elements = stack.back().elements();
+ if (!CombineArrayElementTypes(cx, obj, elements.begin(), elements.length()))
+ return false;
+ }
+
+ return true;
+}
+
+template <typename CharT>
+bool
+JSONParser<CharT>::parse(MutableHandleValue vp)
+{
+ RootedValue value(cx);
+ MOZ_ASSERT(stack.empty());
+
+ vp.setUndefined();
+
+ Token token;
+ ParserState state = JSONValue;
+ while (true) {
+ switch (state) {
+ case FinishObjectMember: {
+ PropertyVector& properties = stack.back().properties();
+ properties.back().value = value;
+
+ token = advanceAfterProperty();
+ if (token == ObjectClose) {
+ if (!finishObject(&value, properties))
+ return false;
+ break;
+ }
+ if (token != Comma) {
+ if (token == OOM)
+ return false;
+ if (token != Error)
+ error("expected ',' or '}' after property-value pair in object literal");
+ return errorReturn();
+ }
+ token = advancePropertyName();
+ /* FALL THROUGH */
+ }
+
+ JSONMember:
+ if (token == String) {
+ jsid id = AtomToId(atomValue());
+ PropertyVector& properties = stack.back().properties();
+ if (!properties.append(IdValuePair(id)))
+ return false;
+ token = advancePropertyColon();
+ if (token != Colon) {
+ MOZ_ASSERT(token == Error);
+ return errorReturn();
+ }
+ goto JSONValue;
+ }
+ if (token == OOM)
+ return false;
+ if (token != Error)
+ error("property names must be double-quoted strings");
+ return errorReturn();
+
+ case FinishArrayElement: {
+ ElementVector& elements = stack.back().elements();
+ if (!elements.append(value.get()))
+ return false;
+ token = advanceAfterArrayElement();
+ if (token == Comma)
+ goto JSONValue;
+ if (token == ArrayClose) {
+ if (!finishArray(&value, elements))
+ return false;
+ break;
+ }
+ MOZ_ASSERT(token == Error);
+ return errorReturn();
+ }
+
+ JSONValue:
+ case JSONValue:
+ token = advance();
+ JSONValueSwitch:
+ switch (token) {
+ case String:
+ value = stringValue();
+ break;
+ case Number:
+ value = numberValue();
+ break;
+ case True:
+ value = BooleanValue(true);
+ break;
+ case False:
+ value = BooleanValue(false);
+ break;
+ case Null:
+ value = NullValue();
+ break;
+
+ case ArrayOpen: {
+ ElementVector* elements;
+ if (!freeElements.empty()) {
+ elements = freeElements.popCopy();
+ elements->clear();
+ } else {
+ elements = cx->new_<ElementVector>(cx);
+ if (!elements)
+ return false;
+ }
+ if (!stack.append(elements))
+ return false;
+
+ token = advance();
+ if (token == ArrayClose) {
+ if (!finishArray(&value, *elements))
+ return false;
+ break;
+ }
+ goto JSONValueSwitch;
+ }
+
+ case ObjectOpen: {
+ PropertyVector* properties;
+ if (!freeProperties.empty()) {
+ properties = freeProperties.popCopy();
+ properties->clear();
+ } else {
+ properties = cx->new_<PropertyVector>(cx);
+ if (!properties)
+ return false;
+ }
+ if (!stack.append(properties))
+ return false;
+
+ token = advanceAfterObjectOpen();
+ if (token == ObjectClose) {
+ if (!finishObject(&value, *properties))
+ return false;
+ break;
+ }
+ goto JSONMember;
+ }
+
+ case ArrayClose:
+ case ObjectClose:
+ case Colon:
+ case Comma:
+ // Move the current pointer backwards so that the position
+ // reported in the error message is correct.
+ --current;
+ error("unexpected character");
+ return errorReturn();
+
+ case OOM:
+ return false;
+
+ case Error:
+ return errorReturn();
+ }
+ break;
+ }
+
+ if (stack.empty())
+ break;
+ state = stack.back().state;
+ }
+
+ for (; current < end; current++) {
+ if (!IsJSONWhitespace(*current)) {
+ error("unexpected non-whitespace character after JSON data");
+ return errorReturn();
+ }
+ }
+
+ MOZ_ASSERT(end == current);
+ MOZ_ASSERT(stack.empty());
+
+ vp.set(value);
+ return true;
+}
+
+template class js::JSONParser<Latin1Char>;
+template class js::JSONParser<char16_t>;