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 :
47 : #include "vm/StringBuffer-inl.h"
48 :
49 : using namespace js;
50 :
51 : void
52 3173 : JSONParser::error(const char *msg)
53 : {
54 3173 : if (errorHandling == RaiseError)
55 210 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE, msg);
56 3173 : }
57 :
58 : bool
59 3173 : JSONParser::errorReturn()
60 : {
61 3173 : return errorHandling == NoError;
62 : }
63 :
64 : template<JSONParser::StringType ST>
65 : JSONParser::Token
66 350881 : JSONParser::readString()
67 : {
68 350881 : JS_ASSERT(current < end);
69 350881 : JS_ASSERT(*current == '"');
70 :
71 : /*
72 : * JSONString:
73 : * /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
74 : */
75 :
76 350881 : if (++current == end) {
77 1 : error("unterminated string literal");
78 1 : return token(Error);
79 : }
80 :
81 : /*
82 : * Optimization: if the source contains no escaped characters, create the
83 : * string directly from the source text.
84 : */
85 350880 : RangedPtr<const jschar> start = current;
86 9630847 : for (; current < end; current++) {
87 9630847 : if (*current == '"') {
88 346968 : size_t length = current - start;
89 346968 : current++;
90 : JSFlatString *str = (ST == JSONParser::PropertyName)
91 : ? js_AtomizeChars(cx, start.get(), length)
92 346968 : : js_NewStringCopyN(cx, start.get(), length);
93 346968 : if (!str)
94 0 : return token(OOM);
95 346968 : return stringToken(str);
96 : }
97 :
98 9283879 : if (*current == '\\')
99 3912 : break;
100 :
101 9279967 : if (*current <= 0x001F) {
102 0 : error("bad control character in string literal");
103 0 : return token(Error);
104 : }
105 : }
106 :
107 : /*
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.
111 : */
112 7824 : StringBuffer buffer(cx);
113 104080 : do {
114 107992 : if (start < current && !buffer.append(start.get(), current.get()))
115 0 : return token(OOM);
116 :
117 107992 : if (current >= end)
118 0 : break;
119 :
120 107992 : jschar c = *current++;
121 107992 : if (c == '"') {
122 : JSFlatString *str = (ST == JSONParser::PropertyName)
123 : ? buffer.finishAtom()
124 3912 : : buffer.finishString();
125 3912 : if (!str)
126 0 : return token(OOM);
127 3912 : return stringToken(str);
128 : }
129 :
130 104080 : if (c != '\\') {
131 0 : error("bad character in string literal");
132 0 : return token(Error);
133 : }
134 :
135 104080 : if (current >= end)
136 0 : break;
137 :
138 104080 : switch (*current++) {
139 74776 : case '"': c = '"'; break;
140 0 : case '/': c = '/'; break;
141 29299 : case '\\': c = '\\'; break;
142 1 : case 'b': c = '\b'; break;
143 0 : case 'f': c = '\f'; break;
144 1 : case 'n': c = '\n'; break;
145 0 : case 'r': c = '\r'; break;
146 0 : case 't': c = '\t'; break;
147 :
148 : case 'u':
149 3 : if (end - current < 4) {
150 0 : error("bad Unicode escape");
151 0 : return token(Error);
152 : }
153 3 : if (JS7_ISHEX(current[0]) &&
154 : JS7_ISHEX(current[1]) &&
155 : JS7_ISHEX(current[2]) &&
156 : JS7_ISHEX(current[3]))
157 : {
158 3 : c = (JS7_UNHEX(current[0]) << 12)
159 : | (JS7_UNHEX(current[1]) << 8)
160 : | (JS7_UNHEX(current[2]) << 4)
161 : | (JS7_UNHEX(current[3]));
162 3 : current += 4;
163 3 : break;
164 : }
165 : /* FALL THROUGH */
166 :
167 : default:
168 0 : error("bad escaped character");
169 0 : return token(Error);
170 : }
171 104080 : if (!buffer.append(c))
172 0 : return token(OOM);
173 :
174 104080 : start = current;
175 628707 : for (; current < end; current++) {
176 628707 : if (*current == '"' || *current == '\\' || *current <= 0x001F)
177 208160 : break;
178 : }
179 : } while (current < end);
180 :
181 0 : error("unterminated string");
182 0 : return token(Error);
183 : }
184 :
185 : JSONParser::Token
186 63779 : JSONParser::readNumber()
187 : {
188 63779 : JS_ASSERT(current < end);
189 63779 : JS_ASSERT(JS7_ISDEC(*current) || *current == '-');
190 :
191 : /*
192 : * JSONNumber:
193 : * /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
194 : */
195 :
196 63779 : bool negative = *current == '-';
197 :
198 : /* -? */
199 63779 : if (negative && ++current == end) {
200 0 : error("no number after minus sign");
201 0 : return token(Error);
202 : }
203 :
204 63779 : const RangedPtr<const jschar> digitStart = current;
205 :
206 : /* 0|[1-9][0-9]+ */
207 63779 : if (!JS7_ISDEC(*current)) {
208 162 : error("unexpected non-digit");
209 162 : return token(Error);
210 : }
211 63617 : if (*current++ != '0') {
212 547735 : for (; current < end; current++) {
213 547729 : if (!JS7_ISDEC(*current))
214 61732 : break;
215 : }
216 : }
217 :
218 : /* Fast path: no fractional or exponent part. */
219 63617 : if (current == end || (*current != '.' && *current != 'e' && *current != 'E')) {
220 : const jschar *dummy;
221 : double d;
222 61307 : if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
223 0 : return token(OOM);
224 61307 : JS_ASSERT(current == dummy);
225 61307 : return numberToken(negative ? -d : d);
226 : }
227 :
228 : /* (\.[0-9]+)? */
229 2310 : if (current < end && *current == '.') {
230 2308 : if (++current == end) {
231 0 : error("missing digits after decimal point");
232 0 : return token(Error);
233 : }
234 2308 : if (!JS7_ISDEC(*current)) {
235 0 : error("unterminated fractional number");
236 0 : return token(Error);
237 : }
238 10079 : while (++current < end) {
239 7744 : if (!JS7_ISDEC(*current))
240 2281 : break;
241 : }
242 : }
243 :
244 : /* ([eE][\+\-]?[0-9]+)? */
245 2310 : if (current < end && (*current == 'e' || *current == 'E')) {
246 2 : if (++current == end) {
247 0 : error("missing digits after exponent indicator");
248 0 : return token(Error);
249 : }
250 2 : if (*current == '+' || *current == '-') {
251 0 : if (++current == end) {
252 0 : error("missing digits after exponent sign");
253 0 : return token(Error);
254 : }
255 : }
256 2 : if (!JS7_ISDEC(*current)) {
257 0 : error("exponent part is missing a number");
258 0 : return token(Error);
259 : }
260 8 : while (++current < end) {
261 4 : if (!JS7_ISDEC(*current))
262 0 : break;
263 : }
264 : }
265 :
266 : double d;
267 : const jschar *finish;
268 2310 : if (!js_strtod(cx, digitStart.get(), current.get(), &finish, &d))
269 0 : return token(OOM);
270 2310 : JS_ASSERT(current == finish);
271 2310 : return numberToken(negative ? -d : d);
272 : }
273 :
274 : static inline bool
275 1393490 : IsJSONWhitespace(jschar c)
276 : {
277 1393490 : return c == '\t' || c == '\r' || c == '\n' || c == ' ';
278 : }
279 :
280 : JSONParser::Token
281 273894 : JSONParser::advance()
282 : {
283 666032 : while (current < end && IsJSONWhitespace(*current))
284 118244 : current++;
285 273894 : if (current >= end) {
286 1 : error("unexpected end of data");
287 1 : return token(Error);
288 : }
289 :
290 273893 : switch (*current) {
291 : case '"':
292 139436 : return readString<LiteralValue>();
293 :
294 : case '-':
295 : case '0':
296 : case '1':
297 : case '2':
298 : case '3':
299 : case '4':
300 : case '5':
301 : case '6':
302 : case '7':
303 : case '8':
304 : case '9':
305 63779 : return readNumber();
306 :
307 : case 't':
308 247 : if (end - current < 4 || current[1] != 'r' || current[2] != 'u' || current[3] != 'e') {
309 0 : error("unexpected keyword");
310 0 : return token(Error);
311 : }
312 247 : current += 4;
313 247 : return token(True);
314 :
315 : case 'f':
316 18235 : if (end - current < 5 ||
317 14021 : current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e')
318 : {
319 945 : error("unexpected keyword");
320 945 : return token(Error);
321 : }
322 3269 : current += 5;
323 3269 : return token(False);
324 :
325 : case 'n':
326 3952 : if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') {
327 18 : error("unexpected keyword");
328 18 : return token(Error);
329 : }
330 3934 : current += 4;
331 3934 : return token(Null);
332 :
333 : case '[':
334 9161 : current++;
335 9161 : return token(ArrayOpen);
336 : case ']':
337 1896 : current++;
338 1896 : return token(ArrayClose);
339 :
340 : case '{':
341 49365 : current++;
342 49365 : return token(ObjectOpen);
343 : case '}':
344 1 : current++;
345 1 : return token(ObjectClose);
346 :
347 : case ',':
348 28 : current++;
349 28 : return token(Comma);
350 :
351 : case ':':
352 0 : current++;
353 0 : return token(Colon);
354 :
355 : default:
356 1814 : error("unexpected character");
357 1814 : return token(Error);
358 : }
359 : }
360 :
361 : JSONParser::Token
362 49365 : JSONParser::advanceAfterObjectOpen()
363 : {
364 49365 : JS_ASSERT(current[-1] == '{');
365 :
366 219003 : while (current < end && IsJSONWhitespace(*current))
367 120273 : current++;
368 49365 : if (current >= end) {
369 0 : error("end of data while reading object contents");
370 0 : return token(Error);
371 : }
372 :
373 49365 : if (*current == '"')
374 48042 : return readString<PropertyName>();
375 :
376 1323 : if (*current == '}') {
377 1303 : current++;
378 1303 : return token(ObjectClose);
379 : }
380 :
381 20 : error("expected property name or '}'");
382 20 : return token(Error);
383 : }
384 :
385 : static inline void
386 256724 : AssertPastValue(const RangedPtr<const jschar> current)
387 : {
388 : /*
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.
392 : */
393 1513165 : 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 1513165 : JS7_ISDEC(current[-1]));
410 256724 : }
411 :
412 : JSONParser::Token
413 45279 : JSONParser::advanceAfterArrayElement()
414 : {
415 45279 : AssertPastValue(current);
416 :
417 90954 : while (current < end && IsJSONWhitespace(*current))
418 396 : current++;
419 45279 : if (current >= end) {
420 0 : error("end of data when ',' or ']' was expected");
421 0 : return token(Error);
422 : }
423 :
424 45279 : if (*current == ',') {
425 39782 : current++;
426 39782 : return token(Comma);
427 : }
428 :
429 5497 : if (*current == ']') {
430 5362 : current++;
431 5362 : return token(ArrayClose);
432 : }
433 :
434 135 : error("expected ',' or ']' after array element");
435 135 : return token(Error);
436 : }
437 :
438 : JSONParser::Token
439 163404 : JSONParser::advancePropertyName()
440 : {
441 163404 : JS_ASSERT(current[-1] == ',');
442 :
443 454303 : while (current < end && IsJSONWhitespace(*current))
444 127495 : current++;
445 163404 : if (current >= end) {
446 0 : error("end of data when property name was expected");
447 0 : return token(Error);
448 : }
449 :
450 163404 : if (*current == '"')
451 163403 : return readString<PropertyName>();
452 :
453 1 : if (parsingMode == LegacyJSON && *current == '}') {
454 : /*
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
460 : * requested.
461 : */
462 0 : current++;
463 0 : return token(ObjectClose);
464 : }
465 :
466 1 : error("expected double-quoted property name");
467 1 : return token(Error);
468 : }
469 :
470 : JSONParser::Token
471 211445 : JSONParser::advancePropertyColon()
472 : {
473 211445 : JS_ASSERT(current[-1] == '"');
474 :
475 422890 : while (current < end && IsJSONWhitespace(*current))
476 0 : current++;
477 211445 : if (current >= end) {
478 0 : error("end of data after property name when ':' was expected");
479 0 : return token(Error);
480 : }
481 :
482 211445 : if (*current == ':') {
483 211445 : current++;
484 211445 : return token(Colon);
485 : }
486 :
487 0 : error("expected ':' after property name in object");
488 0 : return token(Error);
489 : }
490 :
491 : JSONParser::Token
492 211445 : JSONParser::advanceAfterProperty()
493 : {
494 211445 : AssertPastValue(current);
495 :
496 495103 : while (current < end && IsJSONWhitespace(*current))
497 72213 : current++;
498 211445 : if (current >= end) {
499 0 : error("end of data after property value in object");
500 0 : return token(Error);
501 : }
502 :
503 211445 : if (*current == ',') {
504 163404 : current++;
505 163404 : return token(Comma);
506 : }
507 :
508 48041 : if (*current == '}') {
509 48041 : current++;
510 48041 : return token(ObjectClose);
511 : }
512 :
513 0 : error("expected ',' or '}' after property value in object");
514 0 : return token(Error);
515 : }
516 :
517 : /*
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!
520 : */
521 : enum ParserState { FinishArrayElement, FinishObjectMember, JSONValue };
522 :
523 : bool
524 13506 : JSONParser::parse(Value *vp)
525 : {
526 27012 : Vector<ParserState> stateStack(cx);
527 27012 : AutoValueVector valueStack(cx);
528 :
529 13506 : *vp = UndefinedValue();
530 :
531 : Token token;
532 13506 : ParserState state = JSONValue;
533 256724 : while (true) {
534 270230 : switch (state) {
535 : case FinishObjectMember: {
536 211445 : Value v = valueStack.popCopy();
537 : /*
538 : * NB: Relies on js_DefineNativeProperty performing
539 : * js_CheckForStringIndex.
540 : */
541 211445 : jsid propid = ATOM_TO_JSID(&valueStack.popCopy().toString()->asAtom());
542 422890 : if (!DefineNativeProperty(cx, &valueStack.back().toObject(), propid, v,
543 : JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE,
544 211445 : 0, 0))
545 : {
546 0 : return false;
547 : }
548 211445 : token = advanceAfterProperty();
549 211445 : if (token == ObjectClose)
550 48041 : break;
551 163404 : if (token != Comma) {
552 0 : if (token == OOM)
553 0 : return false;
554 0 : if (token != Error)
555 0 : error("expected ',' or '}' after property-value pair in object literal");
556 0 : return errorReturn();
557 : }
558 163404 : token = advancePropertyName();
559 : /* FALL THROUGH */
560 : }
561 :
562 : JSONMember:
563 211466 : if (token == String) {
564 211445 : if (!valueStack.append(atomValue()))
565 0 : return false;
566 211445 : token = advancePropertyColon();
567 211445 : if (token != Colon) {
568 0 : JS_ASSERT(token == Error);
569 0 : return errorReturn();
570 : }
571 211445 : if (!stateStack.append(FinishObjectMember))
572 0 : return false;
573 211445 : goto JSONValue;
574 : }
575 21 : if (token == ObjectClose) {
576 0 : JS_ASSERT(state == FinishObjectMember);
577 0 : JS_ASSERT(parsingMode == LegacyJSON);
578 0 : break;
579 : }
580 21 : if (token == OOM)
581 0 : return false;
582 21 : if (token != Error)
583 0 : error("property names must be double-quoted strings");
584 21 : return errorReturn();
585 :
586 : case FinishArrayElement: {
587 45279 : Value v = valueStack.popCopy();
588 45279 : if (!js_NewbornArrayPush(cx, &valueStack.back().toObject(), v))
589 0 : return false;
590 45279 : token = advanceAfterArrayElement();
591 45279 : if (token == Comma) {
592 39782 : if (!stateStack.append(FinishArrayElement))
593 0 : return false;
594 39782 : goto JSONValue;
595 : }
596 5497 : if (token == ArrayClose)
597 5362 : break;
598 135 : JS_ASSERT(token == Error);
599 135 : return errorReturn();
600 : }
601 :
602 : JSONValue:
603 : case JSONValue:
604 264733 : token = advance();
605 : JSONValueSwitch:
606 272012 : switch (token) {
607 : case String:
608 : case Number:
609 203052 : if (!valueStack.append(token == String ? stringValue() : numberValue()))
610 0 : return false;
611 203052 : break;
612 : case True:
613 247 : if (!valueStack.append(BooleanValue(true)))
614 0 : return false;
615 247 : break;
616 : case False:
617 3269 : if (!valueStack.append(BooleanValue(false)))
618 0 : return false;
619 3269 : break;
620 : case Null:
621 3934 : if (!valueStack.append(NullValue()))
622 0 : return false;
623 3934 : break;
624 :
625 : case ArrayOpen: {
626 9161 : JSObject *obj = NewDenseEmptyArray(cx);
627 9161 : if (!obj || !valueStack.append(ObjectValue(*obj)))
628 0 : return false;
629 9161 : token = advance();
630 9161 : if (token == ArrayClose)
631 1882 : break;
632 7279 : if (!stateStack.append(FinishArrayElement))
633 0 : return false;
634 7279 : goto JSONValueSwitch;
635 : }
636 :
637 : case ObjectOpen: {
638 49365 : JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
639 49365 : if (!obj || !valueStack.append(ObjectValue(*obj)))
640 0 : return false;
641 49365 : token = advanceAfterObjectOpen();
642 49365 : if (token == ObjectClose)
643 1303 : break;
644 48062 : goto JSONMember;
645 : }
646 :
647 : case ArrayClose:
648 20 : if (parsingMode == LegacyJSON &&
649 3 : !stateStack.empty() &&
650 3 : stateStack.back() == FinishArrayElement) {
651 : /*
652 : * Previous JSON parsing accepted trailing commas in
653 : * non-empty array syntax, and some users depend on this.
654 : * (Specifically, Places data serialization in versions of
655 : * Firefox prior to 4.0. We can remove this mode when
656 : * profile upgrades from 3.6 become unsupported.) Permit
657 : * such trailing commas only when specifically
658 : * instructed to do so.
659 : */
660 3 : stateStack.popBack();
661 3 : break;
662 : }
663 : /* FALL THROUGH */
664 :
665 : case ObjectClose:
666 : case Colon:
667 : case Comma:
668 40 : error("unexpected character");
669 40 : return errorReturn();
670 :
671 : case OOM:
672 0 : return false;
673 :
674 : case Error:
675 2941 : return errorReturn();
676 : }
677 213690 : break;
678 : }
679 :
680 267093 : if (stateStack.empty())
681 : break;
682 256724 : state = stateStack.popCopy();
683 : }
684 :
685 10371 : for (; current < end; current++) {
686 38 : if (!IsJSONWhitespace(*current)) {
687 36 : error("unexpected non-whitespace character after JSON data");
688 36 : return errorReturn();
689 : }
690 : }
691 :
692 10333 : JS_ASSERT(end == current);
693 10333 : JS_ASSERT(valueStack.length() == 1);
694 10333 : *vp = valueStack[0];
695 10333 : return true;
696 : }
|