1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=99:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
17 * The Original Code is SpiderMonkey JSON.
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.
25 * Jeff Walden <jwalden+code@mit.edu> (original author)
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.
39 * ***** END LICENSE BLOCK ***** */
43 #include "jsonparser.h"
45 #include "vm/StringBuffer.h"
47 #include "jsobjinlines.h"
52 JSONParser::error(const char *msg
)
54 if (errorHandling
== RaiseError
)
55 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_JSON_BAD_PARSE
, msg
);
59 JSONParser::errorReturn()
61 return errorHandling
== NoError
;
64 template<JSONParser::StringType ST
>
66 JSONParser::readString()
68 JS_ASSERT(current
< end
);
69 JS_ASSERT(*current
== '"');
73 * /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
76 if (++current
== end
) {
77 error("unterminated string literal");
82 * Optimization: if the source contains no escaped characters, create the
83 * string directly from the source text.
85 RangedPtr
<const jschar
> start
= current
;
86 for (; current
< end
; current
++) {
87 if (*current
== '"') {
88 size_t length
= current
- start
;
90 JSFlatString
*str
= (ST
== JSONParser::PropertyName
)
91 ? js_AtomizeChars(cx
, start
.get(), length
)
92 : js_NewStringCopyN(cx
, start
.get(), length
);
95 return stringToken(str
);
101 if (*current
<= 0x001F) {
102 error("bad control character in string literal");
108 * Slow case: string contains escaped characters. Copy a maximal sequence
109 * of unescaped characters into a temporary buffer, then an escaped
110 * character, and repeat until the entire string is consumed.
112 StringBuffer
buffer(cx
);
114 if (start
< current
&& !buffer
.append(start
.get(), current
.get()))
120 jschar c
= *current
++;
122 JSFlatString
*str
= (ST
== JSONParser::PropertyName
)
123 ? buffer
.finishAtom()
124 : buffer
.finishString();
127 return stringToken(str
);
131 error("bad character in string literal");
138 switch (*current
++) {
139 case '"': c
= '"'; break;
140 case '/': c
= '/'; break;
141 case '\\': c
= '\\'; break;
142 case 'b': c
= '\b'; break;
143 case 'f': c
= '\f'; break;
144 case 'n': c
= '\n'; break;
145 case 'r': c
= '\r'; break;
146 case 't': c
= '\t'; break;
149 if (end
- current
< 4) {
150 error("bad Unicode escape");
153 if (JS7_ISHEX(current
[0]) &&
154 JS7_ISHEX(current
[1]) &&
155 JS7_ISHEX(current
[2]) &&
156 JS7_ISHEX(current
[3]))
158 c
= (JS7_UNHEX(current
[0]) << 12)
159 | (JS7_UNHEX(current
[1]) << 8)
160 | (JS7_UNHEX(current
[2]) << 4)
161 | (JS7_UNHEX(current
[3]));
168 error("bad escaped character");
171 if (!buffer
.append(c
))
175 for (; current
< end
; current
++) {
176 if (*current
== '"' || *current
== '\\' || *current
<= 0x001F)
179 } while (current
< end
);
181 error("unterminated string");
186 JSONParser::readNumber()
188 JS_ASSERT(current
< end
);
189 JS_ASSERT(JS7_ISDEC(*current
) || *current
== '-');
193 * /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
196 bool negative
= *current
== '-';
199 if (negative
&& ++current
== end
) {
200 error("no number after minus sign");
204 const RangedPtr
<const jschar
> digitStart
= current
;
207 if (!JS7_ISDEC(*current
)) {
208 error("unexpected non-digit");
211 if (*current
++ != '0') {
212 for (; current
< end
; current
++) {
213 if (!JS7_ISDEC(*current
))
218 /* Fast path: no fractional or exponent part. */
219 if (current
== end
|| (*current
!= '.' && *current
!= 'e' && *current
!= 'E')) {
222 if (!GetPrefixInteger(cx
, digitStart
.get(), current
.get(), 10, &dummy
, &d
))
224 JS_ASSERT(current
== dummy
);
225 return numberToken(negative
? -d
: d
);
229 if (current
< end
&& *current
== '.') {
230 if (++current
== end
) {
231 error("missing digits after decimal point");
234 if (!JS7_ISDEC(*current
)) {
235 error("unterminated fractional number");
238 while (++current
< end
) {
239 if (!JS7_ISDEC(*current
))
244 /* ([eE][\+\-]?[0-9]+)? */
245 if (current
< end
&& (*current
== 'e' || *current
== 'E')) {
246 if (++current
== end
) {
247 error("missing digits after exponent indicator");
250 if (*current
== '+' || *current
== '-') {
251 if (++current
== end
) {
252 error("missing digits after exponent sign");
256 if (!JS7_ISDEC(*current
)) {
257 error("exponent part is missing a number");
260 while (++current
< end
) {
261 if (!JS7_ISDEC(*current
))
267 const jschar
*finish
;
268 if (!js_strtod(cx
, digitStart
.get(), current
.get(), &finish
, &d
))
270 JS_ASSERT(current
== finish
);
271 return numberToken(negative
? -d
: d
);
275 IsJSONWhitespace(jschar c
)
277 return c
== '\t' || c
== '\r' || c
== '\n' || c
== ' ';
281 JSONParser::advance()
283 while (current
< end
&& IsJSONWhitespace(*current
))
285 if (current
>= end
) {
286 error("unexpected end of data");
292 return readString
<LiteralValue
>();
308 if (end
- current
< 4 || current
[1] != 'r' || current
[2] != 'u' || current
[3] != 'e') {
309 error("unexpected keyword");
316 if (end
- current
< 5 ||
317 current
[1] != 'a' || current
[2] != 'l' || current
[3] != 's' || current
[4] != 'e')
319 error("unexpected keyword");
326 if (end
- current
< 4 || current
[1] != 'u' || current
[2] != 'l' || current
[3] != 'l') {
327 error("unexpected keyword");
335 return token(ArrayOpen
);
338 return token(ArrayClose
);
342 return token(ObjectOpen
);
345 return token(ObjectClose
);
356 error("unexpected character");
362 JSONParser::advanceAfterObjectOpen()
364 JS_ASSERT(current
[-1] == '{');
366 while (current
< end
&& IsJSONWhitespace(*current
))
368 if (current
>= end
) {
369 error("end of data while reading object contents");
374 return readString
<PropertyName
>();
376 if (*current
== '}') {
378 return token(ObjectClose
);
381 error("expected property name or '}'");
386 AssertPastValue(const RangedPtr
<const jschar
> current
)
389 * We're past an arbitrary JSON value, so the previous character is
390 * *somewhat* constrained, even if this assertion is pretty broad. Don't
391 * knock it till you tried it: this assertion *did* catch a bug once.
393 JS_ASSERT((current
[-1] == 'l' &&
394 current
[-2] == 'l' &&
395 current
[-3] == 'u' &&
396 current
[-4] == 'n') ||
397 (current
[-1] == 'e' &&
398 current
[-2] == 'u' &&
399 current
[-3] == 'r' &&
400 current
[-4] == 't') ||
401 (current
[-1] == 'e' &&
402 current
[-2] == 's' &&
403 current
[-3] == 'l' &&
404 current
[-4] == 'a' &&
405 current
[-5] == 'f') ||
406 current
[-1] == '}' ||
407 current
[-1] == ']' ||
408 current
[-1] == '"' ||
409 JS7_ISDEC(current
[-1]));
413 JSONParser::advanceAfterArrayElement()
415 AssertPastValue(current
);
417 while (current
< end
&& IsJSONWhitespace(*current
))
419 if (current
>= end
) {
420 error("end of data when ',' or ']' was expected");
424 if (*current
== ',') {
429 if (*current
== ']') {
431 return token(ArrayClose
);
434 error("expected ',' or ']' after array element");
439 JSONParser::advancePropertyName()
441 JS_ASSERT(current
[-1] == ',');
443 while (current
< end
&& IsJSONWhitespace(*current
))
445 if (current
>= end
) {
446 error("end of data when property name was expected");
451 return readString
<PropertyName
>();
453 if (parsingMode
== LegacyJSON
&& *current
== '}') {
455 * Previous JSON parsing accepted trailing commas in non-empty object
456 * syntax, and some users depend on this. (Specifically, Places data
457 * serialization in versions of Firefox before 4.0. We can remove this
458 * mode when profile upgrades from 3.6 become unsupported.) Permit
459 * such trailing commas only when legacy parsing is specifically
463 return token(ObjectClose
);
466 error("expected double-quoted property name");
471 JSONParser::advancePropertyColon()
473 JS_ASSERT(current
[-1] == '"');
475 while (current
< end
&& IsJSONWhitespace(*current
))
477 if (current
>= end
) {
478 error("end of data after property name when ':' was expected");
482 if (*current
== ':') {
487 error("expected ':' after property name in object");
492 JSONParser::advanceAfterProperty()
494 AssertPastValue(current
);
496 while (current
< end
&& IsJSONWhitespace(*current
))
498 if (current
>= end
) {
499 error("end of data after property value in object");
503 if (*current
== ',') {
508 if (*current
== '}') {
510 return token(ObjectClose
);
513 error("expected ',' or '}' after property value in object");
518 * This enum is local to JSONParser::parse, below, but ISO C++98 doesn't allow
519 * templates to depend on local types. Boo-urns!
521 enum ParserState
{ FinishArrayElement
, FinishObjectMember
, JSONValue
};
524 JSONParser::parse(Value
*vp
)
526 Vector
<ParserState
> stateStack(cx
);
527 AutoValueVector
valueStack(cx
);
529 *vp
= UndefinedValue();
532 ParserState state
= JSONValue
;
535 case FinishObjectMember
: {
536 Value v
= valueStack
.popCopy();
537 jsid propid
= AtomToId(&valueStack
.popCopy().toString()->asAtom());
538 RootedVarObject
obj(cx
, &valueStack
.back().toObject());
539 if (!DefineNativeProperty(cx
, obj
, propid
, v
,
540 JS_PropertyStub
, JS_StrictPropertyStub
, JSPROP_ENUMERATE
,
545 token
= advanceAfterProperty();
546 if (token
== ObjectClose
)
548 if (token
!= Comma
) {
552 error("expected ',' or '}' after property-value pair in object literal");
553 return errorReturn();
555 token
= advancePropertyName();
560 if (token
== String
) {
561 if (!valueStack
.append(atomValue()))
563 token
= advancePropertyColon();
564 if (token
!= Colon
) {
565 JS_ASSERT(token
== Error
);
566 return errorReturn();
568 if (!stateStack
.append(FinishObjectMember
))
572 if (token
== ObjectClose
) {
573 JS_ASSERT(state
== FinishObjectMember
);
574 JS_ASSERT(parsingMode
== LegacyJSON
);
580 error("property names must be double-quoted strings");
581 return errorReturn();
583 case FinishArrayElement
: {
584 Value v
= valueStack
.popCopy();
585 if (!js_NewbornArrayPush(cx
, &valueStack
.back().toObject(), v
))
587 token
= advanceAfterArrayElement();
588 if (token
== Comma
) {
589 if (!stateStack
.append(FinishArrayElement
))
593 if (token
== ArrayClose
)
595 JS_ASSERT(token
== Error
);
596 return errorReturn();
606 if (!valueStack
.append(token
== String
? stringValue() : numberValue()))
610 if (!valueStack
.append(BooleanValue(true)))
614 if (!valueStack
.append(BooleanValue(false)))
618 if (!valueStack
.append(NullValue()))
623 JSObject
*obj
= NewDenseEmptyArray(cx
);
624 if (!obj
|| !valueStack
.append(ObjectValue(*obj
)))
627 if (token
== ArrayClose
)
629 if (!stateStack
.append(FinishArrayElement
))
631 goto JSONValueSwitch
;
635 JSObject
*obj
= NewBuiltinClassInstance(cx
, &ObjectClass
);
636 if (!obj
|| !valueStack
.append(ObjectValue(*obj
)))
638 token
= advanceAfterObjectOpen();
639 if (token
== ObjectClose
)
645 if (parsingMode
== LegacyJSON
&&
646 !stateStack
.empty() &&
647 stateStack
.back() == FinishArrayElement
) {
649 * Previous JSON parsing accepted trailing commas in
650 * non-empty array syntax, and some users depend on this.
651 * (Specifically, Places data serialization in versions of
652 * Firefox prior to 4.0. We can remove this mode when
653 * profile upgrades from 3.6 become unsupported.) Permit
654 * such trailing commas only when specifically
655 * instructed to do so.
657 stateStack
.popBack();
665 error("unexpected character");
666 return errorReturn();
672 return errorReturn();
677 if (stateStack
.empty())
679 state
= stateStack
.popCopy();
682 for (; current
< end
; current
++) {
683 if (!IsJSONWhitespace(*current
)) {
684 error("unexpected non-whitespace character after JSON data");
685 return errorReturn();
689 JS_ASSERT(end
== current
);
690 JS_ASSERT(valueStack
.length() == 1);