1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "jsonparser.h"
9 #include "mozilla/Range.h"
10 #include "mozilla/RangedPtr.h"
15 #include "jscompartment.h"
19 #include "vm/StringBuffer.h"
21 #include "jsobjinlines.h"
25 using mozilla::RangedPtr
;
27 JSONParserBase::~JSONParserBase()
29 for (size_t i
= 0; i
< stack
.length(); i
++) {
30 if (stack
[i
].state
== FinishArrayElement
)
31 js_delete(&stack
[i
].elements());
33 js_delete(&stack
[i
].properties());
36 for (size_t i
= 0; i
< freeElements
.length(); i
++)
37 js_delete(freeElements
[i
]);
39 for (size_t i
= 0; i
< freeProperties
.length(); i
++)
40 js_delete(freeProperties
[i
]);
44 JSONParserBase::trace(JSTracer
*trc
)
46 for (size_t i
= 0; i
< stack
.length(); i
++) {
47 if (stack
[i
].state
== FinishArrayElement
) {
48 ElementVector
&elements
= stack
[i
].elements();
49 for (size_t j
= 0; j
< elements
.length(); j
++)
50 gc::MarkValueRoot(trc
, &elements
[j
], "JSONParser element");
52 PropertyVector
&properties
= stack
[i
].properties();
53 for (size_t j
= 0; j
< properties
.length(); j
++) {
54 gc::MarkValueRoot(trc
, &properties
[j
].value
, "JSONParser property value");
55 gc::MarkIdRoot(trc
, &properties
[j
].id
, "JSONParser property id");
61 template <typename CharT
>
63 JSONParser
<CharT
>::getTextPosition(uint32_t *column
, uint32_t *line
)
68 for (; ptr
< current
; ptr
++) {
69 if (*ptr
== '\n' || *ptr
== '\r') {
72 // \r\n is treated as a single newline.
73 if (ptr
+ 1 < current
&& *ptr
== '\r' && *(ptr
+ 1) == '\n')
83 template <typename CharT
>
85 JSONParser
<CharT
>::error(const char *msg
)
87 if (errorHandling
== RaiseError
) {
88 uint32_t column
= 1, line
= 1;
89 getTextPosition(&column
, &line
);
91 const size_t MaxWidth
= sizeof("4294967295");
92 char columnNumber
[MaxWidth
];
93 JS_snprintf(columnNumber
, sizeof columnNumber
, "%lu", column
);
94 char lineNumber
[MaxWidth
];
95 JS_snprintf(lineNumber
, sizeof lineNumber
, "%lu", line
);
97 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_JSON_BAD_PARSE
,
98 msg
, lineNumber
, columnNumber
);
103 JSONParserBase::errorReturn()
105 return errorHandling
== NoError
;
108 template <typename CharT
>
109 template <JSONParserBase::StringType ST
>
110 JSONParserBase::Token
111 JSONParser
<CharT
>::readString()
113 JS_ASSERT(current
< end
);
114 JS_ASSERT(*current
== '"');
118 * /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
121 if (++current
== end
) {
122 error("unterminated string literal");
127 * Optimization: if the source contains no escaped characters, create the
128 * string directly from the source text.
130 CharPtr start
= current
;
131 for (; current
< end
; current
++) {
132 if (*current
== '"') {
133 size_t length
= current
- start
;
135 JSFlatString
*str
= (ST
== JSONParser::PropertyName
)
136 ? AtomizeChars(cx
, start
.get(), length
)
137 : NewStringCopyN
<CanGC
>(cx
, start
.get(), length
);
140 return stringToken(str
);
143 if (*current
== '\\')
146 if (*current
<= 0x001F) {
147 error("bad control character in string literal");
153 * Slow case: string contains escaped characters. Copy a maximal sequence
154 * of unescaped characters into a temporary buffer, then an escaped
155 * character, and repeat until the entire string is consumed.
157 StringBuffer
buffer(cx
);
159 if (start
< current
&& !buffer
.append(start
.get(), current
.get()))
165 jschar c
= *current
++;
167 JSFlatString
*str
= (ST
== JSONParser::PropertyName
)
168 ? buffer
.finishAtom()
169 : buffer
.finishString();
172 return stringToken(str
);
177 error("bad character in string literal");
184 switch (*current
++) {
185 case '"': c
= '"'; break;
186 case '/': c
= '/'; break;
187 case '\\': c
= '\\'; break;
188 case 'b': c
= '\b'; break;
189 case 'f': c
= '\f'; break;
190 case 'n': c
= '\n'; break;
191 case 'r': c
= '\r'; break;
192 case 't': c
= '\t'; break;
195 if (end
- current
< 4 ||
196 !(JS7_ISHEX(current
[0]) &&
197 JS7_ISHEX(current
[1]) &&
198 JS7_ISHEX(current
[2]) &&
199 JS7_ISHEX(current
[3])))
201 // Point to the first non-hexadecimal character (which may be
203 if (current
== end
|| !JS7_ISHEX(current
[0]))
204 ; // already at correct location
205 else if (current
+ 1 == end
|| !JS7_ISHEX(current
[1]))
207 else if (current
+ 2 == end
|| !JS7_ISHEX(current
[2]))
209 else if (current
+ 3 == end
|| !JS7_ISHEX(current
[3]))
212 MOZ_CRASH("logic error determining first erroneous character");
214 error("bad Unicode escape");
217 c
= (JS7_UNHEX(current
[0]) << 12)
218 | (JS7_UNHEX(current
[1]) << 8)
219 | (JS7_UNHEX(current
[2]) << 4)
220 | (JS7_UNHEX(current
[3]));
226 error("bad escaped character");
229 if (!buffer
.append(c
))
233 for (; current
< end
; current
++) {
234 if (*current
== '"' || *current
== '\\' || *current
<= 0x001F)
237 } while (current
< end
);
239 error("unterminated string");
243 template <typename CharT
>
244 JSONParserBase::Token
245 JSONParser
<CharT
>::readNumber()
247 JS_ASSERT(current
< end
);
248 JS_ASSERT(JS7_ISDEC(*current
) || *current
== '-');
252 * /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
255 bool negative
= *current
== '-';
258 if (negative
&& ++current
== end
) {
259 error("no number after minus sign");
263 const CharPtr digitStart
= current
;
266 if (!JS7_ISDEC(*current
)) {
267 error("unexpected non-digit");
270 if (*current
++ != '0') {
271 for (; current
< end
; current
++) {
272 if (!JS7_ISDEC(*current
))
277 /* Fast path: no fractional or exponent part. */
278 if (current
== end
|| (*current
!= '.' && *current
!= 'e' && *current
!= 'E')) {
279 mozilla::Range
<const CharT
> chars(digitStart
.get(), current
- digitStart
);
280 if (chars
.length() < strlen("9007199254740992")) {
281 // If the decimal number is shorter than the length of 2**53, (the
282 // largest number a double can represent with integral precision),
283 // parse it using a decimal-only parser. This comparison is
284 // conservative but faster than a fully-precise check.
285 double d
= ParseDecimalNumber(chars
);
286 return numberToken(negative
? -d
: d
);
291 if (!GetPrefixInteger(cx
, digitStart
.get(), current
.get(), 10, &dummy
, &d
))
293 JS_ASSERT(current
== dummy
);
294 return numberToken(negative
? -d
: d
);
298 if (current
< end
&& *current
== '.') {
299 if (++current
== end
) {
300 error("missing digits after decimal point");
303 if (!JS7_ISDEC(*current
)) {
304 error("unterminated fractional number");
307 while (++current
< end
) {
308 if (!JS7_ISDEC(*current
))
313 /* ([eE][\+\-]?[0-9]+)? */
314 if (current
< end
&& (*current
== 'e' || *current
== 'E')) {
315 if (++current
== end
) {
316 error("missing digits after exponent indicator");
319 if (*current
== '+' || *current
== '-') {
320 if (++current
== end
) {
321 error("missing digits after exponent sign");
325 if (!JS7_ISDEC(*current
)) {
326 error("exponent part is missing a number");
329 while (++current
< end
) {
330 if (!JS7_ISDEC(*current
))
337 if (!js_strtod(cx
, digitStart
.get(), current
.get(), &finish
, &d
))
339 JS_ASSERT(current
== finish
);
340 return numberToken(negative
? -d
: d
);
344 IsJSONWhitespace(jschar c
)
346 return c
== '\t' || c
== '\r' || c
== '\n' || c
== ' ';
349 template <typename CharT
>
350 JSONParserBase::Token
351 JSONParser
<CharT
>::advance()
353 while (current
< end
&& IsJSONWhitespace(*current
))
355 if (current
>= end
) {
356 error("unexpected end of data");
362 return readString
<LiteralValue
>();
378 if (end
- current
< 4 || current
[1] != 'r' || current
[2] != 'u' || current
[3] != 'e') {
379 error("unexpected keyword");
386 if (end
- current
< 5 ||
387 current
[1] != 'a' || current
[2] != 'l' || current
[3] != 's' || current
[4] != 'e')
389 error("unexpected keyword");
396 if (end
- current
< 4 || current
[1] != 'u' || current
[2] != 'l' || current
[3] != 'l') {
397 error("unexpected keyword");
405 return token(ArrayOpen
);
408 return token(ArrayClose
);
412 return token(ObjectOpen
);
415 return token(ObjectClose
);
426 error("unexpected character");
431 template <typename CharT
>
432 JSONParserBase::Token
433 JSONParser
<CharT
>::advanceAfterObjectOpen()
435 JS_ASSERT(current
[-1] == '{');
437 while (current
< end
&& IsJSONWhitespace(*current
))
439 if (current
>= end
) {
440 error("end of data while reading object contents");
445 return readString
<PropertyName
>();
447 if (*current
== '}') {
449 return token(ObjectClose
);
452 error("expected property name or '}'");
456 template <typename CharT
>
458 AssertPastValue(const RangedPtr
<const CharT
> current
)
461 * We're past an arbitrary JSON value, so the previous character is
462 * *somewhat* constrained, even if this assertion is pretty broad. Don't
463 * knock it till you tried it: this assertion *did* catch a bug once.
465 JS_ASSERT((current
[-1] == 'l' &&
466 current
[-2] == 'l' &&
467 current
[-3] == 'u' &&
468 current
[-4] == 'n') ||
469 (current
[-1] == 'e' &&
470 current
[-2] == 'u' &&
471 current
[-3] == 'r' &&
472 current
[-4] == 't') ||
473 (current
[-1] == 'e' &&
474 current
[-2] == 's' &&
475 current
[-3] == 'l' &&
476 current
[-4] == 'a' &&
477 current
[-5] == 'f') ||
478 current
[-1] == '}' ||
479 current
[-1] == ']' ||
480 current
[-1] == '"' ||
481 JS7_ISDEC(current
[-1]));
484 template <typename CharT
>
485 JSONParserBase::Token
486 JSONParser
<CharT
>::advanceAfterArrayElement()
488 AssertPastValue(current
);
490 while (current
< end
&& IsJSONWhitespace(*current
))
492 if (current
>= end
) {
493 error("end of data when ',' or ']' was expected");
497 if (*current
== ',') {
502 if (*current
== ']') {
504 return token(ArrayClose
);
507 error("expected ',' or ']' after array element");
511 template <typename CharT
>
512 JSONParserBase::Token
513 JSONParser
<CharT
>::advancePropertyName()
515 JS_ASSERT(current
[-1] == ',');
517 while (current
< end
&& IsJSONWhitespace(*current
))
519 if (current
>= end
) {
520 error("end of data when property name was expected");
525 return readString
<PropertyName
>();
527 error("expected double-quoted property name");
531 template <typename CharT
>
532 JSONParserBase::Token
533 JSONParser
<CharT
>::advancePropertyColon()
535 JS_ASSERT(current
[-1] == '"');
537 while (current
< end
&& IsJSONWhitespace(*current
))
539 if (current
>= end
) {
540 error("end of data after property name when ':' was expected");
544 if (*current
== ':') {
549 error("expected ':' after property name in object");
553 template <typename CharT
>
554 JSONParserBase::Token
555 JSONParser
<CharT
>::advanceAfterProperty()
557 AssertPastValue(current
);
559 while (current
< end
&& IsJSONWhitespace(*current
))
561 if (current
>= end
) {
562 error("end of data after property value in object");
566 if (*current
== ',') {
571 if (*current
== '}') {
573 return token(ObjectClose
);
576 error("expected ',' or '}' after property value in object");
581 JSONParserBase::createFinishedObject(PropertyVector
&properties
)
584 * Look for an existing cached type and shape for objects with this set of
588 JSObject
*obj
= cx
->compartment()->types
.newTypedObject(cx
, properties
.begin(),
589 properties
.length());
595 * Make a new object sized for the given number of properties and fill its
598 gc::AllocKind allocKind
= gc::GetGCObjectKind(properties
.length());
599 RootedObject
obj(cx
, NewBuiltinClassInstance(cx
, &JSObject::class_
, allocKind
));
604 RootedValue
value(cx
);
606 for (size_t i
= 0; i
< properties
.length(); i
++) {
607 propid
= properties
[i
].id
;
608 value
= properties
[i
].value
;
609 if (!DefineNativeProperty(cx
, obj
, propid
, value
, JS_PropertyStub
, JS_StrictPropertyStub
,
616 * Try to assign a new type to the object with type information for its
617 * properties, and update the initializer type object cache with this
618 * object's final shape.
620 cx
->compartment()->types
.fixObjectType(cx
, obj
);
626 JSONParserBase::finishObject(MutableHandleValue vp
, PropertyVector
&properties
)
628 JS_ASSERT(&properties
== &stack
.back().properties());
630 JSObject
*obj
= createFinishedObject(properties
);
635 if (!freeProperties
.append(&properties
))
642 JSONParserBase::finishArray(MutableHandleValue vp
, ElementVector
&elements
)
644 JS_ASSERT(&elements
== &stack
.back().elements());
646 JSObject
*obj
= NewDenseCopiedArray(cx
, elements
.length(), elements
.begin());
650 /* Try to assign a new type to the array according to its elements. */
651 cx
->compartment()->types
.fixArrayType(cx
, obj
);
654 if (!freeElements
.append(&elements
))
660 template <typename CharT
>
662 JSONParser
<CharT
>::parse(MutableHandleValue vp
)
664 RootedValue
value(cx
);
665 JS_ASSERT(stack
.empty());
670 ParserState state
= JSONValue
;
673 case FinishObjectMember
: {
674 PropertyVector
&properties
= stack
.back().properties();
675 properties
.back().value
= value
;
677 token
= advanceAfterProperty();
678 if (token
== ObjectClose
) {
679 if (!finishObject(&value
, properties
))
683 if (token
!= Comma
) {
687 error("expected ',' or '}' after property-value pair in object literal");
688 return errorReturn();
690 token
= advancePropertyName();
695 if (token
== String
) {
696 jsid id
= AtomToId(atomValue());
697 PropertyVector
&properties
= stack
.back().properties();
698 if (!properties
.append(IdValuePair(id
)))
700 token
= advancePropertyColon();
701 if (token
!= Colon
) {
702 JS_ASSERT(token
== Error
);
703 return errorReturn();
710 error("property names must be double-quoted strings");
711 return errorReturn();
713 case FinishArrayElement
: {
714 ElementVector
&elements
= stack
.back().elements();
715 if (!elements
.append(value
.get()))
717 token
= advanceAfterArrayElement();
720 if (token
== ArrayClose
) {
721 if (!finishArray(&value
, elements
))
725 JS_ASSERT(token
== Error
);
726 return errorReturn();
735 value
= stringValue();
738 value
= numberValue();
741 value
= BooleanValue(true);
744 value
= BooleanValue(false);
751 ElementVector
*elements
;
752 if (!freeElements
.empty()) {
753 elements
= freeElements
.popCopy();
756 elements
= cx
->new_
<ElementVector
>(cx
);
760 if (!stack
.append(elements
))
764 if (token
== ArrayClose
) {
765 if (!finishArray(&value
, *elements
))
769 goto JSONValueSwitch
;
773 PropertyVector
*properties
;
774 if (!freeProperties
.empty()) {
775 properties
= freeProperties
.popCopy();
778 properties
= cx
->new_
<PropertyVector
>(cx
);
782 if (!stack
.append(properties
))
785 token
= advanceAfterObjectOpen();
786 if (token
== ObjectClose
) {
787 if (!finishObject(&value
, *properties
))
798 // Move the current pointer backwards so that the position
799 // reported in the error message is correct.
801 error("unexpected character");
802 return errorReturn();
808 return errorReturn();
815 state
= stack
.back().state
;
818 for (; current
< end
; current
++) {
819 if (!IsJSONWhitespace(*current
)) {
820 error("unexpected non-whitespace character after JSON data");
821 return errorReturn();
825 JS_ASSERT(end
== current
);
826 JS_ASSERT(stack
.empty());
832 template class js::JSONParser
<Latin1Char
>;
833 template class js::JSONParser
<jschar
>;