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 : * Mozilla Corporation.
21 : * Portions created by the Initial Developer are Copyright (C) 1998-1999
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Robert Sayre <sayrer@gmail.com>
26 : * Dave Camp <dcamp@mozilla.com>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either of the GNU General Public License Version 2 or later (the "GPL"),
30 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 : #include <string.h>
43 : #include "jsapi.h"
44 : #include "jsarray.h"
45 : #include "jsatom.h"
46 : #include "jsbool.h"
47 : #include "jscntxt.h"
48 : #include "jsfun.h"
49 : #include "jsinterp.h"
50 : #include "jsiter.h"
51 : #include "jsnum.h"
52 : #include "jsobj.h"
53 : #include "json.h"
54 : #include "jsonparser.h"
55 : #include "jsprf.h"
56 : #include "jsstr.h"
57 : #include "jstypes.h"
58 : #include "jsutil.h"
59 : #include "jsxml.h"
60 :
61 : #include "frontend/TokenStream.h"
62 :
63 : #include "jsatominlines.h"
64 : #include "jsboolinlines.h"
65 : #include "jsinferinlines.h"
66 : #include "jsobjinlines.h"
67 :
68 : #include "vm/Stack-inl.h"
69 : #include "vm/StringBuffer-inl.h"
70 :
71 : using namespace js;
72 : using namespace js::gc;
73 : using namespace js::types;
74 :
75 : Class js::JSONClass = {
76 : js_JSON_str,
77 : JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
78 : JS_PropertyStub, /* addProperty */
79 : JS_PropertyStub, /* delProperty */
80 : JS_PropertyStub, /* getProperty */
81 : JS_StrictPropertyStub, /* setProperty */
82 : JS_EnumerateStub,
83 : JS_ResolveStub,
84 : JS_ConvertStub
85 : };
86 :
87 : /* ES5 15.12.2. */
88 : JSBool
89 8177 : js_json_parse(JSContext *cx, unsigned argc, Value *vp)
90 : {
91 : /* Step 1. */
92 : JSLinearString *linear;
93 8177 : if (argc >= 1) {
94 7997 : JSString *str = ToString(cx, vp[2]);
95 7997 : if (!str)
96 0 : return false;
97 7997 : linear = str->ensureLinear(cx);
98 7997 : if (!linear)
99 0 : return false;
100 : } else {
101 180 : linear = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
102 : }
103 16354 : JS::Anchor<JSString *> anchor(linear);
104 :
105 8177 : Value reviver = (argc >= 2) ? vp[3] : UndefinedValue();
106 :
107 : /* Steps 2-5. */
108 8177 : return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, vp);
109 : }
110 :
111 : /* ES5 15.12.3. */
112 : JSBool
113 16298 : js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
114 : {
115 16298 : *vp = (argc >= 1) ? vp[2] : UndefinedValue();
116 2644 : JSObject *replacer = (argc >= 2 && vp[3].isObject())
117 1860 : ? &vp[3].toObject()
118 20802 : : NULL;
119 16298 : Value space = (argc >= 3) ? vp[4] : UndefinedValue();
120 :
121 32596 : StringBuffer sb(cx);
122 16298 : if (!js_Stringify(cx, vp, replacer, space, sb))
123 0 : return false;
124 :
125 : // XXX This can never happen to nsJSON.cpp, but the JSON object
126 : // needs to support returning undefined. So this is a little awkward
127 : // for the API, because we want to support streaming writers.
128 16298 : if (!sb.empty()) {
129 16281 : JSString *str = sb.finishString();
130 16281 : if (!str)
131 0 : return false;
132 16281 : vp->setString(str);
133 : } else {
134 17 : vp->setUndefined();
135 : }
136 :
137 16298 : return true;
138 : }
139 :
140 8726255 : static inline bool IsQuoteSpecialCharacter(jschar c)
141 : {
142 : JS_STATIC_ASSERT('\b' < ' ');
143 : JS_STATIC_ASSERT('\f' < ' ');
144 : JS_STATIC_ASSERT('\n' < ' ');
145 : JS_STATIC_ASSERT('\r' < ' ');
146 : JS_STATIC_ASSERT('\t' < ' ');
147 8726255 : return c == '"' || c == '\\' || c < ' ';
148 : }
149 :
150 : /* ES5 15.12.3 Quote. */
151 : static bool
152 188022 : Quote(JSContext *cx, StringBuffer &sb, JSString *str)
153 : {
154 376044 : JS::Anchor<JSString *> anchor(str);
155 188022 : size_t len = str->length();
156 188022 : const jschar *buf = str->getChars(cx);
157 188022 : if (!buf)
158 0 : return false;
159 :
160 : /* Step 1. */
161 188022 : if (!sb.append('"'))
162 0 : return false;
163 :
164 : /* Step 2. */
165 354224 : for (size_t i = 0; i < len; ++i) {
166 : /* Batch-append maximal character sequences containing no escapes. */
167 354035 : size_t mark = i;
168 8560053 : do {
169 8726255 : if (IsQuoteSpecialCharacter(buf[i]))
170 166202 : break;
171 : } while (++i < len);
172 354035 : if (i > mark) {
173 303745 : if (!sb.append(&buf[mark], i - mark))
174 0 : return false;
175 303745 : if (i == len)
176 187833 : break;
177 : }
178 :
179 166202 : jschar c = buf[i];
180 166202 : if (c == '"' || c == '\\') {
181 331104 : if (!sb.append('\\') || !sb.append(c))
182 0 : return false;
183 650 : } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
184 : jschar abbrev = (c == '\b')
185 : ? 'b'
186 : : (c == '\f')
187 : ? 'f'
188 : : (c == '\n')
189 : ? 'n'
190 : : (c == '\r')
191 : ? 'r'
192 1 : : 't';
193 1 : if (!sb.append('\\') || !sb.append(abbrev))
194 0 : return false;
195 : } else {
196 649 : JS_ASSERT(c < ' ');
197 649 : if (!sb.append("\\u00"))
198 0 : return false;
199 649 : JS_ASSERT((c >> 4) < 10);
200 649 : uint8_t x = c >> 4, y = c % 16;
201 649 : if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10)))
202 0 : return false;
203 : }
204 : }
205 :
206 : /* Steps 3-4. */
207 188022 : return sb.append('"');
208 : }
209 :
210 : class StringifyContext
211 : {
212 : public:
213 16866 : StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap,
214 : JSObject *replacer, const AutoIdVector &propertyList)
215 : : sb(sb),
216 : gap(gap),
217 : replacer(replacer),
218 : propertyList(propertyList),
219 : depth(0),
220 16866 : objectStack(cx)
221 16866 : {}
222 :
223 16866 : bool init() {
224 16866 : return objectStack.init(16);
225 : }
226 :
227 : #ifdef DEBUG
228 16866 : ~StringifyContext() { JS_ASSERT(objectStack.empty()); }
229 : #endif
230 :
231 : StringBuffer &sb;
232 : const StringBuffer ⪆
233 : JSObject * const replacer;
234 : const AutoIdVector &propertyList;
235 : uint32_t depth;
236 : HashSet<JSObject *> objectStack;
237 : };
238 :
239 : static JSBool Str(JSContext *cx, const Value &v, StringifyContext *scx);
240 :
241 : static JSBool
242 167267 : WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
243 : {
244 167267 : if (!scx->gap.empty()) {
245 6291 : if (!scx->sb.append('\n'))
246 0 : return JS_FALSE;
247 17566 : for (uint32_t i = 0; i < limit; i++) {
248 11275 : if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
249 0 : return JS_FALSE;
250 : }
251 : }
252 :
253 167267 : return JS_TRUE;
254 : }
255 :
256 : class CycleDetector
257 : {
258 : public:
259 39350 : CycleDetector(StringifyContext *scx, JSObject *obj)
260 39350 : : objectStack(scx->objectStack), obj(obj) {
261 39350 : }
262 :
263 39350 : bool init(JSContext *cx) {
264 39350 : HashSet<JSObject *>::AddPtr ptr = objectStack.lookupForAdd(obj);
265 39350 : if (ptr) {
266 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CYCLIC_VALUE, js_object_str);
267 0 : return false;
268 : }
269 39350 : return objectStack.add(ptr, obj);
270 : }
271 :
272 39350 : ~CycleDetector() {
273 39350 : objectStack.remove(obj);
274 39350 : }
275 :
276 : private:
277 : HashSet<JSObject *> &objectStack;
278 : JSObject *const obj;
279 : };
280 :
281 : template<typename KeyType>
282 : class KeyStringifier {
283 : };
284 :
285 : template<>
286 : class KeyStringifier<uint32_t> {
287 : public:
288 616 : static JSString *toString(JSContext *cx, uint32_t index) {
289 616 : return IndexToString(cx, index);
290 : }
291 : };
292 :
293 : template<>
294 : class KeyStringifier<jsid> {
295 : public:
296 500 : static JSString *toString(JSContext *cx, jsid id) {
297 500 : return IdToString(cx, id);
298 : }
299 : };
300 :
301 : /*
302 : * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
303 : * values when stringifying objects in JO.
304 : */
305 : template<typename KeyType>
306 : static bool
307 150176 : PreprocessValue(JSContext *cx, JSObject *holder, KeyType key, Value *vp, StringifyContext *scx)
308 : {
309 150176 : JSString *keyStr = NULL;
310 :
311 : /* Step 2. */
312 150176 : if (vp->isObject()) {
313 : Value toJSON;
314 39402 : jsid id = ATOM_TO_JSID(cx->runtime->atomState.toJSONAtom);
315 39402 : if (!js_GetMethod(cx, &vp->toObject(), id, JSGET_NO_METHOD_BARRIER, &toJSON))
316 0 : return false;
317 :
318 39402 : if (js_IsCallable(toJSON)) {
319 1116 : keyStr = KeyStringifier<KeyType>::toString(cx, key);
320 1116 : if (!keyStr)
321 0 : return false;
322 :
323 2232 : InvokeArgsGuard args;
324 1116 : if (!cx->stack.pushInvokeArgs(cx, 1, &args))
325 0 : return false;
326 :
327 1116 : args.calleev() = toJSON;
328 1116 : args.thisv() = *vp;
329 1116 : args[0] = StringValue(keyStr);
330 :
331 1116 : if (!Invoke(cx, args))
332 0 : return false;
333 2232 : *vp = args.rval();
334 : }
335 : }
336 :
337 : /* Step 3. */
338 150176 : if (scx->replacer && scx->replacer->isCallable()) {
339 0 : if (!keyStr) {
340 0 : keyStr = KeyStringifier<KeyType>::toString(cx, key);
341 0 : if (!keyStr)
342 0 : return false;
343 : }
344 :
345 0 : InvokeArgsGuard args;
346 0 : if (!cx->stack.pushInvokeArgs(cx, 2, &args))
347 0 : return false;
348 :
349 0 : args.calleev() = ObjectValue(*scx->replacer);
350 0 : args.thisv() = ObjectValue(*holder);
351 0 : args[0] = StringValue(keyStr);
352 0 : args[1] = *vp;
353 :
354 0 : if (!Invoke(cx, args))
355 0 : return false;
356 0 : *vp = args.rval();
357 : }
358 :
359 : /* Step 4. */
360 150176 : if (vp->isObject()) {
361 39402 : JSObject &obj = vp->toObject();
362 39402 : if (ObjectClassIs(obj, ESClass_Number, cx)) {
363 : double d;
364 12 : if (!ToNumber(cx, *vp, &d))
365 0 : return false;
366 12 : vp->setNumber(d);
367 39390 : } else if (ObjectClassIs(obj, ESClass_String, cx)) {
368 9 : JSString *str = ToStringSlow(cx, *vp);
369 9 : if (!str)
370 0 : return false;
371 9 : vp->setString(str);
372 39381 : } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
373 9 : if (!BooleanGetPrimitiveValue(cx, obj, vp))
374 0 : return false;
375 9 : JS_ASSERT(vp->isBoolean());
376 : }
377 : }
378 :
379 150176 : return true;
380 : }
381 :
382 : /*
383 : * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
384 : * gauntlet will result in Str returning |undefined|. This function is used to
385 : * properly omit properties resulting in such values when stringifying objects,
386 : * while properly stringifying such properties as null when they're encountered
387 : * in arrays.
388 : */
389 : static inline bool
390 300056 : IsFilteredValue(const Value &v)
391 : {
392 300056 : return v.isUndefined() || js_IsCallable(v) || (v.isObject() && v.toObject().isXML());
393 : }
394 :
395 : /* ES5 15.12.3 JO. */
396 : static JSBool
397 29752 : JO(JSContext *cx, JSObject *obj, StringifyContext *scx)
398 : {
399 : /*
400 : * This method implements the JO algorithm in ES5 15.12.3, but:
401 : *
402 : * * The algorithm is somewhat reformulated to allow the final string to
403 : * be streamed into a single buffer, rather than be created and copied
404 : * into place incrementally as the ES5 algorithm specifies it. This
405 : * requires moving portions of the Str call in 8a into this algorithm
406 : * (and in JA as well).
407 : */
408 :
409 : /* Steps 1-2, 11. */
410 59504 : CycleDetector detect(scx, obj);
411 29752 : if (!detect.init(cx))
412 0 : return JS_FALSE;
413 :
414 29752 : if (!scx->sb.append('{'))
415 0 : return JS_FALSE;
416 :
417 : /* Steps 5-7. */
418 59504 : Maybe<AutoIdVector> ids;
419 : const AutoIdVector *props;
420 29752 : if (scx->replacer && !scx->replacer->isCallable()) {
421 1860 : JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
422 1860 : props = &scx->propertyList;
423 : } else {
424 27892 : JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
425 27892 : ids.construct(cx);
426 27892 : if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr()))
427 0 : return false;
428 27892 : props = ids.addr();
429 : }
430 :
431 : /* My kingdom for not-quite-initialized-from-the-start references. */
432 29752 : const AutoIdVector &propertyList = *props;
433 :
434 : /* Steps 8-10, 13. */
435 29752 : bool wroteMember = false;
436 145177 : for (size_t i = 0, len = propertyList.length(); i < len; i++) {
437 : /*
438 : * Steps 8a-8b. Note that the call to Str is broken up into 1) getting
439 : * the property; 2) processing for toJSON, calling the replacer, and
440 : * handling boxed Number/String/Boolean objects; 3) filtering out
441 : * values which process to |undefined|, and 4) stringifying all values
442 : * which pass the filter.
443 : */
444 115425 : const jsid &id = propertyList[i];
445 : Value outputValue;
446 115425 : if (!obj->getGeneric(cx, id, &outputValue))
447 0 : return false;
448 115425 : if (!PreprocessValue(cx, obj, id, &outputValue, scx))
449 0 : return false;
450 115425 : if (IsFilteredValue(outputValue))
451 269 : continue;
452 :
453 : /* Output a comma unless this is the first member to write. */
454 115156 : if (wroteMember && !scx->sb.append(','))
455 0 : return false;
456 115156 : wroteMember = true;
457 :
458 115156 : if (!WriteIndent(cx, scx, scx->depth))
459 0 : return false;
460 :
461 115156 : JSString *s = IdToString(cx, id);
462 115156 : if (!s)
463 0 : return false;
464 :
465 460624 : if (!Quote(cx, scx->sb, s) ||
466 115156 : !scx->sb.append(':') ||
467 119646 : !(scx->gap.empty() || scx->sb.append(' ')) ||
468 115156 : !Str(cx, outputValue, scx))
469 : {
470 0 : return false;
471 : }
472 : }
473 :
474 29752 : if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
475 0 : return false;
476 :
477 29752 : return scx->sb.append('}');
478 : }
479 :
480 : /* ES5 15.12.3 JA. */
481 : static JSBool
482 9598 : JA(JSContext *cx, JSObject *obj, StringifyContext *scx)
483 : {
484 : /*
485 : * This method implements the JA algorithm in ES5 15.12.3, but:
486 : *
487 : * * The algorithm is somewhat reformulated to allow the final string to
488 : * be streamed into a single buffer, rather than be created and copied
489 : * into place incrementally as the ES5 algorithm specifies it. This
490 : * requires moving portions of the Str call in 8a into this algorithm
491 : * (and in JO as well).
492 : */
493 :
494 : /* Steps 1-2, 11. */
495 19196 : CycleDetector detect(scx, obj);
496 9598 : if (!detect.init(cx))
497 0 : return JS_FALSE;
498 :
499 9598 : if (!scx->sb.append('['))
500 0 : return JS_FALSE;
501 :
502 : /* Step 6. */
503 : uint32_t length;
504 9598 : if (!js_GetLengthProperty(cx, obj, &length))
505 0 : return JS_FALSE;
506 :
507 : /* Steps 7-10. */
508 9598 : if (length != 0) {
509 : /* Steps 4, 10b(i). */
510 6529 : if (!WriteIndent(cx, scx, scx->depth))
511 0 : return JS_FALSE;
512 :
513 : /* Steps 7-10. */
514 : Value outputValue;
515 24414 : for (uint32_t i = 0; i < length; i++) {
516 : /*
517 : * Steps 8a-8c. Again note how the call to the spec's Str method
518 : * is broken up into getting the property, running it past toJSON
519 : * and the replacer and maybe unboxing, and interpreting some
520 : * values as |null| in separate steps.
521 : */
522 17885 : if (!obj->getElement(cx, i, &outputValue))
523 0 : return JS_FALSE;
524 17885 : if (!PreprocessValue(cx, obj, i, &outputValue, scx))
525 0 : return JS_FALSE;
526 17885 : if (IsFilteredValue(outputValue)) {
527 10 : if (!scx->sb.append("null"))
528 0 : return JS_FALSE;
529 : } else {
530 17875 : if (!Str(cx, outputValue, scx))
531 0 : return JS_FALSE;
532 : }
533 :
534 : /* Steps 3, 4, 10b(i). */
535 17885 : if (i < length - 1) {
536 11356 : if (!scx->sb.append(','))
537 0 : return JS_FALSE;
538 11356 : if (!WriteIndent(cx, scx, scx->depth))
539 0 : return JS_FALSE;
540 : }
541 : }
542 :
543 : /* Step 10(b)(iii). */
544 6529 : if (!WriteIndent(cx, scx, scx->depth - 1))
545 0 : return JS_FALSE;
546 : }
547 :
548 9598 : return scx->sb.append(']');
549 : }
550 :
551 : static JSBool
552 149880 : Str(JSContext *cx, const Value &v, StringifyContext *scx)
553 : {
554 : /* Step 11 must be handled by the caller. */
555 149880 : JS_ASSERT(!IsFilteredValue(v));
556 :
557 149880 : JS_CHECK_RECURSION(cx, return false);
558 :
559 : /*
560 : * This method implements the Str algorithm in ES5 15.12.3, but:
561 : *
562 : * * We move property retrieval (step 1) into callers to stream the
563 : * stringification process and avoid constantly copying strings.
564 : * * We move the preprocessing in steps 2-4 into a helper function to
565 : * allow both JO and JA to use this method. While JA could use it
566 : * without this move, JO must omit any |undefined|-valued property per
567 : * so it can't stream out a value using the Str method exactly as
568 : * defined by ES5.
569 : * * We move step 11 into callers, again to ease streaming.
570 : */
571 :
572 : /* Step 8. */
573 149880 : if (v.isString())
574 72866 : return Quote(cx, scx->sb, v.toString());
575 :
576 : /* Step 5. */
577 77014 : if (v.isNull())
578 4700 : return scx->sb.append("null");
579 :
580 : /* Steps 6-7. */
581 72314 : if (v.isBoolean())
582 6149 : return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
583 :
584 : /* Step 9. */
585 66165 : if (v.isNumber()) {
586 26815 : if (v.isDouble()) {
587 19148 : if (!JSDOUBLE_IS_FINITE(v.toDouble()))
588 3 : return scx->sb.append("null");
589 : }
590 :
591 53624 : StringBuffer sb(cx);
592 26812 : if (!NumberValueToStringBuffer(cx, v, sb))
593 0 : return false;
594 :
595 26812 : return scx->sb.append(sb.begin(), sb.length());
596 : }
597 :
598 : /* Step 10. */
599 39350 : JS_ASSERT(v.isObject());
600 39350 : JSObject *obj = &v.toObject();
601 :
602 39350 : scx->depth++;
603 : JSBool ok;
604 39350 : if (ObjectClassIs(v.toObject(), ESClass_Array, cx))
605 9598 : ok = JA(cx, obj, scx);
606 : else
607 29752 : ok = JO(cx, obj, scx);
608 39350 : scx->depth--;
609 :
610 39350 : return ok;
611 : }
612 :
613 : /* ES5 15.12.3. */
614 : JSBool
615 16866 : js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, Value space, StringBuffer &sb)
616 : {
617 : /* Step 4. */
618 33732 : AutoIdVector propertyList(cx);
619 16866 : if (replacer) {
620 1860 : if (replacer->isCallable()) {
621 : /* Step 4a(i): use replacer to transform values. */
622 1860 : } else if (ObjectClassIs(*replacer, ESClass_Array, cx)) {
623 : /*
624 : * Step 4b: The spec algorithm is unhelpfully vague about the exact
625 : * steps taken when the replacer is an array, regarding the exact
626 : * sequence of [[Get]] calls for the array's elements, when its
627 : * overall length is calculated, whether own or own plus inherited
628 : * properties are considered, and so on. A rewrite was proposed in
629 : * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
630 : * whose steps are copied below, and which are implemented here.
631 : *
632 : * i. Let PropertyList be an empty internal List.
633 : * ii. Let len be the result of calling the [[Get]] internal
634 : * method of replacer with the argument "length".
635 : * iii. Let i be 0.
636 : * iv. While i < len:
637 : * 1. Let item be undefined.
638 : * 2. Let v be the result of calling the [[Get]] internal
639 : * method of replacer with the argument ToString(i).
640 : * 3. If Type(v) is String then let item be v.
641 : * 4. Else if Type(v) is Number then let item be ToString(v).
642 : * 5. Else if Type(v) is Object then
643 : * a. If the [[Class]] internal property of v is "String"
644 : * or "Number" then let item be ToString(v).
645 : * 6. If item is not undefined and item is not currently an
646 : * element of PropertyList then,
647 : * a. Append item to the end of PropertyList.
648 : * 7. Let i be i + 1.
649 : */
650 :
651 : /* Step 4b(ii). */
652 : uint32_t len;
653 1860 : JS_ALWAYS_TRUE(js_GetLengthProperty(cx, replacer, &len));
654 1860 : if (replacer->isDenseArray())
655 1851 : len = JS_MIN(len, replacer->getDenseArrayCapacity());
656 :
657 3720 : HashSet<jsid> idSet(cx);
658 1860 : if (!idSet.init(len))
659 0 : return false;
660 :
661 : /* Step 4b(iii). */
662 1860 : uint32_t i = 0;
663 :
664 : /* Step 4b(iv). */
665 7431 : for (; i < len; i++) {
666 : /* Step 4b(iv)(2). */
667 : Value v;
668 5571 : if (!replacer->getElement(cx, i, &v))
669 0 : return false;
670 :
671 : jsid id;
672 5571 : if (v.isNumber()) {
673 : /* Step 4b(iv)(4). */
674 : int32_t n;
675 27 : if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
676 27 : id = INT_TO_JSID(n);
677 : } else {
678 0 : if (!js_ValueToStringId(cx, v, &id))
679 0 : return false;
680 0 : id = js_CheckForStringIndex(id);
681 : }
682 5589 : } else if (v.isString() ||
683 18 : (v.isObject() &&
684 18 : (ObjectClassIs(v.toObject(), ESClass_String, cx) ||
685 9 : ObjectClassIs(v.toObject(), ESClass_Number, cx))))
686 : {
687 : /* Step 4b(iv)(3), 4b(iv)(5). */
688 5544 : if (!js_ValueToStringId(cx, v, &id))
689 0 : return false;
690 5544 : id = js_CheckForStringIndex(id);
691 : } else {
692 0 : continue;
693 : }
694 :
695 : /* Step 4b(iv)(6). */
696 5571 : HashSet<jsid>::AddPtr p = idSet.lookupForAdd(id);
697 5571 : if (!p) {
698 : /* Step 4b(iv)(6)(a). */
699 5571 : if (!idSet.add(p, id) || !propertyList.append(id))
700 0 : return false;
701 : }
702 : }
703 : } else {
704 0 : replacer = NULL;
705 : }
706 : }
707 :
708 : /* Step 5. */
709 16866 : if (space.isObject()) {
710 18 : JSObject &spaceObj = space.toObject();
711 18 : if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
712 : double d;
713 9 : if (!ToNumber(cx, space, &d))
714 0 : return false;
715 9 : space = NumberValue(d);
716 9 : } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
717 9 : JSString *str = ToStringSlow(cx, space);
718 9 : if (!str)
719 0 : return false;
720 9 : space = StringValue(str);
721 : }
722 : }
723 :
724 33732 : StringBuffer gap(cx);
725 :
726 16866 : if (space.isNumber()) {
727 : /* Step 6. */
728 : double d;
729 775 : JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
730 775 : d = JS_MIN(10, d);
731 775 : if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
732 0 : return false;
733 16091 : } else if (space.isString()) {
734 : /* Step 7. */
735 9 : JSLinearString *str = space.toString()->ensureLinear(cx);
736 9 : if (!str)
737 0 : return false;
738 18 : JS::Anchor<JSString *> anchor(str);
739 9 : size_t len = JS_MIN(10, space.toString()->length());
740 9 : if (!gap.append(str->chars(), len))
741 0 : return false;
742 : } else {
743 : /* Step 8. */
744 16082 : JS_ASSERT(gap.empty());
745 : }
746 :
747 : /* Step 9. */
748 16866 : JSObject *wrapper = NewBuiltinClassInstance(cx, &ObjectClass);
749 16866 : if (!wrapper)
750 0 : return false;
751 :
752 : /* Step 10. */
753 16866 : jsid emptyId = ATOM_TO_JSID(cx->runtime->atomState.emptyAtom);
754 16866 : if (!DefineNativeProperty(cx, wrapper, emptyId, *vp, JS_PropertyStub, JS_StrictPropertyStub,
755 16866 : JSPROP_ENUMERATE, 0, 0))
756 : {
757 0 : return false;
758 : }
759 :
760 : /* Step 11. */
761 33732 : StringifyContext scx(cx, sb, gap, replacer, propertyList);
762 16866 : if (!scx.init())
763 0 : return false;
764 :
765 16866 : if (!PreprocessValue(cx, wrapper, emptyId, vp, &scx))
766 0 : return false;
767 16866 : if (IsFilteredValue(*vp))
768 17 : return true;
769 :
770 16849 : return Str(cx, *vp, &scx);
771 : }
772 :
773 : /* ES5 15.12.2 Walk. */
774 : static bool
775 9 : Walk(JSContext *cx, JSObject *holder, jsid name, const Value &reviver, Value *vp)
776 : {
777 9 : JS_CHECK_RECURSION(cx, return false);
778 :
779 : /* Step 1. */
780 : Value val;
781 9 : if (!holder->getGeneric(cx, name, &val))
782 0 : return false;
783 :
784 : /* Step 2. */
785 9 : if (val.isObject()) {
786 3 : JSObject *obj = &val.toObject();
787 :
788 : /* 'val' must have been produced by the JSON parser, so not a proxy. */
789 3 : JS_ASSERT(!obj->isProxy());
790 3 : if (obj->isArray()) {
791 : /* Step 2a(ii). */
792 2 : uint32_t length = obj->getArrayLength();
793 :
794 : /* Step 2a(i), 2a(iii-iv). */
795 3 : for (uint32_t i = 0; i < length; i++) {
796 : jsid id;
797 1 : if (!IndexToId(cx, i, &id))
798 0 : return false;
799 :
800 : /* Step 2a(iii)(1). */
801 : Value newElement;
802 1 : if (!Walk(cx, obj, id, reviver, &newElement))
803 0 : return false;
804 :
805 : /*
806 : * Arrays which begin empty and whose properties are always
807 : * incrementally appended are always dense, no matter their
808 : * length, under current dense/slow array heuristics.
809 : * Also, deleting a property from a dense array which is not
810 : * currently being enumerated never makes it slow. This array
811 : * is never exposed until the reviver sees it below, so it must
812 : * be dense and isn't currently being enumerated. Therefore
813 : * property definition and deletion will always succeed,
814 : * and we need not check for failure.
815 : */
816 1 : if (newElement.isUndefined()) {
817 : /* Step 2a(iii)(2). */
818 0 : JS_ALWAYS_TRUE(array_deleteElement(cx, obj, i, &newElement, false));
819 : } else {
820 : /* Step 2a(iii)(3). */
821 1 : JS_ALWAYS_TRUE(array_defineElement(cx, obj, i, &newElement, JS_PropertyStub,
822 1 : JS_StrictPropertyStub, JSPROP_ENUMERATE));
823 : }
824 : }
825 : } else {
826 : /* Step 2b(i). */
827 2 : AutoIdVector keys(cx);
828 1 : if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
829 0 : return false;
830 :
831 : /* Step 2b(ii). */
832 1 : for (size_t i = 0, len = keys.length(); i < len; i++) {
833 : /* Step 2b(ii)(1). */
834 : Value newElement;
835 0 : jsid id = keys[i];
836 0 : if (!Walk(cx, obj, id, reviver, &newElement))
837 0 : return false;
838 :
839 0 : if (newElement.isUndefined()) {
840 : /* Step 2b(ii)(2). */
841 0 : if (!obj->deleteByValue(cx, IdToValue(id), &newElement, false))
842 0 : return false;
843 : } else {
844 : /* Step 2b(ii)(3). */
845 0 : JS_ASSERT(obj->isNative());
846 0 : if (!DefineNativeProperty(cx, obj, id, newElement, JS_PropertyStub,
847 0 : JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0))
848 : {
849 0 : return false;
850 : }
851 : }
852 : }
853 : }
854 : }
855 :
856 : /* Step 3. */
857 9 : JSString *key = IdToString(cx, name);
858 9 : if (!key)
859 0 : return false;
860 :
861 18 : InvokeArgsGuard args;
862 9 : if (!cx->stack.pushInvokeArgs(cx, 2, &args))
863 0 : return false;
864 :
865 9 : args.calleev() = reviver;
866 9 : args.thisv() = ObjectValue(*holder);
867 9 : args[0] = StringValue(key);
868 9 : args[1] = val;
869 :
870 9 : if (!Invoke(cx, args))
871 0 : return false;
872 9 : *vp = args.rval();
873 9 : return true;
874 : }
875 :
876 : static bool
877 8 : Revive(JSContext *cx, const Value &reviver, Value *vp)
878 : {
879 :
880 8 : JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
881 8 : if (!obj)
882 0 : return false;
883 :
884 8 : if (!obj->defineProperty(cx, cx->runtime->atomState.emptyAtom, *vp))
885 0 : return false;
886 :
887 8 : return Walk(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), reviver, vp);
888 : }
889 :
890 : namespace js {
891 :
892 : JSBool
893 8518 : ParseJSONWithReviver(JSContext *cx, const jschar *chars, size_t length, const Value &reviver,
894 : Value *vp, DecodingMode decodingMode /* = STRICT */)
895 : {
896 : /* 15.12.2 steps 2-3. */
897 : JSONParser parser(cx, chars, length,
898 8518 : decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
899 8518 : if (!parser.parse(vp))
900 210 : return false;
901 :
902 : /* 15.12.2 steps 4-5. */
903 8308 : if (js_IsCallable(reviver))
904 8 : return Revive(cx, reviver, vp);
905 8300 : return true;
906 : }
907 :
908 : } /* namespace js */
909 :
910 : #if JS_HAS_TOSOURCE
911 : static JSBool
912 0 : json_toSource(JSContext *cx, unsigned argc, Value *vp)
913 : {
914 0 : vp->setString(CLASS_ATOM(cx, JSON));
915 0 : return JS_TRUE;
916 : }
917 : #endif
918 :
919 : static JSFunctionSpec json_static_methods[] = {
920 : #if JS_HAS_TOSOURCE
921 : JS_FN(js_toSource_str, json_toSource, 0, 0),
922 : #endif
923 : JS_FN("parse", js_json_parse, 2, 0),
924 : JS_FN("stringify", js_json_stringify, 3, 0),
925 : JS_FS_END
926 : };
927 :
928 : JSObject *
929 2925 : js_InitJSONClass(JSContext *cx, JSObject *obj)
930 : {
931 2925 : JSObject *JSON = NewObjectWithClassProto(cx, &JSONClass, NULL, obj);
932 2925 : if (!JSON || !JSON->setSingletonType(cx))
933 0 : return NULL;
934 :
935 2925 : if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
936 2925 : JS_PropertyStub, JS_StrictPropertyStub, 0))
937 0 : return NULL;
938 :
939 2925 : if (!JS_DefineFunctions(cx, JSON, json_static_methods))
940 0 : return NULL;
941 :
942 2925 : MarkStandardClassInitializedNoProto(obj, &JSONClass);
943 :
944 2925 : return JSON;
945 : }
|