Bumping manifests a=b2g-bump
[gecko.git] / js / src / json.cpp
blob806ffbc44d63998f6616afd8d51b08e9299d6b3f
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"
10 #include "mozilla/Range.h"
12 #include "jsarray.h"
13 #include "jsatom.h"
14 #include "jscntxt.h"
15 #include "jsnum.h"
16 #include "jsobj.h"
17 #include "jsonparser.h"
18 #include "jsstr.h"
19 #include "jstypes.h"
20 #include "jsutil.h"
22 #include "vm/Interpreter.h"
23 #include "vm/StringBuffer.h"
25 #include "jsatominlines.h"
26 #include "jsboolinlines.h"
27 #include "jsobjinlines.h"
29 using namespace js;
30 using namespace js::gc;
31 using namespace js::types;
33 using mozilla::IsFinite;
34 using mozilla::Maybe;
35 using mozilla::RangedPtr;
37 const Class js::JSONClass = {
38 js_JSON_str,
39 JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
40 JS_PropertyStub, /* addProperty */
41 JS_DeletePropertyStub, /* delProperty */
42 JS_PropertyStub, /* getProperty */
43 JS_StrictPropertyStub, /* setProperty */
44 JS_EnumerateStub,
45 JS_ResolveStub,
46 JS_ConvertStub
49 static inline bool IsQuoteSpecialCharacter(jschar c)
51 JS_STATIC_ASSERT('\b' < ' ');
52 JS_STATIC_ASSERT('\f' < ' ');
53 JS_STATIC_ASSERT('\n' < ' ');
54 JS_STATIC_ASSERT('\r' < ' ');
55 JS_STATIC_ASSERT('\t' < ' ');
56 return c == '"' || c == '\\' || c < ' ';
59 /* ES5 15.12.3 Quote. */
60 template <typename CharT>
61 static bool
62 Quote(StringBuffer &sb, JSLinearString *str)
64 size_t len = str->length();
66 /* Step 1. */
67 if (!sb.append('"'))
68 return false;
70 /* Step 2. */
71 JS::AutoCheckCannotGC nogc;
72 const RangedPtr<const CharT> buf(str->chars<CharT>(nogc), len);
73 for (size_t i = 0; i < len; ++i) {
74 /* Batch-append maximal character sequences containing no escapes. */
75 size_t mark = i;
76 do {
77 if (IsQuoteSpecialCharacter(buf[i]))
78 break;
79 } while (++i < len);
80 if (i > mark) {
81 if (!sb.appendSubstring(str, mark, i - mark))
82 return false;
83 if (i == len)
84 break;
87 jschar c = buf[i];
88 if (c == '"' || c == '\\') {
89 if (!sb.append('\\') || !sb.append(c))
90 return false;
91 } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
92 jschar abbrev = (c == '\b')
93 ? 'b'
94 : (c == '\f')
95 ? 'f'
96 : (c == '\n')
97 ? 'n'
98 : (c == '\r')
99 ? 'r'
100 : 't';
101 if (!sb.append('\\') || !sb.append(abbrev))
102 return false;
103 } else {
104 JS_ASSERT(c < ' ');
105 if (!sb.append("\\u00"))
106 return false;
107 JS_ASSERT((c >> 4) < 10);
108 uint8_t x = c >> 4, y = c % 16;
109 if (!sb.append(Latin1Char('0' + x)) ||
110 !sb.append(Latin1Char(y < 10 ? '0' + y : 'a' + (y - 10))))
112 return false;
117 /* Steps 3-4. */
118 return sb.append('"');
121 static bool
122 Quote(JSContext *cx, StringBuffer &sb, JSString *str)
124 JS::Anchor<JSString *> anchor(str);
125 JSLinearString *linear = str->ensureLinear(cx);
126 if (!linear)
127 return false;
129 return linear->hasLatin1Chars()
130 ? Quote<Latin1Char>(sb, linear)
131 : Quote<jschar>(sb, linear);
134 namespace {
136 class StringifyContext
138 public:
139 StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap,
140 HandleObject replacer, const AutoIdVector &propertyList)
141 : sb(sb),
142 gap(gap),
143 replacer(cx, replacer),
144 propertyList(propertyList),
145 depth(0)
148 StringBuffer &sb;
149 const StringBuffer &gap;
150 RootedObject replacer;
151 const AutoIdVector &propertyList;
152 uint32_t depth;
155 } /* anonymous namespace */
157 static bool Str(JSContext *cx, const Value &v, StringifyContext *scx);
159 static bool
160 WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
162 if (!scx->gap.empty()) {
163 if (!scx->sb.append('\n'))
164 return false;
166 if (scx->gap.isUnderlyingBufferLatin1()) {
167 for (uint32_t i = 0; i < limit; i++) {
168 if (!scx->sb.append(scx->gap.rawLatin1Begin(), scx->gap.rawLatin1End()))
169 return false;
171 } else {
172 for (uint32_t i = 0; i < limit; i++) {
173 if (!scx->sb.append(scx->gap.rawTwoByteBegin(), scx->gap.rawTwoByteEnd()))
174 return false;
179 return true;
182 namespace {
184 template<typename KeyType>
185 class KeyStringifier {
188 template<>
189 class KeyStringifier<uint32_t> {
190 public:
191 static JSString *toString(JSContext *cx, uint32_t index) {
192 return IndexToString(cx, index);
196 template<>
197 class KeyStringifier<HandleId> {
198 public:
199 static JSString *toString(JSContext *cx, HandleId id) {
200 return IdToString(cx, id);
204 } /* anonymous namespace */
207 * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
208 * values when stringifying objects in JO.
210 template<typename KeyType>
211 static bool
212 PreprocessValue(JSContext *cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext *scx)
214 RootedString keyStr(cx);
216 /* Step 2. */
217 if (vp.isObject()) {
218 RootedValue toJSON(cx);
219 RootedObject obj(cx, &vp.toObject());
220 if (!JSObject::getProperty(cx, obj, obj, cx->names().toJSON, &toJSON))
221 return false;
223 if (IsCallable(toJSON)) {
224 keyStr = KeyStringifier<KeyType>::toString(cx, key);
225 if (!keyStr)
226 return false;
228 InvokeArgs args(cx);
229 if (!args.init(1))
230 return false;
232 args.setCallee(toJSON);
233 args.setThis(vp);
234 args[0].setString(keyStr);
236 if (!Invoke(cx, args))
237 return false;
238 vp.set(args.rval());
242 /* Step 3. */
243 if (scx->replacer && scx->replacer->isCallable()) {
244 if (!keyStr) {
245 keyStr = KeyStringifier<KeyType>::toString(cx, key);
246 if (!keyStr)
247 return false;
250 InvokeArgs args(cx);
251 if (!args.init(2))
252 return false;
254 args.setCallee(ObjectValue(*scx->replacer));
255 args.setThis(ObjectValue(*holder));
256 args[0].setString(keyStr);
257 args[1].set(vp);
259 if (!Invoke(cx, args))
260 return false;
261 vp.set(args.rval());
264 /* Step 4. */
265 if (vp.get().isObject()) {
266 RootedObject obj(cx, &vp.get().toObject());
267 if (ObjectClassIs(obj, ESClass_Number, cx)) {
268 double d;
269 if (!ToNumber(cx, vp, &d))
270 return false;
271 vp.setNumber(d);
272 } else if (ObjectClassIs(obj, ESClass_String, cx)) {
273 JSString *str = ToStringSlow<CanGC>(cx, vp);
274 if (!str)
275 return false;
276 vp.setString(str);
277 } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
278 if (!Unbox(cx, obj, vp))
279 return false;
283 return true;
287 * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
288 * gauntlet will result in Str returning |undefined|. This function is used to
289 * properly omit properties resulting in such values when stringifying objects,
290 * while properly stringifying such properties as null when they're encountered
291 * in arrays.
293 static inline bool
294 IsFilteredValue(const Value &v)
296 return v.isUndefined() || v.isSymbol() || IsCallable(v);
299 /* ES5 15.12.3 JO. */
300 static bool
301 JO(JSContext *cx, HandleObject obj, StringifyContext *scx)
304 * This method implements the JO algorithm in ES5 15.12.3, but:
306 * * The algorithm is somewhat reformulated to allow the final string to
307 * be streamed into a single buffer, rather than be created and copied
308 * into place incrementally as the ES5 algorithm specifies it. This
309 * requires moving portions of the Str call in 8a into this algorithm
310 * (and in JA as well).
313 /* Steps 1-2, 11. */
314 AutoCycleDetector detect(cx, obj);
315 if (!detect.init())
316 return false;
317 if (detect.foundCycle()) {
318 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE,
319 js_object_str);
320 return false;
323 if (!scx->sb.append('{'))
324 return false;
326 /* Steps 5-7. */
327 Maybe<AutoIdVector> ids;
328 const AutoIdVector *props;
329 if (scx->replacer && !scx->replacer->isCallable()) {
330 JS_ASSERT(IsArray(scx->replacer, cx));
331 props = &scx->propertyList;
332 } else {
333 JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
334 ids.emplace(cx);
335 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.ptr()))
336 return false;
337 props = ids.ptr();
340 /* My kingdom for not-quite-initialized-from-the-start references. */
341 const AutoIdVector &propertyList = *props;
343 /* Steps 8-10, 13. */
344 bool wroteMember = false;
345 RootedId id(cx);
346 for (size_t i = 0, len = propertyList.length(); i < len; i++) {
348 * Steps 8a-8b. Note that the call to Str is broken up into 1) getting
349 * the property; 2) processing for toJSON, calling the replacer, and
350 * handling boxed Number/String/Boolean objects; 3) filtering out
351 * values which process to |undefined|, and 4) stringifying all values
352 * which pass the filter.
354 id = propertyList[i];
355 RootedValue outputValue(cx);
356 if (!JSObject::getGeneric(cx, obj, obj, id, &outputValue))
357 return false;
358 if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx))
359 return false;
360 if (IsFilteredValue(outputValue))
361 continue;
363 /* Output a comma unless this is the first member to write. */
364 if (wroteMember && !scx->sb.append(','))
365 return false;
366 wroteMember = true;
368 if (!WriteIndent(cx, scx, scx->depth))
369 return false;
371 JSString *s = IdToString(cx, id);
372 if (!s)
373 return false;
375 if (!Quote(cx, scx->sb, s) ||
376 !scx->sb.append(':') ||
377 !(scx->gap.empty() || scx->sb.append(' ')) ||
378 !Str(cx, outputValue, scx))
380 return false;
384 if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
385 return false;
387 return scx->sb.append('}');
390 /* ES5 15.12.3 JA. */
391 static bool
392 JA(JSContext *cx, HandleObject obj, StringifyContext *scx)
395 * This method implements the JA algorithm in ES5 15.12.3, but:
397 * * The algorithm is somewhat reformulated to allow the final string to
398 * be streamed into a single buffer, rather than be created and copied
399 * into place incrementally as the ES5 algorithm specifies it. This
400 * requires moving portions of the Str call in 8a into this algorithm
401 * (and in JO as well).
404 /* Steps 1-2, 11. */
405 AutoCycleDetector detect(cx, obj);
406 if (!detect.init())
407 return false;
408 if (detect.foundCycle()) {
409 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE,
410 js_object_str);
411 return false;
414 if (!scx->sb.append('['))
415 return false;
417 /* Step 6. */
418 uint32_t length;
419 if (!GetLengthProperty(cx, obj, &length))
420 return false;
422 /* Steps 7-10. */
423 if (length != 0) {
424 /* Steps 4, 10b(i). */
425 if (!WriteIndent(cx, scx, scx->depth))
426 return false;
428 /* Steps 7-10. */
429 RootedValue outputValue(cx);
430 for (uint32_t i = 0; i < length; i++) {
432 * Steps 8a-8c. Again note how the call to the spec's Str method
433 * is broken up into getting the property, running it past toJSON
434 * and the replacer and maybe unboxing, and interpreting some
435 * values as |null| in separate steps.
437 if (!JSObject::getElement(cx, obj, obj, i, &outputValue))
438 return false;
439 if (!PreprocessValue(cx, obj, i, &outputValue, scx))
440 return false;
441 if (IsFilteredValue(outputValue)) {
442 if (!scx->sb.append("null"))
443 return false;
444 } else {
445 if (!Str(cx, outputValue, scx))
446 return false;
449 /* Steps 3, 4, 10b(i). */
450 if (i < length - 1) {
451 if (!scx->sb.append(','))
452 return false;
453 if (!WriteIndent(cx, scx, scx->depth))
454 return false;
458 /* Step 10(b)(iii). */
459 if (!WriteIndent(cx, scx, scx->depth - 1))
460 return false;
463 return scx->sb.append(']');
466 static bool
467 Str(JSContext *cx, const Value &v, StringifyContext *scx)
469 /* Step 11 must be handled by the caller. */
470 JS_ASSERT(!IsFilteredValue(v));
472 JS_CHECK_RECURSION(cx, return false);
475 * This method implements the Str algorithm in ES5 15.12.3, but:
477 * * We move property retrieval (step 1) into callers to stream the
478 * stringification process and avoid constantly copying strings.
479 * * We move the preprocessing in steps 2-4 into a helper function to
480 * allow both JO and JA to use this method. While JA could use it
481 * without this move, JO must omit any |undefined|-valued property per
482 * so it can't stream out a value using the Str method exactly as
483 * defined by ES5.
484 * * We move step 11 into callers, again to ease streaming.
487 /* Step 8. */
488 if (v.isString())
489 return Quote(cx, scx->sb, v.toString());
491 /* Step 5. */
492 if (v.isNull())
493 return scx->sb.append("null");
495 /* Steps 6-7. */
496 if (v.isBoolean())
497 return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
499 /* Step 9. */
500 if (v.isNumber()) {
501 if (v.isDouble()) {
502 if (!IsFinite(v.toDouble()))
503 return scx->sb.append("null");
506 return NumberValueToStringBuffer(cx, v, scx->sb);
509 /* Step 10. */
510 JS_ASSERT(v.isObject());
511 RootedObject obj(cx, &v.toObject());
513 scx->depth++;
514 bool ok;
515 if (IsArray(obj, cx))
516 ok = JA(cx, obj, scx);
517 else
518 ok = JO(cx, obj, scx);
519 scx->depth--;
521 return ok;
524 /* ES5 15.12.3. */
525 bool
526 js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value space_,
527 StringBuffer &sb)
529 RootedObject replacer(cx, replacer_);
530 RootedValue space(cx, space_);
532 /* Step 4. */
533 AutoIdVector propertyList(cx);
534 if (replacer) {
535 if (replacer->isCallable()) {
536 /* Step 4a(i): use replacer to transform values. */
537 } else if (IsArray(replacer, cx)) {
539 * Step 4b: The spec algorithm is unhelpfully vague about the exact
540 * steps taken when the replacer is an array, regarding the exact
541 * sequence of [[Get]] calls for the array's elements, when its
542 * overall length is calculated, whether own or own plus inherited
543 * properties are considered, and so on. A rewrite was proposed in
544 * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
545 * whose steps are copied below, and which are implemented here.
547 * i. Let PropertyList be an empty internal List.
548 * ii. Let len be the result of calling the [[Get]] internal
549 * method of replacer with the argument "length".
550 * iii. Let i be 0.
551 * iv. While i < len:
552 * 1. Let item be undefined.
553 * 2. Let v be the result of calling the [[Get]] internal
554 * method of replacer with the argument ToString(i).
555 * 3. If Type(v) is String then let item be v.
556 * 4. Else if Type(v) is Number then let item be ToString(v).
557 * 5. Else if Type(v) is Object then
558 * a. If the [[Class]] internal property of v is "String"
559 * or "Number" then let item be ToString(v).
560 * 6. If item is not undefined and item is not currently an
561 * element of PropertyList then,
562 * a. Append item to the end of PropertyList.
563 * 7. Let i be i + 1.
566 /* Step 4b(ii). */
567 uint32_t len;
568 if (!GetLengthProperty(cx, replacer, &len))
569 return false;
570 if (replacer->is<ArrayObject>() && !replacer->isIndexed())
571 len = Min(len, replacer->getDenseInitializedLength());
573 // Cap the initial size to a moderately small value. This avoids
574 // ridiculous over-allocation if an array with bogusly-huge length
575 // is passed in. If we end up having to add elements past this
576 // size, the set will naturally resize to accommodate them.
577 const uint32_t MaxInitialSize = 1024;
578 HashSet<jsid, JsidHasher> idSet(cx);
579 if (!idSet.init(Min(len, MaxInitialSize)))
580 return false;
582 /* Step 4b(iii). */
583 uint32_t i = 0;
585 /* Step 4b(iv). */
586 RootedValue v(cx);
587 for (; i < len; i++) {
588 if (!CheckForInterrupt(cx))
589 return false;
591 /* Step 4b(iv)(2). */
592 if (!JSObject::getElement(cx, replacer, replacer, i, &v))
593 return false;
595 RootedId id(cx);
596 if (v.isNumber()) {
597 /* Step 4b(iv)(4). */
598 int32_t n;
599 if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
600 id = INT_TO_JSID(n);
601 } else {
602 if (!ValueToId<CanGC>(cx, v, &id))
603 return false;
605 } else if (v.isString() ||
606 IsObjectWithClass(v, ESClass_String, cx) ||
607 IsObjectWithClass(v, ESClass_Number, cx))
609 /* Step 4b(iv)(3), 4b(iv)(5). */
610 if (!ValueToId<CanGC>(cx, v, &id))
611 return false;
612 } else {
613 continue;
616 /* Step 4b(iv)(6). */
617 HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id);
618 if (!p) {
619 /* Step 4b(iv)(6)(a). */
620 if (!idSet.add(p, id) || !propertyList.append(id))
621 return false;
624 } else {
625 replacer = nullptr;
629 /* Step 5. */
630 if (space.isObject()) {
631 RootedObject spaceObj(cx, &space.toObject());
632 if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
633 double d;
634 if (!ToNumber(cx, space, &d))
635 return false;
636 space = NumberValue(d);
637 } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
638 JSString *str = ToStringSlow<CanGC>(cx, space);
639 if (!str)
640 return false;
641 space = StringValue(str);
645 StringBuffer gap(cx);
647 if (space.isNumber()) {
648 /* Step 6. */
649 double d;
650 JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
651 d = Min(10.0, d);
652 if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
653 return false;
654 } else if (space.isString()) {
655 /* Step 7. */
656 JSLinearString *str = space.toString()->ensureLinear(cx);
657 if (!str)
658 return false;
659 JS::Anchor<JSString *> anchor(str);
660 size_t len = Min(size_t(10), str->length());
661 if (!gap.appendSubstring(str, 0, len))
662 return false;
663 } else {
664 /* Step 8. */
665 JS_ASSERT(gap.empty());
668 /* Step 9. */
669 RootedObject wrapper(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
670 if (!wrapper)
671 return false;
673 /* Step 10. */
674 RootedId emptyId(cx, NameToId(cx->names().empty));
675 if (!DefineNativeProperty(cx, wrapper, emptyId, vp, JS_PropertyStub, JS_StrictPropertyStub,
676 JSPROP_ENUMERATE))
678 return false;
681 /* Step 11. */
682 StringifyContext scx(cx, sb, gap, replacer, propertyList);
683 if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
684 return false;
685 if (IsFilteredValue(vp))
686 return true;
688 return Str(cx, vp, &scx);
691 /* ES5 15.12.2 Walk. */
692 static bool
693 Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp)
695 JS_CHECK_RECURSION(cx, return false);
697 /* Step 1. */
698 RootedValue val(cx);
699 if (!JSObject::getGeneric(cx, holder, holder, name, &val))
700 return false;
702 /* Step 2. */
703 if (val.isObject()) {
704 RootedObject obj(cx, &val.toObject());
706 if (IsArray(obj, cx)) {
707 /* Step 2a(ii). */
708 uint32_t length;
709 if (!GetLengthProperty(cx, obj, &length))
710 return false;
712 /* Step 2a(i), 2a(iii-iv). */
713 RootedId id(cx);
714 RootedValue newElement(cx);
715 for (uint32_t i = 0; i < length; i++) {
716 if (!IndexToId(cx, i, &id))
717 return false;
719 /* Step 2a(iii)(1). */
720 if (!Walk(cx, obj, id, reviver, &newElement))
721 return false;
723 if (newElement.isUndefined()) {
724 /* Step 2a(iii)(2). */
725 bool succeeded;
726 if (!JSObject::deleteGeneric(cx, obj, id, &succeeded))
727 return false;
728 } else {
729 /* Step 2a(iii)(3). */
730 // XXX This definition should ignore success/failure, when
731 // our property-definition APIs indicate that.
732 if (!JSObject::defineGeneric(cx, obj, id, newElement))
733 return false;
736 } else {
737 /* Step 2b(i). */
738 AutoIdVector keys(cx);
739 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
740 return false;
742 /* Step 2b(ii). */
743 RootedId id(cx);
744 RootedValue newElement(cx);
745 for (size_t i = 0, len = keys.length(); i < len; i++) {
746 /* Step 2b(ii)(1). */
747 id = keys[i];
748 if (!Walk(cx, obj, id, reviver, &newElement))
749 return false;
751 if (newElement.isUndefined()) {
752 /* Step 2b(ii)(2). */
753 bool succeeded;
754 if (!JSObject::deleteGeneric(cx, obj, id, &succeeded))
755 return false;
756 } else {
757 /* Step 2b(ii)(3). */
758 // XXX This definition should ignore success/failure, when
759 // our property-definition APIs indicate that.
760 if (!JSObject::defineGeneric(cx, obj, id, newElement))
761 return false;
767 /* Step 3. */
768 RootedString key(cx, IdToString(cx, name));
769 if (!key)
770 return false;
772 InvokeArgs args(cx);
773 if (!args.init(2))
774 return false;
776 args.setCallee(reviver);
777 args.setThis(ObjectValue(*holder));
778 args[0].setString(key);
779 args[1].set(val);
781 if (!Invoke(cx, args))
782 return false;
783 vp.set(args.rval());
784 return true;
787 static bool
788 Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp)
790 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
791 if (!obj)
792 return false;
794 if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
795 return false;
797 Rooted<jsid> id(cx, NameToId(cx->names().empty));
798 return Walk(cx, obj, id, reviver, vp);
801 template <typename CharT>
802 bool
803 js::ParseJSONWithReviver(JSContext *cx, const mozilla::Range<const CharT> chars, HandleValue reviver,
804 MutableHandleValue vp)
806 /* 15.12.2 steps 2-3. */
807 JSONParser<CharT> parser(cx, chars);
808 if (!parser.parse(vp))
809 return false;
811 /* 15.12.2 steps 4-5. */
812 if (IsCallable(reviver))
813 return Revive(cx, reviver, vp);
814 return true;
817 template bool
818 js::ParseJSONWithReviver(JSContext *cx, const mozilla::Range<const Latin1Char> chars,
819 HandleValue reviver, MutableHandleValue vp);
821 template bool
822 js::ParseJSONWithReviver(JSContext *cx, const mozilla::Range<const jschar> chars, HandleValue reviver,
823 MutableHandleValue vp);
825 #if JS_HAS_TOSOURCE
826 static bool
827 json_toSource(JSContext *cx, unsigned argc, Value *vp)
829 CallArgs args = CallArgsFromVp(argc, vp);
830 args.rval().setString(cx->names().JSON);
831 return true;
833 #endif
835 /* ES5 15.12.2. */
836 static bool
837 json_parse(JSContext *cx, unsigned argc, Value *vp)
839 CallArgs args = CallArgsFromVp(argc, vp);
841 /* Step 1. */
842 JSString *str = (args.length() >= 1)
843 ? ToString<CanGC>(cx, args[0])
844 : cx->names().undefined;
845 if (!str)
846 return false;
848 JSFlatString *flat = str->ensureFlat(cx);
849 if (!flat)
850 return false;
852 JS::Anchor<JSString *> anchor(flat);
854 AutoStableStringChars flatChars(cx);
855 if (!flatChars.init(cx, flat))
856 return false;
858 RootedValue reviver(cx, args.get(1));
860 /* Steps 2-5. */
861 return flatChars.isLatin1()
862 ? ParseJSONWithReviver(cx, flatChars.latin1Range(), reviver, args.rval())
863 : ParseJSONWithReviver(cx, flatChars.twoByteRange(), reviver, args.rval());
866 /* ES5 15.12.3. */
867 bool
868 json_stringify(JSContext *cx, unsigned argc, Value *vp)
870 CallArgs args = CallArgsFromVp(argc, vp);
871 RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr);
872 RootedValue value(cx, args.get(0));
873 RootedValue space(cx, args.get(2));
875 StringBuffer sb(cx);
876 if (!js_Stringify(cx, &value, replacer, space, sb))
877 return false;
879 // XXX This can never happen to nsJSON.cpp, but the JSON object
880 // needs to support returning undefined. So this is a little awkward
881 // for the API, because we want to support streaming writers.
882 if (!sb.empty()) {
883 JSString *str = sb.finishString();
884 if (!str)
885 return false;
886 args.rval().setString(str);
887 } else {
888 args.rval().setUndefined();
891 return true;
894 static const JSFunctionSpec json_static_methods[] = {
895 #if JS_HAS_TOSOURCE
896 JS_FN(js_toSource_str, json_toSource, 0, 0),
897 #endif
898 JS_FN("parse", json_parse, 2, 0),
899 JS_FN("stringify", json_stringify, 3, 0),
900 JS_FS_END
903 JSObject *
904 js_InitJSONClass(JSContext *cx, HandleObject obj)
906 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
908 RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
909 if (!proto)
910 return nullptr;
911 RootedObject JSON(cx, NewObjectWithGivenProto(cx, &JSONClass, proto, global, SingletonObject));
912 if (!JSON)
913 return nullptr;
915 if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, 0,
916 JS_PropertyStub, JS_StrictPropertyStub))
917 return nullptr;
919 if (!JS_DefineFunctions(cx, JSON, json_static_methods))
920 return nullptr;
922 global->setConstructor(JSProto_JSON, ObjectValue(*JSON));
924 return JSON;