1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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 "builtin/JSON.h"
9 #include "mozilla/CheckedInt.h"
10 #include "mozilla/FloatingPoint.h"
11 #include "mozilla/Range.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/Variant.h"
20 #include "builtin/Array.h"
21 #include "builtin/BigInt.h"
22 #include "builtin/ParseRecordObject.h"
23 #include "builtin/RawJSONObject.h"
24 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
25 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
26 #include "js/Object.h" // JS::GetBuiltinClass
27 #include "js/Prefs.h" // JS::Prefs
28 #include "js/PropertySpec.h"
29 #include "js/StableStringChars.h"
30 #include "js/TypeDecls.h"
32 #include "util/StringBuffer.h"
33 #include "vm/BooleanObject.h" // js::BooleanObject
34 #include "vm/EqualityOperations.h" // js::SameValue
35 #include "vm/Interpreter.h"
36 #include "vm/Iteration.h"
37 #include "vm/JSAtomUtils.h" // ToAtom
38 #include "vm/JSContext.h"
39 #include "vm/JSObject.h"
40 #include "vm/JSONParser.h"
41 #include "vm/NativeObject.h"
42 #include "vm/NumberObject.h" // js::NumberObject
43 #include "vm/PlainObject.h" // js::PlainObject
44 #include "vm/StringObject.h" // js::StringObject
45 #ifdef ENABLE_RECORD_TUPLE
46 # include "builtin/RecordObject.h"
47 # include "builtin/TupleObject.h"
48 # include "vm/RecordType.h"
51 #include "builtin/Array-inl.h"
52 #include "vm/GeckoProfiler-inl.h"
53 #include "vm/JSAtomUtils-inl.h" // AtomToId, PrimitiveValueToId, IndexToId, IdToString,
54 #include "vm/NativeObject-inl.h"
58 using mozilla::AsVariant
;
59 using mozilla::CheckedInt
;
61 using mozilla::RangedPtr
;
62 using mozilla::Variant
;
64 using JS::AutoStableStringChars
;
66 /* https://262.ecma-international.org/14.0/#sec-quotejsonstring
67 * Requires that the destination has enough space allocated for src after
68 * escaping (that is, `2 + 6 * (srcEnd - srcBegin)` characters).
70 template <typename SrcCharT
, typename DstCharT
>
71 static MOZ_ALWAYS_INLINE RangedPtr
<DstCharT
> InfallibleQuoteJSONString(
72 RangedPtr
<const SrcCharT
> srcBegin
, RangedPtr
<const SrcCharT
> srcEnd
,
73 RangedPtr
<DstCharT
> dstPtr
) {
74 // Maps characters < 256 to the value that must follow the '\\' in the quoted
75 // string. Entries with 'u' are handled as \\u00xy, and entries with 0 are not
76 // escaped in any way. Characters >= 256 are all assumed to be unescaped.
77 static const Latin1Char escapeLookup
[256] = {
79 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't',
80 'n', 'u', 'f', 'r', 'u', 'u', 'u', 'u', 'u', 'u',
81 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
82 'u', 'u', 0, 0, '\"', 0, 0, 0, 0, 0,
83 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
84 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
85 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
86 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
87 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
88 0, 0, '\\', // rest are all zeros
95 auto ToLowerHex
= [](uint8_t u
) {
97 return "0123456789abcdef"[u
];
101 while (srcBegin
!= srcEnd
) {
102 const SrcCharT c
= *srcBegin
++;
104 // Handle the Latin-1 cases.
105 if (MOZ_LIKELY(c
< sizeof(escapeLookup
))) {
106 Latin1Char escaped
= escapeLookup
[c
];
108 // Directly copy non-escaped code points.
114 // Escape the rest, elaborating Unicode escapes when needed.
117 if (escaped
== 'u') {
125 *dstPtr
++ = ToLowerHex(c
& 0xF);
131 // Non-ASCII non-surrogates are directly copied.
132 if (!unicode::IsSurrogate(c
)) {
137 // So too for complete surrogate pairs.
138 if (MOZ_LIKELY(unicode::IsLeadSurrogate(c
) && srcBegin
< srcEnd
&&
139 unicode::IsTrailSurrogate(*srcBegin
))) {
141 *dstPtr
++ = *srcBegin
++;
145 // But lone surrogates are Unicode-escaped.
146 char32_t as32
= char32_t(c
);
149 *dstPtr
++ = ToLowerHex(as32
>> 12);
150 *dstPtr
++ = ToLowerHex((as32
>> 8) & 0xF);
151 *dstPtr
++ = ToLowerHex((as32
>> 4) & 0xF);
152 *dstPtr
++ = ToLowerHex(as32
& 0xF);
160 template <typename SrcCharT
, typename DstCharT
>
161 static size_t QuoteJSONStringHelper(const JSLinearString
& linear
,
162 StringBuffer
& sb
, size_t sbOffset
) {
163 size_t len
= linear
.length();
165 JS::AutoCheckCannotGC nogc
;
166 RangedPtr
<const SrcCharT
> srcBegin
{linear
.chars
<SrcCharT
>(nogc
), len
};
167 RangedPtr
<DstCharT
> dstBegin
{sb
.begin
<DstCharT
>(), sb
.begin
<DstCharT
>(),
169 RangedPtr
<DstCharT
> dstEnd
=
170 InfallibleQuoteJSONString(srcBegin
, srcBegin
+ len
, dstBegin
+ sbOffset
);
172 return dstEnd
- dstBegin
;
175 static bool QuoteJSONString(JSContext
* cx
, StringBuffer
& sb
, JSString
* str
) {
176 JSLinearString
* linear
= str
->ensureLinear(cx
);
181 if (linear
->hasTwoByteChars() && !sb
.ensureTwoByteChars()) {
185 // We resize the backing buffer to the maximum size we could possibly need,
186 // write the escaped string into it, and shrink it back to the size we ended
189 size_t len
= linear
->length();
190 size_t sbInitialLen
= sb
.length();
192 CheckedInt
<size_t> reservedLen
= CheckedInt
<size_t>(len
) * 6 + 2;
193 if (MOZ_UNLIKELY(!reservedLen
.isValid())) {
194 ReportAllocationOverflow(cx
);
198 if (!sb
.growByUninitialized(reservedLen
.value())) {
204 if (linear
->hasTwoByteChars()) {
206 QuoteJSONStringHelper
<char16_t
, char16_t
>(*linear
, sb
, sbInitialLen
);
207 } else if (sb
.isUnderlyingBufferLatin1()) {
208 newSize
= QuoteJSONStringHelper
<Latin1Char
, Latin1Char
>(*linear
, sb
,
212 QuoteJSONStringHelper
<Latin1Char
, char16_t
>(*linear
, sb
, sbInitialLen
);
215 sb
.shrinkTo(newSize
);
222 using ObjectVector
= GCVector
<JSObject
*, 8>;
224 class StringifyContext
{
226 StringifyContext(JSContext
* cx
, StringBuffer
& sb
, const StringBuffer
& gap
,
227 HandleObject replacer
, const RootedIdVector
& propertyList
,
231 replacer(cx
, replacer
),
232 stack(cx
, ObjectVector(cx
)),
233 propertyList(propertyList
),
235 maybeSafely(maybeSafely
) {
236 MOZ_ASSERT_IF(maybeSafely
, !replacer
);
237 MOZ_ASSERT_IF(maybeSafely
, gap
.empty());
241 const StringBuffer
& gap
;
242 RootedObject replacer
;
243 Rooted
<ObjectVector
> stack
;
244 const RootedIdVector
& propertyList
;
249 } /* anonymous namespace */
251 static bool SerializeJSONProperty(JSContext
* cx
, const Value
& v
,
252 StringifyContext
* scx
);
254 static bool WriteIndent(StringifyContext
* scx
, uint32_t limit
) {
255 if (!scx
->gap
.empty()) {
256 if (!scx
->sb
.append('\n')) {
260 if (scx
->gap
.isUnderlyingBufferLatin1()) {
261 for (uint32_t i
= 0; i
< limit
; i
++) {
262 if (!scx
->sb
.append(scx
->gap
.rawLatin1Begin(),
263 scx
->gap
.rawLatin1End())) {
268 for (uint32_t i
= 0; i
< limit
; i
++) {
269 if (!scx
->sb
.append(scx
->gap
.rawTwoByteBegin(),
270 scx
->gap
.rawTwoByteEnd())) {
282 template <typename KeyType
>
283 class KeyStringifier
{};
286 class KeyStringifier
<uint32_t> {
288 static JSString
* toString(JSContext
* cx
, uint32_t index
) {
289 return IndexToString(cx
, index
);
294 class KeyStringifier
<HandleId
> {
296 static JSString
* toString(JSContext
* cx
, HandleId id
) {
297 return IdToString(cx
, id
);
301 } /* anonymous namespace */
304 * https://262.ecma-international.org/14.0/#sec-serializejsonproperty, steps
305 * 2-4, extracted to enable preprocessing of property values when stringifying
306 * objects in SerializeJSONObject.
308 template <typename KeyType
>
309 static bool PreprocessValue(JSContext
* cx
, HandleObject holder
, KeyType key
,
310 MutableHandleValue vp
, StringifyContext
* scx
) {
311 // We don't want to do any preprocessing here if scx->maybeSafely,
312 // since the stuff we do here can have side-effects.
313 if (scx
->maybeSafely
) {
317 RootedString
keyStr(cx
);
319 // Step 2. Modified by BigInt spec 6.1 to check for a toJSON method on the
320 // BigInt prototype when the value is a BigInt, and to pass the BigInt
321 // primitive value as receiver.
322 if (vp
.isObject() || vp
.isBigInt()) {
323 RootedValue
toJSON(cx
);
324 RootedObject
obj(cx
, JS::ToObject(cx
, vp
));
329 if (!GetProperty(cx
, obj
, vp
, cx
->names().toJSON
, &toJSON
)) {
333 if (IsCallable(toJSON
)) {
334 keyStr
= KeyStringifier
<KeyType
>::toString(cx
, key
);
339 RootedValue
arg0(cx
, StringValue(keyStr
));
340 if (!js::Call(cx
, toJSON
, vp
, arg0
, vp
)) {
347 if (scx
->replacer
&& scx
->replacer
->isCallable()) {
348 MOZ_ASSERT(holder
!= nullptr,
349 "holder object must be present when replacer is callable");
352 keyStr
= KeyStringifier
<KeyType
>::toString(cx
, key
);
358 RootedValue
arg0(cx
, StringValue(keyStr
));
359 RootedValue
replacerVal(cx
, ObjectValue(*scx
->replacer
));
360 if (!js::Call(cx
, replacerVal
, holder
, arg0
, vp
, vp
)) {
366 if (vp
.get().isObject()) {
367 RootedObject
obj(cx
, &vp
.get().toObject());
370 if (!JS::GetBuiltinClass(cx
, obj
, &cls
)) {
374 if (cls
== ESClass::Number
) {
376 if (!ToNumber(cx
, vp
, &d
)) {
380 } else if (cls
== ESClass::String
) {
381 JSString
* str
= ToStringSlow
<CanGC
>(cx
, vp
);
386 } else if (cls
== ESClass::Boolean
|| cls
== ESClass::BigInt
||
388 obj
->is
<RecordObject
>() || obj
->is
<TupleObject
>(), false)) {
389 if (!Unbox(cx
, obj
, vp
)) {
399 * Determines whether a value which has passed by
400 * https://262.ecma-international.org/14.0/#sec-serializejsonproperty steps
401 * 1-4's gauntlet will result in SerializeJSONProperty returning |undefined|.
402 * This function is used to properly omit properties resulting in such values
403 * when stringifying objects, while properly stringifying such properties as
404 * null when they're encountered in arrays.
406 static inline bool IsFilteredValue(const Value
& v
) {
407 MOZ_ASSERT_IF(v
.isMagic(), v
.isMagic(JS_ELEMENTS_HOLE
));
408 return v
.isUndefined() || v
.isSymbol() || v
.isMagic() || IsCallable(v
);
411 class CycleDetector
{
413 CycleDetector(StringifyContext
* scx
, HandleObject obj
)
414 : stack_(&scx
->stack
), obj_(obj
), appended_(false) {}
416 MOZ_ALWAYS_INLINE
bool foundCycle(JSContext
* cx
) {
417 JSObject
* obj
= obj_
;
418 for (JSObject
* obj2
: stack_
) {
419 if (MOZ_UNLIKELY(obj
== obj2
)) {
420 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
421 JSMSG_JSON_CYCLIC_VALUE
);
425 appended_
= stack_
.append(obj
);
430 if (MOZ_LIKELY(appended_
)) {
431 MOZ_ASSERT(stack_
.back() == obj_
);
437 MutableHandle
<ObjectVector
> stack_
;
442 static inline JSString
* MaybeGetRawJSON(JSContext
* cx
, JSObject
* obj
) {
443 auto* unwrappedObj
= obj
->maybeUnwrapIf
<js::RawJSONObject
>();
447 JSAutoRealm
ar(cx
, unwrappedObj
);
449 JSString
* rawJSON
= unwrappedObj
->rawJSON(cx
);
454 #ifdef ENABLE_RECORD_TUPLE
455 enum class JOType
{ Record
, Object
};
456 template <JOType type
= JOType::Object
>
458 /* https://262.ecma-international.org/14.0/#sec-serializejsonobject */
459 static bool SerializeJSONObject(JSContext
* cx
, HandleObject obj
,
460 StringifyContext
* scx
) {
462 * This method implements the SerializeJSONObject algorithm, but:
464 * * The algorithm is somewhat reformulated to allow the final string to
465 * be streamed into a single buffer, rather than be created and copied
466 * into place incrementally as the algorithm specifies it. This
467 * requires moving portions of the SerializeJSONProperty call in 8a into
468 * this algorithm (and in SerializeJSONArray as well).
471 #ifdef ENABLE_RECORD_TUPLE
474 if constexpr (type
== JOType::Record
) {
475 MOZ_ASSERT(obj
->is
<RecordType
>());
476 rec
= &obj
->as
<RecordType
>();
478 MOZ_ASSERT(!IsExtendedPrimitive(*obj
));
481 MOZ_ASSERT_IF(scx
->maybeSafely
, obj
->is
<PlainObject
>());
484 CycleDetector
detect(scx
, obj
);
485 if (!detect
.foundCycle(cx
)) {
489 if (!scx
->sb
.append('{')) {
494 Maybe
<RootedIdVector
> ids
;
495 const RootedIdVector
* props
;
496 if (scx
->replacer
&& !scx
->replacer
->isCallable()) {
497 // NOTE: We can't assert |IsArray(scx->replacer)| because the replacer
498 // might have been a revocable proxy to an array. Such a proxy
499 // satisfies |IsArray|, but any side effect of JSON.stringify
500 // could revoke the proxy so that |!IsArray(scx->replacer)|. See
502 props
= &scx
->propertyList
;
504 MOZ_ASSERT_IF(scx
->replacer
, scx
->propertyList
.length() == 0);
506 if (!GetPropertyKeys(cx
, obj
, JSITER_OWNONLY
, ids
.ptr())) {
512 /* My kingdom for not-quite-initialized-from-the-start references. */
513 const RootedIdVector
& propertyList
= *props
;
515 /* Steps 8-10, 13. */
516 bool wroteMember
= false;
518 for (size_t i
= 0, len
= propertyList
.length(); i
< len
; i
++) {
519 if (!CheckForInterrupt(cx
)) {
524 * Steps 8a-8b. Note that the call to SerializeJSONProperty is broken up
525 * into 1) getting the property; 2) processing for toJSON, calling the
526 * replacer, and handling boxed Number/String/Boolean objects; 3) filtering
527 * out values which process to |undefined|, and 4) stringifying all values
528 * which pass the filter.
530 id
= propertyList
[i
];
531 RootedValue
outputValue(cx
);
533 if (scx
->maybeSafely
) {
535 if (!NativeLookupOwnPropertyNoResolve(cx
, &obj
->as
<NativeObject
>(), id
,
539 MOZ_ASSERT(prop
.isNativeProperty() &&
540 prop
.propertyInfo().isDataDescriptor());
544 #ifdef ENABLE_RECORD_TUPLE
545 if constexpr (type
== JOType::Record
) {
546 MOZ_ALWAYS_TRUE(rec
->getOwnProperty(cx
, id
, &outputValue
));
550 RootedValue
objValue(cx
, ObjectValue(*obj
));
551 if (!GetProperty(cx
, obj
, objValue
, id
, &outputValue
)) {
555 if (!PreprocessValue(cx
, obj
, HandleId(id
), &outputValue
, scx
)) {
558 if (IsFilteredValue(outputValue
)) {
562 /* Output a comma unless this is the first member to write. */
563 if (wroteMember
&& !scx
->sb
.append(',')) {
568 if (!WriteIndent(scx
, scx
->depth
)) {
572 JSString
* s
= IdToString(cx
, id
);
577 if (!QuoteJSONString(cx
, scx
->sb
, s
) || !scx
->sb
.append(':') ||
578 !(scx
->gap
.empty() || scx
->sb
.append(' ')) ||
579 !SerializeJSONProperty(cx
, outputValue
, scx
)) {
584 if (wroteMember
&& !WriteIndent(scx
, scx
->depth
- 1)) {
588 return scx
->sb
.append('}');
591 // For JSON.stringify and JSON.parse with a reviver function, we need to know
592 // the length of an object for which JS::IsArray returned true. This must be
593 // either an ArrayObject or a proxy wrapping one.
594 static MOZ_ALWAYS_INLINE
bool GetLengthPropertyForArrayLike(JSContext
* cx
,
597 if (MOZ_LIKELY(obj
->is
<ArrayObject
>())) {
598 *lengthp
= obj
->as
<ArrayObject
>().length();
601 #ifdef ENABLE_RECORD_TUPLE
602 if (obj
->is
<TupleType
>()) {
603 *lengthp
= obj
->as
<TupleType
>().length();
608 MOZ_ASSERT(obj
->is
<ProxyObject
>());
611 if (!GetLengthProperty(cx
, obj
, &len
)) {
615 // A scripted proxy wrapping an array can return a length value larger than
616 // UINT32_MAX. Stringification will likely report an OOM in this case. Match
617 // other JS engines and report an early error in this case, although
618 // technically this is observable, for example when stringifying with a
619 // replacer function.
620 if (len
> UINT32_MAX
) {
621 ReportAllocationOverflow(cx
);
625 *lengthp
= uint32_t(len
);
629 /* https://262.ecma-international.org/14.0/#sec-serializejsonarray */
630 static bool SerializeJSONArray(JSContext
* cx
, HandleObject obj
,
631 StringifyContext
* scx
) {
633 * This method implements the SerializeJSONArray algorithm, but:
635 * * The algorithm is somewhat reformulated to allow the final string to
636 * be streamed into a single buffer, rather than be created and copied
637 * into place incrementally as the algorithm specifies it. This
638 * requires moving portions of the SerializeJSONProperty call in 8a into
639 * this algorithm (and in SerializeJSONObject as well).
643 CycleDetector
detect(scx
, obj
);
644 if (!detect
.foundCycle(cx
)) {
648 if (!scx
->sb
.append('[')) {
654 if (!GetLengthPropertyForArrayLike(cx
, obj
, &length
)) {
660 /* Steps 4, 10b(i). */
661 if (!WriteIndent(scx
, scx
->depth
)) {
666 RootedValue
outputValue(cx
);
667 for (uint32_t i
= 0; i
< length
; i
++) {
668 if (!CheckForInterrupt(cx
)) {
673 * Steps 8a-8c. Again note how the call to the spec's
674 * SerializeJSONProperty method is broken up into getting the property,
675 * running it past toJSON and the replacer and maybe unboxing, and
676 * interpreting some values as |null| in separate steps.
679 if (scx
->maybeSafely
) {
681 * Trying to do a JS_AlreadyHasOwnElement runs the risk of
682 * hitting OOM on jsid creation. Let's just assert sanity for
683 * small enough indices.
685 MOZ_ASSERT(obj
->is
<ArrayObject
>());
686 MOZ_ASSERT(obj
->is
<NativeObject
>());
687 Rooted
<NativeObject
*> nativeObj(cx
, &obj
->as
<NativeObject
>());
688 if (i
<= PropertyKey::IntMax
) {
690 nativeObj
->containsDenseElement(i
) != nativeObj
->isIndexed(),
691 "the array must either be small enough to remain "
692 "fully dense (and otherwise un-indexed), *or* "
693 "all its initially-dense elements were sparsified "
694 "and the object is indexed");
696 MOZ_ASSERT(nativeObj
->isIndexed());
700 if (!GetElement(cx
, obj
, i
, &outputValue
)) {
703 if (!PreprocessValue(cx
, obj
, i
, &outputValue
, scx
)) {
706 if (IsFilteredValue(outputValue
)) {
707 if (!scx
->sb
.append("null")) {
711 if (!SerializeJSONProperty(cx
, outputValue
, scx
)) {
716 /* Steps 3, 4, 10b(i). */
717 if (i
< length
- 1) {
718 if (!scx
->sb
.append(',')) {
721 if (!WriteIndent(scx
, scx
->depth
)) {
727 /* Step 10(b)(iii). */
728 if (!WriteIndent(scx
, scx
->depth
- 1)) {
733 return scx
->sb
.append(']');
736 /* https://262.ecma-international.org/14.0/#sec-serializejsonproperty */
737 static bool SerializeJSONProperty(JSContext
* cx
, const Value
& v
,
738 StringifyContext
* scx
) {
739 /* Step 12 must be handled by the caller. */
740 MOZ_ASSERT(!IsFilteredValue(v
));
743 * This method implements the SerializeJSONProperty algorithm, but:
745 * * We move property retrieval (step 1) into callers to stream the
746 * stringification process and avoid constantly copying strings.
747 * * We move the preprocessing in steps 2-4 into a helper function to
748 * allow both SerializeJSONObject and SerializeJSONArray to use this
749 * method. While SerializeJSONArray could use it without this move,
750 * SerializeJSONObject must omit any |undefined|-valued property per so it
751 * can't stream out a value using the SerializeJSONProperty method exactly as
752 * defined by the spec.
753 * * We move step 12 into callers, again to ease streaming.
758 return QuoteJSONString(cx
, scx
->sb
, v
.toString());
763 return scx
->sb
.append("null");
768 return v
.toBoolean() ? scx
->sb
.append("true") : scx
->sb
.append("false");
774 if (!std::isfinite(v
.toDouble())) {
775 MOZ_ASSERT(!scx
->maybeSafely
,
776 "input JS::ToJSONMaybeSafely must not include "
777 "reachable non-finite numbers");
778 return scx
->sb
.append("null");
782 return NumberValueToStringBuffer(v
, scx
->sb
);
787 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
788 JSMSG_BIGINT_NOT_SERIALIZABLE
);
792 AutoCheckRecursionLimit
recursion(cx
);
793 if (!recursion
.check(cx
)) {
798 MOZ_ASSERT(v
.hasObjectPayload());
799 RootedObject
obj(cx
, &v
.getObjectPayload());
801 /* https://tc39.es/proposal-json-parse-with-source/#sec-serializejsonproperty
803 if (JSString
* rawJSON
= MaybeGetRawJSON(cx
, obj
)) {
804 return scx
->sb
.append(rawJSON
);
808 !scx
->maybeSafely
|| obj
->is
<PlainObject
>() || obj
->is
<ArrayObject
>(),
809 "input to JS::ToJSONMaybeSafely must not include reachable "
810 "objects that are neither arrays nor plain objects");
813 auto dec
= mozilla::MakeScopeExit([&] { scx
->depth
--; });
815 #ifdef ENABLE_RECORD_TUPLE
816 if (v
.isExtendedPrimitive()) {
817 if (obj
->is
<RecordType
>()) {
818 return SerializeJSONObject
<JOType::Record
>(cx
, obj
, scx
);
820 if (obj
->is
<TupleType
>()) {
821 return SerializeJSONArray(cx
, obj
, scx
);
823 MOZ_CRASH("Unexpected extended primitive - boxes cannot be stringified.");
828 if (!IsArray(cx
, obj
, &isArray
)) {
832 return isArray
? SerializeJSONArray(cx
, obj
, scx
)
833 : SerializeJSONObject(cx
, obj
, scx
);
836 static bool CanFastStringifyObject(NativeObject
* obj
) {
837 if (ClassCanHaveExtraEnumeratedProperties(obj
->getClass())) {
841 if (obj
->is
<ArrayObject
>()) {
842 // Arrays will look up all keys [0..length) so disallow anything that could
843 // find those keys anywhere but in the dense elements.
844 if (!IsPackedArray(obj
) && ObjectMayHaveExtraIndexedProperties(obj
)) {
848 // Non-Arrays will only look at own properties, but still disallow any
849 // indexed properties other than in the dense elements because they would
851 if (ObjectMayHaveExtraIndexedOwnProperties(obj
)) {
856 // Only used for internal environment objects that should never be passed to
858 MOZ_ASSERT(!obj
->getOpsLookupProperty());
860 #ifdef ENABLE_RECORD_TUPLE
861 if (ObjectValue(*obj
).isExtendedPrimitive()) {
869 #define FOR_EACH_STRINGIFY_BAIL_REASON(MACRO) \
871 MACRO(INELIGIBLE_OBJECT) \
872 MACRO(DEEP_RECURSION) \
873 MACRO(NON_DATA_PROPERTY) \
874 MACRO(TOO_MANY_PROPERTIES) \
877 MACRO(HAVE_REPLACER) \
881 MACRO(IMPURE_LOOKUP) \
884 enum class BailReason
: uint8_t {
885 #define DECLARE_ENUM(name) name,
886 FOR_EACH_STRINGIFY_BAIL_REASON(DECLARE_ENUM
)
890 static const char* DescribeStringifyBailReason(BailReason whySlow
) {
892 #define ENUM_NAME(name) \
893 case BailReason::name: \
895 FOR_EACH_STRINGIFY_BAIL_REASON(ENUM_NAME
)
902 // Iterator over all the dense elements of an object. Used
903 // for both Arrays and non-Arrays.
904 class DenseElementsIteratorForJSON
{
905 HeapSlotArray elements
;
908 // Arrays can have a length less than getDenseInitializedLength(), in which
909 // case the remaining Array elements are treated as UndefinedValue.
910 uint32_t numElements
;
914 explicit DenseElementsIteratorForJSON(NativeObject
* nobj
)
915 : elements(nobj
->getDenseElements()),
917 numElements(nobj
->getDenseInitializedLength()) {
918 length
= nobj
->is
<ArrayObject
>() ? nobj
->as
<ArrayObject
>().length()
922 bool done() const { return element
== length
; }
925 // For Arrays, steps 6-8 of
926 // https://262.ecma-international.org/14.0/#sec-serializejsonarray. For
927 // non-Arrays, step 6a of
928 // https://262.ecma-international.org/14.0/#sec-serializejsonobject
929 // following the order from
930 // https://262.ecma-international.org/14.0/#sec-ordinaryownpropertykeys
934 // Consider specializing the iterator for Arrays vs non-Arrays to avoid this
936 return i
< numElements
? elements
.begin()[i
] : UndefinedValue();
939 uint32_t getIndex() const { return element
; }
942 // An iterator over the non-element properties of a Shape, returned in forward
943 // (creation) order. Note that it is fallible, so after iteration is complete
944 // isOverflowed() should be called to verify that the results are actually
947 class ShapePropertyForwardIterNoGC
{
948 // Pointer to the current PropMap with length and an index within it.
953 // Stack of PropMaps to iterate through, oldest properties on top. The current
954 // map (map_, above) is never on this stack.
955 mozilla::Vector
<PropMap
*> stack_
;
957 const NativeShape
* shape_
;
959 MOZ_ALWAYS_INLINE
void settle() {
961 if (MOZ_UNLIKELY(i_
== mapLength_
)) {
963 if (stack_
.empty()) {
964 mapLength_
= 0; // Done
967 map_
= stack_
.back();
970 stack_
.empty() ? shape_
->propMapLength() : PropMap::Capacity
;
971 } else if (MOZ_UNLIKELY(shape_
->isDictionary() && !map_
->hasKey(i_
))) {
972 // Dictionary maps can have "holes" for removed properties, so keep
973 // going until we find a non-hole slot.
982 explicit ShapePropertyForwardIterNoGC(NativeShape
* shape
) : shape_(shape
) {
983 // Set map_ to the PropMap containing the first property (the deepest map in
984 // the previous() chain). Push pointers to all other PropMaps onto stack_.
985 map_
= shape
->propMap();
991 while (map_
->hasPrevious()) {
992 if (!stack_
.append(map_
)) {
994 i_
= mapLength_
= UINT32_MAX
;
997 map_
= map_
->asLinked()->previous();
1000 // Set mapLength_ to the number of properties in map_ (including dictionary
1002 mapLength_
= stack_
.empty() ? shape_
->propMapLength() : PropMap::Capacity
;
1007 bool done() const { return i_
== mapLength_
; }
1008 bool isOverflowed() const { return i_
== UINT32_MAX
; }
1010 void operator++(int) {
1011 MOZ_ASSERT(!done());
1016 PropertyInfoWithKey
get() const {
1017 MOZ_ASSERT(!done());
1018 return map_
->getPropertyInfoWithKey(i_
);
1021 PropertyInfoWithKey
operator*() const { return get(); }
1023 // Fake pointer struct to make operator-> work.
1024 // See https://stackoverflow.com/a/52856349.
1026 PropertyInfoWithKey val_
;
1027 const PropertyInfoWithKey
* operator->() const { return &val_
; }
1029 FakePtr
operator->() const { return {get()}; }
1032 // Iterator over EnumerableOwnProperties
1033 // https://262.ecma-international.org/14.0/#sec-enumerableownproperties
1034 // that fails if it encounters any accessor properties, as they are not handled
1035 // by JSON FastSerializeJSONProperty, or if it sees too many properties on one
1037 class OwnNonIndexKeysIterForJSON
{
1038 ShapePropertyForwardIterNoGC shapeIter
;
1040 BailReason fastFailed_
= BailReason::NO_REASON
;
1043 // Skip over any non-enumerable or Symbol properties, and permanently fail
1044 // if any enumerable non-data properties are encountered.
1045 for (; !shapeIter
.done(); shapeIter
++) {
1046 if (!shapeIter
->enumerable()) {
1049 if (!shapeIter
->isDataProperty()) {
1050 fastFailed_
= BailReason::NON_DATA_PROPERTY
;
1054 PropertyKey id
= shapeIter
->key();
1055 if (!id
.isSymbol()) {
1063 explicit OwnNonIndexKeysIterForJSON(const NativeObject
* nobj
)
1064 : shapeIter(nobj
->shape()) {
1065 if (MOZ_UNLIKELY(shapeIter
.isOverflowed())) {
1066 fastFailed_
= BailReason::TOO_MANY_PROPERTIES
;
1070 if (!nobj
->hasEnumerableProperty()) {
1071 // Non-Arrays with no enumerable properties can just be skipped.
1072 MOZ_ASSERT(!nobj
->is
<ArrayObject
>());
1079 bool done() const { return done_
|| shapeIter
.done(); }
1080 BailReason
cannotFastStringify() const { return fastFailed_
; }
1082 PropertyInfoWithKey
next() {
1083 MOZ_ASSERT(!done());
1084 PropertyInfoWithKey prop
= shapeIter
.get();
1091 // Steps from https://262.ecma-international.org/14.0/#sec-serializejsonproperty
1092 static bool EmitSimpleValue(JSContext
* cx
, StringBuffer
& sb
, const Value
& v
) {
1095 return QuoteJSONString(cx
, sb
, v
.toString());
1100 return sb
.append("null");
1104 if (v
.isBoolean()) {
1105 return v
.toBoolean() ? sb
.append("true") : sb
.append("false");
1111 if (!std::isfinite(v
.toDouble())) {
1112 return sb
.append("null");
1116 return NumberValueToStringBuffer(v
, sb
);
1119 // Unrepresentable values.
1120 if (v
.isUndefined() || v
.isMagic()) {
1121 MOZ_ASSERT_IF(v
.isMagic(), v
.isMagic(JS_ELEMENTS_HOLE
));
1122 return sb
.append("null");
1126 MOZ_CRASH("should have validated printable simple value already");
1129 // https://262.ecma-international.org/14.0/#sec-serializejsonproperty step 8b
1130 // where K is an integer index.
1131 static bool EmitQuotedIndexColon(StringBuffer
& sb
, uint32_t index
) {
1132 Int32ToCStringBuf cbuf
;
1134 const char* cstr
= ::Int32ToCString(&cbuf
, index
, &cstrlen
);
1135 if (!sb
.reserve(sb
.length() + 1 + cstrlen
+ 1 + 1)) {
1138 sb
.infallibleAppend('"');
1139 sb
.infallibleAppend(cstr
, cstrlen
);
1140 sb
.infallibleAppend('"');
1141 sb
.infallibleAppend(':');
1145 // Similar to PreprocessValue: replace the value with a simpler one to
1146 // stringify, but also detect whether the value is compatible with the fast
1147 // path. If not, bail out by setting *whySlow and returning true.
1148 static bool PreprocessFastValue(JSContext
* cx
, Value
* vp
, StringifyContext
* scx
,
1149 BailReason
* whySlow
) {
1150 MOZ_ASSERT(!scx
->maybeSafely
);
1153 // https://262.ecma-international.org/14.0/#sec-serializejsonproperty
1155 // Disallow BigInts to avoid caring about BigInt.prototype.toJSON.
1156 if (vp
->isBigInt()) {
1157 *whySlow
= BailReason::BIGINT
;
1161 if (!vp
->isObject()) {
1165 if (!vp
->toObject().is
<NativeObject
>()) {
1166 *whySlow
= BailReason::INELIGIBLE_OBJECT
;
1170 // Step 2: lookup a .toJSON property (and bail if found).
1171 NativeObject
* obj
= &vp
->toObject().as
<NativeObject
>();
1172 PropertyResult toJSON
;
1173 NativeObject
* holder
;
1174 PropertyKey id
= NameToId(cx
->names().toJSON
);
1175 if (!NativeLookupPropertyInline
<NoGC
, LookupResolveMode::CheckMayResolve
>(
1176 cx
, obj
, id
, &holder
, &toJSON
)) {
1177 // Looking up this property would require a side effect.
1178 *whySlow
= BailReason::IMPURE_LOOKUP
;
1181 if (toJSON
.isFound()) {
1182 *whySlow
= BailReason::HAVE_TOJSON
;
1186 // Step 4: convert primitive wrapper objects to primitives. Disallowed for
1188 if (obj
->is
<NumberObject
>() || obj
->is
<StringObject
>() ||
1189 obj
->is
<BooleanObject
>() || obj
->is
<BigIntObject
>() ||
1190 IF_RECORD_TUPLE(obj
->is
<RecordObject
>() || obj
->is
<TupleObject
>(),
1192 // Primitive wrapper objects can invoke arbitrary code when being coerced to
1193 // their primitive values (eg via @@toStringTag).
1194 *whySlow
= BailReason::INELIGIBLE_OBJECT
;
1198 if (obj
->isCallable()) {
1199 // Steps 11,12: Callable objects are treated as undefined.
1204 if (!CanFastStringifyObject(obj
)) {
1205 *whySlow
= BailReason::INELIGIBLE_OBJECT
;
1212 // FastSerializeJSONProperty maintains an explicit stack to handle nested
1213 // objects. For each object, first the dense elements are iterated, then the
1214 // named properties (included sparse indexes, which will cause
1215 // FastSerializeJSONProperty to bail out.)
1217 // The iterators for each of those parts are not merged into a single common
1218 // iterator because the interface is different for the two parts, and they are
1219 // handled separately in the FastSerializeJSONProperty code.
1220 struct FastStackEntry
{
1222 Variant
<DenseElementsIteratorForJSON
, OwnNonIndexKeysIterForJSON
> iter
;
1223 bool isArray
; // Cached nobj->is<ArrayObject>()
1225 // Given an object, a FastStackEntry starts with the dense elements. The
1226 // caller is expected to inspect the variant to use it differently based on
1227 // which iterator is active.
1228 explicit FastStackEntry(NativeObject
* obj
)
1230 iter(AsVariant(DenseElementsIteratorForJSON(obj
))),
1231 isArray(obj
->is
<ArrayObject
>()) {}
1233 // Called by Vector when moving data around.
1234 FastStackEntry(FastStackEntry
&& other
) noexcept
1235 : nobj(other
.nobj
), iter(std::move(other
.iter
)), isArray(other
.isArray
) {}
1237 // Move assignment, called when updating the `top` entry.
1238 void operator=(FastStackEntry
&& other
) noexcept
{
1240 iter
= std::move(other
.iter
);
1241 isArray
= other
.isArray
;
1244 // Advance from dense elements to the named properties.
1245 void advanceToProperties() {
1246 iter
= AsVariant(OwnNonIndexKeysIterForJSON(nobj
));
1250 /* https://262.ecma-international.org/14.0/#sec-serializejsonproperty */
1251 static bool FastSerializeJSONProperty(JSContext
* cx
, Handle
<Value
> v
,
1252 StringifyContext
* scx
,
1253 BailReason
* whySlow
) {
1254 MOZ_ASSERT(*whySlow
== BailReason::NO_REASON
);
1255 MOZ_ASSERT(v
.isObject());
1257 if (JSString
* rawJSON
= MaybeGetRawJSON(cx
, &v
.toObject())) {
1258 return scx
->sb
.append(rawJSON
);
1262 * FastSerializeJSONProperty is an optimistic fast path for the
1263 * SerializeJSONProperty algorithm that applies in limited situations. It
1264 * falls back to SerializeJSONProperty() if:
1266 * * Any externally visible code attempts to run: getter, enumerate
1267 * hook, toJSON property.
1268 * * Sparse index found (this would require accumulating props and sorting.)
1269 * * Max stack depth is reached. (This will also detect self-referential
1276 * wroteMember = false
1277 * OUTER: while true:
1280 * while !top.done():
1281 * key, value = top.next()
1282 * if top is a non-Array and value is skippable:
1286 * wroteMember = true
1287 * if value is object:
1288 * emit(key + ":") if top is iterating a non-Array
1291 * wroteMember = false
1294 * emit(value) or emit(key + ":" + value)
1296 * if stack is empty: done!
1297 * top <- stack.pop()
1298 * wroteMember = true
1302 * * The `while !top.done()` loop is split into the dense element portion
1303 * and the slot portion. Each is iterated to completion before advancing
1306 * * For Arrays, the named properties are not output, but they are still
1307 * scanned to bail if any numeric keys are found that could be indexes.
1310 // FastSerializeJSONProperty will bail if an interrupt is requested in the
1311 // middle of an operation, so handle any interrupts now before starting. Note:
1312 // this can GC, but after this point nothing should be able to GC unless
1313 // something fails, so rooting is unnecessary.
1314 if (!CheckForInterrupt(cx
)) {
1318 constexpr size_t MAX_STACK_DEPTH
= 20;
1319 Vector
<FastStackEntry
> stack(cx
);
1320 if (!stack
.reserve(MAX_STACK_DEPTH
- 1)) {
1323 // Construct an iterator for the object,
1324 // https://262.ecma-international.org/14.0/#sec-serializejsonobject step 6:
1325 // EnumerableOwnPropertyNames or
1326 // https://262.ecma-international.org/14.0/#sec-serializejsonarray step 7-8.
1327 FastStackEntry
top(&v
.toObject().as
<NativeObject
>());
1328 bool wroteMember
= false;
1330 if (!CanFastStringifyObject(top
.nobj
)) {
1331 *whySlow
= BailReason::INELIGIBLE_OBJECT
;
1337 if (!scx
->sb
.append(top
.isArray
? '[' : '{')) {
1342 if (top
.iter
.is
<DenseElementsIteratorForJSON
>()) {
1343 auto& iter
= top
.iter
.as
<DenseElementsIteratorForJSON
>();
1344 bool nestedObject
= false;
1345 while (!iter
.done()) {
1346 // Interrupts can GC and we are working with unrooted pointers.
1347 if (cx
->hasPendingInterrupt(InterruptReason::CallbackUrgent
) ||
1348 cx
->hasPendingInterrupt(InterruptReason::CallbackCanWait
)) {
1349 *whySlow
= BailReason::INTERRUPT
;
1353 uint32_t index
= iter
.getIndex();
1354 Value val
= iter
.next();
1356 if (!PreprocessFastValue(cx
, &val
, scx
, whySlow
)) {
1359 if (*whySlow
!= BailReason::NO_REASON
) {
1362 if (IsFilteredValue(val
)) {
1364 // Arrays convert unrepresentable values to "null".
1365 val
= UndefinedValue();
1367 // Objects skip unrepresentable values.
1372 if (wroteMember
&& !scx
->sb
.append(',')) {
1378 if (!EmitQuotedIndexColon(scx
->sb
, index
)) {
1383 if (val
.isObject()) {
1384 if (JSString
* rawJSON
= MaybeGetRawJSON(cx
, &val
.toObject())) {
1385 if (!scx
->sb
.append(rawJSON
)) {
1389 if (stack
.length() >= MAX_STACK_DEPTH
- 1) {
1390 *whySlow
= BailReason::DEEP_RECURSION
;
1393 // Save the current iterator position on the stack and
1394 // switch to processing the nested value.
1395 stack
.infallibleAppend(std::move(top
));
1396 top
= FastStackEntry(&val
.toObject().as
<NativeObject
>());
1397 wroteMember
= false;
1398 nestedObject
= true; // Break out to the outer loop.
1401 } else if (!EmitSimpleValue(cx
, scx
->sb
, val
)) {
1407 continue; // Break out to outer loop.
1410 MOZ_ASSERT(iter
.done());
1412 MOZ_ASSERT(!top
.nobj
->isIndexed() || IsPackedArray(top
.nobj
));
1414 top
.advanceToProperties();
1418 if (top
.iter
.is
<OwnNonIndexKeysIterForJSON
>()) {
1419 auto& iter
= top
.iter
.as
<OwnNonIndexKeysIterForJSON
>();
1420 bool nesting
= false;
1421 while (!iter
.done()) {
1422 // Interrupts can GC and we are working with unrooted pointers.
1423 if (cx
->hasPendingInterrupt(InterruptReason::CallbackUrgent
) ||
1424 cx
->hasPendingInterrupt(InterruptReason::CallbackCanWait
)) {
1425 *whySlow
= BailReason::INTERRUPT
;
1429 PropertyInfoWithKey prop
= iter
.next();
1431 // A non-Array with indexed elements would need to sort the indexes
1432 // numerically, which this code does not support. These objects are
1433 // skipped when obj->isIndexed(), so no index properties should be found
1435 mozilla::DebugOnly
<uint32_t> index
= -1;
1436 MOZ_ASSERT(!IdIsIndex(prop
.key(), &index
));
1438 Value val
= top
.nobj
->getSlot(prop
.slot());
1439 if (!PreprocessFastValue(cx
, &val
, scx
, whySlow
)) {
1442 if (*whySlow
!= BailReason::NO_REASON
) {
1445 if (IsFilteredValue(val
)) {
1446 // Undefined check in
1447 // https://262.ecma-international.org/14.0/#sec-serializejsonobject
1448 // step 8b, covering undefined, symbol
1452 if (wroteMember
&& !scx
->sb
.append(",")) {
1457 MOZ_ASSERT(prop
.key().isString());
1458 if (!QuoteJSONString(cx
, scx
->sb
, prop
.key().toString())) {
1462 if (!scx
->sb
.append(':')) {
1465 if (val
.isObject()) {
1466 if (JSString
* rawJSON
= MaybeGetRawJSON(cx
, &val
.toObject())) {
1467 if (!scx
->sb
.append(rawJSON
)) {
1471 if (stack
.length() >= MAX_STACK_DEPTH
- 1) {
1472 *whySlow
= BailReason::DEEP_RECURSION
;
1475 // Save the current iterator position on the stack and
1476 // switch to processing the nested value.
1477 stack
.infallibleAppend(std::move(top
));
1478 top
= FastStackEntry(&val
.toObject().as
<NativeObject
>());
1479 wroteMember
= false;
1480 nesting
= true; // Break out to the outer loop.
1483 } else if (!EmitSimpleValue(cx
, scx
->sb
, val
)) {
1487 *whySlow
= iter
.cannotFastStringify();
1488 if (*whySlow
!= BailReason::NO_REASON
) {
1492 continue; // Break out to outer loop.
1494 MOZ_ASSERT(iter
.done());
1497 if (!scx
->sb
.append(top
.isArray
? ']' : '}')) {
1500 if (stack
.empty()) {
1501 return true; // Success!
1503 top
= std::move(stack
.back());
1510 /* https://262.ecma-international.org/14.0/#sec-json.stringify */
1511 bool js::Stringify(JSContext
* cx
, MutableHandleValue vp
, JSObject
* replacer_
,
1512 const Value
& space_
, StringBuffer
& sb
,
1513 StringifyBehavior stringifyBehavior
) {
1514 RootedObject
replacer(cx
, replacer_
);
1515 RootedValue
space(cx
, space_
);
1517 MOZ_ASSERT_IF(stringifyBehavior
== StringifyBehavior::RestrictedSafe
,
1519 MOZ_ASSERT_IF(stringifyBehavior
== StringifyBehavior::RestrictedSafe
,
1522 * This uses MOZ_ASSERT, since it's actually asserting something jsapi
1523 * consumers could get wrong, so needs a better error message.
1525 MOZ_ASSERT(stringifyBehavior
!= StringifyBehavior::RestrictedSafe
||
1526 vp
.toObject().is
<PlainObject
>() ||
1527 vp
.toObject().is
<ArrayObject
>(),
1528 "input to JS::ToJSONMaybeSafely must be a plain object or array");
1531 RootedIdVector
propertyList(cx
);
1532 BailReason whySlow
= BailReason::NO_REASON
;
1533 if (stringifyBehavior
== StringifyBehavior::SlowOnly
||
1534 stringifyBehavior
== StringifyBehavior::RestrictedSafe
) {
1535 whySlow
= BailReason::API
;
1538 whySlow
= BailReason::HAVE_REPLACER
;
1540 if (replacer
->isCallable()) {
1541 /* Step 5a(i): use replacer to transform values. */
1542 } else if (!IsArray(cx
, replacer
, &isArray
)) {
1544 } else if (isArray
) {
1547 /* Step 5b(ii)(2). */
1549 if (!GetLengthPropertyForArrayLike(cx
, replacer
, &len
)) {
1553 // Cap the initial size to a moderately small value. This avoids
1554 // ridiculous over-allocation if an array with bogusly-huge length
1555 // is passed in. If we end up having to add elements past this
1556 // size, the set will naturally resize to accommodate them.
1557 const uint32_t MaxInitialSize
= 32;
1558 Rooted
<GCHashSet
<jsid
>> idSet(
1559 cx
, GCHashSet
<jsid
>(cx
, std::min(len
, MaxInitialSize
)));
1561 /* Step 5b(ii)(3). */
1564 /* Step 5b(ii)(4). */
1565 RootedValue
item(cx
);
1566 for (; k
< len
; k
++) {
1567 if (!CheckForInterrupt(cx
)) {
1571 /* Step 5b(ii)(4)(a-b). */
1572 if (!GetElement(cx
, replacer
, k
, &item
)) {
1576 /* Step 5b(ii)(4)(c-g). */
1578 if (item
.isNumber() || item
.isString()) {
1579 if (!PrimitiveValueToId
<CanGC
>(cx
, item
, &id
)) {
1584 if (!GetClassOfValue(cx
, item
, &cls
)) {
1588 if (cls
!= ESClass::String
&& cls
!= ESClass::Number
) {
1592 JSAtom
* atom
= ToAtom
<CanGC
>(cx
, item
);
1597 id
.set(AtomToId(atom
));
1600 /* Step 5b(ii)(4)(g). */
1601 auto p
= idSet
.lookupForAdd(id
);
1603 /* Step 5b(ii)(4)(g)(i). */
1604 if (!idSet
.add(p
, id
) || !propertyList
.append(id
)) {
1615 if (space
.isObject()) {
1616 RootedObject
spaceObj(cx
, &space
.toObject());
1619 if (!JS::GetBuiltinClass(cx
, spaceObj
, &cls
)) {
1623 if (cls
== ESClass::Number
) {
1625 if (!ToNumber(cx
, space
, &d
)) {
1628 space
= NumberValue(d
);
1629 } else if (cls
== ESClass::String
) {
1630 JSString
* str
= ToStringSlow
<CanGC
>(cx
, space
);
1634 space
= StringValue(str
);
1638 StringBuffer
gap(cx
);
1640 if (space
.isNumber()) {
1643 MOZ_ALWAYS_TRUE(ToInteger(cx
, space
, &d
));
1644 d
= std::min(10.0, d
);
1645 if (d
>= 1 && !gap
.appendN(' ', uint32_t(d
))) {
1648 } else if (space
.isString()) {
1650 JSLinearString
* str
= space
.toString()->ensureLinear(cx
);
1654 size_t len
= std::min(size_t(10), str
->length());
1655 if (!gap
.appendSubstring(str
, 0, len
)) {
1660 MOZ_ASSERT(gap
.empty());
1663 whySlow
= BailReason::HAVE_SPACE
;
1666 Rooted
<PlainObject
*> wrapper(cx
);
1667 RootedId
emptyId(cx
, NameToId(cx
->names().empty_
));
1668 if (replacer
&& replacer
->isCallable()) {
1669 // We can skip creating the initial wrapper object if no replacer
1670 // function is present.
1673 wrapper
= NewPlainObject(cx
);
1679 if (!NativeDefineDataProperty(cx
, wrapper
, emptyId
, vp
, JSPROP_ENUMERATE
)) {
1685 Rooted
<JSAtom
*> fastJSON(cx
);
1686 if (whySlow
== BailReason::NO_REASON
) {
1687 MOZ_ASSERT(propertyList
.empty());
1688 MOZ_ASSERT(stringifyBehavior
!= StringifyBehavior::RestrictedSafe
);
1689 StringifyContext
scx(cx
, sb
, gap
, nullptr, propertyList
, false);
1690 if (!PreprocessFastValue(cx
, vp
.address(), &scx
, &whySlow
)) {
1693 if (!vp
.isObject()) {
1694 // "Fast" stringify of primitives would create a wrapper object and thus
1695 // be slower than regular stringify.
1696 whySlow
= BailReason::PRIMITIVE
;
1698 if (whySlow
== BailReason::NO_REASON
) {
1699 if (!FastSerializeJSONProperty(cx
, vp
, &scx
, &whySlow
)) {
1702 if (whySlow
== BailReason::NO_REASON
) {
1703 // Fast stringify succeeded!
1704 if (stringifyBehavior
!= StringifyBehavior::Compare
) {
1707 fastJSON
= scx
.sb
.finishAtom();
1712 scx
.sb
.clear(); // Preserves allocated space.
1716 if (MOZ_UNLIKELY((stringifyBehavior
== StringifyBehavior::FastOnly
) &&
1717 (whySlow
!= BailReason::NO_REASON
))) {
1718 JS_ReportErrorASCII(cx
, "JSON stringify failed mandatory fast path: %s",
1719 DescribeStringifyBailReason(whySlow
));
1723 // Slow, general path.
1725 StringifyContext
scx(cx
, sb
, gap
, replacer
, propertyList
,
1726 stringifyBehavior
== StringifyBehavior::RestrictedSafe
);
1727 if (!PreprocessValue(cx
, wrapper
, HandleId(emptyId
), vp
, &scx
)) {
1730 if (IsFilteredValue(vp
)) {
1734 if (!SerializeJSONProperty(cx
, vp
, &scx
)) {
1738 // For StringBehavior::Compare, when the fast path succeeded.
1739 if (MOZ_UNLIKELY(fastJSON
)) {
1740 JSAtom
* slowJSON
= scx
.sb
.finishAtom();
1744 if (fastJSON
!= slowJSON
) {
1745 MOZ_CRASH("JSON.stringify mismatch between fast and slow paths");
1747 // Put the JSON back into the StringBuffer for returning.
1748 if (!sb
.append(slowJSON
)) {
1756 /* https://262.ecma-international.org/14.0/#sec-internalizejsonproperty */
1757 static bool InternalizeJSONProperty(
1758 JSContext
* cx
, HandleObject holder
, HandleId name
, HandleValue reviver
,
1759 MutableHandle
<ParseRecordObject
> parseRecord
, MutableHandleValue vp
) {
1760 AutoCheckRecursionLimit
recursion(cx
);
1761 if (!recursion
.check(cx
)) {
1766 RootedValue
val(cx
);
1767 if (!GetProperty(cx
, holder
, holder
, name
, &val
)) {
1771 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
1772 RootedObject
context(cx
);
1773 Rooted
<UniquePtr
<ParseRecordObject::EntryMap
>> entries(cx
);
1774 if (JS::Prefs::experimental_json_parse_with_source()) {
1775 // https://tc39.es/proposal-json-parse-with-source/#sec-internalizejsonproperty
1776 bool sameVal
= false;
1777 Rooted
<Value
> parsedValue(cx
, parseRecord
.get().value
);
1778 if (!SameValue(cx
, parsedValue
, val
, &sameVal
)) {
1781 if (!parseRecord
.get().isEmpty() && sameVal
) {
1782 if (parseRecord
.get().parseNode
) {
1783 MOZ_ASSERT(!val
.isObject());
1784 Rooted
<IdValueVector
> props(cx
, cx
);
1785 if (!props
.emplaceBack(
1786 IdValuePair(NameToId(cx
->names().source
),
1787 StringValue(parseRecord
.get().parseNode
)))) {
1790 context
= NewPlainObjectWithUniqueNames(cx
, props
);
1795 entries
= std::move(parseRecord
.get().entries
);
1798 context
= NewPlainObject(cx
);
1807 if (val
.isObject()) {
1808 RootedObject
obj(cx
, &val
.toObject());
1811 if (!IsArray(cx
, obj
, &isArray
)) {
1818 if (!GetLengthPropertyForArrayLike(cx
, obj
, &length
)) {
1822 /* Steps 2b(ii-iii). */
1824 RootedValue
newElement(cx
);
1825 for (uint32_t i
= 0; i
< length
; i
++) {
1826 if (!CheckForInterrupt(cx
)) {
1830 if (!IndexToId(cx
, i
, &id
)) {
1834 /* Step 2a(iii)(1). */
1835 Rooted
<ParseRecordObject
> elementRecord(cx
);
1836 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
1838 if (auto entry
= entries
->lookup(id
)) {
1839 elementRecord
= std::move(entry
->value());
1843 if (!InternalizeJSONProperty(cx
, obj
, id
, reviver
, &elementRecord
,
1848 ObjectOpResult ignored
;
1849 if (newElement
.isUndefined()) {
1850 /* Step 2b(iii)(3). The spec deliberately ignores strict failure. */
1851 if (!DeleteProperty(cx
, obj
, id
, ignored
)) {
1855 /* Step 2b(iii)(4). The spec deliberately ignores strict failure. */
1856 Rooted
<PropertyDescriptor
> desc(
1857 cx
, PropertyDescriptor::Data(newElement
,
1858 {JS::PropertyAttribute::Configurable
,
1859 JS::PropertyAttribute::Enumerable
,
1860 JS::PropertyAttribute::Writable
}));
1861 if (!DefineProperty(cx
, obj
, id
, desc
, ignored
)) {
1868 RootedIdVector
keys(cx
);
1869 if (!GetPropertyKeys(cx
, obj
, JSITER_OWNONLY
, &keys
)) {
1875 RootedValue
newElement(cx
);
1876 for (size_t i
= 0, len
= keys
.length(); i
< len
; i
++) {
1877 if (!CheckForInterrupt(cx
)) {
1881 /* Step 2c(ii)(1). */
1883 Rooted
<ParseRecordObject
> entryRecord(cx
);
1884 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
1886 if (auto entry
= entries
->lookup(id
)) {
1887 entryRecord
= std::move(entry
->value());
1891 if (!InternalizeJSONProperty(cx
, obj
, id
, reviver
, &entryRecord
,
1896 ObjectOpResult ignored
;
1897 if (newElement
.isUndefined()) {
1898 /* Step 2c(ii)(2). The spec deliberately ignores strict failure. */
1899 if (!DeleteProperty(cx
, obj
, id
, ignored
)) {
1903 /* Step 2c(ii)(3). The spec deliberately ignores strict failure. */
1904 Rooted
<PropertyDescriptor
> desc(
1905 cx
, PropertyDescriptor::Data(newElement
,
1906 {JS::PropertyAttribute::Configurable
,
1907 JS::PropertyAttribute::Enumerable
,
1908 JS::PropertyAttribute::Writable
}));
1909 if (!DefineProperty(cx
, obj
, id
, desc
, ignored
)) {
1918 RootedString
key(cx
, IdToString(cx
, name
));
1923 RootedValue
keyVal(cx
, StringValue(key
));
1924 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
1925 if (JS::Prefs::experimental_json_parse_with_source()) {
1926 RootedValue
contextVal(cx
, ObjectValue(*context
));
1927 return js::Call(cx
, reviver
, holder
, keyVal
, val
, contextVal
, vp
);
1930 return js::Call(cx
, reviver
, holder
, keyVal
, val
, vp
);
1933 static bool Revive(JSContext
* cx
, HandleValue reviver
,
1934 MutableHandle
<ParseRecordObject
> pro
,
1935 MutableHandleValue vp
) {
1936 Rooted
<PlainObject
*> obj(cx
, NewPlainObject(cx
));
1941 if (!DefineDataProperty(cx
, obj
, cx
->names().empty_
, vp
)) {
1945 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
1946 MOZ_ASSERT_IF(JS::Prefs::experimental_json_parse_with_source(),
1947 pro
.get().value
== vp
.get());
1949 Rooted
<jsid
> id(cx
, NameToId(cx
->names().empty_
));
1950 return InternalizeJSONProperty(cx
, obj
, id
, reviver
, pro
, vp
);
1953 template <typename CharT
>
1954 bool ParseJSON(JSContext
* cx
, const mozilla::Range
<const CharT
> chars
,
1955 MutableHandleValue vp
) {
1956 Rooted
<JSONParser
<CharT
>> parser(cx
, cx
, chars
,
1957 JSONParser
<CharT
>::ParseType::JSONParse
);
1958 return parser
.parse(vp
);
1961 template <typename CharT
>
1962 bool js::ParseJSONWithReviver(JSContext
* cx
,
1963 const mozilla::Range
<const CharT
> chars
,
1964 HandleValue reviver
, MutableHandleValue vp
) {
1965 /* https://262.ecma-international.org/14.0/#sec-json.parse steps 2-10. */
1966 Rooted
<ParseRecordObject
> pro(cx
);
1967 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
1968 if (JS::Prefs::experimental_json_parse_with_source() && IsCallable(reviver
)) {
1969 Rooted
<JSONReviveParser
<CharT
>> parser(cx
, cx
, chars
);
1970 if (!parser
.get().parse(vp
, &pro
)) {
1975 if (!ParseJSON(cx
, chars
, vp
)) {
1980 if (IsCallable(reviver
)) {
1981 return Revive(cx
, reviver
, &pro
, vp
);
1986 template bool js::ParseJSONWithReviver(
1987 JSContext
* cx
, const mozilla::Range
<const Latin1Char
> chars
,
1988 HandleValue reviver
, MutableHandleValue vp
);
1990 template bool js::ParseJSONWithReviver(
1991 JSContext
* cx
, const mozilla::Range
<const char16_t
> chars
,
1992 HandleValue reviver
, MutableHandleValue vp
);
1994 static bool json_toSource(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1995 CallArgs args
= CallArgsFromVp(argc
, vp
);
1996 args
.rval().setString(cx
->names().JSON
);
2000 /* https://262.ecma-international.org/14.0/#sec-json.parse */
2001 static bool json_parse(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2002 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "JSON", "parse");
2003 CallArgs args
= CallArgsFromVp(argc
, vp
);
2006 JSString
* str
= (args
.length() >= 1) ? ToString
<CanGC
>(cx
, args
[0])
2007 : cx
->names().undefined
;
2012 JSLinearString
* linear
= str
->ensureLinear(cx
);
2017 AutoStableStringChars
linearChars(cx
);
2018 if (!linearChars
.init(cx
, linear
)) {
2022 HandleValue reviver
= args
.get(1);
2025 return linearChars
.isLatin1()
2026 ? ParseJSONWithReviver(cx
, linearChars
.latin1Range(), reviver
,
2028 : ParseJSONWithReviver(cx
, linearChars
.twoByteRange(), reviver
,
2032 #ifdef ENABLE_RECORD_TUPLE
2033 bool BuildImmutableProperty(JSContext
* cx
, HandleValue value
, HandleId name
,
2034 HandleValue reviver
,
2035 MutableHandleValue immutableRes
) {
2036 MOZ_ASSERT(!name
.isSymbol());
2039 if (value
.isObject()) {
2040 RootedValue
childValue(cx
), newElement(cx
);
2041 RootedId
childName(cx
);
2044 if (value
.toObject().is
<ArrayObject
>()) {
2045 Rooted
<ArrayObject
*> arr(cx
, &value
.toObject().as
<ArrayObject
>());
2048 uint32_t len
= arr
->length();
2050 TupleType
* tup
= TupleType::createUninitialized(cx
, len
);
2054 immutableRes
.setExtendedPrimitive(*tup
);
2057 for (uint32_t i
= 0; i
< len
; i
++) {
2059 childName
.set(PropertyKey::Int(i
));
2062 if (!GetProperty(cx
, arr
, value
, childName
, &childValue
)) {
2067 if (!BuildImmutableProperty(cx
, childValue
, childName
, reviver
,
2071 MOZ_ASSERT(newElement
.isPrimitive());
2074 if (!tup
->initializeNextElement(cx
, newElement
)) {
2080 tup
->finishInitialization(cx
);
2082 RootedObject
obj(cx
, &value
.toObject());
2084 // Step 1.c.i - We only get the property keys rather than the
2085 // entries, but the difference is not observable from user code
2086 // because `obj` is a plan object not exposed externally
2087 RootedIdVector
props(cx
);
2088 if (!GetPropertyKeys(cx
, obj
, JSITER_OWNONLY
, &props
)) {
2092 RecordType
* rec
= RecordType::createUninitialized(cx
, props
.length());
2096 immutableRes
.setExtendedPrimitive(*rec
);
2098 for (uint32_t i
= 0; i
< props
.length(); i
++) {
2100 childName
.set(props
[i
]);
2103 if (!GetProperty(cx
, obj
, value
, childName
, &childValue
)) {
2108 if (!BuildImmutableProperty(cx
, childValue
, childName
, reviver
,
2112 MOZ_ASSERT(newElement
.isPrimitive());
2115 if (!newElement
.isUndefined()) {
2116 // Step 1.c.iii.5.a-b
2117 rec
->initializeNextProperty(cx
, childName
, newElement
);
2122 rec
->finishInitialization(cx
);
2126 immutableRes
.set(value
);
2130 if (IsCallable(reviver
)) {
2131 RootedValue
keyVal(cx
, StringValue(IdToString(cx
, name
)));
2134 if (!Call(cx
, reviver
, UndefinedHandleValue
, keyVal
, immutableRes
,
2140 if (!immutableRes
.isPrimitive()) {
2141 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2142 JSMSG_RECORD_TUPLE_NO_OBJECT
);
2150 static bool json_parseImmutable(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2151 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "JSON", "parseImmutable");
2152 CallArgs args
= CallArgsFromVp(argc
, vp
);
2155 JSString
* str
= (args
.length() >= 1) ? ToString
<CanGC
>(cx
, args
[0])
2156 : cx
->names().undefined
;
2161 JSLinearString
* linear
= str
->ensureLinear(cx
);
2166 AutoStableStringChars
linearChars(cx
);
2167 if (!linearChars
.init(cx
, linear
)) {
2171 HandleValue reviver
= args
.get(1);
2172 RootedValue
unfiltered(cx
);
2174 if (linearChars
.isLatin1()) {
2175 if (!ParseJSON(cx
, linearChars
.latin1Range(), &unfiltered
)) {
2179 if (!ParseJSON(cx
, linearChars
.twoByteRange(), &unfiltered
)) {
2184 RootedId
id(cx
, NameToId(cx
->names().empty_
));
2185 return BuildImmutableProperty(cx
, unfiltered
, id
, reviver
, args
.rval());
2189 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
2190 /* https://tc39.es/proposal-json-parse-with-source/#sec-json.israwjson */
2191 static bool json_isRawJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2192 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "JSON", "isRawJSON");
2193 CallArgs args
= CallArgsFromVp(argc
, vp
);
2196 if (args
.get(0).isObject()) {
2197 Rooted
<JSObject
*> obj(cx
, &args
[0].toObject());
2199 if (obj
->is
<RawJSONObject
>()) {
2200 bool objIsFrozen
= false;
2201 MOZ_ASSERT(js::TestIntegrityLevel(cx
, obj
, IntegrityLevel::Frozen
,
2203 MOZ_ASSERT(objIsFrozen
);
2206 args
.rval().setBoolean(obj
->is
<RawJSONObject
>() ||
2207 obj
->canUnwrapAs
<RawJSONObject
>());
2212 args
.rval().setBoolean(false);
2216 static inline bool IsJSONWhitespace(char16_t ch
) {
2217 return ch
== '\t' || ch
== '\n' || ch
== '\r' || ch
== ' ';
2220 /* https://tc39.es/proposal-json-parse-with-source/#sec-json.rawjson */
2221 static bool json_rawJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2222 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "JSON", "rawJSON");
2223 CallArgs args
= CallArgsFromVp(argc
, vp
);
2226 JSString
* jsonString
= ToString
<CanGC
>(cx
, args
.get(0));
2231 Rooted
<JSLinearString
*> linear(cx
, jsonString
->ensureLinear(cx
));
2236 AutoStableStringChars
linearChars(cx
);
2237 if (!linearChars
.init(cx
, linear
)) {
2242 if (linear
->empty()) {
2243 JS_ReportErrorNumberASCII(cx
, js::GetErrorMessage
, nullptr,
2244 JSMSG_JSON_RAW_EMPTY
);
2247 if (IsJSONWhitespace(linear
->latin1OrTwoByteChar(0)) ||
2248 IsJSONWhitespace(linear
->latin1OrTwoByteChar(linear
->length() - 1))) {
2249 JS_ReportErrorNumberASCII(cx
, js::GetErrorMessage
, nullptr,
2250 JSMSG_JSON_RAW_WHITESPACE
);
2255 RootedValue
parsedValue(cx
);
2256 if (linearChars
.isLatin1()) {
2257 if (!ParseJSON(cx
, linearChars
.latin1Range(), &parsedValue
)) {
2261 if (!ParseJSON(cx
, linearChars
.twoByteRange(), &parsedValue
)) {
2266 if (parsedValue
.isObject()) {
2267 JS_ReportErrorNumberASCII(cx
, js::GetErrorMessage
, nullptr,
2268 JSMSG_JSON_RAW_ARRAY_OR_OBJECT
);
2273 Rooted
<RawJSONObject
*> obj(cx
, RawJSONObject::create(cx
, linear
));
2279 if (!js::FreezeObject(cx
, obj
)) {
2283 args
.rval().setObject(*obj
);
2286 #endif // ENABLE_JSON_PARSE_WITH_SOURCE
2288 /* https://262.ecma-international.org/14.0/#sec-json.stringify */
2289 bool json_stringify(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2290 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "JSON", "stringify");
2291 CallArgs args
= CallArgsFromVp(argc
, vp
);
2293 RootedObject
replacer(cx
,
2294 args
.get(1).isObject() ? &args
[1].toObject() : nullptr);
2295 RootedValue
value(cx
, args
.get(0));
2296 RootedValue
space(cx
, args
.get(2));
2299 StringifyBehavior behavior
= StringifyBehavior::Compare
;
2301 StringifyBehavior behavior
= StringifyBehavior::Normal
;
2304 JSStringBuilder
sb(cx
);
2305 if (!Stringify(cx
, &value
, replacer
, space
, sb
, behavior
)) {
2309 // XXX This can never happen to nsJSON.cpp, but the JSON object
2310 // needs to support returning undefined. So this is a little awkward
2311 // for the API, because we want to support streaming writers.
2313 JSString
* str
= sb
.finishString();
2317 args
.rval().setString(str
);
2319 args
.rval().setUndefined();
2325 static const JSFunctionSpec json_static_methods
[] = {
2326 JS_FN("toSource", json_toSource
, 0, 0),
2327 JS_FN("parse", json_parse
, 2, 0),
2328 JS_FN("stringify", json_stringify
, 3, 0),
2329 #ifdef ENABLE_RECORD_TUPLE
2330 JS_FN("parseImmutable", json_parseImmutable
, 2, 0),
2332 #ifdef ENABLE_JSON_PARSE_WITH_SOURCE
2333 JS_FN("isRawJSON", json_isRawJSON
, 1, 0),
2334 JS_FN("rawJSON", json_rawJSON
, 1, 0),
2338 static const JSPropertySpec json_static_properties
[] = {
2339 JS_STRING_SYM_PS(toStringTag
, "JSON", JSPROP_READONLY
), JS_PS_END
};
2341 static JSObject
* CreateJSONObject(JSContext
* cx
, JSProtoKey key
) {
2342 RootedObject
proto(cx
, &cx
->global()->getObjectPrototype());
2343 return NewTenuredObjectWithGivenProto(cx
, &JSONClass
, proto
);
2346 static const ClassSpec JSONClassSpec
= {
2347 CreateJSONObject
, nullptr, json_static_methods
, json_static_properties
};
2349 const JSClass
js::JSONClass
= {"JSON", JSCLASS_HAS_CACHED_PROTO(JSProto_JSON
),
2350 JS_NULL_CLASS_OPS
, &JSONClassSpec
};