michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsonparser.h" michael@0: michael@0: #include "mozilla/RangedPtr.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsarray.h" michael@0: #include "jscompartment.h" michael@0: #include "jsnum.h" michael@0: #include "jsprf.h" michael@0: michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::RangedPtr; michael@0: michael@0: JSONParser::~JSONParser() michael@0: { michael@0: for (size_t i = 0; i < stack.length(); i++) { michael@0: if (stack[i].state == FinishArrayElement) michael@0: js_delete(&stack[i].elements()); michael@0: else michael@0: js_delete(&stack[i].properties()); michael@0: } michael@0: michael@0: for (size_t i = 0; i < freeElements.length(); i++) michael@0: js_delete(freeElements[i]); michael@0: michael@0: for (size_t i = 0; i < freeProperties.length(); i++) michael@0: js_delete(freeProperties[i]); michael@0: } michael@0: michael@0: void michael@0: JSONParser::trace(JSTracer *trc) michael@0: { michael@0: for (size_t i = 0; i < stack.length(); i++) { michael@0: if (stack[i].state == FinishArrayElement) { michael@0: ElementVector &elements = stack[i].elements(); michael@0: for (size_t j = 0; j < elements.length(); j++) michael@0: gc::MarkValueRoot(trc, &elements[j], "JSONParser element"); michael@0: } else { michael@0: PropertyVector &properties = stack[i].properties(); michael@0: for (size_t j = 0; j < properties.length(); j++) { michael@0: gc::MarkValueRoot(trc, &properties[j].value, "JSONParser property value"); michael@0: gc::MarkIdRoot(trc, &properties[j].id, "JSONParser property id"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSONParser::getTextPosition(uint32_t *column, uint32_t *line) michael@0: { michael@0: ConstTwoByteChars ptr = begin; michael@0: uint32_t col = 1; michael@0: uint32_t row = 1; michael@0: for (; ptr < current; ptr++) { michael@0: if (*ptr == '\n' || *ptr == '\r') { michael@0: ++row; michael@0: col = 1; michael@0: // \r\n is treated as a single newline. michael@0: if (ptr + 1 < current && *ptr == '\r' && *(ptr + 1) == '\n') michael@0: ++ptr; michael@0: } else { michael@0: ++col; michael@0: } michael@0: } michael@0: *column = col; michael@0: *line = row; michael@0: } michael@0: michael@0: void michael@0: JSONParser::error(const char *msg) michael@0: { michael@0: if (errorHandling == RaiseError) { michael@0: uint32_t column = 1, line = 1; michael@0: getTextPosition(&column, &line); michael@0: michael@0: const size_t MaxWidth = sizeof("4294967295"); michael@0: char columnNumber[MaxWidth]; michael@0: JS_snprintf(columnNumber, sizeof columnNumber, "%lu", column); michael@0: char lineNumber[MaxWidth]; michael@0: JS_snprintf(lineNumber, sizeof lineNumber, "%lu", line); michael@0: michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_JSON_BAD_PARSE, michael@0: msg, lineNumber, columnNumber); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSONParser::errorReturn() michael@0: { michael@0: return errorHandling == NoError; michael@0: } michael@0: michael@0: template michael@0: JSONParser::Token michael@0: JSONParser::readString() michael@0: { michael@0: JS_ASSERT(current < end); michael@0: JS_ASSERT(*current == '"'); michael@0: michael@0: /* michael@0: * JSONString: michael@0: * /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/ michael@0: */ michael@0: michael@0: if (++current == end) { michael@0: error("unterminated string literal"); michael@0: return token(Error); michael@0: } michael@0: michael@0: /* michael@0: * Optimization: if the source contains no escaped characters, create the michael@0: * string directly from the source text. michael@0: */ michael@0: RangedPtr start = current; michael@0: for (; current < end; current++) { michael@0: if (*current == '"') { michael@0: size_t length = current - start; michael@0: current++; michael@0: JSFlatString *str = (ST == JSONParser::PropertyName) michael@0: ? AtomizeChars(cx, start.get(), length) michael@0: : js_NewStringCopyN(cx, start.get(), length); michael@0: if (!str) michael@0: return token(OOM); michael@0: return stringToken(str); michael@0: } michael@0: michael@0: if (*current == '\\') michael@0: break; michael@0: michael@0: if (*current <= 0x001F) { michael@0: error("bad control character in string literal"); michael@0: return token(Error); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Slow case: string contains escaped characters. Copy a maximal sequence michael@0: * of unescaped characters into a temporary buffer, then an escaped michael@0: * character, and repeat until the entire string is consumed. michael@0: */ michael@0: StringBuffer buffer(cx); michael@0: do { michael@0: if (start < current && !buffer.append(start.get(), current.get())) michael@0: return token(OOM); michael@0: michael@0: if (current >= end) michael@0: break; michael@0: michael@0: jschar c = *current++; michael@0: if (c == '"') { michael@0: JSFlatString *str = (ST == JSONParser::PropertyName) michael@0: ? buffer.finishAtom() michael@0: : buffer.finishString(); michael@0: if (!str) michael@0: return token(OOM); michael@0: return stringToken(str); michael@0: } michael@0: michael@0: if (c != '\\') { michael@0: --current; michael@0: error("bad character in string literal"); michael@0: return token(Error); michael@0: } michael@0: michael@0: if (current >= end) michael@0: break; michael@0: michael@0: switch (*current++) { michael@0: case '"': c = '"'; break; michael@0: case '/': c = '/'; break; michael@0: case '\\': c = '\\'; break; michael@0: case 'b': c = '\b'; break; michael@0: case 'f': c = '\f'; break; michael@0: case 'n': c = '\n'; break; michael@0: case 'r': c = '\r'; break; michael@0: case 't': c = '\t'; break; michael@0: michael@0: case 'u': michael@0: if (end - current < 4 || michael@0: !(JS7_ISHEX(current[0]) && michael@0: JS7_ISHEX(current[1]) && michael@0: JS7_ISHEX(current[2]) && michael@0: JS7_ISHEX(current[3]))) michael@0: { michael@0: // Point to the first non-hexadecimal character (which may be michael@0: // missing). michael@0: if (current == end || !JS7_ISHEX(current[0])) michael@0: ; // already at correct location michael@0: else if (current + 1 == end || !JS7_ISHEX(current[1])) michael@0: current += 1; michael@0: else if (current + 2 == end || !JS7_ISHEX(current[2])) michael@0: current += 2; michael@0: else if (current + 3 == end || !JS7_ISHEX(current[3])) michael@0: current += 3; michael@0: else michael@0: MOZ_ASSUME_UNREACHABLE("logic error determining first erroneous character"); michael@0: michael@0: error("bad Unicode escape"); michael@0: return token(Error); michael@0: } michael@0: c = (JS7_UNHEX(current[0]) << 12) michael@0: | (JS7_UNHEX(current[1]) << 8) michael@0: | (JS7_UNHEX(current[2]) << 4) michael@0: | (JS7_UNHEX(current[3])); michael@0: current += 4; michael@0: break; michael@0: michael@0: default: michael@0: current--; michael@0: error("bad escaped character"); michael@0: return token(Error); michael@0: } michael@0: if (!buffer.append(c)) michael@0: return token(OOM); michael@0: michael@0: start = current; michael@0: for (; current < end; current++) { michael@0: if (*current == '"' || *current == '\\' || *current <= 0x001F) michael@0: break; michael@0: } michael@0: } while (current < end); michael@0: michael@0: error("unterminated string"); michael@0: return token(Error); michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::readNumber() michael@0: { michael@0: JS_ASSERT(current < end); michael@0: JS_ASSERT(JS7_ISDEC(*current) || *current == '-'); michael@0: michael@0: /* michael@0: * JSONNumber: michael@0: * /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/ michael@0: */ michael@0: michael@0: bool negative = *current == '-'; michael@0: michael@0: /* -? */ michael@0: if (negative && ++current == end) { michael@0: error("no number after minus sign"); michael@0: return token(Error); michael@0: } michael@0: michael@0: const RangedPtr digitStart = current; michael@0: michael@0: /* 0|[1-9][0-9]+ */ michael@0: if (!JS7_ISDEC(*current)) { michael@0: error("unexpected non-digit"); michael@0: return token(Error); michael@0: } michael@0: if (*current++ != '0') { michael@0: for (; current < end; current++) { michael@0: if (!JS7_ISDEC(*current)) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* Fast path: no fractional or exponent part. */ michael@0: if (current == end || (*current != '.' && *current != 'e' && *current != 'E')) { michael@0: TwoByteChars chars(digitStart.get(), current - digitStart); michael@0: if (chars.length() < strlen("9007199254740992")) { michael@0: // If the decimal number is shorter than the length of 2**53, (the michael@0: // largest number a double can represent with integral precision), michael@0: // parse it using a decimal-only parser. This comparison is michael@0: // conservative but faster than a fully-precise check. michael@0: double d = ParseDecimalNumber(chars); michael@0: return numberToken(negative ? -d : d); michael@0: } michael@0: michael@0: double d; michael@0: const jschar *dummy; michael@0: if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d)) michael@0: return token(OOM); michael@0: JS_ASSERT(current == dummy); michael@0: return numberToken(negative ? -d : d); michael@0: } michael@0: michael@0: /* (\.[0-9]+)? */ michael@0: if (current < end && *current == '.') { michael@0: if (++current == end) { michael@0: error("missing digits after decimal point"); michael@0: return token(Error); michael@0: } michael@0: if (!JS7_ISDEC(*current)) { michael@0: error("unterminated fractional number"); michael@0: return token(Error); michael@0: } michael@0: while (++current < end) { michael@0: if (!JS7_ISDEC(*current)) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* ([eE][\+\-]?[0-9]+)? */ michael@0: if (current < end && (*current == 'e' || *current == 'E')) { michael@0: if (++current == end) { michael@0: error("missing digits after exponent indicator"); michael@0: return token(Error); michael@0: } michael@0: if (*current == '+' || *current == '-') { michael@0: if (++current == end) { michael@0: error("missing digits after exponent sign"); michael@0: return token(Error); michael@0: } michael@0: } michael@0: if (!JS7_ISDEC(*current)) { michael@0: error("exponent part is missing a number"); michael@0: return token(Error); michael@0: } michael@0: while (++current < end) { michael@0: if (!JS7_ISDEC(*current)) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: double d; michael@0: const jschar *finish; michael@0: if (!js_strtod(cx, digitStart.get(), current.get(), &finish, &d)) michael@0: return token(OOM); michael@0: JS_ASSERT(current == finish); michael@0: return numberToken(negative ? -d : d); michael@0: } michael@0: michael@0: static inline bool michael@0: IsJSONWhitespace(jschar c) michael@0: { michael@0: return c == '\t' || c == '\r' || c == '\n' || c == ' '; michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::advance() michael@0: { michael@0: while (current < end && IsJSONWhitespace(*current)) michael@0: current++; michael@0: if (current >= end) { michael@0: error("unexpected end of data"); michael@0: return token(Error); michael@0: } michael@0: michael@0: switch (*current) { michael@0: case '"': michael@0: return readString(); michael@0: michael@0: case '-': michael@0: case '0': michael@0: case '1': michael@0: case '2': michael@0: case '3': michael@0: case '4': michael@0: case '5': michael@0: case '6': michael@0: case '7': michael@0: case '8': michael@0: case '9': michael@0: return readNumber(); michael@0: michael@0: case 't': michael@0: if (end - current < 4 || current[1] != 'r' || current[2] != 'u' || current[3] != 'e') { michael@0: error("unexpected keyword"); michael@0: return token(Error); michael@0: } michael@0: current += 4; michael@0: return token(True); michael@0: michael@0: case 'f': michael@0: if (end - current < 5 || michael@0: current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e') michael@0: { michael@0: error("unexpected keyword"); michael@0: return token(Error); michael@0: } michael@0: current += 5; michael@0: return token(False); michael@0: michael@0: case 'n': michael@0: if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') { michael@0: error("unexpected keyword"); michael@0: return token(Error); michael@0: } michael@0: current += 4; michael@0: return token(Null); michael@0: michael@0: case '[': michael@0: current++; michael@0: return token(ArrayOpen); michael@0: case ']': michael@0: current++; michael@0: return token(ArrayClose); michael@0: michael@0: case '{': michael@0: current++; michael@0: return token(ObjectOpen); michael@0: case '}': michael@0: current++; michael@0: return token(ObjectClose); michael@0: michael@0: case ',': michael@0: current++; michael@0: return token(Comma); michael@0: michael@0: case ':': michael@0: current++; michael@0: return token(Colon); michael@0: michael@0: default: michael@0: error("unexpected character"); michael@0: return token(Error); michael@0: } michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::advanceAfterObjectOpen() michael@0: { michael@0: JS_ASSERT(current[-1] == '{'); michael@0: michael@0: while (current < end && IsJSONWhitespace(*current)) michael@0: current++; michael@0: if (current >= end) { michael@0: error("end of data while reading object contents"); michael@0: return token(Error); michael@0: } michael@0: michael@0: if (*current == '"') michael@0: return readString(); michael@0: michael@0: if (*current == '}') { michael@0: current++; michael@0: return token(ObjectClose); michael@0: } michael@0: michael@0: error("expected property name or '}'"); michael@0: return token(Error); michael@0: } michael@0: michael@0: static inline void michael@0: AssertPastValue(const RangedPtr current) michael@0: { michael@0: /* michael@0: * We're past an arbitrary JSON value, so the previous character is michael@0: * *somewhat* constrained, even if this assertion is pretty broad. Don't michael@0: * knock it till you tried it: this assertion *did* catch a bug once. michael@0: */ michael@0: JS_ASSERT((current[-1] == 'l' && michael@0: current[-2] == 'l' && michael@0: current[-3] == 'u' && michael@0: current[-4] == 'n') || michael@0: (current[-1] == 'e' && michael@0: current[-2] == 'u' && michael@0: current[-3] == 'r' && michael@0: current[-4] == 't') || michael@0: (current[-1] == 'e' && michael@0: current[-2] == 's' && michael@0: current[-3] == 'l' && michael@0: current[-4] == 'a' && michael@0: current[-5] == 'f') || michael@0: current[-1] == '}' || michael@0: current[-1] == ']' || michael@0: current[-1] == '"' || michael@0: JS7_ISDEC(current[-1])); michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::advanceAfterArrayElement() michael@0: { michael@0: AssertPastValue(current); michael@0: michael@0: while (current < end && IsJSONWhitespace(*current)) michael@0: current++; michael@0: if (current >= end) { michael@0: error("end of data when ',' or ']' was expected"); michael@0: return token(Error); michael@0: } michael@0: michael@0: if (*current == ',') { michael@0: current++; michael@0: return token(Comma); michael@0: } michael@0: michael@0: if (*current == ']') { michael@0: current++; michael@0: return token(ArrayClose); michael@0: } michael@0: michael@0: error("expected ',' or ']' after array element"); michael@0: return token(Error); michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::advancePropertyName() michael@0: { michael@0: JS_ASSERT(current[-1] == ','); michael@0: michael@0: while (current < end && IsJSONWhitespace(*current)) michael@0: current++; michael@0: if (current >= end) { michael@0: error("end of data when property name was expected"); michael@0: return token(Error); michael@0: } michael@0: michael@0: if (*current == '"') michael@0: return readString(); michael@0: michael@0: error("expected double-quoted property name"); michael@0: return token(Error); michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::advancePropertyColon() michael@0: { michael@0: JS_ASSERT(current[-1] == '"'); michael@0: michael@0: while (current < end && IsJSONWhitespace(*current)) michael@0: current++; michael@0: if (current >= end) { michael@0: error("end of data after property name when ':' was expected"); michael@0: return token(Error); michael@0: } michael@0: michael@0: if (*current == ':') { michael@0: current++; michael@0: return token(Colon); michael@0: } michael@0: michael@0: error("expected ':' after property name in object"); michael@0: return token(Error); michael@0: } michael@0: michael@0: JSONParser::Token michael@0: JSONParser::advanceAfterProperty() michael@0: { michael@0: AssertPastValue(current); michael@0: michael@0: while (current < end && IsJSONWhitespace(*current)) michael@0: current++; michael@0: if (current >= end) { michael@0: error("end of data after property value in object"); michael@0: return token(Error); michael@0: } michael@0: michael@0: if (*current == ',') { michael@0: current++; michael@0: return token(Comma); michael@0: } michael@0: michael@0: if (*current == '}') { michael@0: current++; michael@0: return token(ObjectClose); michael@0: } michael@0: michael@0: error("expected ',' or '}' after property value in object"); michael@0: return token(Error); michael@0: } michael@0: michael@0: JSObject * michael@0: JSONParser::createFinishedObject(PropertyVector &properties) michael@0: { michael@0: /* michael@0: * Look for an existing cached type and shape for objects with this set of michael@0: * properties. michael@0: */ michael@0: { michael@0: JSObject *obj = cx->compartment()->types.newTypedObject(cx, properties.begin(), michael@0: properties.length()); michael@0: if (obj) michael@0: return obj; michael@0: } michael@0: michael@0: /* michael@0: * Make a new object sized for the given number of properties and fill its michael@0: * shape in manually. michael@0: */ michael@0: gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length()); michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: RootedId propid(cx); michael@0: RootedValue value(cx); michael@0: michael@0: for (size_t i = 0; i < properties.length(); i++) { michael@0: propid = properties[i].id; michael@0: value = properties[i].value; michael@0: if (!DefineNativeProperty(cx, obj, propid, value, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JSPROP_ENUMERATE)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Try to assign a new type to the object with type information for its michael@0: * properties, and update the initializer type object cache with this michael@0: * object's final shape. michael@0: */ michael@0: cx->compartment()->types.fixObjectType(cx, obj); michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: inline bool michael@0: JSONParser::finishObject(MutableHandleValue vp, PropertyVector &properties) michael@0: { michael@0: JS_ASSERT(&properties == &stack.back().properties()); michael@0: michael@0: JSObject *obj = createFinishedObject(properties); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: vp.setObject(*obj); michael@0: if (!freeProperties.append(&properties)) michael@0: return false; michael@0: stack.popBack(); michael@0: return true; michael@0: } michael@0: michael@0: inline bool michael@0: JSONParser::finishArray(MutableHandleValue vp, ElementVector &elements) michael@0: { michael@0: JS_ASSERT(&elements == &stack.back().elements()); michael@0: michael@0: JSObject *obj = NewDenseCopiedArray(cx, elements.length(), elements.begin()); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Try to assign a new type to the array according to its elements. */ michael@0: cx->compartment()->types.fixArrayType(cx, obj); michael@0: michael@0: vp.setObject(*obj); michael@0: if (!freeElements.append(&elements)) michael@0: return false; michael@0: stack.popBack(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSONParser::parse(MutableHandleValue vp) michael@0: { michael@0: RootedValue value(cx); michael@0: JS_ASSERT(stack.empty()); michael@0: michael@0: vp.setUndefined(); michael@0: michael@0: Token token; michael@0: ParserState state = JSONValue; michael@0: while (true) { michael@0: switch (state) { michael@0: case FinishObjectMember: { michael@0: PropertyVector &properties = stack.back().properties(); michael@0: properties.back().value = value; michael@0: michael@0: token = advanceAfterProperty(); michael@0: if (token == ObjectClose) { michael@0: if (!finishObject(&value, properties)) michael@0: return false; michael@0: break; michael@0: } michael@0: if (token != Comma) { michael@0: if (token == OOM) michael@0: return false; michael@0: if (token != Error) michael@0: error("expected ',' or '}' after property-value pair in object literal"); michael@0: return errorReturn(); michael@0: } michael@0: token = advancePropertyName(); michael@0: /* FALL THROUGH */ michael@0: } michael@0: michael@0: JSONMember: michael@0: if (token == String) { michael@0: jsid id = AtomToId(atomValue()); michael@0: PropertyVector &properties = stack.back().properties(); michael@0: if (!properties.append(IdValuePair(id))) michael@0: return false; michael@0: token = advancePropertyColon(); michael@0: if (token != Colon) { michael@0: JS_ASSERT(token == Error); michael@0: return errorReturn(); michael@0: } michael@0: goto JSONValue; michael@0: } michael@0: if (token == OOM) michael@0: return false; michael@0: if (token != Error) michael@0: error("property names must be double-quoted strings"); michael@0: return errorReturn(); michael@0: michael@0: case FinishArrayElement: { michael@0: ElementVector &elements = stack.back().elements(); michael@0: if (!elements.append(value.get())) michael@0: return false; michael@0: token = advanceAfterArrayElement(); michael@0: if (token == Comma) michael@0: goto JSONValue; michael@0: if (token == ArrayClose) { michael@0: if (!finishArray(&value, elements)) michael@0: return false; michael@0: break; michael@0: } michael@0: JS_ASSERT(token == Error); michael@0: return errorReturn(); michael@0: } michael@0: michael@0: JSONValue: michael@0: case JSONValue: michael@0: token = advance(); michael@0: JSONValueSwitch: michael@0: switch (token) { michael@0: case String: michael@0: value = stringValue(); michael@0: break; michael@0: case Number: michael@0: value = numberValue(); michael@0: break; michael@0: case True: michael@0: value = BooleanValue(true); michael@0: break; michael@0: case False: michael@0: value = BooleanValue(false); michael@0: break; michael@0: case Null: michael@0: value = NullValue(); michael@0: break; michael@0: michael@0: case ArrayOpen: { michael@0: ElementVector *elements; michael@0: if (!freeElements.empty()) { michael@0: elements = freeElements.popCopy(); michael@0: elements->clear(); michael@0: } else { michael@0: elements = cx->new_(cx); michael@0: if (!elements) michael@0: return false; michael@0: } michael@0: if (!stack.append(elements)) michael@0: return false; michael@0: michael@0: token = advance(); michael@0: if (token == ArrayClose) { michael@0: if (!finishArray(&value, *elements)) michael@0: return false; michael@0: break; michael@0: } michael@0: goto JSONValueSwitch; michael@0: } michael@0: michael@0: case ObjectOpen: { michael@0: PropertyVector *properties; michael@0: if (!freeProperties.empty()) { michael@0: properties = freeProperties.popCopy(); michael@0: properties->clear(); michael@0: } else { michael@0: properties = cx->new_(cx); michael@0: if (!properties) michael@0: return false; michael@0: } michael@0: if (!stack.append(properties)) michael@0: return false; michael@0: michael@0: token = advanceAfterObjectOpen(); michael@0: if (token == ObjectClose) { michael@0: if (!finishObject(&value, *properties)) michael@0: return false; michael@0: break; michael@0: } michael@0: goto JSONMember; michael@0: } michael@0: michael@0: case ArrayClose: michael@0: case ObjectClose: michael@0: case Colon: michael@0: case Comma: michael@0: // Move the current pointer backwards so that the position michael@0: // reported in the error message is correct. michael@0: --current; michael@0: error("unexpected character"); michael@0: return errorReturn(); michael@0: michael@0: case OOM: michael@0: return false; michael@0: michael@0: case Error: michael@0: return errorReturn(); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if (stack.empty()) michael@0: break; michael@0: state = stack.back().state; michael@0: } michael@0: michael@0: for (; current < end; current++) { michael@0: if (!IsJSONWhitespace(*current)) { michael@0: error("unexpected non-whitespace character after JSON data"); michael@0: return errorReturn(); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(end == current); michael@0: JS_ASSERT(stack.empty()); michael@0: michael@0: vp.set(value); michael@0: return true; michael@0: }