Bumping manifests a=b2g-bump
[gecko.git] / js / src / json.cpp
blobc3e03cbf0e926ed0a79ebb4d0cd50079e093f9a1
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.set(NumberValue(d));
272 } else if (ObjectClassIs(obj, ESClass_String, cx)) {
273 JSString *str = ToStringSlow<CanGC>(cx, vp);
274 if (!str)
275 return false;
276 vp.set(StringValue(str));
277 } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
278 vp.setBoolean(BooleanGetPrimitiveValue(obj));
282 return true;
286 * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
287 * gauntlet will result in Str returning |undefined|. This function is used to
288 * properly omit properties resulting in such values when stringifying objects,
289 * while properly stringifying such properties as null when they're encountered
290 * in arrays.
292 static inline bool
293 IsFilteredValue(const Value &v)
295 return v.isUndefined() || v.isSymbol() || IsCallable(v);
298 /* ES5 15.12.3 JO. */
299 static bool
300 JO(JSContext *cx, HandleObject obj, StringifyContext *scx)
303 * This method implements the JO algorithm in ES5 15.12.3, but:
305 * * The algorithm is somewhat reformulated to allow the final string to
306 * be streamed into a single buffer, rather than be created and copied
307 * into place incrementally as the ES5 algorithm specifies it. This
308 * requires moving portions of the Str call in 8a into this algorithm
309 * (and in JA as well).
312 /* Steps 1-2, 11. */
313 AutoCycleDetector detect(cx, obj);
314 if (!detect.init())
315 return false;
316 if (detect.foundCycle()) {
317 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE,
318 js_object_str);
319 return false;
322 if (!scx->sb.append('{'))
323 return false;
325 /* Steps 5-7. */
326 Maybe<AutoIdVector> ids;
327 const AutoIdVector *props;
328 if (scx->replacer && !scx->replacer->isCallable()) {
329 JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
330 props = &scx->propertyList;
331 } else {
332 JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
333 ids.emplace(cx);
334 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.ptr()))
335 return false;
336 props = ids.ptr();
339 /* My kingdom for not-quite-initialized-from-the-start references. */
340 const AutoIdVector &propertyList = *props;
342 /* Steps 8-10, 13. */
343 bool wroteMember = false;
344 RootedId id(cx);
345 for (size_t i = 0, len = propertyList.length(); i < len; i++) {
347 * Steps 8a-8b. Note that the call to Str is broken up into 1) getting
348 * the property; 2) processing for toJSON, calling the replacer, and
349 * handling boxed Number/String/Boolean objects; 3) filtering out
350 * values which process to |undefined|, and 4) stringifying all values
351 * which pass the filter.
353 id = propertyList[i];
354 RootedValue outputValue(cx);
355 if (!JSObject::getGeneric(cx, obj, obj, id, &outputValue))
356 return false;
357 if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx))
358 return false;
359 if (IsFilteredValue(outputValue))
360 continue;
362 /* Output a comma unless this is the first member to write. */
363 if (wroteMember && !scx->sb.append(','))
364 return false;
365 wroteMember = true;
367 if (!WriteIndent(cx, scx, scx->depth))
368 return false;
370 JSString *s = IdToString(cx, id);
371 if (!s)
372 return false;
374 if (!Quote(cx, scx->sb, s) ||
375 !scx->sb.append(':') ||
376 !(scx->gap.empty() || scx->sb.append(' ')) ||
377 !Str(cx, outputValue, scx))
379 return false;
383 if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
384 return false;
386 return scx->sb.append('}');
389 /* ES5 15.12.3 JA. */
390 static bool
391 JA(JSContext *cx, HandleObject obj, StringifyContext *scx)
394 * This method implements the JA algorithm in ES5 15.12.3, but:
396 * * The algorithm is somewhat reformulated to allow the final string to
397 * be streamed into a single buffer, rather than be created and copied
398 * into place incrementally as the ES5 algorithm specifies it. This
399 * requires moving portions of the Str call in 8a into this algorithm
400 * (and in JO as well).
403 /* Steps 1-2, 11. */
404 AutoCycleDetector detect(cx, obj);
405 if (!detect.init())
406 return false;
407 if (detect.foundCycle()) {
408 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE,
409 js_object_str);
410 return false;
413 if (!scx->sb.append('['))
414 return false;
416 /* Step 6. */
417 uint32_t length;
418 if (!GetLengthProperty(cx, obj, &length))
419 return false;
421 /* Steps 7-10. */
422 if (length != 0) {
423 /* Steps 4, 10b(i). */
424 if (!WriteIndent(cx, scx, scx->depth))
425 return false;
427 /* Steps 7-10. */
428 RootedValue outputValue(cx);
429 for (uint32_t i = 0; i < length; i++) {
431 * Steps 8a-8c. Again note how the call to the spec's Str method
432 * is broken up into getting the property, running it past toJSON
433 * and the replacer and maybe unboxing, and interpreting some
434 * values as |null| in separate steps.
436 if (!JSObject::getElement(cx, obj, obj, i, &outputValue))
437 return false;
438 if (!PreprocessValue(cx, obj, i, &outputValue, scx))
439 return false;
440 if (IsFilteredValue(outputValue)) {
441 if (!scx->sb.append("null"))
442 return false;
443 } else {
444 if (!Str(cx, outputValue, scx))
445 return false;
448 /* Steps 3, 4, 10b(i). */
449 if (i < length - 1) {
450 if (!scx->sb.append(','))
451 return false;
452 if (!WriteIndent(cx, scx, scx->depth))
453 return false;
457 /* Step 10(b)(iii). */
458 if (!WriteIndent(cx, scx, scx->depth - 1))
459 return false;
462 return scx->sb.append(']');
465 static bool
466 Str(JSContext *cx, const Value &v, StringifyContext *scx)
468 /* Step 11 must be handled by the caller. */
469 JS_ASSERT(!IsFilteredValue(v));
471 JS_CHECK_RECURSION(cx, return false);
474 * This method implements the Str algorithm in ES5 15.12.3, but:
476 * * We move property retrieval (step 1) into callers to stream the
477 * stringification process and avoid constantly copying strings.
478 * * We move the preprocessing in steps 2-4 into a helper function to
479 * allow both JO and JA to use this method. While JA could use it
480 * without this move, JO must omit any |undefined|-valued property per
481 * so it can't stream out a value using the Str method exactly as
482 * defined by ES5.
483 * * We move step 11 into callers, again to ease streaming.
486 /* Step 8. */
487 if (v.isString())
488 return Quote(cx, scx->sb, v.toString());
490 /* Step 5. */
491 if (v.isNull())
492 return scx->sb.append("null");
494 /* Steps 6-7. */
495 if (v.isBoolean())
496 return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
498 /* Step 9. */
499 if (v.isNumber()) {
500 if (v.isDouble()) {
501 if (!IsFinite(v.toDouble()))
502 return scx->sb.append("null");
505 return NumberValueToStringBuffer(cx, v, scx->sb);
508 /* Step 10. */
509 JS_ASSERT(v.isObject());
510 RootedObject obj(cx, &v.toObject());
512 scx->depth++;
513 bool ok;
514 if (ObjectClassIs(obj, ESClass_Array, cx))
515 ok = JA(cx, obj, scx);
516 else
517 ok = JO(cx, obj, scx);
518 scx->depth--;
520 return ok;
523 /* ES5 15.12.3. */
524 bool
525 js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value space_,
526 StringBuffer &sb)
528 RootedObject replacer(cx, replacer_);
529 RootedValue space(cx, space_);
531 /* Step 4. */
532 AutoIdVector propertyList(cx);
533 if (replacer) {
534 if (replacer->isCallable()) {
535 /* Step 4a(i): use replacer to transform values. */
536 } else if (ObjectClassIs(replacer, ESClass_Array, cx)) {
538 * Step 4b: The spec algorithm is unhelpfully vague about the exact
539 * steps taken when the replacer is an array, regarding the exact
540 * sequence of [[Get]] calls for the array's elements, when its
541 * overall length is calculated, whether own or own plus inherited
542 * properties are considered, and so on. A rewrite was proposed in
543 * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
544 * whose steps are copied below, and which are implemented here.
546 * i. Let PropertyList be an empty internal List.
547 * ii. Let len be the result of calling the [[Get]] internal
548 * method of replacer with the argument "length".
549 * iii. Let i be 0.
550 * iv. While i < len:
551 * 1. Let item be undefined.
552 * 2. Let v be the result of calling the [[Get]] internal
553 * method of replacer with the argument ToString(i).
554 * 3. If Type(v) is String then let item be v.
555 * 4. Else if Type(v) is Number then let item be ToString(v).
556 * 5. Else if Type(v) is Object then
557 * a. If the [[Class]] internal property of v is "String"
558 * or "Number" then let item be ToString(v).
559 * 6. If item is not undefined and item is not currently an
560 * element of PropertyList then,
561 * a. Append item to the end of PropertyList.
562 * 7. Let i be i + 1.
565 /* Step 4b(ii). */
566 uint32_t len;
567 JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len));
568 if (replacer->is<ArrayObject>() && !replacer->isIndexed())
569 len = Min(len, replacer->getDenseInitializedLength());
571 // Cap the initial size to a moderately small value. This avoids
572 // ridiculous over-allocation if an array with bogusly-huge length
573 // is passed in. If we end up having to add elements past this
574 // size, the set will naturally resize to accommodate them.
575 const uint32_t MaxInitialSize = 1024;
576 HashSet<jsid, JsidHasher> idSet(cx);
577 if (!idSet.init(Min(len, MaxInitialSize)))
578 return false;
580 /* Step 4b(iii). */
581 uint32_t i = 0;
583 /* Step 4b(iv). */
584 RootedValue v(cx);
585 for (; i < len; i++) {
586 if (!CheckForInterrupt(cx))
587 return false;
589 /* Step 4b(iv)(2). */
590 if (!JSObject::getElement(cx, replacer, replacer, i, &v))
591 return false;
593 RootedId id(cx);
594 if (v.isNumber()) {
595 /* Step 4b(iv)(4). */
596 int32_t n;
597 if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
598 id = INT_TO_JSID(n);
599 } else {
600 if (!ValueToId<CanGC>(cx, v, &id))
601 return false;
603 } else if (v.isString() ||
604 IsObjectWithClass(v, ESClass_String, cx) ||
605 IsObjectWithClass(v, ESClass_Number, cx))
607 /* Step 4b(iv)(3), 4b(iv)(5). */
608 if (!ValueToId<CanGC>(cx, v, &id))
609 return false;
610 } else {
611 continue;
614 /* Step 4b(iv)(6). */
615 HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id);
616 if (!p) {
617 /* Step 4b(iv)(6)(a). */
618 if (!idSet.add(p, id) || !propertyList.append(id))
619 return false;
622 } else {
623 replacer = nullptr;
627 /* Step 5. */
628 if (space.isObject()) {
629 RootedObject spaceObj(cx, &space.toObject());
630 if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
631 double d;
632 if (!ToNumber(cx, space, &d))
633 return false;
634 space = NumberValue(d);
635 } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
636 JSString *str = ToStringSlow<CanGC>(cx, space);
637 if (!str)
638 return false;
639 space = StringValue(str);
643 StringBuffer gap(cx);
645 if (space.isNumber()) {
646 /* Step 6. */
647 double d;
648 JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
649 d = Min(10.0, d);
650 if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
651 return false;
652 } else if (space.isString()) {
653 /* Step 7. */
654 JSLinearString *str = space.toString()->ensureLinear(cx);
655 if (!str)
656 return false;
657 JS::Anchor<JSString *> anchor(str);
658 size_t len = Min(size_t(10), str->length());
659 if (!gap.appendSubstring(str, 0, len))
660 return false;
661 } else {
662 /* Step 8. */
663 JS_ASSERT(gap.empty());
666 /* Step 9. */
667 RootedObject wrapper(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
668 if (!wrapper)
669 return false;
671 /* Step 10. */
672 RootedId emptyId(cx, NameToId(cx->names().empty));
673 if (!DefineNativeProperty(cx, wrapper, emptyId, vp, JS_PropertyStub, JS_StrictPropertyStub,
674 JSPROP_ENUMERATE))
676 return false;
679 /* Step 11. */
680 StringifyContext scx(cx, sb, gap, replacer, propertyList);
681 if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
682 return false;
683 if (IsFilteredValue(vp))
684 return true;
686 return Str(cx, vp, &scx);
689 /* ES5 15.12.2 Walk. */
690 static bool
691 Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp)
693 JS_CHECK_RECURSION(cx, return false);
695 /* Step 1. */
696 RootedValue val(cx);
697 if (!JSObject::getGeneric(cx, holder, holder, name, &val))
698 return false;
700 /* Step 2. */
701 if (val.isObject()) {
702 RootedObject obj(cx, &val.toObject());
704 if (ObjectClassIs(obj, ESClass_Array, cx)) {
705 /* Step 2a(ii). */
706 uint32_t length;
707 if (!GetLengthProperty(cx, obj, &length))
708 return false;
710 /* Step 2a(i), 2a(iii-iv). */
711 RootedId id(cx);
712 RootedValue newElement(cx);
713 for (uint32_t i = 0; i < length; i++) {
714 if (!IndexToId(cx, i, &id))
715 return false;
717 /* Step 2a(iii)(1). */
718 if (!Walk(cx, obj, id, reviver, &newElement))
719 return false;
721 if (newElement.isUndefined()) {
722 /* Step 2a(iii)(2). */
723 bool succeeded;
724 if (!JSObject::deleteGeneric(cx, obj, id, &succeeded))
725 return false;
726 } else {
727 /* Step 2a(iii)(3). */
728 // XXX This definition should ignore success/failure, when
729 // our property-definition APIs indicate that.
730 if (!JSObject::defineGeneric(cx, obj, id, newElement))
731 return false;
734 } else {
735 /* Step 2b(i). */
736 AutoIdVector keys(cx);
737 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
738 return false;
740 /* Step 2b(ii). */
741 RootedId id(cx);
742 RootedValue newElement(cx);
743 for (size_t i = 0, len = keys.length(); i < len; i++) {
744 /* Step 2b(ii)(1). */
745 id = keys[i];
746 if (!Walk(cx, obj, id, reviver, &newElement))
747 return false;
749 if (newElement.isUndefined()) {
750 /* Step 2b(ii)(2). */
751 bool succeeded;
752 if (!JSObject::deleteGeneric(cx, obj, id, &succeeded))
753 return false;
754 } else {
755 /* Step 2b(ii)(3). */
756 // XXX This definition should ignore success/failure, when
757 // our property-definition APIs indicate that.
758 if (!JSObject::defineGeneric(cx, obj, id, newElement))
759 return false;
765 /* Step 3. */
766 RootedString key(cx, IdToString(cx, name));
767 if (!key)
768 return false;
770 InvokeArgs args(cx);
771 if (!args.init(2))
772 return false;
774 args.setCallee(reviver);
775 args.setThis(ObjectValue(*holder));
776 args[0].setString(key);
777 args[1].set(val);
779 if (!Invoke(cx, args))
780 return false;
781 vp.set(args.rval());
782 return true;
785 static bool
786 Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp)
788 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
789 if (!obj)
790 return false;
792 if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
793 return false;
795 Rooted<jsid> id(cx, NameToId(cx->names().empty));
796 return Walk(cx, obj, id, reviver, vp);
799 template <typename CharT>
800 bool
801 js::ParseJSONWithReviver(JSContext *cx, const mozilla::Range<const CharT> chars, HandleValue reviver,
802 MutableHandleValue vp)
804 /* 15.12.2 steps 2-3. */
805 JSONParser<CharT> parser(cx, chars);
806 if (!parser.parse(vp))
807 return false;
809 /* 15.12.2 steps 4-5. */
810 if (IsCallable(reviver))
811 return Revive(cx, reviver, vp);
812 return true;
815 template bool
816 js::ParseJSONWithReviver(JSContext *cx, const mozilla::Range<const Latin1Char> chars,
817 HandleValue reviver, MutableHandleValue vp);
819 template bool
820 js::ParseJSONWithReviver(JSContext *cx, const mozilla::Range<const jschar> chars, HandleValue reviver,
821 MutableHandleValue vp);
823 #if JS_HAS_TOSOURCE
824 static bool
825 json_toSource(JSContext *cx, unsigned argc, Value *vp)
827 CallArgs args = CallArgsFromVp(argc, vp);
828 args.rval().setString(cx->names().JSON);
829 return true;
831 #endif
833 /* ES5 15.12.2. */
834 static bool
835 json_parse(JSContext *cx, unsigned argc, Value *vp)
837 CallArgs args = CallArgsFromVp(argc, vp);
839 /* Step 1. */
840 JSString *str = (args.length() >= 1)
841 ? ToString<CanGC>(cx, args[0])
842 : cx->names().undefined;
843 if (!str)
844 return false;
846 JSFlatString *flat = str->ensureFlat(cx);
847 if (!flat)
848 return false;
850 JS::Anchor<JSString *> anchor(flat);
852 AutoStableStringChars flatChars(cx);
853 if (!flatChars.init(cx, flat))
854 return false;
856 RootedValue reviver(cx, args.get(1));
858 /* Steps 2-5. */
859 return flatChars.isLatin1()
860 ? ParseJSONWithReviver(cx, flatChars.latin1Range(), reviver, args.rval())
861 : ParseJSONWithReviver(cx, flatChars.twoByteRange(), reviver, args.rval());
864 /* ES5 15.12.3. */
865 bool
866 json_stringify(JSContext *cx, unsigned argc, Value *vp)
868 CallArgs args = CallArgsFromVp(argc, vp);
869 RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr);
870 RootedValue value(cx, args.get(0));
871 RootedValue space(cx, args.get(2));
873 StringBuffer sb(cx);
874 if (!js_Stringify(cx, &value, replacer, space, sb))
875 return false;
877 // XXX This can never happen to nsJSON.cpp, but the JSON object
878 // needs to support returning undefined. So this is a little awkward
879 // for the API, because we want to support streaming writers.
880 if (!sb.empty()) {
881 JSString *str = sb.finishString();
882 if (!str)
883 return false;
884 args.rval().setString(str);
885 } else {
886 args.rval().setUndefined();
889 return true;
892 static const JSFunctionSpec json_static_methods[] = {
893 #if JS_HAS_TOSOURCE
894 JS_FN(js_toSource_str, json_toSource, 0, 0),
895 #endif
896 JS_FN("parse", json_parse, 2, 0),
897 JS_FN("stringify", json_stringify, 3, 0),
898 JS_FS_END
901 JSObject *
902 js_InitJSONClass(JSContext *cx, HandleObject obj)
904 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
907 * JSON requires that Boolean.prototype.valueOf be created and stashed in a
908 * reserved slot on the global object; see js::BooleanGetPrimitiveValueSlow
909 * called from PreprocessValue above.
911 if (!GlobalObject::getOrCreateBooleanPrototype(cx, global))
912 return nullptr;
914 RootedObject proto(cx, obj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
915 RootedObject JSON(cx, NewObjectWithClassProto(cx, &JSONClass, proto, global, SingletonObject));
916 if (!JSON)
917 return nullptr;
919 if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, 0,
920 JS_PropertyStub, JS_StrictPropertyStub))
921 return nullptr;
923 if (!JS_DefineFunctions(cx, JSON, json_static_methods))
924 return nullptr;
926 global->setConstructor(JSProto_JSON, ObjectValue(*JSON));
928 return JSON;