Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 "json.h"
9 #include "mozilla/FloatingPoint.h"
11 #include "jsarray.h"
12 #include "jsatom.h"
13 #include "jscntxt.h"
14 #include "jsnum.h"
15 #include "jsobj.h"
16 #include "jsonparser.h"
17 #include "jsstr.h"
18 #include "jstypes.h"
19 #include "jsutil.h"
21 #include "vm/Interpreter.h"
22 #include "vm/StringBuffer.h"
24 #include "jsatominlines.h"
25 #include "jsboolinlines.h"
26 #include "jsobjinlines.h"
28 using namespace js;
29 using namespace js::gc;
30 using namespace js::types;
32 using mozilla::IsFinite;
33 using mozilla::Maybe;
35 const Class js::JSONClass = {
36 js_JSON_str,
37 JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
38 JS_PropertyStub, /* addProperty */
39 JS_DeletePropertyStub, /* delProperty */
40 JS_PropertyStub, /* getProperty */
41 JS_StrictPropertyStub, /* setProperty */
42 JS_EnumerateStub,
43 JS_ResolveStub,
44 JS_ConvertStub
45 };
47 static inline bool IsQuoteSpecialCharacter(jschar c)
48 {
49 JS_STATIC_ASSERT('\b' < ' ');
50 JS_STATIC_ASSERT('\f' < ' ');
51 JS_STATIC_ASSERT('\n' < ' ');
52 JS_STATIC_ASSERT('\r' < ' ');
53 JS_STATIC_ASSERT('\t' < ' ');
54 return c == '"' || c == '\\' || c < ' ';
55 }
57 /* ES5 15.12.3 Quote. */
58 static bool
59 Quote(JSContext *cx, StringBuffer &sb, JSString *str)
60 {
61 JS::Anchor<JSString *> anchor(str);
62 size_t len = str->length();
63 const jschar *buf = str->getChars(cx);
64 if (!buf)
65 return false;
67 /* Step 1. */
68 if (!sb.append('"'))
69 return false;
71 /* Step 2. */
72 for (size_t i = 0; i < len; ++i) {
73 /* Batch-append maximal character sequences containing no escapes. */
74 size_t mark = i;
75 do {
76 if (IsQuoteSpecialCharacter(buf[i]))
77 break;
78 } while (++i < len);
79 if (i > mark) {
80 if (!sb.append(&buf[mark], i - mark))
81 return false;
82 if (i == len)
83 break;
84 }
86 jschar c = buf[i];
87 if (c == '"' || c == '\\') {
88 if (!sb.append('\\') || !sb.append(c))
89 return false;
90 } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
91 jschar abbrev = (c == '\b')
92 ? 'b'
93 : (c == '\f')
94 ? 'f'
95 : (c == '\n')
96 ? 'n'
97 : (c == '\r')
98 ? 'r'
99 : 't';
100 if (!sb.append('\\') || !sb.append(abbrev))
101 return false;
102 } else {
103 JS_ASSERT(c < ' ');
104 if (!sb.append("\\u00"))
105 return false;
106 JS_ASSERT((c >> 4) < 10);
107 uint8_t x = c >> 4, y = c % 16;
108 if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10)))
109 return false;
110 }
111 }
113 /* Steps 3-4. */
114 return sb.append('"');
115 }
117 namespace {
119 class StringifyContext
120 {
121 public:
122 StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap,
123 HandleObject replacer, const AutoIdVector &propertyList)
124 : sb(sb),
125 gap(gap),
126 replacer(cx, replacer),
127 propertyList(propertyList),
128 depth(0)
129 {}
131 StringBuffer &sb;
132 const StringBuffer ⪆
133 RootedObject replacer;
134 const AutoIdVector &propertyList;
135 uint32_t depth;
136 };
138 } /* anonymous namespace */
140 static bool Str(JSContext *cx, const Value &v, StringifyContext *scx);
142 static bool
143 WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
144 {
145 if (!scx->gap.empty()) {
146 if (!scx->sb.append('\n'))
147 return false;
148 for (uint32_t i = 0; i < limit; i++) {
149 if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
150 return false;
151 }
152 }
154 return true;
155 }
157 namespace {
159 template<typename KeyType>
160 class KeyStringifier {
161 };
163 template<>
164 class KeyStringifier<uint32_t> {
165 public:
166 static JSString *toString(JSContext *cx, uint32_t index) {
167 return IndexToString(cx, index);
168 }
169 };
171 template<>
172 class KeyStringifier<HandleId> {
173 public:
174 static JSString *toString(JSContext *cx, HandleId id) {
175 return IdToString(cx, id);
176 }
177 };
179 } /* anonymous namespace */
181 /*
182 * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
183 * values when stringifying objects in JO.
184 */
185 template<typename KeyType>
186 static bool
187 PreprocessValue(JSContext *cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext *scx)
188 {
189 RootedString keyStr(cx);
191 /* Step 2. */
192 if (vp.isObject()) {
193 RootedValue toJSON(cx);
194 RootedObject obj(cx, &vp.toObject());
195 if (!JSObject::getProperty(cx, obj, obj, cx->names().toJSON, &toJSON))
196 return false;
198 if (js_IsCallable(toJSON)) {
199 keyStr = KeyStringifier<KeyType>::toString(cx, key);
200 if (!keyStr)
201 return false;
203 InvokeArgs args(cx);
204 if (!args.init(1))
205 return false;
207 args.setCallee(toJSON);
208 args.setThis(vp);
209 args[0].setString(keyStr);
211 if (!Invoke(cx, args))
212 return false;
213 vp.set(args.rval());
214 }
215 }
217 /* Step 3. */
218 if (scx->replacer && scx->replacer->isCallable()) {
219 if (!keyStr) {
220 keyStr = KeyStringifier<KeyType>::toString(cx, key);
221 if (!keyStr)
222 return false;
223 }
225 InvokeArgs args(cx);
226 if (!args.init(2))
227 return false;
229 args.setCallee(ObjectValue(*scx->replacer));
230 args.setThis(ObjectValue(*holder));
231 args[0].setString(keyStr);
232 args[1].set(vp);
234 if (!Invoke(cx, args))
235 return false;
236 vp.set(args.rval());
237 }
239 /* Step 4. */
240 if (vp.get().isObject()) {
241 RootedObject obj(cx, &vp.get().toObject());
242 if (ObjectClassIs(obj, ESClass_Number, cx)) {
243 double d;
244 if (!ToNumber(cx, vp, &d))
245 return false;
246 vp.set(NumberValue(d));
247 } else if (ObjectClassIs(obj, ESClass_String, cx)) {
248 JSString *str = ToStringSlow<CanGC>(cx, vp);
249 if (!str)
250 return false;
251 vp.set(StringValue(str));
252 } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
253 vp.setBoolean(BooleanGetPrimitiveValue(obj));
254 }
255 }
257 return true;
258 }
260 /*
261 * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
262 * gauntlet will result in Str returning |undefined|. This function is used to
263 * properly omit properties resulting in such values when stringifying objects,
264 * while properly stringifying such properties as null when they're encountered
265 * in arrays.
266 */
267 static inline bool
268 IsFilteredValue(const Value &v)
269 {
270 return v.isUndefined() || js_IsCallable(v);
271 }
273 /* ES5 15.12.3 JO. */
274 static bool
275 JO(JSContext *cx, HandleObject obj, StringifyContext *scx)
276 {
277 /*
278 * This method implements the JO algorithm in ES5 15.12.3, but:
279 *
280 * * The algorithm is somewhat reformulated to allow the final string to
281 * be streamed into a single buffer, rather than be created and copied
282 * into place incrementally as the ES5 algorithm specifies it. This
283 * requires moving portions of the Str call in 8a into this algorithm
284 * (and in JA as well).
285 */
287 /* Steps 1-2, 11. */
288 AutoCycleDetector detect(cx, obj);
289 if (!detect.init())
290 return false;
291 if (detect.foundCycle()) {
292 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str);
293 return false;
294 }
296 if (!scx->sb.append('{'))
297 return false;
299 /* Steps 5-7. */
300 Maybe<AutoIdVector> ids;
301 const AutoIdVector *props;
302 if (scx->replacer && !scx->replacer->isCallable()) {
303 JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
304 props = &scx->propertyList;
305 } else {
306 JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
307 ids.construct(cx);
308 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr()))
309 return false;
310 props = ids.addr();
311 }
313 /* My kingdom for not-quite-initialized-from-the-start references. */
314 const AutoIdVector &propertyList = *props;
316 /* Steps 8-10, 13. */
317 bool wroteMember = false;
318 RootedId id(cx);
319 for (size_t i = 0, len = propertyList.length(); i < len; i++) {
320 /*
321 * Steps 8a-8b. Note that the call to Str is broken up into 1) getting
322 * the property; 2) processing for toJSON, calling the replacer, and
323 * handling boxed Number/String/Boolean objects; 3) filtering out
324 * values which process to |undefined|, and 4) stringifying all values
325 * which pass the filter.
326 */
327 id = propertyList[i];
328 RootedValue outputValue(cx);
329 if (!JSObject::getGeneric(cx, obj, obj, id, &outputValue))
330 return false;
331 if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx))
332 return false;
333 if (IsFilteredValue(outputValue))
334 continue;
336 /* Output a comma unless this is the first member to write. */
337 if (wroteMember && !scx->sb.append(','))
338 return false;
339 wroteMember = true;
341 if (!WriteIndent(cx, scx, scx->depth))
342 return false;
344 JSString *s = IdToString(cx, id);
345 if (!s)
346 return false;
348 if (!Quote(cx, scx->sb, s) ||
349 !scx->sb.append(':') ||
350 !(scx->gap.empty() || scx->sb.append(' ')) ||
351 !Str(cx, outputValue, scx))
352 {
353 return false;
354 }
355 }
357 if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
358 return false;
360 return scx->sb.append('}');
361 }
363 /* ES5 15.12.3 JA. */
364 static bool
365 JA(JSContext *cx, HandleObject obj, StringifyContext *scx)
366 {
367 /*
368 * This method implements the JA algorithm in ES5 15.12.3, but:
369 *
370 * * The algorithm is somewhat reformulated to allow the final string to
371 * be streamed into a single buffer, rather than be created and copied
372 * into place incrementally as the ES5 algorithm specifies it. This
373 * requires moving portions of the Str call in 8a into this algorithm
374 * (and in JO as well).
375 */
377 /* Steps 1-2, 11. */
378 AutoCycleDetector detect(cx, obj);
379 if (!detect.init())
380 return false;
381 if (detect.foundCycle()) {
382 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str);
383 return false;
384 }
386 if (!scx->sb.append('['))
387 return false;
389 /* Step 6. */
390 uint32_t length;
391 if (!GetLengthProperty(cx, obj, &length))
392 return false;
394 /* Steps 7-10. */
395 if (length != 0) {
396 /* Steps 4, 10b(i). */
397 if (!WriteIndent(cx, scx, scx->depth))
398 return false;
400 /* Steps 7-10. */
401 RootedValue outputValue(cx);
402 for (uint32_t i = 0; i < length; i++) {
403 /*
404 * Steps 8a-8c. Again note how the call to the spec's Str method
405 * is broken up into getting the property, running it past toJSON
406 * and the replacer and maybe unboxing, and interpreting some
407 * values as |null| in separate steps.
408 */
409 if (!JSObject::getElement(cx, obj, obj, i, &outputValue))
410 return false;
411 if (!PreprocessValue(cx, obj, i, &outputValue, scx))
412 return false;
413 if (IsFilteredValue(outputValue)) {
414 if (!scx->sb.append("null"))
415 return false;
416 } else {
417 if (!Str(cx, outputValue, scx))
418 return false;
419 }
421 /* Steps 3, 4, 10b(i). */
422 if (i < length - 1) {
423 if (!scx->sb.append(','))
424 return false;
425 if (!WriteIndent(cx, scx, scx->depth))
426 return false;
427 }
428 }
430 /* Step 10(b)(iii). */
431 if (!WriteIndent(cx, scx, scx->depth - 1))
432 return false;
433 }
435 return scx->sb.append(']');
436 }
438 static bool
439 Str(JSContext *cx, const Value &v, StringifyContext *scx)
440 {
441 /* Step 11 must be handled by the caller. */
442 JS_ASSERT(!IsFilteredValue(v));
444 JS_CHECK_RECURSION(cx, return false);
446 /*
447 * This method implements the Str algorithm in ES5 15.12.3, but:
448 *
449 * * We move property retrieval (step 1) into callers to stream the
450 * stringification process and avoid constantly copying strings.
451 * * We move the preprocessing in steps 2-4 into a helper function to
452 * allow both JO and JA to use this method. While JA could use it
453 * without this move, JO must omit any |undefined|-valued property per
454 * so it can't stream out a value using the Str method exactly as
455 * defined by ES5.
456 * * We move step 11 into callers, again to ease streaming.
457 */
459 /* Step 8. */
460 if (v.isString())
461 return Quote(cx, scx->sb, v.toString());
463 /* Step 5. */
464 if (v.isNull())
465 return scx->sb.append("null");
467 /* Steps 6-7. */
468 if (v.isBoolean())
469 return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
471 /* Step 9. */
472 if (v.isNumber()) {
473 if (v.isDouble()) {
474 if (!IsFinite(v.toDouble()))
475 return scx->sb.append("null");
476 }
478 StringBuffer sb(cx);
479 if (!NumberValueToStringBuffer(cx, v, sb))
480 return false;
482 return scx->sb.append(sb.begin(), sb.length());
483 }
485 /* Step 10. */
486 JS_ASSERT(v.isObject());
487 RootedObject obj(cx, &v.toObject());
489 scx->depth++;
490 bool ok;
491 if (ObjectClassIs(obj, ESClass_Array, cx))
492 ok = JA(cx, obj, scx);
493 else
494 ok = JO(cx, obj, scx);
495 scx->depth--;
497 return ok;
498 }
500 /* ES5 15.12.3. */
501 bool
502 js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value space_,
503 StringBuffer &sb)
504 {
505 RootedObject replacer(cx, replacer_);
506 RootedValue space(cx, space_);
508 /* Step 4. */
509 AutoIdVector propertyList(cx);
510 if (replacer) {
511 if (replacer->isCallable()) {
512 /* Step 4a(i): use replacer to transform values. */
513 } else if (ObjectClassIs(replacer, ESClass_Array, cx)) {
514 /*
515 * Step 4b: The spec algorithm is unhelpfully vague about the exact
516 * steps taken when the replacer is an array, regarding the exact
517 * sequence of [[Get]] calls for the array's elements, when its
518 * overall length is calculated, whether own or own plus inherited
519 * properties are considered, and so on. A rewrite was proposed in
520 * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
521 * whose steps are copied below, and which are implemented here.
522 *
523 * i. Let PropertyList be an empty internal List.
524 * ii. Let len be the result of calling the [[Get]] internal
525 * method of replacer with the argument "length".
526 * iii. Let i be 0.
527 * iv. While i < len:
528 * 1. Let item be undefined.
529 * 2. Let v be the result of calling the [[Get]] internal
530 * method of replacer with the argument ToString(i).
531 * 3. If Type(v) is String then let item be v.
532 * 4. Else if Type(v) is Number then let item be ToString(v).
533 * 5. Else if Type(v) is Object then
534 * a. If the [[Class]] internal property of v is "String"
535 * or "Number" then let item be ToString(v).
536 * 6. If item is not undefined and item is not currently an
537 * element of PropertyList then,
538 * a. Append item to the end of PropertyList.
539 * 7. Let i be i + 1.
540 */
542 /* Step 4b(ii). */
543 uint32_t len;
544 JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len));
545 if (replacer->is<ArrayObject>() && !replacer->isIndexed())
546 len = Min(len, replacer->getDenseInitializedLength());
548 // Cap the initial size to a moderately small value. This avoids
549 // ridiculous over-allocation if an array with bogusly-huge length
550 // is passed in. If we end up having to add elements past this
551 // size, the set will naturally resize to accommodate them.
552 const uint32_t MaxInitialSize = 1024;
553 HashSet<jsid, JsidHasher> idSet(cx);
554 if (!idSet.init(Min(len, MaxInitialSize)))
555 return false;
557 /* Step 4b(iii). */
558 uint32_t i = 0;
560 /* Step 4b(iv). */
561 RootedValue v(cx);
562 for (; i < len; i++) {
563 if (!CheckForInterrupt(cx))
564 return false;
566 /* Step 4b(iv)(2). */
567 if (!JSObject::getElement(cx, replacer, replacer, i, &v))
568 return false;
570 RootedId id(cx);
571 if (v.isNumber()) {
572 /* Step 4b(iv)(4). */
573 int32_t n;
574 if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
575 id = INT_TO_JSID(n);
576 } else {
577 if (!ValueToId<CanGC>(cx, v, &id))
578 return false;
579 }
580 } else if (v.isString() ||
581 IsObjectWithClass(v, ESClass_String, cx) ||
582 IsObjectWithClass(v, ESClass_Number, cx))
583 {
584 /* Step 4b(iv)(3), 4b(iv)(5). */
585 if (!ValueToId<CanGC>(cx, v, &id))
586 return false;
587 } else {
588 continue;
589 }
591 /* Step 4b(iv)(6). */
592 HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id);
593 if (!p) {
594 /* Step 4b(iv)(6)(a). */
595 if (!idSet.add(p, id) || !propertyList.append(id))
596 return false;
597 }
598 }
599 } else {
600 replacer = nullptr;
601 }
602 }
604 /* Step 5. */
605 if (space.isObject()) {
606 RootedObject spaceObj(cx, &space.toObject());
607 if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
608 double d;
609 if (!ToNumber(cx, space, &d))
610 return false;
611 space = NumberValue(d);
612 } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
613 JSString *str = ToStringSlow<CanGC>(cx, space);
614 if (!str)
615 return false;
616 space = StringValue(str);
617 }
618 }
620 StringBuffer gap(cx);
622 if (space.isNumber()) {
623 /* Step 6. */
624 double d;
625 JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
626 d = Min(10.0, d);
627 if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
628 return false;
629 } else if (space.isString()) {
630 /* Step 7. */
631 JSLinearString *str = space.toString()->ensureLinear(cx);
632 if (!str)
633 return false;
634 JS::Anchor<JSString *> anchor(str);
635 size_t len = Min(size_t(10), space.toString()->length());
636 if (!gap.append(str->chars(), len))
637 return false;
638 } else {
639 /* Step 8. */
640 JS_ASSERT(gap.empty());
641 }
643 /* Step 9. */
644 RootedObject wrapper(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
645 if (!wrapper)
646 return false;
648 /* Step 10. */
649 RootedId emptyId(cx, NameToId(cx->names().empty));
650 if (!DefineNativeProperty(cx, wrapper, emptyId, vp, JS_PropertyStub, JS_StrictPropertyStub,
651 JSPROP_ENUMERATE))
652 {
653 return false;
654 }
656 /* Step 11. */
657 StringifyContext scx(cx, sb, gap, replacer, propertyList);
658 if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
659 return false;
660 if (IsFilteredValue(vp))
661 return true;
663 return Str(cx, vp, &scx);
664 }
666 /* ES5 15.12.2 Walk. */
667 static bool
668 Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp)
669 {
670 JS_CHECK_RECURSION(cx, return false);
672 /* Step 1. */
673 RootedValue val(cx);
674 if (!JSObject::getGeneric(cx, holder, holder, name, &val))
675 return false;
677 /* Step 2. */
678 if (val.isObject()) {
679 RootedObject obj(cx, &val.toObject());
681 if (ObjectClassIs(obj, ESClass_Array, cx)) {
682 /* Step 2a(ii). */
683 uint32_t length;
684 if (!GetLengthProperty(cx, obj, &length))
685 return false;
687 /* Step 2a(i), 2a(iii-iv). */
688 RootedId id(cx);
689 RootedValue newElement(cx);
690 for (uint32_t i = 0; i < length; i++) {
691 if (!IndexToId(cx, i, &id))
692 return false;
694 /* Step 2a(iii)(1). */
695 if (!Walk(cx, obj, id, reviver, &newElement))
696 return false;
698 if (newElement.isUndefined()) {
699 /* Step 2a(iii)(2). */
700 bool succeeded;
701 if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded))
702 return false;
703 } else {
704 /* Step 2a(iii)(3). */
705 // XXX This definition should ignore success/failure, when
706 // our property-definition APIs indicate that.
707 if (!JSObject::defineGeneric(cx, obj, id, newElement))
708 return false;
709 }
710 }
711 } else {
712 /* Step 2b(i). */
713 AutoIdVector keys(cx);
714 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
715 return false;
717 /* Step 2b(ii). */
718 RootedId id(cx);
719 RootedValue newElement(cx);
720 for (size_t i = 0, len = keys.length(); i < len; i++) {
721 /* Step 2b(ii)(1). */
722 id = keys[i];
723 if (!Walk(cx, obj, id, reviver, &newElement))
724 return false;
726 if (newElement.isUndefined()) {
727 /* Step 2b(ii)(2). */
728 bool succeeded;
729 if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded))
730 return false;
731 } else {
732 /* Step 2b(ii)(3). */
733 // XXX This definition should ignore success/failure, when
734 // our property-definition APIs indicate that.
735 if (!JSObject::defineGeneric(cx, obj, id, newElement))
736 return false;
737 }
738 }
739 }
740 }
742 /* Step 3. */
743 RootedString key(cx, IdToString(cx, name));
744 if (!key)
745 return false;
747 InvokeArgs args(cx);
748 if (!args.init(2))
749 return false;
751 args.setCallee(reviver);
752 args.setThis(ObjectValue(*holder));
753 args[0].setString(key);
754 args[1].set(val);
756 if (!Invoke(cx, args))
757 return false;
758 vp.set(args.rval());
759 return true;
760 }
762 static bool
763 Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp)
764 {
765 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
766 if (!obj)
767 return false;
769 if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
770 return false;
772 Rooted<jsid> id(cx, NameToId(cx->names().empty));
773 return Walk(cx, obj, id, reviver, vp);
774 }
776 bool
777 js::ParseJSONWithReviver(JSContext *cx, ConstTwoByteChars chars, size_t length,
778 HandleValue reviver, MutableHandleValue vp)
779 {
780 /* 15.12.2 steps 2-3. */
781 JSONParser parser(cx, chars, length);
782 if (!parser.parse(vp))
783 return false;
785 /* 15.12.2 steps 4-5. */
786 if (js_IsCallable(reviver))
787 return Revive(cx, reviver, vp);
788 return true;
789 }
791 #if JS_HAS_TOSOURCE
792 static bool
793 json_toSource(JSContext *cx, unsigned argc, Value *vp)
794 {
795 CallArgs args = CallArgsFromVp(argc, vp);
796 args.rval().setString(cx->names().JSON);
797 return true;
798 }
799 #endif
801 /* ES5 15.12.2. */
802 static bool
803 json_parse(JSContext *cx, unsigned argc, Value *vp)
804 {
805 CallArgs args = CallArgsFromVp(argc, vp);
807 /* Step 1. */
808 JSString *str = (args.length() >= 1)
809 ? ToString<CanGC>(cx, args[0])
810 : cx->names().undefined;
811 if (!str)
812 return false;
814 Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
815 if (!flat)
816 return false;
818 JS::Anchor<JSString *> anchor(flat);
820 RootedValue reviver(cx, args.get(1));
822 /* Steps 2-5. */
823 return ParseJSONWithReviver(cx, ConstTwoByteChars(flat->chars(), flat->length()),
824 flat->length(), reviver, args.rval());
825 }
827 /* ES5 15.12.3. */
828 bool
829 json_stringify(JSContext *cx, unsigned argc, Value *vp)
830 {
831 CallArgs args = CallArgsFromVp(argc, vp);
832 RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr);
833 RootedValue value(cx, args.get(0));
834 RootedValue space(cx, args.get(2));
836 StringBuffer sb(cx);
837 if (!js_Stringify(cx, &value, replacer, space, sb))
838 return false;
840 // XXX This can never happen to nsJSON.cpp, but the JSON object
841 // needs to support returning undefined. So this is a little awkward
842 // for the API, because we want to support streaming writers.
843 if (!sb.empty()) {
844 JSString *str = sb.finishString();
845 if (!str)
846 return false;
847 args.rval().setString(str);
848 } else {
849 args.rval().setUndefined();
850 }
852 return true;
853 }
855 static const JSFunctionSpec json_static_methods[] = {
856 #if JS_HAS_TOSOURCE
857 JS_FN(js_toSource_str, json_toSource, 0, 0),
858 #endif
859 JS_FN("parse", json_parse, 2, 0),
860 JS_FN("stringify", json_stringify, 3, 0),
861 JS_FS_END
862 };
864 JSObject *
865 js_InitJSONClass(JSContext *cx, HandleObject obj)
866 {
867 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
869 /*
870 * JSON requires that Boolean.prototype.valueOf be created and stashed in a
871 * reserved slot on the global object; see js::BooleanGetPrimitiveValueSlow
872 * called from PreprocessValue above.
873 */
874 if (!GlobalObject::getOrCreateBooleanPrototype(cx, global))
875 return nullptr;
877 RootedObject proto(cx, obj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
878 RootedObject JSON(cx, NewObjectWithClassProto(cx, &JSONClass, proto, global, SingletonObject));
879 if (!JSON)
880 return nullptr;
882 if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, 0,
883 JS_PropertyStub, JS_StrictPropertyStub))
884 return nullptr;
886 if (!JS_DefineFunctions(cx, JSON, json_static_methods))
887 return nullptr;
889 global->setConstructor(JSProto_JSON, ObjectValue(*JSON));
891 return JSON;
892 }