1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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 "vm/JSONParser.h"
9 #include "mozilla/Range.h"
10 #include "mozilla/RangedPtr.h"
11 #include "mozilla/Sprintf.h"
12 #include "mozilla/TextUtils.h"
16 #include "builtin/Array.h"
17 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
18 #include "util/StringBuffer.h"
19 #include "vm/PlainObject.h" // js::NewPlainObjectWithProperties
22 #include "vm/NativeObject-inl.h"
26 using mozilla::AsciiAlphanumericToNumber
;
27 using mozilla::IsAsciiDigit
;
28 using mozilla::IsAsciiHexDigit
;
29 using mozilla::RangedPtr
;
31 JSONParserBase::~JSONParserBase() {
32 for (size_t i
= 0; i
< stack
.length(); i
++) {
33 if (stack
[i
].state
== FinishArrayElement
) {
34 js_delete(&stack
[i
].elements());
36 js_delete(&stack
[i
].properties());
40 for (size_t i
= 0; i
< freeElements
.length(); i
++) {
41 js_delete(freeElements
[i
]);
44 for (size_t i
= 0; i
< freeProperties
.length(); i
++) {
45 js_delete(freeProperties
[i
]);
49 void JSONParserBase::trace(JSTracer
* trc
) {
50 for (auto& elem
: stack
) {
51 if (elem
.state
== FinishArrayElement
) {
52 elem
.elements().trace(trc
);
54 elem
.properties().trace(trc
);
59 template <typename CharT
>
60 void JSONParser
<CharT
>::getTextPosition(uint32_t* column
, uint32_t* line
) {
64 for (; ptr
< current
; ptr
++) {
65 if (*ptr
== '\n' || *ptr
== '\r') {
68 // \r\n is treated as a single newline.
69 if (ptr
+ 1 < current
&& *ptr
== '\r' && *(ptr
+ 1) == '\n') {
80 template <typename CharT
>
81 void JSONParser
<CharT
>::error(const char* msg
) {
82 if (parseType
== ParseType::JSONParse
) {
83 uint32_t column
= 1, line
= 1;
84 getTextPosition(&column
, &line
);
86 const size_t MaxWidth
= sizeof("4294967295");
87 char columnNumber
[MaxWidth
];
88 SprintfLiteral(columnNumber
, "%" PRIu32
, column
);
89 char lineNumber
[MaxWidth
];
90 SprintfLiteral(lineNumber
, "%" PRIu32
, line
);
92 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
93 JSMSG_JSON_BAD_PARSE
, msg
, lineNumber
,
98 bool JSONParserBase::errorReturn() {
99 return parseType
== ParseType::AttemptForEval
;
102 template <typename CharT
>
103 template <JSONParserBase::StringType ST
>
104 JSONParserBase::Token JSONParser
<CharT
>::readString() {
105 MOZ_ASSERT(current
< end
);
106 MOZ_ASSERT(*current
== '"');
110 * /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
113 if (++current
== end
) {
114 error("unterminated string literal");
119 * Optimization: if the source contains no escaped characters, create the
120 * string directly from the source text.
122 CharPtr start
= current
;
123 for (; current
< end
; current
++) {
124 if (*current
== '"') {
125 size_t length
= current
- start
;
127 JSLinearString
* str
=
128 (ST
== JSONParser::PropertyName
)
129 ? AtomizeChars(cx
, start
.get(), length
)
130 : NewStringCopyN
<CanGC
>(cx
, start
.get(), length
);
134 return stringToken(str
);
137 if (*current
== '\\') {
141 if (*current
<= 0x001F) {
142 error("bad control character in string literal");
148 * Slow case: string contains escaped characters. Copy a maximal sequence
149 * of unescaped characters into a temporary buffer, then an escaped
150 * character, and repeat until the entire string is consumed.
152 JSStringBuilder
buffer(cx
);
154 if (start
< current
&& !buffer
.append(start
.get(), current
.get())) {
158 if (current
>= end
) {
162 char16_t c
= *current
++;
164 JSLinearString
* str
= (ST
== JSONParser::PropertyName
)
165 ? buffer
.finishAtom()
166 : buffer
.finishString();
170 return stringToken(str
);
175 error("bad character in string literal");
179 if (current
>= end
) {
183 switch (*current
++) {
210 if (end
- current
< 4 ||
211 !(IsAsciiHexDigit(current
[0]) && IsAsciiHexDigit(current
[1]) &&
212 IsAsciiHexDigit(current
[2]) && IsAsciiHexDigit(current
[3]))) {
213 // Point to the first non-hexadecimal character (which may be
215 if (current
== end
|| !IsAsciiHexDigit(current
[0])) {
216 ; // already at correct location
217 } else if (current
+ 1 == end
|| !IsAsciiHexDigit(current
[1])) {
219 } else if (current
+ 2 == end
|| !IsAsciiHexDigit(current
[2])) {
221 } else if (current
+ 3 == end
|| !IsAsciiHexDigit(current
[3])) {
224 MOZ_CRASH("logic error determining first erroneous character");
227 error("bad Unicode escape");
230 c
= (AsciiAlphanumericToNumber(current
[0]) << 12) |
231 (AsciiAlphanumericToNumber(current
[1]) << 8) |
232 (AsciiAlphanumericToNumber(current
[2]) << 4) |
233 (AsciiAlphanumericToNumber(current
[3]));
239 error("bad escaped character");
242 if (!buffer
.append(c
)) {
247 for (; current
< end
; current
++) {
248 if (*current
== '"' || *current
== '\\' || *current
<= 0x001F) {
252 } while (current
< end
);
254 error("unterminated string");
258 template <typename CharT
>
259 JSONParserBase::Token JSONParser
<CharT
>::readNumber() {
260 MOZ_ASSERT(current
< end
);
261 MOZ_ASSERT(IsAsciiDigit(*current
) || *current
== '-');
265 * /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
268 bool negative
= *current
== '-';
271 if (negative
&& ++current
== end
) {
272 error("no number after minus sign");
276 const CharPtr digitStart
= current
;
279 if (!IsAsciiDigit(*current
)) {
280 error("unexpected non-digit");
283 if (*current
++ != '0') {
284 for (; current
< end
; current
++) {
285 if (!IsAsciiDigit(*current
)) {
291 /* Fast path: no fractional or exponent part. */
292 if (current
== end
||
293 (*current
!= '.' && *current
!= 'e' && *current
!= 'E')) {
294 mozilla::Range
<const CharT
> chars(digitStart
.get(), current
- digitStart
);
295 if (chars
.length() < strlen("9007199254740992")) {
296 // If the decimal number is shorter than the length of 2**53, (the
297 // largest number a double can represent with integral precision),
298 // parse it using a decimal-only parser. This comparison is
299 // conservative but faster than a fully-precise check.
300 double d
= ParseDecimalNumber(chars
);
301 return numberToken(negative
? -d
: d
);
305 if (!GetFullInteger(cx
, digitStart
.get(), current
.get(), 10,
306 IntegerSeparatorHandling::None
, &d
)) {
309 return numberToken(negative
? -d
: d
);
313 if (current
< end
&& *current
== '.') {
314 if (++current
== end
) {
315 error("missing digits after decimal point");
318 if (!IsAsciiDigit(*current
)) {
319 error("unterminated fractional number");
322 while (++current
< end
) {
323 if (!IsAsciiDigit(*current
)) {
329 /* ([eE][\+\-]?[0-9]+)? */
330 if (current
< end
&& (*current
== 'e' || *current
== 'E')) {
331 if (++current
== end
) {
332 error("missing digits after exponent indicator");
335 if (*current
== '+' || *current
== '-') {
336 if (++current
== end
) {
337 error("missing digits after exponent sign");
341 if (!IsAsciiDigit(*current
)) {
342 error("exponent part is missing a number");
345 while (++current
< end
) {
346 if (!IsAsciiDigit(*current
)) {
353 if (!FullStringToDouble(cx
, digitStart
.get(), current
.get(), &d
)) {
356 return numberToken(negative
? -d
: d
);
359 static inline bool IsJSONWhitespace(char16_t c
) {
360 return c
== '\t' || c
== '\r' || c
== '\n' || c
== ' ';
363 template <typename CharT
>
364 JSONParserBase::Token JSONParser
<CharT
>::advance() {
365 while (current
< end
&& IsJSONWhitespace(*current
)) {
368 if (current
>= end
) {
369 error("unexpected end of data");
375 return readString
<LiteralValue
>();
391 if (end
- current
< 4 || current
[1] != 'r' || current
[2] != 'u' ||
393 error("unexpected keyword");
400 if (end
- current
< 5 || current
[1] != 'a' || current
[2] != 'l' ||
401 current
[3] != 's' || current
[4] != 'e') {
402 error("unexpected keyword");
409 if (end
- current
< 4 || current
[1] != 'u' || current
[2] != 'l' ||
411 error("unexpected keyword");
419 return token(ArrayOpen
);
422 return token(ArrayClose
);
426 return token(ObjectOpen
);
429 return token(ObjectClose
);
440 error("unexpected character");
445 template <typename CharT
>
446 JSONParserBase::Token JSONParser
<CharT
>::advanceAfterObjectOpen() {
447 MOZ_ASSERT(current
[-1] == '{');
449 while (current
< end
&& IsJSONWhitespace(*current
)) {
452 if (current
>= end
) {
453 error("end of data while reading object contents");
457 if (*current
== '"') {
458 return readString
<PropertyName
>();
461 if (*current
== '}') {
463 return token(ObjectClose
);
466 error("expected property name or '}'");
470 template <typename CharT
>
471 static inline void AssertPastValue(const RangedPtr
<const CharT
> current
) {
473 * We're past an arbitrary JSON value, so the previous character is
474 * *somewhat* constrained, even if this assertion is pretty broad. Don't
475 * knock it till you tried it: this assertion *did* catch a bug once.
477 MOZ_ASSERT((current
[-1] == 'l' && current
[-2] == 'l' && current
[-3] == 'u' &&
478 current
[-4] == 'n') ||
479 (current
[-1] == 'e' && current
[-2] == 'u' && current
[-3] == 'r' &&
480 current
[-4] == 't') ||
481 (current
[-1] == 'e' && current
[-2] == 's' && current
[-3] == 'l' &&
482 current
[-4] == 'a' && current
[-5] == 'f') ||
483 current
[-1] == '}' || current
[-1] == ']' || current
[-1] == '"' ||
484 IsAsciiDigit(current
[-1]));
487 template <typename CharT
>
488 JSONParserBase::Token JSONParser
<CharT
>::advanceAfterArrayElement() {
489 AssertPastValue(current
);
491 while (current
< end
&& IsJSONWhitespace(*current
)) {
494 if (current
>= end
) {
495 error("end of data when ',' or ']' was expected");
499 if (*current
== ',') {
504 if (*current
== ']') {
506 return token(ArrayClose
);
509 error("expected ',' or ']' after array element");
513 template <typename CharT
>
514 JSONParserBase::Token JSONParser
<CharT
>::advancePropertyName() {
515 MOZ_ASSERT(current
[-1] == ',');
517 while (current
< end
&& IsJSONWhitespace(*current
)) {
520 if (current
>= end
) {
521 error("end of data when property name was expected");
525 if (*current
== '"') {
526 return readString
<PropertyName
>();
529 error("expected double-quoted property name");
533 template <typename CharT
>
534 JSONParserBase::Token JSONParser
<CharT
>::advancePropertyColon() {
535 MOZ_ASSERT(current
[-1] == '"');
537 while (current
< end
&& IsJSONWhitespace(*current
)) {
540 if (current
>= end
) {
541 error("end of data after property name when ':' was expected");
545 if (*current
== ':') {
550 error("expected ':' after property name in object");
554 template <typename CharT
>
555 JSONParserBase::Token JSONParser
<CharT
>::advanceAfterProperty() {
556 AssertPastValue(current
);
558 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");
580 inline bool JSONParserBase::finishObject(MutableHandleValue vp
,
581 PropertyVector
& properties
) {
582 MOZ_ASSERT(&properties
== &stack
.back().properties());
584 JSObject
* obj
= NewPlainObjectWithProperties(
585 cx
, properties
.begin(), properties
.length(), GenericObject
);
591 if (!freeProperties
.append(&properties
)) {
598 inline bool JSONParserBase::finishArray(MutableHandleValue vp
,
599 ElementVector
& elements
) {
600 MOZ_ASSERT(&elements
== &stack
.back().elements());
603 NewDenseCopiedArray(cx
, elements
.length(), elements
.begin());
609 if (!freeElements
.append(&elements
)) {
616 template <typename CharT
>
617 bool JSONParser
<CharT
>::parse(MutableHandleValue vp
) {
618 RootedValue
value(cx
);
619 MOZ_ASSERT(stack
.empty());
624 ParserState state
= JSONValue
;
627 case FinishObjectMember
: {
628 PropertyVector
& properties
= stack
.back().properties();
629 properties
.back().value
= value
;
631 token
= advanceAfterProperty();
632 if (token
== ObjectClose
) {
633 if (!finishObject(&value
, properties
)) {
638 if (token
!= Comma
) {
642 if (token
!= Error
) {
644 "expected ',' or '}' after property-value pair in object "
647 return errorReturn();
649 token
= advancePropertyName();
654 if (token
== String
) {
655 jsid id
= AtomToId(atomValue());
656 if (parseType
== ParseType::AttemptForEval
) {
657 // In |JSON.parse|, "__proto__" is a property like any other and may
658 // appear multiple times. In object literal syntax, "__proto__" is
659 // prototype mutation and can appear at most once. |JSONParser| only
660 // supports the former semantics, so if this parse attempt is for
661 // |eval|, return true (without reporting an error) to indicate the
662 // JSON parse attempt was unsuccessful.
663 if (id
== NameToId(cx
->names().proto
)) {
667 PropertyVector
& properties
= stack
.back().properties();
668 if (!properties
.emplaceBack(id
)) {
671 token
= advancePropertyColon();
672 if (token
!= Colon
) {
673 MOZ_ASSERT(token
== Error
);
674 return errorReturn();
681 if (token
!= Error
) {
682 error("property names must be double-quoted strings");
684 return errorReturn();
686 case FinishArrayElement
: {
687 ElementVector
& elements
= stack
.back().elements();
688 if (!elements
.append(value
.get())) {
691 token
= advanceAfterArrayElement();
692 if (token
== Comma
) {
695 if (token
== ArrayClose
) {
696 if (!finishArray(&value
, elements
)) {
701 MOZ_ASSERT(token
== Error
);
702 return errorReturn();
711 value
= stringValue();
714 value
= numberValue();
717 value
= BooleanValue(true);
720 value
= BooleanValue(false);
727 ElementVector
* elements
;
728 if (!freeElements
.empty()) {
729 elements
= freeElements
.popCopy();
732 elements
= cx
->new_
<ElementVector
>(cx
);
737 if (!stack
.append(elements
)) {
743 if (token
== ArrayClose
) {
744 if (!finishArray(&value
, *elements
)) {
749 goto JSONValueSwitch
;
753 PropertyVector
* properties
;
754 if (!freeProperties
.empty()) {
755 properties
= freeProperties
.popCopy();
758 properties
= cx
->new_
<PropertyVector
>(cx
);
763 if (!stack
.append(properties
)) {
764 js_delete(properties
);
768 token
= advanceAfterObjectOpen();
769 if (token
== ObjectClose
) {
770 if (!finishObject(&value
, *properties
)) {
782 // Move the current pointer backwards so that the position
783 // reported in the error message is correct.
785 error("unexpected character");
786 return errorReturn();
792 return errorReturn();
800 state
= stack
.back().state
;
803 for (; current
< end
; current
++) {
804 if (!IsJSONWhitespace(*current
)) {
805 error("unexpected non-whitespace character after JSON data");
806 return errorReturn();
810 MOZ_ASSERT(end
== current
);
811 MOZ_ASSERT(stack
.empty());
817 template class js::JSONParser
<Latin1Char
>;
818 template class js::JSONParser
<char16_t
>;