Bumping manifests a=b2g-bump
[gecko.git] / js / src / jsonparser.cpp
blob67ce7a53fcaeef6f3b5bb270e81dc7fc73ef921e
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"
12 #include <ctype.h>
14 #include "jsarray.h"
15 #include "jscompartment.h"
16 #include "jsnum.h"
17 #include "jsprf.h"
19 #include "vm/StringBuffer.h"
21 #include "jsobjinlines.h"
23 using namespace js;
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());
32 else
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]);
43 void
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");
51 } else {
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>
62 void
63 JSONParser<CharT>::getTextPosition(uint32_t *column, uint32_t *line)
65 CharPtr ptr = begin;
66 uint32_t col = 1;
67 uint32_t row = 1;
68 for (; ptr < current; ptr++) {
69 if (*ptr == '\n' || *ptr == '\r') {
70 ++row;
71 col = 1;
72 // \r\n is treated as a single newline.
73 if (ptr + 1 < current && *ptr == '\r' && *(ptr + 1) == '\n')
74 ++ptr;
75 } else {
76 ++col;
79 *column = col;
80 *line = row;
83 template <typename CharT>
84 void
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);
102 bool
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 == '"');
117 * JSONString:
118 * /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
121 if (++current == end) {
122 error("unterminated string literal");
123 return token(Error);
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;
134 current++;
135 JSFlatString *str = (ST == JSONParser::PropertyName)
136 ? AtomizeChars(cx, start.get(), length)
137 : NewStringCopyN<CanGC>(cx, start.get(), length);
138 if (!str)
139 return token(OOM);
140 return stringToken(str);
143 if (*current == '\\')
144 break;
146 if (*current <= 0x001F) {
147 error("bad control character in string literal");
148 return token(Error);
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);
158 do {
159 if (start < current && !buffer.append(start.get(), current.get()))
160 return token(OOM);
162 if (current >= end)
163 break;
165 jschar c = *current++;
166 if (c == '"') {
167 JSFlatString *str = (ST == JSONParser::PropertyName)
168 ? buffer.finishAtom()
169 : buffer.finishString();
170 if (!str)
171 return token(OOM);
172 return stringToken(str);
175 if (c != '\\') {
176 --current;
177 error("bad character in string literal");
178 return token(Error);
181 if (current >= end)
182 break;
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;
194 case 'u':
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
202 // missing).
203 if (current == end || !JS7_ISHEX(current[0]))
204 ; // already at correct location
205 else if (current + 1 == end || !JS7_ISHEX(current[1]))
206 current += 1;
207 else if (current + 2 == end || !JS7_ISHEX(current[2]))
208 current += 2;
209 else if (current + 3 == end || !JS7_ISHEX(current[3]))
210 current += 3;
211 else
212 MOZ_CRASH("logic error determining first erroneous character");
214 error("bad Unicode escape");
215 return token(Error);
217 c = (JS7_UNHEX(current[0]) << 12)
218 | (JS7_UNHEX(current[1]) << 8)
219 | (JS7_UNHEX(current[2]) << 4)
220 | (JS7_UNHEX(current[3]));
221 current += 4;
222 break;
224 default:
225 current--;
226 error("bad escaped character");
227 return token(Error);
229 if (!buffer.append(c))
230 return token(OOM);
232 start = current;
233 for (; current < end; current++) {
234 if (*current == '"' || *current == '\\' || *current <= 0x001F)
235 break;
237 } while (current < end);
239 error("unterminated string");
240 return token(Error);
243 template <typename CharT>
244 JSONParserBase::Token
245 JSONParser<CharT>::readNumber()
247 JS_ASSERT(current < end);
248 JS_ASSERT(JS7_ISDEC(*current) || *current == '-');
251 * JSONNumber:
252 * /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
255 bool negative = *current == '-';
257 /* -? */
258 if (negative && ++current == end) {
259 error("no number after minus sign");
260 return token(Error);
263 const CharPtr digitStart = current;
265 /* 0|[1-9][0-9]+ */
266 if (!JS7_ISDEC(*current)) {
267 error("unexpected non-digit");
268 return token(Error);
270 if (*current++ != '0') {
271 for (; current < end; current++) {
272 if (!JS7_ISDEC(*current))
273 break;
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);
289 double d;
290 const CharT *dummy;
291 if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
292 return token(OOM);
293 JS_ASSERT(current == dummy);
294 return numberToken(negative ? -d : d);
297 /* (\.[0-9]+)? */
298 if (current < end && *current == '.') {
299 if (++current == end) {
300 error("missing digits after decimal point");
301 return token(Error);
303 if (!JS7_ISDEC(*current)) {
304 error("unterminated fractional number");
305 return token(Error);
307 while (++current < end) {
308 if (!JS7_ISDEC(*current))
309 break;
313 /* ([eE][\+\-]?[0-9]+)? */
314 if (current < end && (*current == 'e' || *current == 'E')) {
315 if (++current == end) {
316 error("missing digits after exponent indicator");
317 return token(Error);
319 if (*current == '+' || *current == '-') {
320 if (++current == end) {
321 error("missing digits after exponent sign");
322 return token(Error);
325 if (!JS7_ISDEC(*current)) {
326 error("exponent part is missing a number");
327 return token(Error);
329 while (++current < end) {
330 if (!JS7_ISDEC(*current))
331 break;
335 double d;
336 const CharT *finish;
337 if (!js_strtod(cx, digitStart.get(), current.get(), &finish, &d))
338 return token(OOM);
339 JS_ASSERT(current == finish);
340 return numberToken(negative ? -d : d);
343 static inline bool
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))
354 current++;
355 if (current >= end) {
356 error("unexpected end of data");
357 return token(Error);
360 switch (*current) {
361 case '"':
362 return readString<LiteralValue>();
364 case '-':
365 case '0':
366 case '1':
367 case '2':
368 case '3':
369 case '4':
370 case '5':
371 case '6':
372 case '7':
373 case '8':
374 case '9':
375 return readNumber();
377 case 't':
378 if (end - current < 4 || current[1] != 'r' || current[2] != 'u' || current[3] != 'e') {
379 error("unexpected keyword");
380 return token(Error);
382 current += 4;
383 return token(True);
385 case 'f':
386 if (end - current < 5 ||
387 current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e')
389 error("unexpected keyword");
390 return token(Error);
392 current += 5;
393 return token(False);
395 case 'n':
396 if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') {
397 error("unexpected keyword");
398 return token(Error);
400 current += 4;
401 return token(Null);
403 case '[':
404 current++;
405 return token(ArrayOpen);
406 case ']':
407 current++;
408 return token(ArrayClose);
410 case '{':
411 current++;
412 return token(ObjectOpen);
413 case '}':
414 current++;
415 return token(ObjectClose);
417 case ',':
418 current++;
419 return token(Comma);
421 case ':':
422 current++;
423 return token(Colon);
425 default:
426 error("unexpected character");
427 return token(Error);
431 template <typename CharT>
432 JSONParserBase::Token
433 JSONParser<CharT>::advanceAfterObjectOpen()
435 JS_ASSERT(current[-1] == '{');
437 while (current < end && IsJSONWhitespace(*current))
438 current++;
439 if (current >= end) {
440 error("end of data while reading object contents");
441 return token(Error);
444 if (*current == '"')
445 return readString<PropertyName>();
447 if (*current == '}') {
448 current++;
449 return token(ObjectClose);
452 error("expected property name or '}'");
453 return token(Error);
456 template <typename CharT>
457 static inline void
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))
491 current++;
492 if (current >= end) {
493 error("end of data when ',' or ']' was expected");
494 return token(Error);
497 if (*current == ',') {
498 current++;
499 return token(Comma);
502 if (*current == ']') {
503 current++;
504 return token(ArrayClose);
507 error("expected ',' or ']' after array element");
508 return token(Error);
511 template <typename CharT>
512 JSONParserBase::Token
513 JSONParser<CharT>::advancePropertyName()
515 JS_ASSERT(current[-1] == ',');
517 while (current < end && IsJSONWhitespace(*current))
518 current++;
519 if (current >= end) {
520 error("end of data when property name was expected");
521 return token(Error);
524 if (*current == '"')
525 return readString<PropertyName>();
527 error("expected double-quoted property name");
528 return token(Error);
531 template <typename CharT>
532 JSONParserBase::Token
533 JSONParser<CharT>::advancePropertyColon()
535 JS_ASSERT(current[-1] == '"');
537 while (current < end && IsJSONWhitespace(*current))
538 current++;
539 if (current >= end) {
540 error("end of data after property name when ':' was expected");
541 return token(Error);
544 if (*current == ':') {
545 current++;
546 return token(Colon);
549 error("expected ':' after property name in object");
550 return token(Error);
553 template <typename CharT>
554 JSONParserBase::Token
555 JSONParser<CharT>::advanceAfterProperty()
557 AssertPastValue(current);
559 while (current < end && IsJSONWhitespace(*current))
560 current++;
561 if (current >= end) {
562 error("end of data after property value in object");
563 return token(Error);
566 if (*current == ',') {
567 current++;
568 return token(Comma);
571 if (*current == '}') {
572 current++;
573 return token(ObjectClose);
576 error("expected ',' or '}' after property value in object");
577 return token(Error);
580 JSObject *
581 JSONParserBase::createFinishedObject(PropertyVector &properties)
584 * Look for an existing cached type and shape for objects with this set of
585 * properties.
588 JSObject *obj = cx->compartment()->types.newTypedObject(cx, properties.begin(),
589 properties.length());
590 if (obj)
591 return obj;
595 * Make a new object sized for the given number of properties and fill its
596 * shape in manually.
598 gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length());
599 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind));
600 if (!obj)
601 return nullptr;
603 RootedId propid(cx);
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,
610 JSPROP_ENUMERATE)) {
611 return nullptr;
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);
622 return obj;
625 inline bool
626 JSONParserBase::finishObject(MutableHandleValue vp, PropertyVector &properties)
628 JS_ASSERT(&properties == &stack.back().properties());
630 JSObject *obj = createFinishedObject(properties);
631 if (!obj)
632 return false;
634 vp.setObject(*obj);
635 if (!freeProperties.append(&properties))
636 return false;
637 stack.popBack();
638 return true;
641 inline bool
642 JSONParserBase::finishArray(MutableHandleValue vp, ElementVector &elements)
644 JS_ASSERT(&elements == &stack.back().elements());
646 JSObject *obj = NewDenseCopiedArray(cx, elements.length(), elements.begin());
647 if (!obj)
648 return false;
650 /* Try to assign a new type to the array according to its elements. */
651 cx->compartment()->types.fixArrayType(cx, obj);
653 vp.setObject(*obj);
654 if (!freeElements.append(&elements))
655 return false;
656 stack.popBack();
657 return true;
660 template <typename CharT>
661 bool
662 JSONParser<CharT>::parse(MutableHandleValue vp)
664 RootedValue value(cx);
665 JS_ASSERT(stack.empty());
667 vp.setUndefined();
669 Token token;
670 ParserState state = JSONValue;
671 while (true) {
672 switch (state) {
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))
680 return false;
681 break;
683 if (token != Comma) {
684 if (token == OOM)
685 return false;
686 if (token != Error)
687 error("expected ',' or '}' after property-value pair in object literal");
688 return errorReturn();
690 token = advancePropertyName();
691 /* FALL THROUGH */
694 JSONMember:
695 if (token == String) {
696 jsid id = AtomToId(atomValue());
697 PropertyVector &properties = stack.back().properties();
698 if (!properties.append(IdValuePair(id)))
699 return false;
700 token = advancePropertyColon();
701 if (token != Colon) {
702 JS_ASSERT(token == Error);
703 return errorReturn();
705 goto JSONValue;
707 if (token == OOM)
708 return false;
709 if (token != Error)
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()))
716 return false;
717 token = advanceAfterArrayElement();
718 if (token == Comma)
719 goto JSONValue;
720 if (token == ArrayClose) {
721 if (!finishArray(&value, elements))
722 return false;
723 break;
725 JS_ASSERT(token == Error);
726 return errorReturn();
729 JSONValue:
730 case JSONValue:
731 token = advance();
732 JSONValueSwitch:
733 switch (token) {
734 case String:
735 value = stringValue();
736 break;
737 case Number:
738 value = numberValue();
739 break;
740 case True:
741 value = BooleanValue(true);
742 break;
743 case False:
744 value = BooleanValue(false);
745 break;
746 case Null:
747 value = NullValue();
748 break;
750 case ArrayOpen: {
751 ElementVector *elements;
752 if (!freeElements.empty()) {
753 elements = freeElements.popCopy();
754 elements->clear();
755 } else {
756 elements = cx->new_<ElementVector>(cx);
757 if (!elements)
758 return false;
760 if (!stack.append(elements))
761 return false;
763 token = advance();
764 if (token == ArrayClose) {
765 if (!finishArray(&value, *elements))
766 return false;
767 break;
769 goto JSONValueSwitch;
772 case ObjectOpen: {
773 PropertyVector *properties;
774 if (!freeProperties.empty()) {
775 properties = freeProperties.popCopy();
776 properties->clear();
777 } else {
778 properties = cx->new_<PropertyVector>(cx);
779 if (!properties)
780 return false;
782 if (!stack.append(properties))
783 return false;
785 token = advanceAfterObjectOpen();
786 if (token == ObjectClose) {
787 if (!finishObject(&value, *properties))
788 return false;
789 break;
791 goto JSONMember;
794 case ArrayClose:
795 case ObjectClose:
796 case Colon:
797 case Comma:
798 // Move the current pointer backwards so that the position
799 // reported in the error message is correct.
800 --current;
801 error("unexpected character");
802 return errorReturn();
804 case OOM:
805 return false;
807 case Error:
808 return errorReturn();
810 break;
813 if (stack.empty())
814 break;
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());
828 vp.set(value);
829 return true;
832 template class js::JSONParser<Latin1Char>;
833 template class js::JSONParser<jschar>;