js/src/jsonparser.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/jsonparser.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,817 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "jsonparser.h"
    1.11 +
    1.12 +#include "mozilla/RangedPtr.h"
    1.13 +
    1.14 +#include <ctype.h>
    1.15 +
    1.16 +#include "jsarray.h"
    1.17 +#include "jscompartment.h"
    1.18 +#include "jsnum.h"
    1.19 +#include "jsprf.h"
    1.20 +
    1.21 +#include "vm/StringBuffer.h"
    1.22 +
    1.23 +#include "jsobjinlines.h"
    1.24 +
    1.25 +using namespace js;
    1.26 +
    1.27 +using mozilla::RangedPtr;
    1.28 +
    1.29 +JSONParser::~JSONParser()
    1.30 +{
    1.31 +    for (size_t i = 0; i < stack.length(); i++) {
    1.32 +        if (stack[i].state == FinishArrayElement)
    1.33 +            js_delete(&stack[i].elements());
    1.34 +        else
    1.35 +            js_delete(&stack[i].properties());
    1.36 +    }
    1.37 +
    1.38 +    for (size_t i = 0; i < freeElements.length(); i++)
    1.39 +        js_delete(freeElements[i]);
    1.40 +
    1.41 +    for (size_t i = 0; i < freeProperties.length(); i++)
    1.42 +        js_delete(freeProperties[i]);
    1.43 +}
    1.44 +
    1.45 +void
    1.46 +JSONParser::trace(JSTracer *trc)
    1.47 +{
    1.48 +    for (size_t i = 0; i < stack.length(); i++) {
    1.49 +        if (stack[i].state == FinishArrayElement) {
    1.50 +            ElementVector &elements = stack[i].elements();
    1.51 +            for (size_t j = 0; j < elements.length(); j++)
    1.52 +                gc::MarkValueRoot(trc, &elements[j], "JSONParser element");
    1.53 +        } else {
    1.54 +            PropertyVector &properties = stack[i].properties();
    1.55 +            for (size_t j = 0; j < properties.length(); j++) {
    1.56 +                gc::MarkValueRoot(trc, &properties[j].value, "JSONParser property value");
    1.57 +                gc::MarkIdRoot(trc, &properties[j].id, "JSONParser property id");
    1.58 +            }
    1.59 +        }
    1.60 +    }
    1.61 +}
    1.62 +
    1.63 +void
    1.64 +JSONParser::getTextPosition(uint32_t *column, uint32_t *line)
    1.65 +{
    1.66 +    ConstTwoByteChars ptr = begin;
    1.67 +    uint32_t col = 1;
    1.68 +    uint32_t row = 1;
    1.69 +    for (; ptr < current; ptr++) {
    1.70 +        if (*ptr == '\n' || *ptr == '\r') {
    1.71 +            ++row;
    1.72 +            col = 1;
    1.73 +            // \r\n is treated as a single newline.
    1.74 +            if (ptr + 1 < current && *ptr == '\r' && *(ptr + 1) == '\n')
    1.75 +                ++ptr;
    1.76 +        } else {
    1.77 +            ++col;
    1.78 +        }
    1.79 +    }
    1.80 +    *column = col;
    1.81 +    *line = row;
    1.82 +}
    1.83 +
    1.84 +void
    1.85 +JSONParser::error(const char *msg)
    1.86 +{
    1.87 +    if (errorHandling == RaiseError) {
    1.88 +        uint32_t column = 1, line = 1;
    1.89 +        getTextPosition(&column, &line);
    1.90 +
    1.91 +        const size_t MaxWidth = sizeof("4294967295");
    1.92 +        char columnNumber[MaxWidth];
    1.93 +        JS_snprintf(columnNumber, sizeof columnNumber, "%lu", column);
    1.94 +        char lineNumber[MaxWidth];
    1.95 +        JS_snprintf(lineNumber, sizeof lineNumber, "%lu", line);
    1.96 +
    1.97 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_JSON_BAD_PARSE,
    1.98 +                             msg, lineNumber, columnNumber);
    1.99 +    }
   1.100 +}
   1.101 +
   1.102 +bool
   1.103 +JSONParser::errorReturn()
   1.104 +{
   1.105 +    return errorHandling == NoError;
   1.106 +}
   1.107 +
   1.108 +template<JSONParser::StringType ST>
   1.109 +JSONParser::Token
   1.110 +JSONParser::readString()
   1.111 +{
   1.112 +    JS_ASSERT(current < end);
   1.113 +    JS_ASSERT(*current == '"');
   1.114 +
   1.115 +    /*
   1.116 +     * JSONString:
   1.117 +     *   /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
   1.118 +     */
   1.119 +
   1.120 +    if (++current == end) {
   1.121 +        error("unterminated string literal");
   1.122 +        return token(Error);
   1.123 +    }
   1.124 +
   1.125 +    /*
   1.126 +     * Optimization: if the source contains no escaped characters, create the
   1.127 +     * string directly from the source text.
   1.128 +     */
   1.129 +    RangedPtr<const jschar> start = current;
   1.130 +    for (; current < end; current++) {
   1.131 +        if (*current == '"') {
   1.132 +            size_t length = current - start;
   1.133 +            current++;
   1.134 +            JSFlatString *str = (ST == JSONParser::PropertyName)
   1.135 +                                ? AtomizeChars(cx, start.get(), length)
   1.136 +                                : js_NewStringCopyN<CanGC>(cx, start.get(), length);
   1.137 +            if (!str)
   1.138 +                return token(OOM);
   1.139 +            return stringToken(str);
   1.140 +        }
   1.141 +
   1.142 +        if (*current == '\\')
   1.143 +            break;
   1.144 +
   1.145 +        if (*current <= 0x001F) {
   1.146 +            error("bad control character in string literal");
   1.147 +            return token(Error);
   1.148 +        }
   1.149 +    }
   1.150 +
   1.151 +    /*
   1.152 +     * Slow case: string contains escaped characters.  Copy a maximal sequence
   1.153 +     * of unescaped characters into a temporary buffer, then an escaped
   1.154 +     * character, and repeat until the entire string is consumed.
   1.155 +     */
   1.156 +    StringBuffer buffer(cx);
   1.157 +    do {
   1.158 +        if (start < current && !buffer.append(start.get(), current.get()))
   1.159 +            return token(OOM);
   1.160 +
   1.161 +        if (current >= end)
   1.162 +            break;
   1.163 +
   1.164 +        jschar c = *current++;
   1.165 +        if (c == '"') {
   1.166 +            JSFlatString *str = (ST == JSONParser::PropertyName)
   1.167 +                                ? buffer.finishAtom()
   1.168 +                                : buffer.finishString();
   1.169 +            if (!str)
   1.170 +                return token(OOM);
   1.171 +            return stringToken(str);
   1.172 +        }
   1.173 +
   1.174 +        if (c != '\\') {
   1.175 +            --current;
   1.176 +            error("bad character in string literal");
   1.177 +            return token(Error);
   1.178 +        }
   1.179 +
   1.180 +        if (current >= end)
   1.181 +            break;
   1.182 +
   1.183 +        switch (*current++) {
   1.184 +          case '"':  c = '"';  break;
   1.185 +          case '/':  c = '/';  break;
   1.186 +          case '\\': c = '\\'; break;
   1.187 +          case 'b':  c = '\b'; break;
   1.188 +          case 'f':  c = '\f'; break;
   1.189 +          case 'n':  c = '\n'; break;
   1.190 +          case 'r':  c = '\r'; break;
   1.191 +          case 't':  c = '\t'; break;
   1.192 +
   1.193 +          case 'u':
   1.194 +            if (end - current < 4 ||
   1.195 +                !(JS7_ISHEX(current[0]) &&
   1.196 +                  JS7_ISHEX(current[1]) &&
   1.197 +                  JS7_ISHEX(current[2]) &&
   1.198 +                  JS7_ISHEX(current[3])))
   1.199 +            {
   1.200 +                // Point to the first non-hexadecimal character (which may be
   1.201 +                // missing).
   1.202 +                if (current == end || !JS7_ISHEX(current[0]))
   1.203 +                    ; // already at correct location
   1.204 +                else if (current + 1 == end || !JS7_ISHEX(current[1]))
   1.205 +                    current += 1;
   1.206 +                else if (current + 2 == end || !JS7_ISHEX(current[2]))
   1.207 +                    current += 2;
   1.208 +                else if (current + 3 == end || !JS7_ISHEX(current[3]))
   1.209 +                    current += 3;
   1.210 +                else
   1.211 +                    MOZ_ASSUME_UNREACHABLE("logic error determining first erroneous character");
   1.212 +
   1.213 +                error("bad Unicode escape");
   1.214 +                return token(Error);
   1.215 +            }
   1.216 +            c = (JS7_UNHEX(current[0]) << 12)
   1.217 +              | (JS7_UNHEX(current[1]) << 8)
   1.218 +              | (JS7_UNHEX(current[2]) << 4)
   1.219 +              | (JS7_UNHEX(current[3]));
   1.220 +            current += 4;
   1.221 +            break;
   1.222 +
   1.223 +          default:
   1.224 +            current--;
   1.225 +            error("bad escaped character");
   1.226 +            return token(Error);
   1.227 +        }
   1.228 +        if (!buffer.append(c))
   1.229 +            return token(OOM);
   1.230 +
   1.231 +        start = current;
   1.232 +        for (; current < end; current++) {
   1.233 +            if (*current == '"' || *current == '\\' || *current <= 0x001F)
   1.234 +                break;
   1.235 +        }
   1.236 +    } while (current < end);
   1.237 +
   1.238 +    error("unterminated string");
   1.239 +    return token(Error);
   1.240 +}
   1.241 +
   1.242 +JSONParser::Token
   1.243 +JSONParser::readNumber()
   1.244 +{
   1.245 +    JS_ASSERT(current < end);
   1.246 +    JS_ASSERT(JS7_ISDEC(*current) || *current == '-');
   1.247 +
   1.248 +    /*
   1.249 +     * JSONNumber:
   1.250 +     *   /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
   1.251 +     */
   1.252 +
   1.253 +    bool negative = *current == '-';
   1.254 +
   1.255 +    /* -? */
   1.256 +    if (negative && ++current == end) {
   1.257 +        error("no number after minus sign");
   1.258 +        return token(Error);
   1.259 +    }
   1.260 +
   1.261 +    const RangedPtr<const jschar> digitStart = current;
   1.262 +
   1.263 +    /* 0|[1-9][0-9]+ */
   1.264 +    if (!JS7_ISDEC(*current)) {
   1.265 +        error("unexpected non-digit");
   1.266 +        return token(Error);
   1.267 +    }
   1.268 +    if (*current++ != '0') {
   1.269 +        for (; current < end; current++) {
   1.270 +            if (!JS7_ISDEC(*current))
   1.271 +                break;
   1.272 +        }
   1.273 +    }
   1.274 +
   1.275 +    /* Fast path: no fractional or exponent part. */
   1.276 +    if (current == end || (*current != '.' && *current != 'e' && *current != 'E')) {
   1.277 +        TwoByteChars chars(digitStart.get(), current - digitStart);
   1.278 +        if (chars.length() < strlen("9007199254740992")) {
   1.279 +            // If the decimal number is shorter than the length of 2**53, (the
   1.280 +            // largest number a double can represent with integral precision),
   1.281 +            // parse it using a decimal-only parser.  This comparison is
   1.282 +            // conservative but faster than a fully-precise check.
   1.283 +            double d = ParseDecimalNumber(chars);
   1.284 +            return numberToken(negative ? -d : d);
   1.285 +        }
   1.286 +
   1.287 +        double d;
   1.288 +        const jschar *dummy;
   1.289 +        if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
   1.290 +            return token(OOM);
   1.291 +        JS_ASSERT(current == dummy);
   1.292 +        return numberToken(negative ? -d : d);
   1.293 +    }
   1.294 +
   1.295 +    /* (\.[0-9]+)? */
   1.296 +    if (current < end && *current == '.') {
   1.297 +        if (++current == end) {
   1.298 +            error("missing digits after decimal point");
   1.299 +            return token(Error);
   1.300 +        }
   1.301 +        if (!JS7_ISDEC(*current)) {
   1.302 +            error("unterminated fractional number");
   1.303 +            return token(Error);
   1.304 +        }
   1.305 +        while (++current < end) {
   1.306 +            if (!JS7_ISDEC(*current))
   1.307 +                break;
   1.308 +        }
   1.309 +    }
   1.310 +
   1.311 +    /* ([eE][\+\-]?[0-9]+)? */
   1.312 +    if (current < end && (*current == 'e' || *current == 'E')) {
   1.313 +        if (++current == end) {
   1.314 +            error("missing digits after exponent indicator");
   1.315 +            return token(Error);
   1.316 +        }
   1.317 +        if (*current == '+' || *current == '-') {
   1.318 +            if (++current == end) {
   1.319 +                error("missing digits after exponent sign");
   1.320 +                return token(Error);
   1.321 +            }
   1.322 +        }
   1.323 +        if (!JS7_ISDEC(*current)) {
   1.324 +            error("exponent part is missing a number");
   1.325 +            return token(Error);
   1.326 +        }
   1.327 +        while (++current < end) {
   1.328 +            if (!JS7_ISDEC(*current))
   1.329 +                break;
   1.330 +        }
   1.331 +    }
   1.332 +
   1.333 +    double d;
   1.334 +    const jschar *finish;
   1.335 +    if (!js_strtod(cx, digitStart.get(), current.get(), &finish, &d))
   1.336 +        return token(OOM);
   1.337 +    JS_ASSERT(current == finish);
   1.338 +    return numberToken(negative ? -d : d);
   1.339 +}
   1.340 +
   1.341 +static inline bool
   1.342 +IsJSONWhitespace(jschar c)
   1.343 +{
   1.344 +    return c == '\t' || c == '\r' || c == '\n' || c == ' ';
   1.345 +}
   1.346 +
   1.347 +JSONParser::Token
   1.348 +JSONParser::advance()
   1.349 +{
   1.350 +    while (current < end && IsJSONWhitespace(*current))
   1.351 +        current++;
   1.352 +    if (current >= end) {
   1.353 +        error("unexpected end of data");
   1.354 +        return token(Error);
   1.355 +    }
   1.356 +
   1.357 +    switch (*current) {
   1.358 +      case '"':
   1.359 +        return readString<LiteralValue>();
   1.360 +
   1.361 +      case '-':
   1.362 +      case '0':
   1.363 +      case '1':
   1.364 +      case '2':
   1.365 +      case '3':
   1.366 +      case '4':
   1.367 +      case '5':
   1.368 +      case '6':
   1.369 +      case '7':
   1.370 +      case '8':
   1.371 +      case '9':
   1.372 +        return readNumber();
   1.373 +
   1.374 +      case 't':
   1.375 +        if (end - current < 4 || current[1] != 'r' || current[2] != 'u' || current[3] != 'e') {
   1.376 +            error("unexpected keyword");
   1.377 +            return token(Error);
   1.378 +        }
   1.379 +        current += 4;
   1.380 +        return token(True);
   1.381 +
   1.382 +      case 'f':
   1.383 +        if (end - current < 5 ||
   1.384 +            current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e')
   1.385 +        {
   1.386 +            error("unexpected keyword");
   1.387 +            return token(Error);
   1.388 +        }
   1.389 +        current += 5;
   1.390 +        return token(False);
   1.391 +
   1.392 +      case 'n':
   1.393 +        if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') {
   1.394 +            error("unexpected keyword");
   1.395 +            return token(Error);
   1.396 +        }
   1.397 +        current += 4;
   1.398 +        return token(Null);
   1.399 +
   1.400 +      case '[':
   1.401 +        current++;
   1.402 +        return token(ArrayOpen);
   1.403 +      case ']':
   1.404 +        current++;
   1.405 +        return token(ArrayClose);
   1.406 +
   1.407 +      case '{':
   1.408 +        current++;
   1.409 +        return token(ObjectOpen);
   1.410 +      case '}':
   1.411 +        current++;
   1.412 +        return token(ObjectClose);
   1.413 +
   1.414 +      case ',':
   1.415 +        current++;
   1.416 +        return token(Comma);
   1.417 +
   1.418 +      case ':':
   1.419 +        current++;
   1.420 +        return token(Colon);
   1.421 +
   1.422 +      default:
   1.423 +        error("unexpected character");
   1.424 +        return token(Error);
   1.425 +    }
   1.426 +}
   1.427 +
   1.428 +JSONParser::Token
   1.429 +JSONParser::advanceAfterObjectOpen()
   1.430 +{
   1.431 +    JS_ASSERT(current[-1] == '{');
   1.432 +
   1.433 +    while (current < end && IsJSONWhitespace(*current))
   1.434 +        current++;
   1.435 +    if (current >= end) {
   1.436 +        error("end of data while reading object contents");
   1.437 +        return token(Error);
   1.438 +    }
   1.439 +
   1.440 +    if (*current == '"')
   1.441 +        return readString<PropertyName>();
   1.442 +
   1.443 +    if (*current == '}') {
   1.444 +        current++;
   1.445 +        return token(ObjectClose);
   1.446 +    }
   1.447 +
   1.448 +    error("expected property name or '}'");
   1.449 +    return token(Error);
   1.450 +}
   1.451 +
   1.452 +static inline void
   1.453 +AssertPastValue(const RangedPtr<const jschar> current)
   1.454 +{
   1.455 +    /*
   1.456 +     * We're past an arbitrary JSON value, so the previous character is
   1.457 +     * *somewhat* constrained, even if this assertion is pretty broad.  Don't
   1.458 +     * knock it till you tried it: this assertion *did* catch a bug once.
   1.459 +     */
   1.460 +    JS_ASSERT((current[-1] == 'l' &&
   1.461 +               current[-2] == 'l' &&
   1.462 +               current[-3] == 'u' &&
   1.463 +               current[-4] == 'n') ||
   1.464 +              (current[-1] == 'e' &&
   1.465 +               current[-2] == 'u' &&
   1.466 +               current[-3] == 'r' &&
   1.467 +               current[-4] == 't') ||
   1.468 +              (current[-1] == 'e' &&
   1.469 +               current[-2] == 's' &&
   1.470 +               current[-3] == 'l' &&
   1.471 +               current[-4] == 'a' &&
   1.472 +               current[-5] == 'f') ||
   1.473 +              current[-1] == '}' ||
   1.474 +              current[-1] == ']' ||
   1.475 +              current[-1] == '"' ||
   1.476 +              JS7_ISDEC(current[-1]));
   1.477 +}
   1.478 +
   1.479 +JSONParser::Token
   1.480 +JSONParser::advanceAfterArrayElement()
   1.481 +{
   1.482 +    AssertPastValue(current);
   1.483 +
   1.484 +    while (current < end && IsJSONWhitespace(*current))
   1.485 +        current++;
   1.486 +    if (current >= end) {
   1.487 +        error("end of data when ',' or ']' was expected");
   1.488 +        return token(Error);
   1.489 +    }
   1.490 +
   1.491 +    if (*current == ',') {
   1.492 +        current++;
   1.493 +        return token(Comma);
   1.494 +    }
   1.495 +
   1.496 +    if (*current == ']') {
   1.497 +        current++;
   1.498 +        return token(ArrayClose);
   1.499 +    }
   1.500 +
   1.501 +    error("expected ',' or ']' after array element");
   1.502 +    return token(Error);
   1.503 +}
   1.504 +
   1.505 +JSONParser::Token
   1.506 +JSONParser::advancePropertyName()
   1.507 +{
   1.508 +    JS_ASSERT(current[-1] == ',');
   1.509 +
   1.510 +    while (current < end && IsJSONWhitespace(*current))
   1.511 +        current++;
   1.512 +    if (current >= end) {
   1.513 +        error("end of data when property name was expected");
   1.514 +        return token(Error);
   1.515 +    }
   1.516 +
   1.517 +    if (*current == '"')
   1.518 +        return readString<PropertyName>();
   1.519 +
   1.520 +    error("expected double-quoted property name");
   1.521 +    return token(Error);
   1.522 +}
   1.523 +
   1.524 +JSONParser::Token
   1.525 +JSONParser::advancePropertyColon()
   1.526 +{
   1.527 +    JS_ASSERT(current[-1] == '"');
   1.528 +
   1.529 +    while (current < end && IsJSONWhitespace(*current))
   1.530 +        current++;
   1.531 +    if (current >= end) {
   1.532 +        error("end of data after property name when ':' was expected");
   1.533 +        return token(Error);
   1.534 +    }
   1.535 +
   1.536 +    if (*current == ':') {
   1.537 +        current++;
   1.538 +        return token(Colon);
   1.539 +    }
   1.540 +
   1.541 +    error("expected ':' after property name in object");
   1.542 +    return token(Error);
   1.543 +}
   1.544 +
   1.545 +JSONParser::Token
   1.546 +JSONParser::advanceAfterProperty()
   1.547 +{
   1.548 +    AssertPastValue(current);
   1.549 +
   1.550 +    while (current < end && IsJSONWhitespace(*current))
   1.551 +        current++;
   1.552 +    if (current >= end) {
   1.553 +        error("end of data after property value in object");
   1.554 +        return token(Error);
   1.555 +    }
   1.556 +
   1.557 +    if (*current == ',') {
   1.558 +        current++;
   1.559 +        return token(Comma);
   1.560 +    }
   1.561 +
   1.562 +    if (*current == '}') {
   1.563 +        current++;
   1.564 +        return token(ObjectClose);
   1.565 +    }
   1.566 +
   1.567 +    error("expected ',' or '}' after property value in object");
   1.568 +    return token(Error);
   1.569 +}
   1.570 +
   1.571 +JSObject *
   1.572 +JSONParser::createFinishedObject(PropertyVector &properties)
   1.573 +{
   1.574 +    /*
   1.575 +     * Look for an existing cached type and shape for objects with this set of
   1.576 +     * properties.
   1.577 +     */
   1.578 +    {
   1.579 +        JSObject *obj = cx->compartment()->types.newTypedObject(cx, properties.begin(),
   1.580 +                                                                properties.length());
   1.581 +        if (obj)
   1.582 +            return obj;
   1.583 +    }
   1.584 +
   1.585 +    /*
   1.586 +     * Make a new object sized for the given number of properties and fill its
   1.587 +     * shape in manually.
   1.588 +     */
   1.589 +    gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length());
   1.590 +    RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind));
   1.591 +    if (!obj)
   1.592 +        return nullptr;
   1.593 +
   1.594 +    RootedId propid(cx);
   1.595 +    RootedValue value(cx);
   1.596 +
   1.597 +    for (size_t i = 0; i < properties.length(); i++) {
   1.598 +        propid = properties[i].id;
   1.599 +        value = properties[i].value;
   1.600 +        if (!DefineNativeProperty(cx, obj, propid, value, JS_PropertyStub, JS_StrictPropertyStub,
   1.601 +                                  JSPROP_ENUMERATE)) {
   1.602 +            return nullptr;
   1.603 +        }
   1.604 +    }
   1.605 +
   1.606 +    /*
   1.607 +     * Try to assign a new type to the object with type information for its
   1.608 +     * properties, and update the initializer type object cache with this
   1.609 +     * object's final shape.
   1.610 +     */
   1.611 +    cx->compartment()->types.fixObjectType(cx, obj);
   1.612 +
   1.613 +    return obj;
   1.614 +}
   1.615 +
   1.616 +inline bool
   1.617 +JSONParser::finishObject(MutableHandleValue vp, PropertyVector &properties)
   1.618 +{
   1.619 +    JS_ASSERT(&properties == &stack.back().properties());
   1.620 +
   1.621 +    JSObject *obj = createFinishedObject(properties);
   1.622 +    if (!obj)
   1.623 +        return false;
   1.624 +
   1.625 +    vp.setObject(*obj);
   1.626 +    if (!freeProperties.append(&properties))
   1.627 +        return false;
   1.628 +    stack.popBack();
   1.629 +    return true;
   1.630 +}
   1.631 +
   1.632 +inline bool
   1.633 +JSONParser::finishArray(MutableHandleValue vp, ElementVector &elements)
   1.634 +{
   1.635 +    JS_ASSERT(&elements == &stack.back().elements());
   1.636 +
   1.637 +    JSObject *obj = NewDenseCopiedArray(cx, elements.length(), elements.begin());
   1.638 +    if (!obj)
   1.639 +        return false;
   1.640 +
   1.641 +    /* Try to assign a new type to the array according to its elements. */
   1.642 +    cx->compartment()->types.fixArrayType(cx, obj);
   1.643 +
   1.644 +    vp.setObject(*obj);
   1.645 +    if (!freeElements.append(&elements))
   1.646 +        return false;
   1.647 +    stack.popBack();
   1.648 +    return true;
   1.649 +}
   1.650 +
   1.651 +bool
   1.652 +JSONParser::parse(MutableHandleValue vp)
   1.653 +{
   1.654 +    RootedValue value(cx);
   1.655 +    JS_ASSERT(stack.empty());
   1.656 +
   1.657 +    vp.setUndefined();
   1.658 +
   1.659 +    Token token;
   1.660 +    ParserState state = JSONValue;
   1.661 +    while (true) {
   1.662 +        switch (state) {
   1.663 +          case FinishObjectMember: {
   1.664 +            PropertyVector &properties = stack.back().properties();
   1.665 +            properties.back().value = value;
   1.666 +
   1.667 +            token = advanceAfterProperty();
   1.668 +            if (token == ObjectClose) {
   1.669 +                if (!finishObject(&value, properties))
   1.670 +                    return false;
   1.671 +                break;
   1.672 +            }
   1.673 +            if (token != Comma) {
   1.674 +                if (token == OOM)
   1.675 +                    return false;
   1.676 +                if (token != Error)
   1.677 +                    error("expected ',' or '}' after property-value pair in object literal");
   1.678 +                return errorReturn();
   1.679 +            }
   1.680 +            token = advancePropertyName();
   1.681 +            /* FALL THROUGH */
   1.682 +          }
   1.683 +
   1.684 +          JSONMember:
   1.685 +            if (token == String) {
   1.686 +                jsid id = AtomToId(atomValue());
   1.687 +                PropertyVector &properties = stack.back().properties();
   1.688 +                if (!properties.append(IdValuePair(id)))
   1.689 +                    return false;
   1.690 +                token = advancePropertyColon();
   1.691 +                if (token != Colon) {
   1.692 +                    JS_ASSERT(token == Error);
   1.693 +                    return errorReturn();
   1.694 +                }
   1.695 +                goto JSONValue;
   1.696 +            }
   1.697 +            if (token == OOM)
   1.698 +                return false;
   1.699 +            if (token != Error)
   1.700 +                error("property names must be double-quoted strings");
   1.701 +            return errorReturn();
   1.702 +
   1.703 +          case FinishArrayElement: {
   1.704 +            ElementVector &elements = stack.back().elements();
   1.705 +            if (!elements.append(value.get()))
   1.706 +                return false;
   1.707 +            token = advanceAfterArrayElement();
   1.708 +            if (token == Comma)
   1.709 +                goto JSONValue;
   1.710 +            if (token == ArrayClose) {
   1.711 +                if (!finishArray(&value, elements))
   1.712 +                    return false;
   1.713 +                break;
   1.714 +            }
   1.715 +            JS_ASSERT(token == Error);
   1.716 +            return errorReturn();
   1.717 +          }
   1.718 +
   1.719 +          JSONValue:
   1.720 +          case JSONValue:
   1.721 +            token = advance();
   1.722 +          JSONValueSwitch:
   1.723 +            switch (token) {
   1.724 +              case String:
   1.725 +                value = stringValue();
   1.726 +                break;
   1.727 +              case Number:
   1.728 +                value = numberValue();
   1.729 +                break;
   1.730 +              case True:
   1.731 +                value = BooleanValue(true);
   1.732 +                break;
   1.733 +              case False:
   1.734 +                value = BooleanValue(false);
   1.735 +                break;
   1.736 +              case Null:
   1.737 +                value = NullValue();
   1.738 +                break;
   1.739 +
   1.740 +              case ArrayOpen: {
   1.741 +                ElementVector *elements;
   1.742 +                if (!freeElements.empty()) {
   1.743 +                    elements = freeElements.popCopy();
   1.744 +                    elements->clear();
   1.745 +                } else {
   1.746 +                    elements = cx->new_<ElementVector>(cx);
   1.747 +                    if (!elements)
   1.748 +                        return false;
   1.749 +                }
   1.750 +                if (!stack.append(elements))
   1.751 +                    return false;
   1.752 +
   1.753 +                token = advance();
   1.754 +                if (token == ArrayClose) {
   1.755 +                    if (!finishArray(&value, *elements))
   1.756 +                        return false;
   1.757 +                    break;
   1.758 +                }
   1.759 +                goto JSONValueSwitch;
   1.760 +              }
   1.761 +
   1.762 +              case ObjectOpen: {
   1.763 +                PropertyVector *properties;
   1.764 +                if (!freeProperties.empty()) {
   1.765 +                    properties = freeProperties.popCopy();
   1.766 +                    properties->clear();
   1.767 +                } else {
   1.768 +                    properties = cx->new_<PropertyVector>(cx);
   1.769 +                    if (!properties)
   1.770 +                        return false;
   1.771 +                }
   1.772 +                if (!stack.append(properties))
   1.773 +                    return false;
   1.774 +
   1.775 +                token = advanceAfterObjectOpen();
   1.776 +                if (token == ObjectClose) {
   1.777 +                    if (!finishObject(&value, *properties))
   1.778 +                        return false;
   1.779 +                    break;
   1.780 +                }
   1.781 +                goto JSONMember;
   1.782 +              }
   1.783 +
   1.784 +              case ArrayClose:
   1.785 +              case ObjectClose:
   1.786 +              case Colon:
   1.787 +              case Comma:
   1.788 +                // Move the current pointer backwards so that the position
   1.789 +                // reported in the error message is correct.
   1.790 +                --current;
   1.791 +                error("unexpected character");
   1.792 +                return errorReturn();
   1.793 +
   1.794 +              case OOM:
   1.795 +                return false;
   1.796 +
   1.797 +              case Error:
   1.798 +                return errorReturn();
   1.799 +            }
   1.800 +            break;
   1.801 +        }
   1.802 +
   1.803 +        if (stack.empty())
   1.804 +            break;
   1.805 +        state = stack.back().state;
   1.806 +    }
   1.807 +
   1.808 +    for (; current < end; current++) {
   1.809 +        if (!IsJSONWhitespace(*current)) {
   1.810 +            error("unexpected non-whitespace character after JSON data");
   1.811 +            return errorReturn();
   1.812 +        }
   1.813 +    }
   1.814 +
   1.815 +    JS_ASSERT(end == current);
   1.816 +    JS_ASSERT(stack.empty());
   1.817 +
   1.818 +    vp.set(value);
   1.819 +    return true;
   1.820 +}

mercurial