PageRenderTime 62ms CodeModel.GetById 12ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 0ms

/js/src/jsonparser.cpp

http://github.com/zpao/v8monkey
C++ | 695 lines | 598 code | 31 blank | 66 comment | 100 complexity | d103a7b71a86b6ace93972a05f9e8ef5 MD5 | raw file
  1/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2 * vim: set ts=8 sw=4 et tw=99:
  3 *
  4 * ***** BEGIN LICENSE BLOCK *****
  5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  6 *
  7 * The contents of this file are subject to the Mozilla Public License Version
  8 * 1.1 (the "License"); you may not use this file except in compliance with
  9 * the License. You may obtain a copy of the License at
 10 * http://www.mozilla.org/MPL/
 11 *
 12 * Software distributed under the License is distributed on an "AS IS" basis,
 13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 14 * for the specific language governing rights and limitations under the
 15 * License.
 16 *
 17 * The Original Code is SpiderMonkey JSON.
 18 *
 19 * The Initial Developer of the Original Code is
 20 * the Mozilla Foundation.
 21 * Portions created by the Initial Developer are Copyright (C) 2011
 22 * the Initial Developer. All Rights Reserved.
 23 *
 24 * Contributor(s):
 25 *   Jeff Walden <jwalden+code@mit.edu> (original author)
 26 *
 27 * Alternatively, the contents of this file may be used under the terms of
 28 * either of the GNU General Public License Version 2 or later (the "GPL"),
 29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 30 * in which case the provisions of the GPL or the LGPL are applicable instead
 31 * of those above. If you wish to allow use of your version of this file only
 32 * under the terms of either the GPL or the LGPL, and not to allow others to
 33 * use your version of this file under the terms of the MPL, indicate your
 34 * decision by deleting the provisions above and replace them with the notice
 35 * and other provisions required by the GPL or the LGPL. If you do not delete
 36 * the provisions above, a recipient may use your version of this file under
 37 * the terms of any one of the MPL, the GPL or the LGPL.
 38 *
 39 * ***** END LICENSE BLOCK ***** */
 40
 41#include "jsarray.h"
 42#include "jsnum.h"
 43#include "jsonparser.h"
 44
 45#include "jsobjinlines.h"
 46#include "jsstrinlines.h"
 47
 48using namespace js;
 49
 50void
 51JSONParser::error(const char *msg)
 52{
 53    if (errorHandling == RaiseError)
 54        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE, msg);
 55}
 56
 57bool
 58JSONParser::errorReturn()
 59{
 60    return errorHandling == NoError;
 61}
 62
 63template<JSONParser::StringType ST>
 64JSONParser::Token
 65JSONParser::readString()
 66{
 67    JS_ASSERT(current < end);
 68    JS_ASSERT(*current == '"');
 69
 70    /*
 71     * JSONString:
 72     *   /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
 73     */
 74
 75    if (++current == end) {
 76        error("unterminated string literal");
 77        return token(Error);
 78    }
 79
 80    /*
 81     * Optimization: if the source contains no escaped characters, create the
 82     * string directly from the source text.
 83     */
 84    RangedPtr<const jschar> start = current;
 85    for (; current < end; current++) {
 86        if (*current == '"') {
 87            size_t length = current - start;
 88            current++;
 89            JSFlatString *str = (ST == JSONParser::PropertyName)
 90                                ? js_AtomizeChars(cx, start.get(), length)
 91                                : js_NewStringCopyN(cx, start.get(), length);
 92            if (!str)
 93                return token(OOM);
 94            return stringToken(str);
 95        }
 96
 97        if (*current == '\\')
 98            break;
 99
100        if (*current <= 0x001F) {
101            error("bad control character in string literal");
102            return token(Error);
103        }
104    }
105
106    /*
107     * Slow case: string contains escaped characters.  Copy a maximal sequence
108     * of unescaped characters into a temporary buffer, then an escaped
109     * character, and repeat until the entire string is consumed.
110     */
111    StringBuffer buffer(cx);
112    do {
113        if (start < current && !buffer.append(start.get(), current.get()))
114            return token(OOM);
115
116        if (current >= end)
117            break;
118
119        jschar c = *current++;
120        if (c == '"') {
121            JSFlatString *str = (ST == JSONParser::PropertyName)
122                                ? buffer.finishAtom()
123                                : buffer.finishString();
124            if (!str)
125                return token(OOM);
126            return stringToken(str);
127        }
128
129        if (c != '\\') {
130            error("bad character in string literal");
131            return token(Error);
132        }
133
134        if (current >= end)
135            break;
136
137        switch (*current++) {
138          case '"':  c = '"';  break;
139          case '/':  c = '/';  break;
140          case '\\': c = '\\'; break;
141          case 'b':  c = '\b'; break;
142          case 'f':  c = '\f'; break;
143          case 'n':  c = '\n'; break;
144          case 'r':  c = '\r'; break;
145          case 't':  c = '\t'; break;
146
147          case 'u':
148            if (end - current < 4) {
149                error("bad Unicode escape");
150                return token(Error);
151            }
152            if (JS7_ISHEX(current[0]) &&
153                JS7_ISHEX(current[1]) &&
154                JS7_ISHEX(current[2]) &&
155                JS7_ISHEX(current[3]))
156            {
157                c = (JS7_UNHEX(current[0]) << 12)
158                  | (JS7_UNHEX(current[1]) << 8)
159                  | (JS7_UNHEX(current[2]) << 4)
160                  | (JS7_UNHEX(current[3]));
161                current += 4;
162                break;
163            }
164            /* FALL THROUGH */
165
166          default:
167            error("bad escaped character");
168            return token(Error);
169        }
170        if (!buffer.append(c))
171            return token(OOM);
172
173        start = current;
174        for (; current < end; current++) {
175            if (*current == '"' || *current == '\\' || *current <= 0x001F)
176                break;
177        }
178    } while (current < end);
179
180    error("unterminated string");
181    return token(Error);
182}
183
184JSONParser::Token
185JSONParser::readNumber()
186{
187    JS_ASSERT(current < end);
188    JS_ASSERT(JS7_ISDEC(*current) || *current == '-');
189
190    /*
191     * JSONNumber:
192     *   /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
193     */
194
195    bool negative = *current == '-';
196
197    /* -? */
198    if (negative && ++current == end) {
199        error("no number after minus sign");
200        return token(Error);
201    }
202
203    const RangedPtr<const jschar> digitStart = current;
204
205    /* 0|[1-9][0-9]+ */
206    if (!JS7_ISDEC(*current)) {
207        error("unexpected non-digit");
208        return token(Error);
209    }
210    if (*current++ != '0') {
211        for (; current < end; current++) {
212            if (!JS7_ISDEC(*current))
213                break;
214        }
215    }
216
217    /* Fast path: no fractional or exponent part. */
218    if (current == end || (*current != '.' && *current != 'e' && *current != 'E')) {
219        const jschar *dummy;
220        jsdouble d;
221        if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
222            return token(OOM);
223        JS_ASSERT(current == dummy);
224        return numberToken(negative ? -d : d);
225    }
226
227    /* (\.[0-9]+)? */
228    if (current < end && *current == '.') {
229        if (++current == end) {
230            error("missing digits after decimal point");
231            return token(Error);
232        }
233        if (!JS7_ISDEC(*current)) {
234            error("unterminated fractional number");
235            return token(Error);
236        }
237        while (++current < end) {
238            if (!JS7_ISDEC(*current))
239                break;
240        }
241    }
242
243    /* ([eE][\+\-]?[0-9]+)? */
244    if (current < end && (*current == 'e' || *current == 'E')) {
245        if (++current == end) {
246            error("missing digits after exponent indicator");
247            return token(Error);
248        }
249        if (*current == '+' || *current == '-') {
250            if (++current == end) {
251                error("missing digits after exponent sign");
252                return token(Error);
253            }
254        }
255        if (!JS7_ISDEC(*current)) {
256            error("exponent part is missing a number");
257            return token(Error);
258        }
259        while (++current < end) {
260            if (!JS7_ISDEC(*current))
261                break;
262        }
263    }
264
265    jsdouble d;
266    const jschar *finish;
267    if (!js_strtod(cx, digitStart.get(), current.get(), &finish, &d))
268        return token(OOM);
269    JS_ASSERT(current == finish);
270    return numberToken(negative ? -d : d);
271}
272
273static inline bool
274IsJSONWhitespace(jschar c)
275{
276    return c == '\t' || c == '\r' || c == '\n' || c == ' ';
277}
278
279JSONParser::Token
280JSONParser::advance()
281{
282    while (current < end && IsJSONWhitespace(*current))
283        current++;
284    if (current >= end) {
285        error("unexpected end of data");
286        return token(Error);
287    }
288
289    switch (*current) {
290      case '"':
291        return readString<LiteralValue>();
292
293      case '-':
294      case '0':
295      case '1':
296      case '2':
297      case '3':
298      case '4':
299      case '5':
300      case '6':
301      case '7':
302      case '8':
303      case '9':
304        return readNumber();
305
306      case 't':
307        if (end - current < 4 || current[1] != 'r' || current[2] != 'u' || current[3] != 'e') {
308            error("unexpected keyword");
309            return token(Error);
310        }
311        current += 4;
312        return token(True);
313
314      case 'f':
315        if (end - current < 5 ||
316            current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e')
317        {
318            error("unexpected keyword");
319            return token(Error);
320        }
321        current += 5;
322        return token(False);
323
324      case 'n':
325        if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') {
326            error("unexpected keyword");
327            return token(Error);
328        }
329        current += 4;
330        return token(Null);
331
332      case '[':
333        current++;
334        return token(ArrayOpen);
335      case ']':
336        current++;
337        return token(ArrayClose);
338
339      case '{':
340        current++;
341        return token(ObjectOpen);
342      case '}':
343        current++;
344        return token(ObjectClose);
345
346      case ',':
347        current++;
348        return token(Comma);
349
350      case ':':
351        current++;
352        return token(Colon);
353
354      default:
355        error("unexpected character");
356        return token(Error);
357    }
358}
359
360JSONParser::Token
361JSONParser::advanceAfterObjectOpen()
362{
363    JS_ASSERT(current[-1] == '{');
364
365    while (current < end && IsJSONWhitespace(*current))
366        current++;
367    if (current >= end) {
368        error("end of data while reading object contents");
369        return token(Error);
370    }
371
372    if (*current == '"')
373        return readString<PropertyName>();
374
375    if (*current == '}') {
376        current++;
377        return token(ObjectClose);
378    }
379
380    error("expected property name or '}'");
381    return token(Error);
382}
383
384static inline void
385AssertPastValue(const RangedPtr<const jschar> current)
386{
387    /*
388     * We're past an arbitrary JSON value, so the previous character is
389     * *somewhat* constrained, even if this assertion is pretty broad.  Don't
390     * knock it till you tried it: this assertion *did* catch a bug once.
391     */
392    JS_ASSERT((current[-1] == 'l' &&
393               current[-2] == 'l' &&
394               current[-3] == 'u' &&
395               current[-4] == 'n') ||
396              (current[-1] == 'e' &&
397               current[-2] == 'u' &&
398               current[-3] == 'r' &&
399               current[-4] == 't') ||
400              (current[-1] == 'e' &&
401               current[-2] == 's' &&
402               current[-3] == 'l' &&
403               current[-4] == 'a' &&
404               current[-5] == 'f') ||
405              current[-1] == '}' ||
406              current[-1] == ']' ||
407              current[-1] == '"' ||
408              JS7_ISDEC(current[-1]));
409}
410
411JSONParser::Token
412JSONParser::advanceAfterArrayElement()
413{
414    AssertPastValue(current);
415
416    while (current < end && IsJSONWhitespace(*current))
417        current++;
418    if (current >= end) {
419        error("end of data when ',' or ']' was expected");
420        return token(Error);
421    }
422
423    if (*current == ',') {
424        current++;
425        return token(Comma);
426    }
427
428    if (*current == ']') {
429        current++;
430        return token(ArrayClose);
431    }
432
433    error("expected ',' or ']' after array element");
434    return token(Error);
435}
436
437JSONParser::Token
438JSONParser::advancePropertyName()
439{
440    JS_ASSERT(current[-1] == ',');
441
442    while (current < end && IsJSONWhitespace(*current))
443        current++;
444    if (current >= end) {
445        error("end of data when property name was expected");
446        return token(Error);
447    }
448
449    if (*current == '"')
450        return readString<PropertyName>();
451
452    if (parsingMode == LegacyJSON && *current == '}') {
453        /*
454         * Previous JSON parsing accepted trailing commas in non-empty object
455         * syntax, and some users depend on this.  (Specifically, Places data
456         * serialization in versions of Firefox before 4.0.  We can remove this
457         * mode when profile upgrades from 3.6 become unsupported.)  Permit
458         * such trailing commas only when legacy parsing is specifically
459         * requested.
460         */
461        current++;
462        return token(ObjectClose);
463    }
464
465    error("expected double-quoted property name");
466    return token(Error);
467}
468
469JSONParser::Token
470JSONParser::advancePropertyColon()
471{
472    JS_ASSERT(current[-1] == '"');
473
474    while (current < end && IsJSONWhitespace(*current))
475        current++;
476    if (current >= end) {
477        error("end of data after property name when ':' was expected");
478        return token(Error);
479    }
480
481    if (*current == ':') {
482        current++;
483        return token(Colon);
484    }
485
486    error("expected ':' after property name in object");
487    return token(Error);
488}
489
490JSONParser::Token
491JSONParser::advanceAfterProperty()
492{
493    AssertPastValue(current);
494
495    while (current < end && IsJSONWhitespace(*current))
496        current++;
497    if (current >= end) {
498        error("end of data after property value in object");
499        return token(Error);
500    }
501
502    if (*current == ',') {
503        current++;
504        return token(Comma);
505    }
506
507    if (*current == '}') {
508        current++;
509        return token(ObjectClose);
510    }
511
512    error("expected ',' or '}' after property value in object");
513    return token(Error);
514}
515
516/*
517 * This enum is local to JSONParser::parse, below, but ISO C++98 doesn't allow
518 * templates to depend on local types.  Boo-urns!
519 */
520enum ParserState { FinishArrayElement, FinishObjectMember, JSONValue };
521
522bool
523JSONParser::parse(Value *vp)
524{
525    Vector<ParserState> stateStack(cx);
526    AutoValueVector valueStack(cx);
527
528    *vp = UndefinedValue();
529
530    Token token;
531    ParserState state = JSONValue;
532    while (true) {
533        switch (state) {
534          case FinishObjectMember: {
535            Value v = valueStack.popCopy();
536            /*
537             * NB: Relies on js_DefineNativeProperty performing
538             *     js_CheckForStringIndex.
539             */
540            jsid propid = ATOM_TO_JSID(&valueStack.popCopy().toString()->asAtom());
541            if (!DefineNativeProperty(cx, &valueStack.back().toObject(), propid, v,
542                                      JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE,
543                                      0, 0))
544            {
545                return false;
546            }
547            token = advanceAfterProperty();
548            if (token == ObjectClose)
549                break;
550            if (token != Comma) {
551                if (token == OOM)
552                    return false;
553                if (token != Error)
554                    error("expected ',' or '}' after property-value pair in object literal");
555                return errorReturn();
556            }
557            token = advancePropertyName();
558            /* FALL THROUGH */
559          }
560
561          JSONMember:
562            if (token == String) {
563                if (!valueStack.append(atomValue()))
564                    return false;
565                token = advancePropertyColon();
566                if (token != Colon) {
567                    JS_ASSERT(token == Error);
568                    return errorReturn();
569                }
570                if (!stateStack.append(FinishObjectMember))
571                    return false;
572                goto JSONValue;
573            }
574            if (token == ObjectClose) {
575                JS_ASSERT(state == FinishObjectMember);
576                JS_ASSERT(parsingMode == LegacyJSON);
577                break;
578            }
579            if (token == OOM)
580                return false;
581            if (token != Error)
582                error("property names must be double-quoted strings");
583            return errorReturn();
584
585          case FinishArrayElement: {
586            Value v = valueStack.popCopy();
587            if (!js_NewbornArrayPush(cx, &valueStack.back().toObject(), v))
588                return false;
589            token = advanceAfterArrayElement();
590            if (token == Comma) {
591                if (!stateStack.append(FinishArrayElement))
592                    return false;
593                goto JSONValue;
594            }
595            if (token == ArrayClose)
596                break;
597            JS_ASSERT(token == Error);
598            return errorReturn();
599          }
600
601          JSONValue:
602          case JSONValue:
603            token = advance();
604          JSONValueSwitch:
605            switch (token) {
606              case String:
607              case Number:
608                if (!valueStack.append(token == String ? stringValue() : numberValue()))
609                    return false;
610                break;
611              case True:
612                if (!valueStack.append(BooleanValue(true)))
613                    return false;
614                break;
615              case False:
616                if (!valueStack.append(BooleanValue(false)))
617                    return false;
618                break;
619              case Null:
620                if (!valueStack.append(NullValue()))
621                    return false;
622                break;
623
624              case ArrayOpen: {
625                JSObject *obj = NewDenseEmptyArray(cx);
626                if (!obj || !valueStack.append(ObjectValue(*obj)))
627                    return false;
628                token = advance();
629                if (token == ArrayClose)
630                    break;
631                if (!stateStack.append(FinishArrayElement))
632                    return false;
633                goto JSONValueSwitch;
634              }
635
636              case ObjectOpen: {
637                JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
638                if (!obj || !valueStack.append(ObjectValue(*obj)))
639                    return false;
640                token = advanceAfterObjectOpen();
641                if (token == ObjectClose)
642                    break;
643                goto JSONMember;
644              }
645
646              case ArrayClose:
647                if (parsingMode == LegacyJSON &&
648                    !stateStack.empty() &&
649                    stateStack.back() == FinishArrayElement) {
650                    /*
651                     * Previous JSON parsing accepted trailing commas in
652                     * non-empty array syntax, and some users depend on this.
653                     * (Specifically, Places data serialization in versions of
654                     * Firefox prior to 4.0.  We can remove this mode when
655                     * profile upgrades from 3.6 become unsupported.)  Permit
656                     * such trailing commas only when specifically
657                     * instructed to do so.
658                     */
659                    stateStack.popBack();
660                    break;
661                }
662                /* FALL THROUGH */
663
664              case ObjectClose:
665              case Colon:
666              case Comma:
667                error("unexpected character");
668                return errorReturn();
669
670              case OOM:
671                return false;
672
673              case Error:
674                return errorReturn();
675            }
676            break;
677        }
678
679        if (stateStack.empty())
680            break;
681        state = stateStack.popCopy();
682    }
683
684    for (; current < end; current++) {
685        if (!IsJSONWhitespace(*current)) {
686            error("unexpected non-whitespace character after JSON data");
687            return errorReturn();
688        }
689    }
690
691    JS_ASSERT(end == current);
692    JS_ASSERT(valueStack.length() == 1);
693    *vp = valueStack[0];
694    return true;
695}