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/. */
8 * This file implements the structured data algorithms of
9 * https://html.spec.whatwg.org/multipage/structured-data.html
11 * The spec is in two parts:
13 * - StructuredSerialize examines a JS value and produces a graph of Records.
14 * - StructuredDeserialize walks the Records and produces a new JS value.
16 * The differences between our implementation and the spec are minor:
18 * - We call the two phases "write" and "read".
19 * - Our algorithms use an explicit work stack, rather than recursion.
20 * - Serialized data is a flat array of bytes, not a (possibly cyclic) graph
22 * - As a consequence, we handle non-treelike object graphs differently.
23 * We serialize objects that appear in multiple places in the input as
24 * backreferences, using sequential integer indexes.
25 * See `JSStructuredCloneReader::allObjs`, our take on the "memory" map
26 * in the spec's StructuredDeserialize.
29 #include "js/StructuredClone.h"
31 #include "mozilla/Casting.h"
32 #include "mozilla/CheckedInt.h"
33 #include "mozilla/EndianUtils.h"
34 #include "mozilla/FloatingPoint.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/ScopeExit.h"
44 #include "builtin/DataViewObject.h"
45 #include "builtin/MapObject.h"
46 #include "gc/GC.h" // AutoSelectGCHeap
47 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
48 #include "js/ArrayBuffer.h" // JS::{ArrayBufferHasData,DetachArrayBuffer,IsArrayBufferObject,New{,Mapped}ArrayBufferWithContents,ReleaseMappedArrayBufferContents}
49 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
51 #include "js/experimental/TypedData.h" // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer
52 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
54 #include "js/GCHashTable.h"
55 #include "js/Object.h" // JS::GetBuiltinClass
56 #include "js/PropertyAndElement.h" // JS_GetElement
57 #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
58 #include "js/ScalarType.h" // js::Scalar::Type
59 #include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject
60 #include "js/Wrapper.h"
61 #include "util/DifferentialTesting.h"
62 #include "vm/BigIntType.h"
63 #include "vm/ErrorObject.h"
64 #include "vm/JSContext.h"
65 #include "vm/PlainObject.h" // js::PlainObject
66 #include "vm/RegExpObject.h"
67 #include "vm/SavedFrame.h"
68 #include "vm/SharedArrayObject.h"
69 #include "vm/TypedArrayObject.h"
70 #include "wasm/WasmJS.h"
72 #include "vm/ArrayObject-inl.h"
73 #include "vm/Compartment-inl.h"
74 #include "vm/ErrorObject-inl.h"
75 #include "vm/InlineCharBuffer-inl.h"
76 #include "vm/JSContext-inl.h"
77 #include "vm/JSObject-inl.h"
78 #include "vm/NativeObject-inl.h"
79 #include "vm/ObjectOperations-inl.h"
80 #include "vm/Realm-inl.h"
84 using JS::CanonicalizeNaN
;
85 using JS::GetBuiltinClass
;
87 using JS::RegExpFlags
;
88 using JS::RootedValueVector
;
89 using mozilla::AssertedCast
;
90 using mozilla::BitwiseCast
;
92 using mozilla::NativeEndian
;
93 using mozilla::NumbersAreIdentical
;
95 // When you make updates here, make sure you consider whether you need to bump
96 // the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You
97 // will likely need to increment the version if anything at all changes in the
98 // serialization format.
100 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should
101 // have a stable ID, it need not be at the end of the list and should not be
102 // used for sizing data structures.
104 enum StructuredDataType
: uint32_t {
105 // Structured data types provided by the engine
106 SCTAG_FLOAT_MAX
= 0xFFF00000,
107 SCTAG_HEADER
= 0xFFF10000,
108 SCTAG_NULL
= 0xFFFF0000,
117 SCTAG_ARRAY_BUFFER_OBJECT_V2
, // Old version, for backwards compatibility.
118 SCTAG_BOOLEAN_OBJECT
,
121 SCTAG_BACK_REFERENCE_OBJECT
,
122 SCTAG_DO_NOT_USE_1
, // Required for backwards compatibility
123 SCTAG_DO_NOT_USE_2
, // Required for backwards compatibility
124 SCTAG_TYPED_ARRAY_OBJECT_V2
, // Old version, for backwards compatibility.
128 SCTAG_DO_NOT_USE_3
, // Required for backwards compatibility
129 SCTAG_DATA_VIEW_OBJECT_V2
, // Old version, for backwards compatibility.
130 SCTAG_SAVED_FRAME_OBJECT
,
132 // No new tags before principals.
134 SCTAG_NULL_JSPRINCIPALS
,
135 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
,
136 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
,
138 SCTAG_SHARED_ARRAY_BUFFER_OBJECT
,
139 SCTAG_SHARED_WASM_MEMORY_OBJECT
,
144 SCTAG_ARRAY_BUFFER_OBJECT
,
145 SCTAG_TYPED_ARRAY_OBJECT
,
146 SCTAG_DATA_VIEW_OBJECT
,
150 SCTAG_TYPED_ARRAY_V1_MIN
= 0xFFFF0100,
151 SCTAG_TYPED_ARRAY_V1_INT8
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Int8
,
152 SCTAG_TYPED_ARRAY_V1_UINT8
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint8
,
153 SCTAG_TYPED_ARRAY_V1_INT16
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Int16
,
154 SCTAG_TYPED_ARRAY_V1_UINT16
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint16
,
155 SCTAG_TYPED_ARRAY_V1_INT32
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Int32
,
156 SCTAG_TYPED_ARRAY_V1_UINT32
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint32
,
157 SCTAG_TYPED_ARRAY_V1_FLOAT32
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Float32
,
158 SCTAG_TYPED_ARRAY_V1_FLOAT64
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Float64
,
159 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED
=
160 SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint8Clamped
,
161 // BigInt64 and BigUint64 are not supported in the v1 format.
162 SCTAG_TYPED_ARRAY_V1_MAX
= SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED
,
164 // Define a separate range of numbers for Transferable-only tags, since
165 // they are not used for persistent clone buffers and therefore do not
166 // require bumping JS_STRUCTURED_CLONE_VERSION.
167 SCTAG_TRANSFER_MAP_HEADER
= 0xFFFF0200,
168 SCTAG_TRANSFER_MAP_PENDING_ENTRY
,
169 SCTAG_TRANSFER_MAP_ARRAY_BUFFER
,
170 SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
,
171 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES
,
173 SCTAG_END_OF_BUILTIN_TYPES
177 * Format of transfer map:
178 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
179 * numTransferables (64 bits)
181 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
183 * extraData (64 bits), eg byte length for ArrayBuffers
186 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
187 // contents have been read out yet or not.
188 enum TransferableMapHeader
{ SCTAG_TM_UNREAD
= 0, SCTAG_TM_TRANSFERRED
};
190 static inline uint64_t PairToUInt64(uint32_t tag
, uint32_t data
) {
191 return uint64_t(data
) | (uint64_t(tag
) << 32);
196 template <typename T
, typename AllocPolicy
>
197 struct BufferIterator
{
198 using BufferList
= mozilla::BufferList
<AllocPolicy
>;
200 explicit BufferIterator(const BufferList
& buffer
)
201 : mBuffer(buffer
), mIter(buffer
.Iter()) {
202 static_assert(8 % sizeof(T
) == 0);
205 explicit BufferIterator(const JSStructuredCloneData
& data
)
206 : mBuffer(data
.bufList_
), mIter(data
.Start()) {}
208 BufferIterator
& operator=(const BufferIterator
& other
) {
209 MOZ_ASSERT(&mBuffer
== &other
.mBuffer
);
214 [[nodiscard
]] bool advance(size_t size
= sizeof(T
)) {
215 return mIter
.AdvanceAcrossSegments(mBuffer
, size
);
218 BufferIterator
operator++(int) {
219 BufferIterator ret
= *this;
220 if (!advance(sizeof(T
))) {
221 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
226 BufferIterator
& operator+=(size_t size
) {
227 if (!advance(size
)) {
228 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
233 size_t operator-(const BufferIterator
& other
) const {
234 MOZ_ASSERT(&mBuffer
== &other
.mBuffer
);
235 return mBuffer
.RangeLength(other
.mIter
, mIter
);
238 bool operator==(const BufferIterator
& other
) const {
239 return mBuffer
.Start() == other
.mBuffer
.Start() && mIter
== other
.mIter
;
241 bool operator!=(const BufferIterator
& other
) const {
242 return !(*this == other
);
245 bool done() const { return mIter
.Done(); }
247 [[nodiscard
]] bool readBytes(char* outData
, size_t size
) {
248 return mBuffer
.ReadBytes(mIter
, outData
, size
);
251 void write(const T
& data
) {
252 MOZ_ASSERT(mIter
.HasRoomFor(sizeof(T
)));
253 *reinterpret_cast<T
*>(mIter
.Data()) = data
;
257 MOZ_ASSERT(mIter
.HasRoomFor(sizeof(T
)));
258 return *reinterpret_cast<T
*>(mIter
.Data());
261 bool canPeek() const { return mIter
.HasRoomFor(sizeof(T
)); }
263 const BufferList
& mBuffer
;
264 typename
BufferList::IterImpl mIter
;
267 SharedArrayRawBufferRefs
& SharedArrayRawBufferRefs::operator=(
268 SharedArrayRawBufferRefs
&& other
) {
269 takeOwnership(std::move(other
));
273 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }
275 bool SharedArrayRawBufferRefs::acquire(JSContext
* cx
,
276 SharedArrayRawBuffer
* rawbuf
) {
277 if (!refs_
.append(rawbuf
)) {
278 ReportOutOfMemory(cx
);
282 if (!rawbuf
->addReference()) {
284 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
285 JSMSG_SC_SAB_REFCNT_OFLO
);
292 bool SharedArrayRawBufferRefs::acquireAll(
293 JSContext
* cx
, const SharedArrayRawBufferRefs
& that
) {
294 if (!refs_
.reserve(refs_
.length() + that
.refs_
.length())) {
295 ReportOutOfMemory(cx
);
299 for (auto ref
: that
.refs_
) {
300 if (!ref
->addReference()) {
301 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
302 JSMSG_SC_SAB_REFCNT_OFLO
);
305 MOZ_ALWAYS_TRUE(refs_
.append(ref
));
311 void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs
&& other
) {
312 MOZ_ASSERT(refs_
.empty());
313 refs_
= std::move(other
.refs_
);
316 void SharedArrayRawBufferRefs::releaseAll() {
317 for (auto ref
: refs_
) {
318 ref
->dropReference();
323 // SCOutput provides an interface to write raw data -- eg uint64_ts, doubles,
324 // arrays of bytes -- into a structured clone data output stream. It also knows
325 // how to free any transferable data within that stream.
327 // Note that it contains a full JSStructuredCloneData object, which holds the
328 // callbacks necessary to read/write/transfer/free the data. For the purpose of
329 // this class, only the freeTransfer callback is relevant; the rest of the
330 // callbacks are used by the higher-level JSStructuredCloneWriter interface.
333 using Iter
= BufferIterator
<uint64_t, SystemAllocPolicy
>;
335 SCOutput(JSContext
* cx
, JS::StructuredCloneScope scope
);
337 JSContext
* context() const { return cx
; }
338 JS::StructuredCloneScope
scope() const { return buf
.scope(); }
339 void sameProcessScopeRequired() { buf
.sameProcessScopeRequired(); }
341 [[nodiscard
]] bool write(uint64_t u
);
342 [[nodiscard
]] bool writePair(uint32_t tag
, uint32_t data
);
343 [[nodiscard
]] bool writeDouble(double d
);
344 [[nodiscard
]] bool writeBytes(const void* p
, size_t nbytes
);
345 [[nodiscard
]] bool writeChars(const Latin1Char
* p
, size_t nchars
);
346 [[nodiscard
]] bool writeChars(const char16_t
* p
, size_t nchars
);
349 [[nodiscard
]] bool writeArray(const T
* p
, size_t nelems
);
351 void setCallbacks(const JSStructuredCloneCallbacks
* callbacks
, void* closure
,
352 OwnTransferablePolicy policy
) {
353 buf
.setCallbacks(callbacks
, closure
, policy
);
355 void extractBuffer(JSStructuredCloneData
* data
) { *data
= std::move(buf
); }
357 uint64_t tell() const { return buf
.Size(); }
358 uint64_t count() const { return buf
.Size() / sizeof(uint64_t); }
359 Iter
iter() { return Iter(buf
); }
361 size_t offset(Iter dest
) { return dest
- iter(); }
364 JSStructuredCloneData buf
;
369 using BufferIterator
= js::BufferIterator
<uint64_t, SystemAllocPolicy
>;
371 SCInput(JSContext
* cx
, const JSStructuredCloneData
& data
);
373 JSContext
* context() const { return cx
; }
375 static void getPtr(uint64_t data
, void** ptr
);
376 static void getPair(uint64_t data
, uint32_t* tagp
, uint32_t* datap
);
378 [[nodiscard
]] bool read(uint64_t* p
);
379 [[nodiscard
]] bool readPair(uint32_t* tagp
, uint32_t* datap
);
380 [[nodiscard
]] bool readDouble(double* p
);
381 [[nodiscard
]] bool readBytes(void* p
, size_t nbytes
);
382 [[nodiscard
]] bool readChars(Latin1Char
* p
, size_t nchars
);
383 [[nodiscard
]] bool readChars(char16_t
* p
, size_t nchars
);
384 [[nodiscard
]] bool readPtr(void**);
386 [[nodiscard
]] bool get(uint64_t* p
);
387 [[nodiscard
]] bool getPair(uint32_t* tagp
, uint32_t* datap
);
389 const BufferIterator
& tell() const { return point
; }
390 void seekTo(const BufferIterator
& pos
) { point
= pos
; }
391 [[nodiscard
]] bool seekBy(size_t pos
) {
392 if (!point
.advance(pos
)) {
400 [[nodiscard
]] bool readArray(T
* p
, size_t nelems
);
402 bool reportTruncated() {
403 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
404 JSMSG_SC_BAD_SERIALIZED_DATA
, "truncated");
409 void staticAssertions() {
410 static_assert(sizeof(char16_t
) == 2);
411 static_assert(sizeof(uint32_t) == 4);
415 BufferIterator point
;
420 struct JSStructuredCloneReader
{
422 explicit JSStructuredCloneReader(SCInput
& in
, JS::StructuredCloneScope scope
,
423 const JS::CloneDataPolicy
& cloneDataPolicy
,
424 const JSStructuredCloneCallbacks
* cb
,
427 SCInput
& input() { return in
; }
428 bool read(MutableHandleValue vp
, size_t nbytes
);
431 JSContext
* context() { return in
.context(); }
434 bool readTransferMap();
436 [[nodiscard
]] bool readUint32(uint32_t* num
);
438 enum ShouldAtomizeStrings
: bool {
439 DontAtomizeStrings
= false,
440 AtomizeStrings
= true
443 template <typename CharT
>
444 JSString
* readStringImpl(uint32_t nchars
, ShouldAtomizeStrings atomize
);
445 JSString
* readString(uint32_t data
, ShouldAtomizeStrings atomize
);
447 BigInt
* readBigInt(uint32_t data
);
449 [[nodiscard
]] bool readTypedArray(uint32_t arrayType
, uint64_t nelems
,
450 MutableHandleValue vp
, bool v1Read
= false);
452 [[nodiscard
]] bool readDataView(uint64_t byteLength
, MutableHandleValue vp
);
454 [[nodiscard
]] bool readArrayBuffer(StructuredDataType type
, uint32_t data
,
455 MutableHandleValue vp
);
456 [[nodiscard
]] bool readV1ArrayBuffer(uint32_t arrayType
, uint32_t nelems
,
457 MutableHandleValue vp
);
459 [[nodiscard
]] bool readSharedArrayBuffer(MutableHandleValue vp
);
461 [[nodiscard
]] bool readSharedWasmMemory(uint32_t nbytes
,
462 MutableHandleValue vp
);
464 // A serialized SavedFrame contains primitive values in a header followed by
465 // an optional parent frame that is read recursively.
466 [[nodiscard
]] JSObject
* readSavedFrameHeader(uint32_t principalsTag
);
467 [[nodiscard
]] bool readSavedFrameFields(Handle
<SavedFrame
*> frameObj
,
468 HandleValue parent
, bool* state
);
470 // A serialized Error contains primitive values in a header followed by
471 // 'cause', 'errors', and 'stack' fields that are read recursively.
472 [[nodiscard
]] JSObject
* readErrorHeader(uint32_t type
);
473 [[nodiscard
]] bool readErrorFields(Handle
<ErrorObject
*> errorObj
,
474 HandleValue cause
, bool* state
);
476 [[nodiscard
]] bool readMapField(Handle
<MapObject
*> mapObj
, HandleValue key
);
478 [[nodiscard
]] bool readObjectField(HandleObject obj
, HandleValue key
);
480 [[nodiscard
]] bool startRead(
481 MutableHandleValue vp
,
482 ShouldAtomizeStrings atomizeStrings
= DontAtomizeStrings
);
486 // The widest scope that the caller will accept, where
487 // SameProcess is the widest (it can store anything it wants)
488 // and DifferentProcess is the narrowest (it cannot contain pointers and must
489 // be valid cross-process.)
490 JS::StructuredCloneScope allowedScope
;
492 const JS::CloneDataPolicy cloneDataPolicy
;
494 // Stack of objects with properties remaining to be read.
495 RootedValueVector objs
;
497 // Maintain a stack of state values for the `objs` stack. Since this is only
498 // needed for a very small subset of objects (those with a known set of
499 // object children), the state information is stored as a stack of
500 // <object, state> pairs where the object determines which element of the
501 // `objs` stack that it corresponds to. So when reading from the `objs` stack,
502 // the state will be retrieved only if the top object on `objState` matches
503 // the top object of `objs`.
505 // Currently, the only state needed is a boolean indicating whether the fields
506 // have been read yet.
507 Rooted
<GCVector
<std::pair
<HeapPtr
<JSObject
*>, bool>, 8>> objState
;
509 // Array of all objects read during this deserialization, for resolving
512 // For backreferences to work correctly, objects must be added to this
513 // array in exactly the order expected by the version of the Writer that
514 // created the serialized data, even across years and format versions. This
515 // is usually no problem, since both algorithms do a single linear pass
516 // over the serialized data. There is one hitch; see readTypedArray.
518 // The values in this vector are objects, except it can temporarily have
519 // one `undefined` placeholder value (the readTypedArray hack).
520 RootedValueVector allObjs
;
524 // The user defined callbacks that will be used for cloning.
525 const JSStructuredCloneCallbacks
* callbacks
;
527 // Any value passed to JS_ReadStructuredClone.
530 // The heap to use for allocating common GC things. This starts out as the
531 // nursery (the default) but may switch to the tenured heap if nursery
532 // collection occurs, as nursery allocation is pointless after the
533 // deserialized root object is tenured.
535 // This is only used for the most common kind, e.g. plain objects, strings
536 // and a couple of others.
537 AutoSelectGCHeap gcHeap
;
539 friend bool JS_ReadString(JSStructuredCloneReader
* r
,
540 JS::MutableHandleString str
);
541 friend bool JS_ReadTypedArray(JSStructuredCloneReader
* r
,
542 MutableHandleValue vp
);
544 // Provide a way to detect whether any of the clone data is never used. When
545 // "tail" data (currently, this is only stored data for Transferred
546 // ArrayBuffers in the DifferentProcess scope) is read, record the first and
547 // last positions. At the end of deserialization, make sure there's nothing
548 // between the end of the main data and the beginning of the tail, nor after
549 // the end of the tail.
550 mozilla::Maybe
<SCInput::BufferIterator
> tailStartPos
;
551 mozilla::Maybe
<SCInput::BufferIterator
> tailEndPos
;
554 struct JSStructuredCloneWriter
{
556 explicit JSStructuredCloneWriter(JSContext
* cx
,
557 JS::StructuredCloneScope scope
,
558 const JS::CloneDataPolicy
& cloneDataPolicy
,
559 const JSStructuredCloneCallbacks
* cb
,
560 void* cbClosure
, const Value
& tVal
)
569 transferable(cx
, tVal
),
570 transferableObjects(cx
, TransferableObjectsList(cx
)),
571 cloneDataPolicy(cloneDataPolicy
) {
572 out
.setCallbacks(cb
, cbClosure
,
573 OwnTransferablePolicy::OwnsTransferablesIfAny
);
577 return parseTransferable() && writeHeader() && writeTransferMap();
580 bool write(HandleValue v
);
582 SCOutput
& output() { return out
; }
584 void extractBuffer(JSStructuredCloneData
* newData
) {
585 out
.extractBuffer(newData
);
589 JSStructuredCloneWriter() = delete;
590 JSStructuredCloneWriter(const JSStructuredCloneWriter
&) = delete;
592 JSContext
* context() { return out
.context(); }
595 bool writeTransferMap();
597 bool writeString(uint32_t tag
, JSString
* str
);
598 bool writeBigInt(uint32_t tag
, BigInt
* bi
);
599 bool writeArrayBuffer(HandleObject obj
);
600 bool writeTypedArray(HandleObject obj
);
601 bool writeDataView(HandleObject obj
);
602 bool writeSharedArrayBuffer(HandleObject obj
);
603 bool writeSharedWasmMemory(HandleObject obj
);
604 bool startObject(HandleObject obj
, bool* backref
);
605 bool writePrimitive(HandleValue v
);
606 bool startWrite(HandleValue v
);
607 bool traverseObject(HandleObject obj
, ESClass cls
);
608 bool traverseMap(HandleObject obj
);
609 bool traverseSet(HandleObject obj
);
610 bool traverseSavedFrame(HandleObject obj
);
611 bool traverseError(HandleObject obj
);
613 template <typename
... Args
>
614 bool reportDataCloneError(uint32_t errorId
, Args
&&... aArgs
);
616 bool parseTransferable();
617 bool transferOwnership();
619 inline void checkStack();
623 // The user defined callbacks that will be used to signal cloning, in some
625 const JSStructuredCloneCallbacks
* callbacks
;
627 // Any value passed to the callbacks.
630 // Vector of objects with properties remaining to be written.
632 // NB: These can span multiple compartments, so the compartment must be
633 // entered before any manipulation is performed.
634 RootedValueVector objs
;
636 // counts[i] is the number of entries of objs[i] remaining to be written.
637 // counts.length() == objs.length() and sum(counts) == entries.length().
638 Vector
<size_t> counts
;
640 // For JSObject: Property IDs as value
641 RootedIdVector objectEntries
;
643 // For Map: Key followed by value
645 // For SavedFrame: parent SavedFrame
646 // For Error: cause, errors, stack
647 RootedValueVector otherEntries
;
649 // The "memory" list described in the HTML5 internal structured cloning
650 // algorithm. memory is a superset of objs; items are never removed from
651 // Memory until a serialization operation is finished
652 using CloneMemory
= GCHashMap
<JSObject
*, uint32_t,
653 StableCellHasher
<JSObject
*>, SystemAllocPolicy
>;
654 Rooted
<CloneMemory
> memory
;
656 // Set of transferable objects
657 RootedValue transferable
;
658 using TransferableObjectsList
= GCVector
<JSObject
*>;
659 Rooted
<TransferableObjectsList
> transferableObjects
;
661 const JS::CloneDataPolicy cloneDataPolicy
;
663 friend bool JS_WriteString(JSStructuredCloneWriter
* w
, HandleString str
);
664 friend bool JS_WriteTypedArray(JSStructuredCloneWriter
* w
, HandleValue v
);
665 friend bool JS_ObjectNotWritten(JSStructuredCloneWriter
* w
, HandleObject obj
);
668 JS_PUBLIC_API
uint64_t js::GetSCOffset(JSStructuredCloneWriter
* writer
) {
670 return writer
->output().count() * sizeof(uint64_t);
673 static_assert(SCTAG_END_OF_BUILTIN_TYPES
<= JS_SCTAG_USER_MIN
);
674 static_assert(JS_SCTAG_USER_MIN
<= JS_SCTAG_USER_MAX
);
675 static_assert(Scalar::Int8
== 0);
677 template <typename
... Args
>
678 static void ReportDataCloneError(JSContext
* cx
,
679 const JSStructuredCloneCallbacks
* callbacks
,
680 uint32_t errorId
, void* closure
,
682 unsigned errorNumber
;
684 case JS_SCERR_DUP_TRANSFERABLE
:
685 errorNumber
= JSMSG_SC_DUP_TRANSFERABLE
;
688 case JS_SCERR_TRANSFERABLE
:
689 errorNumber
= JSMSG_SC_NOT_TRANSFERABLE
;
692 case JS_SCERR_UNSUPPORTED_TYPE
:
693 errorNumber
= JSMSG_SC_UNSUPPORTED_TYPE
;
696 case JS_SCERR_SHMEM_TRANSFERABLE
:
697 errorNumber
= JSMSG_SC_SHMEM_TRANSFERABLE
;
700 case JS_SCERR_TYPED_ARRAY_DETACHED
:
701 errorNumber
= JSMSG_TYPED_ARRAY_DETACHED
;
704 case JS_SCERR_WASM_NO_TRANSFER
:
705 errorNumber
= JSMSG_WASM_NO_TRANSFER
;
708 case JS_SCERR_NOT_CLONABLE
:
709 errorNumber
= JSMSG_SC_NOT_CLONABLE
;
712 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
:
713 errorNumber
= JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP
;
717 MOZ_CRASH("Unkown errorId");
721 if (callbacks
&& callbacks
->reportError
) {
722 MOZ_RELEASE_ASSERT(!cx
->isExceptionPending());
724 JSErrorReport report
;
725 report
.errorNumber
= errorNumber
;
726 // Get js error message if it's possible and propagate it through callback.
727 if (JS_ExpandErrorArgumentsASCII(cx
, GetErrorMessage
, errorNumber
, &report
,
728 std::forward
<Args
>(aArgs
)...) &&
730 callbacks
->reportError(cx
, errorId
, closure
, report
.message().c_str());
732 ReportOutOfMemory(cx
);
734 callbacks
->reportError(cx
, errorId
, closure
, "");
740 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
,
741 std::forward
<Args
>(aArgs
)...);
744 bool WriteStructuredClone(JSContext
* cx
, HandleValue v
,
745 JSStructuredCloneData
* bufp
,
746 JS::StructuredCloneScope scope
,
747 const JS::CloneDataPolicy
& cloneDataPolicy
,
748 const JSStructuredCloneCallbacks
* cb
, void* cbClosure
,
749 const Value
& transferable
) {
750 JSStructuredCloneWriter
w(cx
, scope
, cloneDataPolicy
, cb
, cbClosure
,
758 w
.extractBuffer(bufp
);
762 bool ReadStructuredClone(JSContext
* cx
, const JSStructuredCloneData
& data
,
763 JS::StructuredCloneScope scope
, MutableHandleValue vp
,
764 const JS::CloneDataPolicy
& cloneDataPolicy
,
765 const JSStructuredCloneCallbacks
* cb
,
767 if (data
.Size() % 8) {
768 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
769 JSMSG_SC_BAD_SERIALIZED_DATA
, "misaligned");
772 SCInput
in(cx
, data
);
773 JSStructuredCloneReader
r(in
, scope
, cloneDataPolicy
, cb
, cbClosure
);
774 return r
.read(vp
, data
.Size());
777 static bool StructuredCloneHasTransferObjects(
778 const JSStructuredCloneData
& data
) {
779 if (data
.Size() < sizeof(uint64_t)) {
784 BufferIterator
<uint64_t, SystemAllocPolicy
> iter(data
);
785 MOZ_ALWAYS_TRUE(iter
.readBytes(reinterpret_cast<char*>(&u
), sizeof(u
)));
786 uint32_t tag
= uint32_t(u
>> 32);
787 return (tag
== SCTAG_TRANSFER_MAP_HEADER
);
792 SCInput::SCInput(JSContext
* cx
, const JSStructuredCloneData
& data
)
793 : cx(cx
), point(data
) {
794 static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment
% 8 == 0,
795 "structured clone buffer reads should be aligned");
796 MOZ_ASSERT(data
.Size() % 8 == 0);
799 bool SCInput::read(uint64_t* p
) {
800 if (!point
.canPeek()) {
801 *p
= 0; // initialize to shut GCC up
802 return reportTruncated();
804 *p
= NativeEndian::swapFromLittleEndian(point
.peek());
805 MOZ_ALWAYS_TRUE(point
.advance());
809 bool SCInput::readPair(uint32_t* tagp
, uint32_t* datap
) {
813 *tagp
= uint32_t(u
>> 32);
814 *datap
= uint32_t(u
);
819 bool SCInput::get(uint64_t* p
) {
820 if (!point
.canPeek()) {
821 return reportTruncated();
823 *p
= NativeEndian::swapFromLittleEndian(point
.peek());
827 bool SCInput::getPair(uint32_t* tagp
, uint32_t* datap
) {
833 *tagp
= uint32_t(u
>> 32);
834 *datap
= uint32_t(u
);
838 void SCInput::getPair(uint64_t data
, uint32_t* tagp
, uint32_t* datap
) {
839 uint64_t u
= NativeEndian::swapFromLittleEndian(data
);
840 *tagp
= uint32_t(u
>> 32);
841 *datap
= uint32_t(u
);
844 bool SCInput::readDouble(double* p
) {
849 *p
= CanonicalizeNaN(mozilla::BitwiseCast
<double>(u
));
853 template <typename T
>
854 static void swapFromLittleEndianInPlace(T
* ptr
, size_t nelems
) {
856 NativeEndian::swapFromLittleEndianInPlace(ptr
, nelems
);
861 void swapFromLittleEndianInPlace(uint8_t* ptr
, size_t nelems
) {}
863 // Data is packed into an integral number of uint64_t words. Compute the
864 // padding required to finish off the final word.
865 static size_t ComputePadding(size_t nelems
, size_t elemSize
) {
866 // We want total length mod 8, where total length is nelems * sizeof(T),
867 // but that might overflow. So reduce nelems to nelems mod 8, since we are
868 // going to be doing a mod 8 later anyway.
869 size_t leftoverLength
= (nelems
% sizeof(uint64_t)) * elemSize
;
870 return (-leftoverLength
) & (sizeof(uint64_t) - 1);
874 bool SCInput::readArray(T
* p
, size_t nelems
) {
879 static_assert(sizeof(uint64_t) % sizeof(T
) == 0);
881 // Fail if nelems is so huge that computing the full size will overflow.
882 mozilla::CheckedInt
<size_t> size
=
883 mozilla::CheckedInt
<size_t>(nelems
) * sizeof(T
);
884 if (!size
.isValid()) {
885 return reportTruncated();
888 if (!point
.readBytes(reinterpret_cast<char*>(p
), size
.value())) {
889 // To avoid any way in which uninitialized data could escape, zero the array
890 // if filling it failed.
891 std::uninitialized_fill_n(p
, nelems
, 0);
895 swapFromLittleEndianInPlace(p
, nelems
);
897 point
+= ComputePadding(nelems
, sizeof(T
));
902 bool SCInput::readBytes(void* p
, size_t nbytes
) {
903 return readArray((uint8_t*)p
, nbytes
);
906 bool SCInput::readChars(Latin1Char
* p
, size_t nchars
) {
907 static_assert(sizeof(Latin1Char
) == sizeof(uint8_t),
908 "Latin1Char must fit in 1 byte");
909 return readBytes(p
, nchars
);
912 bool SCInput::readChars(char16_t
* p
, size_t nchars
) {
913 MOZ_ASSERT(sizeof(char16_t
) == sizeof(uint16_t));
914 return readArray((uint16_t*)p
, nchars
);
917 void SCInput::getPtr(uint64_t data
, void** ptr
) {
918 *ptr
= reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data
));
921 bool SCInput::readPtr(void** p
) {
926 *p
= reinterpret_cast<void*>(u
);
930 SCOutput::SCOutput(JSContext
* cx
, JS::StructuredCloneScope scope
)
931 : cx(cx
), buf(scope
) {}
933 bool SCOutput::write(uint64_t u
) {
934 uint64_t v
= NativeEndian::swapToLittleEndian(u
);
935 if (!buf
.AppendBytes(reinterpret_cast<char*>(&v
), sizeof(u
))) {
936 ReportOutOfMemory(context());
942 bool SCOutput::writePair(uint32_t tag
, uint32_t data
) {
943 // As it happens, the tag word appears after the data word in the output.
944 // This is because exponents occupy the last 2 bytes of doubles on the
945 // little-endian platforms we care most about.
947 // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
948 // PairToUInt64 produces the number 0xFFFF000200000001.
949 // That is written out as the bytes 01 00 00 00 02 00 FF FF.
950 return write(PairToUInt64(tag
, data
));
953 static inline double ReinterpretPairAsDouble(uint32_t tag
, uint32_t data
) {
954 return BitwiseCast
<double>(PairToUInt64(tag
, data
));
957 bool SCOutput::writeDouble(double d
) {
958 return write(BitwiseCast
<uint64_t>(CanonicalizeNaN(d
)));
962 bool SCOutput::writeArray(const T
* p
, size_t nelems
) {
963 static_assert(8 % sizeof(T
) == 0);
964 static_assert(sizeof(uint64_t) % sizeof(T
) == 0);
970 for (size_t i
= 0; i
< nelems
; i
++) {
971 T value
= NativeEndian::swapToLittleEndian(p
[i
]);
972 if (!buf
.AppendBytes(reinterpret_cast<char*>(&value
), sizeof(value
))) {
977 // Zero-pad to 8 bytes boundary.
978 size_t padbytes
= ComputePadding(nelems
, sizeof(T
));
979 char zeroes
[sizeof(uint64_t)] = {0};
980 if (!buf
.AppendBytes(zeroes
, padbytes
)) {
988 bool SCOutput::writeArray
<uint8_t>(const uint8_t* p
, size_t nelems
) {
993 if (!buf
.AppendBytes(reinterpret_cast<const char*>(p
), nelems
)) {
997 // zero-pad to 8 bytes boundary
998 size_t padbytes
= ComputePadding(nelems
, 1);
999 char zeroes
[sizeof(uint64_t)] = {0};
1000 if (!buf
.AppendBytes(zeroes
, padbytes
)) {
1007 bool SCOutput::writeBytes(const void* p
, size_t nbytes
) {
1008 return writeArray((const uint8_t*)p
, nbytes
);
1011 bool SCOutput::writeChars(const char16_t
* p
, size_t nchars
) {
1012 static_assert(sizeof(char16_t
) == sizeof(uint16_t),
1013 "required so that treating char16_t[] memory as uint16_t[] "
1014 "memory is permissible");
1015 return writeArray((const uint16_t*)p
, nchars
);
1018 bool SCOutput::writeChars(const Latin1Char
* p
, size_t nchars
) {
1019 static_assert(sizeof(Latin1Char
) == sizeof(uint8_t),
1020 "Latin1Char must fit in 1 byte");
1021 return writeBytes(p
, nchars
);
1026 JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); }
1028 // If the buffer contains Transferables, free them. Note that custom
1029 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
1030 // delete their transferables.
1031 void JSStructuredCloneData::discardTransferables() {
1036 if (ownTransferables_
!= OwnTransferablePolicy::OwnsTransferablesIfAny
) {
1040 // DifferentProcess clones cannot contain pointers, so nothing needs to be
1042 if (scope() == JS::StructuredCloneScope::DifferentProcess
) {
1046 FreeTransferStructuredCloneOp freeTransfer
= nullptr;
1048 freeTransfer
= callbacks_
->freeTransfer
;
1051 auto point
= BufferIterator
<uint64_t, SystemAllocPolicy
>(*this);
1053 return; // Empty buffer
1057 MOZ_RELEASE_ASSERT(point
.canPeek());
1058 SCInput::getPair(point
.peek(), &tag
, &data
);
1059 MOZ_ALWAYS_TRUE(point
.advance());
1061 if (tag
== SCTAG_HEADER
) {
1066 MOZ_RELEASE_ASSERT(point
.canPeek());
1067 SCInput::getPair(point
.peek(), &tag
, &data
);
1068 MOZ_ALWAYS_TRUE(point
.advance());
1071 if (tag
!= SCTAG_TRANSFER_MAP_HEADER
) {
1075 if (TransferableMapHeader(data
) == SCTAG_TM_TRANSFERRED
) {
1079 // freeTransfer should not GC
1080 JS::AutoSuppressGCAnalysis nogc
;
1086 MOZ_RELEASE_ASSERT(point
.canPeek());
1087 uint64_t numTransferables
= NativeEndian::swapFromLittleEndian(point
.peek());
1088 MOZ_ALWAYS_TRUE(point
.advance());
1089 while (numTransferables
--) {
1090 if (!point
.canPeek()) {
1095 SCInput::getPair(point
.peek(), &tag
, &ownership
);
1096 MOZ_ALWAYS_TRUE(point
.advance());
1097 MOZ_ASSERT(tag
>= SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
1098 if (!point
.canPeek()) {
1103 SCInput::getPtr(point
.peek(), &content
);
1104 MOZ_ALWAYS_TRUE(point
.advance());
1105 if (!point
.canPeek()) {
1109 uint64_t extraData
= NativeEndian::swapFromLittleEndian(point
.peek());
1110 MOZ_ALWAYS_TRUE(point
.advance());
1112 if (ownership
< JS::SCTAG_TMO_FIRST_OWNED
) {
1116 if (ownership
== JS::SCTAG_TMO_ALLOC_DATA
) {
1118 } else if (ownership
== JS::SCTAG_TMO_MAPPED_DATA
) {
1119 JS::ReleaseMappedArrayBufferContents(content
, extraData
);
1120 } else if (freeTransfer
) {
1121 freeTransfer(tag
, JS::TransferableOwnership(ownership
), content
,
1122 extraData
, closure_
);
1124 MOZ_ASSERT(false, "unknown ownership");
1129 static_assert(JSString::MAX_LENGTH
< UINT32_MAX
);
1131 bool JSStructuredCloneWriter::parseTransferable() {
1132 // NOTE: The transferables set is tested for non-emptiness at various
1133 // junctures in structured cloning, so this set must be initialized
1134 // by this method in all non-error cases.
1135 MOZ_ASSERT(transferableObjects
.empty(),
1136 "parseTransferable called with stale data");
1138 if (transferable
.isNull() || transferable
.isUndefined()) {
1142 if (!transferable
.isObject()) {
1143 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1146 JSContext
* cx
= context();
1147 RootedObject
array(cx
, &transferable
.toObject());
1149 if (!JS::IsArrayObject(cx
, array
, &isArray
)) {
1153 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1157 if (!JS::GetArrayLength(cx
, array
, &length
)) {
1161 // Initialize the set for the provided array's length.
1162 if (!transferableObjects
.reserve(length
)) {
1170 RootedValue
v(context());
1171 RootedObject
tObj(context());
1173 for (uint32_t i
= 0; i
< length
; ++i
) {
1174 if (!CheckForInterrupt(cx
)) {
1178 if (!JS_GetElement(cx
, array
, i
, &v
)) {
1182 if (!v
.isObject()) {
1183 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1185 tObj
= &v
.toObject();
1187 RootedObject
unwrappedObj(cx
, CheckedUnwrapStatic(tObj
));
1188 if (!unwrappedObj
) {
1189 ReportAccessDenied(cx
);
1193 // Shared memory cannot be transferred because it is not possible (nor
1194 // desirable) to detach the memory in agents that already hold a
1197 if (unwrappedObj
->is
<SharedArrayBufferObject
>()) {
1198 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE
);
1201 else if (unwrappedObj
->is
<WasmMemoryObject
>()) {
1202 if (unwrappedObj
->as
<WasmMemoryObject
>().isShared()) {
1203 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE
);
1207 // External array buffers may be able to be transferred in the future,
1208 // but that is not currently implemented.
1210 else if (unwrappedObj
->is
<ArrayBufferObject
>()) {
1211 if (unwrappedObj
->as
<ArrayBufferObject
>().isExternal()) {
1212 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1217 if (!out
.buf
.callbacks_
|| !out
.buf
.callbacks_
->canTransfer
) {
1218 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1221 JSAutoRealm
ar(cx
, unwrappedObj
);
1222 bool sameProcessScopeRequired
= false;
1223 if (!out
.buf
.callbacks_
->canTransfer(
1224 cx
, unwrappedObj
, &sameProcessScopeRequired
, out
.buf
.closure_
)) {
1225 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1228 if (sameProcessScopeRequired
) {
1229 output().sameProcessScopeRequired();
1233 // No duplicates allowed
1234 if (std::find(transferableObjects
.begin(), transferableObjects
.end(),
1235 tObj
) != transferableObjects
.end()) {
1236 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE
);
1239 if (!transferableObjects
.append(tObj
)) {
1247 template <typename
... Args
>
1248 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId
,
1250 ReportDataCloneError(context(), out
.buf
.callbacks_
, errorId
, out
.buf
.closure_
,
1251 std::forward
<Args
>(aArgs
)...);
1255 bool JSStructuredCloneWriter::writeString(uint32_t tag
, JSString
* str
) {
1256 JSLinearString
* linear
= str
->ensureLinear(context());
1261 #if FUZZING_JS_FUZZILLI
1262 if (js::SupportDifferentialTesting()) {
1263 // TODO we could always output a twoByteChar string
1268 static_assert(JSString::MAX_LENGTH
<= INT32_MAX
,
1269 "String length must fit in 31 bits");
1271 uint32_t length
= linear
->length();
1272 uint32_t lengthAndEncoding
=
1273 length
| (uint32_t(linear
->hasLatin1Chars()) << 31);
1274 if (!out
.writePair(tag
, lengthAndEncoding
)) {
1278 JS::AutoCheckCannotGC nogc
;
1279 return linear
->hasLatin1Chars()
1280 ? out
.writeChars(linear
->latin1Chars(nogc
), length
)
1281 : out
.writeChars(linear
->twoByteChars(nogc
), length
);
1284 bool JSStructuredCloneWriter::writeBigInt(uint32_t tag
, BigInt
* bi
) {
1285 bool signBit
= bi
->isNegative();
1286 size_t length
= bi
->digitLength();
1287 // The length must fit in 31 bits to leave room for a sign bit.
1288 if (length
> size_t(INT32_MAX
)) {
1291 uint32_t lengthAndSign
= length
| (static_cast<uint32_t>(signBit
) << 31);
1293 if (!out
.writePair(tag
, lengthAndSign
)) {
1296 return out
.writeArray(bi
->digits().data(), length
);
1299 inline void JSStructuredCloneWriter::checkStack() {
1301 // To avoid making serialization O(n^2), limit stack-checking at 10.
1302 const size_t MAX
= 10;
1304 size_t limit
= std::min(counts
.length(), MAX
);
1305 MOZ_ASSERT(objs
.length() == counts
.length());
1307 for (size_t i
= 0; i
< limit
; i
++) {
1308 MOZ_ASSERT(total
+ counts
[i
] >= total
);
1311 if (counts
.length() <= MAX
) {
1312 MOZ_ASSERT(total
== objectEntries
.length() + otherEntries
.length());
1314 MOZ_ASSERT(total
<= objectEntries
.length() + otherEntries
.length());
1317 size_t j
= objs
.length();
1318 for (size_t i
= 0; i
< limit
; i
++) {
1320 MOZ_ASSERT(memory
.has(&objs
[j
].toObject()));
1326 * Write out a typed array. Note that post-v1 structured clone buffers do not
1327 * perform endianness conversion on stored data, so multibyte typed arrays
1328 * cannot be deserialized into a different endianness machine. Endianness
1329 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
1330 * Int16Array views of the same ArrayBuffer, should the data bytes be
1331 * byte-swapped when writing or not? The Int8Array requires them to not be
1332 * swapped; the Int16Array requires that they are.
1334 bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj
) {
1335 Rooted
<FixedLengthTypedArrayObject
*> tarr(
1336 context(), obj
->maybeUnwrapAs
<FixedLengthTypedArrayObject
>());
1337 JSAutoRealm
ar(context(), tarr
);
1339 #ifdef FUZZING_JS_FUZZILLI
1340 if (js::SupportDifferentialTesting() && !tarr
->hasBuffer()) {
1341 // fake oom because differential testing will fail
1342 fprintf(stderr
, "[unhandlable oom]");
1348 if (!TypedArrayObject::ensureHasBuffer(context(), tarr
)) {
1352 if (!out
.writePair(SCTAG_TYPED_ARRAY_OBJECT
, uint32_t(tarr
->type()))) {
1356 uint64_t nelems
= tarr
->length();
1357 if (!out
.write(nelems
)) {
1361 // Write out the ArrayBuffer tag and contents
1362 RootedValue
val(context(), tarr
->bufferValue());
1363 if (!startWrite(val
)) {
1367 uint64_t byteOffset
= tarr
->byteOffset();
1368 return out
.write(byteOffset
);
1371 bool JSStructuredCloneWriter::writeDataView(HandleObject obj
) {
1372 Rooted
<FixedLengthDataViewObject
*> view(
1373 context(), obj
->maybeUnwrapAs
<FixedLengthDataViewObject
>());
1374 JSAutoRealm
ar(context(), view
);
1376 if (!out
.writePair(SCTAG_DATA_VIEW_OBJECT
, 0)) {
1380 uint64_t byteLength
= view
->byteLength();
1381 if (!out
.write(byteLength
)) {
1385 // Write out the ArrayBuffer tag and contents
1386 RootedValue
val(context(), view
->bufferValue());
1387 if (!startWrite(val
)) {
1391 uint64_t byteOffset
= view
->byteOffset();
1392 return out
.write(byteOffset
);
1395 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj
) {
1396 Rooted
<ArrayBufferObject
*> buffer(context(),
1397 obj
->maybeUnwrapAs
<ArrayBufferObject
>());
1398 JSAutoRealm
ar(context(), buffer
);
1400 // FIXME: Support structured cloning for resizable ArrayBuffers.
1401 if (buffer
->isResizable()) {
1402 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
1406 if (!out
.writePair(SCTAG_ARRAY_BUFFER_OBJECT
, 0)) {
1410 uint64_t byteLength
= buffer
->byteLength();
1411 if (!out
.write(byteLength
)) {
1415 return out
.writeBytes(buffer
->dataPointer(), byteLength
);
1418 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj
) {
1419 MOZ_ASSERT(obj
->canUnwrapAs
<SharedArrayBufferObject
>());
1421 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1422 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1423 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1424 : JS_SCERR_NOT_CLONABLE
;
1425 reportDataCloneError(error
, "SharedArrayBuffer");
1429 output().sameProcessScopeRequired();
1431 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1432 // cross-process. The cloneDataPolicy should have guarded against this;
1433 // since it did not then throw, with a very explicit message.
1435 if (output().scope() > JS::StructuredCloneScope::SameProcess
) {
1436 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1437 JSMSG_SC_SHMEM_POLICY
);
1441 Rooted
<SharedArrayBufferObject
*> sharedArrayBuffer(
1442 context(), obj
->maybeUnwrapAs
<SharedArrayBufferObject
>());
1443 SharedArrayRawBuffer
* rawbuf
= sharedArrayBuffer
->rawBufferObject();
1445 if (!out
.buf
.refsHeld_
.acquire(context(), rawbuf
)) {
1449 // We must serialize the length so that the buffer object arrives in the
1450 // receiver with the same length, and not with the length read from the
1451 // rawbuf - that length can be different, and it can change at any time.
1453 intptr_t p
= reinterpret_cast<intptr_t>(rawbuf
);
1454 uint64_t byteLength
= sharedArrayBuffer
->byteLength();
1455 if (!(out
.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT
,
1456 static_cast<uint32_t>(sizeof(p
))) &&
1457 out
.writeBytes(&byteLength
, sizeof(byteLength
)) &&
1458 out
.writeBytes(&p
, sizeof(p
)))) {
1462 if (callbacks
&& callbacks
->sabCloned
&&
1463 !callbacks
->sabCloned(context(), /*receiving=*/false, closure
)) {
1470 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj
) {
1471 MOZ_ASSERT(obj
->canUnwrapAs
<WasmMemoryObject
>());
1473 // Check the policy here so that we can report a sane error.
1474 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1475 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1476 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1477 : JS_SCERR_NOT_CLONABLE
;
1478 reportDataCloneError(error
, "WebAssembly.Memory");
1482 // If this changes, might need to change what we write.
1483 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS
== 3);
1485 Rooted
<WasmMemoryObject
*> memoryObj(context(),
1486 &obj
->unwrapAs
<WasmMemoryObject
>());
1487 Rooted
<SharedArrayBufferObject
*> sab(
1488 context(), &memoryObj
->buffer().as
<SharedArrayBufferObject
>());
1490 return out
.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT
, 0) &&
1491 out
.writePair(SCTAG_BOOLEAN
, memoryObj
->isHuge()) &&
1492 writeSharedArrayBuffer(sab
);
1495 bool JSStructuredCloneWriter::startObject(HandleObject obj
, bool* backref
) {
1496 // Handle cycles in the object graph.
1497 CloneMemory::AddPtr p
= memory
.lookupForAdd(obj
);
1498 if ((*backref
= p
.found())) {
1499 return out
.writePair(SCTAG_BACK_REFERENCE_OBJECT
, p
->value());
1501 if (!memory
.add(p
, obj
, memory
.count())) {
1502 ReportOutOfMemory(context());
1506 if (memory
.count() == UINT32_MAX
) {
1507 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1508 JSMSG_NEED_DIET
, "object graph to serialize");
1515 static bool TryAppendNativeProperties(JSContext
* cx
, HandleObject obj
,
1516 MutableHandleIdVector entries
,
1517 size_t* properties
, bool* optimized
) {
1520 if (!obj
->is
<NativeObject
>()) {
1524 Handle
<NativeObject
*> nobj
= obj
.as
<NativeObject
>();
1525 if (nobj
->isIndexed() || nobj
->is
<TypedArrayObject
>() ||
1526 nobj
->getClass()->getNewEnumerate() || nobj
->getClass()->getEnumerate()) {
1533 // We iterate from the last to the first property, so the property names
1534 // are already in reverse order.
1535 for (ShapePropertyIter
<NoGC
> iter(nobj
->shape()); !iter
.done(); iter
++) {
1536 jsid id
= iter
->key();
1538 // Ignore symbols and non-enumerable properties.
1539 if (!iter
->enumerable() || id
.isSymbol()) {
1543 MOZ_ASSERT(id
.isString());
1544 if (!entries
.append(id
)) {
1551 // Add dense element ids in reverse order.
1552 for (uint32_t i
= nobj
->getDenseInitializedLength(); i
> 0; --i
) {
1553 if (nobj
->getDenseElement(i
- 1).isMagic(JS_ELEMENTS_HOLE
)) {
1557 if (!entries
.append(PropertyKey::Int(i
- 1))) {
1564 *properties
= count
;
1568 // Objects are written as a "preorder" traversal of the object graph: object
1569 // "headers" (the class tag and any data needed for initial construction) are
1570 // visited first, then the children are recursed through (where children are
1571 // properties, Set or Map entries, etc.). So for example
1573 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1575 // would be stored as:
1577 // <Object tag for obj1>
1579 // <Object tag for key1's value>
1584 // <end-of-children marker for key1's value>
1586 // <Object tag for key2's value>
1587 // <end-of-children marker for key2's value>
1588 // <end-of-children marker for obj1>
1590 // This nests nicely (ie, an entire recursive value starts with its tag and
1591 // ends with its end-of-children marker) and so it can be presented indented.
1592 // But see traverseMap below for how this looks different for Maps.
1593 bool JSStructuredCloneWriter::traverseObject(HandleObject obj
, ESClass cls
) {
1595 bool optimized
= false;
1596 if (!js::SupportDifferentialTesting()) {
1597 if (!TryAppendNativeProperties(context(), obj
, &objectEntries
, &count
,
1604 // Get enumerable property ids and put them in reverse order so that they
1605 // will come off the stack in forward order.
1606 RootedIdVector
properties(context());
1607 if (!GetPropertyKeys(context(), obj
, JSITER_OWNONLY
, &properties
)) {
1611 for (size_t i
= properties
.length(); i
> 0; --i
) {
1612 jsid id
= properties
[i
- 1];
1614 MOZ_ASSERT(id
.isString() || id
.isInt());
1615 if (!objectEntries
.append(id
)) {
1620 count
= properties
.length();
1623 // Push obj and count to the stack.
1624 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(count
)) {
1632 if (!GetBuiltinClass(context(), obj
, &cls2
)) {
1635 MOZ_ASSERT(cls2
== cls
);
1638 // Write the header for obj.
1639 if (cls
== ESClass::Array
) {
1640 uint32_t length
= 0;
1641 if (!JS::GetArrayLength(context(), obj
, &length
)) {
1645 return out
.writePair(SCTAG_ARRAY_OBJECT
,
1646 NativeEndian::swapToLittleEndian(length
));
1649 return out
.writePair(SCTAG_OBJECT_OBJECT
, 0);
1652 // Use the same basic setup as for traverseObject, but now keys can themselves
1653 // be complex objects. Keys and values are visited first via startWrite(), then
1654 // the key's children (if any) are handled, then the value's children.
1657 // m.set(key1 = ..., value1 = ...)
1659 // where key1 and value2 are both objects would be stored as
1663 // <value1 class tag>
1664 // ...key1 fields...
1665 // <end-of-children marker for key1>
1666 // ...value1 fields...
1667 // <end-of-children marker for value1>
1668 // <end-of-children marker for Map>
1670 // Notice how the end-of-children marker for key1 is sandwiched between the
1671 // value1 beginning and end.
1672 bool JSStructuredCloneWriter::traverseMap(HandleObject obj
) {
1673 Rooted
<GCVector
<Value
>> newEntries(context(), GCVector
<Value
>(context()));
1675 // If there is no wrapper, the compartment munging is a no-op.
1676 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<MapObject
>());
1677 MOZ_ASSERT(unwrapped
);
1678 JSAutoRealm
ar(context(), unwrapped
);
1679 if (!MapObject::getKeysAndValuesInterleaved(unwrapped
, &newEntries
)) {
1683 if (!context()->compartment()->wrap(context(), &newEntries
)) {
1687 for (size_t i
= newEntries
.length(); i
> 0; --i
) {
1688 if (!otherEntries
.append(newEntries
[i
- 1])) {
1693 // Push obj and count to the stack.
1694 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(newEntries
.length())) {
1700 // Write the header for obj.
1701 return out
.writePair(SCTAG_MAP_OBJECT
, 0);
1704 // Similar to traverseMap, only there is a single value instead of a key and
1705 // value, and thus no interleaving is possible: a value will be fully emitted
1706 // before the next value is begun.
1707 bool JSStructuredCloneWriter::traverseSet(HandleObject obj
) {
1708 Rooted
<GCVector
<Value
>> keys(context(), GCVector
<Value
>(context()));
1710 // If there is no wrapper, the compartment munging is a no-op.
1711 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<SetObject
>());
1712 MOZ_ASSERT(unwrapped
);
1713 JSAutoRealm
ar(context(), unwrapped
);
1714 if (!SetObject::keys(context(), unwrapped
, &keys
)) {
1718 if (!context()->compartment()->wrap(context(), &keys
)) {
1722 for (size_t i
= keys
.length(); i
> 0; --i
) {
1723 if (!otherEntries
.append(keys
[i
- 1])) {
1728 // Push obj and count to the stack.
1729 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(keys
.length())) {
1735 // Write the header for obj.
1736 return out
.writePair(SCTAG_SET_OBJECT
, 0);
1739 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj
) {
1740 Rooted
<SavedFrame
*> savedFrame(context(), obj
->maybeUnwrapAs
<SavedFrame
>());
1741 MOZ_ASSERT(savedFrame
);
1743 RootedObject
parent(context(), savedFrame
->getParent());
1744 if (!context()->compartment()->wrap(context(), &parent
)) {
1748 if (!objs
.append(ObjectValue(*obj
)) ||
1749 !otherEntries
.append(parent
? ObjectValue(*parent
) : NullValue()) ||
1750 !counts
.append(1)) {
1756 // Write the SavedFrame tag and the SavedFrame's principals.
1758 if (savedFrame
->getPrincipals() ==
1759 &ReconstructedSavedFramePrincipals::IsSystem
) {
1760 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
,
1761 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
)) {
1764 } else if (savedFrame
->getPrincipals() ==
1765 &ReconstructedSavedFramePrincipals::IsNotSystem
) {
1767 SCTAG_SAVED_FRAME_OBJECT
,
1768 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
)) {
1772 if (auto principals
= savedFrame
->getPrincipals()) {
1773 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_JSPRINCIPALS
) ||
1774 !principals
->write(context(), this)) {
1778 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_NULL_JSPRINCIPALS
)) {
1784 // Write the SavedFrame's reserved slots, except for the parent, which is
1785 // queued on objs for further traversal.
1787 RootedValue
val(context());
1789 val
= BooleanValue(savedFrame
->getMutedErrors());
1790 if (!writePrimitive(val
)) {
1794 context()->markAtom(savedFrame
->getSource());
1795 val
= StringValue(savedFrame
->getSource());
1796 if (!writePrimitive(val
)) {
1800 val
= NumberValue(savedFrame
->getLine());
1801 if (!writePrimitive(val
)) {
1805 val
= NumberValue(*savedFrame
->getColumn().addressOfValueForTranscode());
1806 if (!writePrimitive(val
)) {
1810 auto name
= savedFrame
->getFunctionDisplayName();
1812 context()->markAtom(name
);
1814 val
= name
? StringValue(name
) : NullValue();
1815 if (!writePrimitive(val
)) {
1819 auto cause
= savedFrame
->getAsyncCause();
1821 context()->markAtom(cause
);
1823 val
= cause
? StringValue(cause
) : NullValue();
1824 if (!writePrimitive(val
)) {
1831 // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
1832 // 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
1834 // Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
1835 // value is not a platform object, then:
1837 // Note: This contains custom extensions for handling non-standard properties.
1838 bool JSStructuredCloneWriter::traverseError(HandleObject obj
) {
1839 JSContext
* cx
= context();
1841 // 1. Let name be ? Get(value, "name").
1842 RootedValue
name(cx
);
1843 if (!GetProperty(cx
, obj
, obj
, cx
->names().name
, &name
)) {
1847 // 2. If name is not one of "Error", "EvalError", "RangeError",
1848 // "ReferenceError", "SyntaxError", "TypeError", or "URIError",
1849 // (not yet specified: or "AggregateError")
1850 // then set name to "Error".
1851 JSExnType type
= JSEXN_ERR
;
1852 if (name
.isString()) {
1853 JSLinearString
* linear
= name
.toString()->ensureLinear(cx
);
1858 if (EqualStrings(linear
, cx
->names().Error
)) {
1860 } else if (EqualStrings(linear
, cx
->names().EvalError
)) {
1861 type
= JSEXN_EVALERR
;
1862 } else if (EqualStrings(linear
, cx
->names().RangeError
)) {
1863 type
= JSEXN_RANGEERR
;
1864 } else if (EqualStrings(linear
, cx
->names().ReferenceError
)) {
1865 type
= JSEXN_REFERENCEERR
;
1866 } else if (EqualStrings(linear
, cx
->names().SyntaxError
)) {
1867 type
= JSEXN_SYNTAXERR
;
1868 } else if (EqualStrings(linear
, cx
->names().TypeError
)) {
1869 type
= JSEXN_TYPEERR
;
1870 } else if (EqualStrings(linear
, cx
->names().URIError
)) {
1871 type
= JSEXN_URIERR
;
1872 } else if (EqualStrings(linear
, cx
->names().AggregateError
)) {
1873 type
= JSEXN_AGGREGATEERR
;
1877 // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
1878 RootedId
messageId(cx
, NameToId(cx
->names().message
));
1879 Rooted
<Maybe
<PropertyDescriptor
>> messageDesc(cx
);
1880 if (!GetOwnPropertyDescriptor(cx
, obj
, messageId
, &messageDesc
)) {
1884 // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
1885 // and ? ToString(valueMessageDesc.[[Value]]) otherwise.
1886 RootedString
message(cx
);
1887 if (messageDesc
.isSome() && messageDesc
->isDataDescriptor()) {
1888 RootedValue
messageVal(cx
, messageDesc
->value());
1889 message
= ToString
<CanGC
>(cx
, messageVal
);
1895 // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
1898 if (!objs
.append(ObjectValue(*obj
))) {
1902 Rooted
<ErrorObject
*> unwrapped(cx
, obj
->maybeUnwrapAs
<ErrorObject
>());
1903 MOZ_ASSERT(unwrapped
);
1905 // Non-standard: Serialize |stack|.
1906 // The Error stack property is saved as SavedFrames.
1907 RootedValue
stack(cx
, NullValue());
1908 RootedObject
stackObj(cx
, unwrapped
->stack());
1909 if (stackObj
&& stackObj
->canUnwrapAs
<SavedFrame
>()) {
1910 stack
.setObject(*stackObj
);
1911 if (!cx
->compartment()->wrap(cx
, &stack
)) {
1915 if (!otherEntries
.append(stack
)) {
1919 // Serialize |errors|
1920 if (type
== JSEXN_AGGREGATEERR
) {
1921 RootedValue
errors(cx
);
1922 if (!GetProperty(cx
, obj
, obj
, cx
->names().errors
, &errors
)) {
1925 if (!otherEntries
.append(errors
)) {
1929 if (!otherEntries
.append(NullValue())) {
1934 // Non-standard: Serialize |cause|. Because this property
1935 // might be missing we also write "hasCause" later.
1936 RootedId
causeId(cx
, NameToId(cx
->names().cause
));
1937 Rooted
<Maybe
<PropertyDescriptor
>> causeDesc(cx
);
1938 if (!GetOwnPropertyDescriptor(cx
, obj
, causeId
, &causeDesc
)) {
1942 Rooted
<Maybe
<Value
>> cause(cx
);
1943 if (causeDesc
.isSome() && causeDesc
->isDataDescriptor()) {
1944 cause
= mozilla::Some(causeDesc
->value());
1946 if (!cx
->compartment()->wrap(cx
, &cause
)) {
1949 if (!otherEntries
.append(cause
.get().valueOr(NullValue()))) {
1953 // |cause| + |errors| + |stack|, pushed in reverse order
1954 if (!counts
.append(3)) {
1960 if (!out
.writePair(SCTAG_ERROR_OBJECT
, type
)) {
1964 RootedValue
val(cx
, message
? StringValue(message
) : NullValue());
1965 if (!writePrimitive(val
)) {
1970 val
= BooleanValue(cause
.isSome());
1971 if (!writePrimitive(val
)) {
1975 // Non-standard: Also serialize fileName, lineNumber and columnNumber.
1977 JSAutoRealm
ar(cx
, unwrapped
);
1978 val
= StringValue(unwrapped
->fileName(cx
));
1980 if (!cx
->compartment()->wrap(cx
, &val
) || !writePrimitive(val
)) {
1984 val
= Int32Value(unwrapped
->lineNumber());
1985 if (!writePrimitive(val
)) {
1989 val
= Int32Value(*unwrapped
->columnNumber().addressOfValueForTranscode());
1990 return writePrimitive(val
);
1993 bool JSStructuredCloneWriter::writePrimitive(HandleValue v
) {
1994 MOZ_ASSERT(v
.isPrimitive());
1995 context()->check(v
);
1998 return writeString(SCTAG_STRING
, v
.toString());
1999 } else if (v
.isInt32()) {
2000 if (js::SupportDifferentialTesting()) {
2001 return out
.writeDouble(v
.toInt32());
2003 return out
.writePair(SCTAG_INT32
, v
.toInt32());
2004 } else if (v
.isDouble()) {
2005 return out
.writeDouble(v
.toDouble());
2006 } else if (v
.isBoolean()) {
2007 return out
.writePair(SCTAG_BOOLEAN
, v
.toBoolean());
2008 } else if (v
.isNull()) {
2009 return out
.writePair(SCTAG_NULL
, 0);
2010 } else if (v
.isUndefined()) {
2011 return out
.writePair(SCTAG_UNDEFINED
, 0);
2012 } else if (v
.isBigInt()) {
2013 return writeBigInt(SCTAG_BIGINT
, v
.toBigInt());
2016 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2019 bool JSStructuredCloneWriter::startWrite(HandleValue v
) {
2020 context()->check(v
);
2022 if (v
.isPrimitive()) {
2023 return writePrimitive(v
);
2026 if (!v
.isObject()) {
2027 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2030 RootedObject
obj(context(), &v
.toObject());
2033 if (!startObject(obj
, &backref
)) {
2041 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2046 case ESClass::Object
:
2047 case ESClass::Array
:
2048 return traverseObject(obj
, cls
);
2049 case ESClass::Number
: {
2050 RootedValue
unboxed(context());
2051 if (!Unbox(context(), obj
, &unboxed
)) {
2054 return out
.writePair(SCTAG_NUMBER_OBJECT
, 0) &&
2055 out
.writeDouble(unboxed
.toNumber());
2057 case ESClass::String
: {
2058 RootedValue
unboxed(context());
2059 if (!Unbox(context(), obj
, &unboxed
)) {
2062 return writeString(SCTAG_STRING_OBJECT
, unboxed
.toString());
2064 case ESClass::Boolean
: {
2065 RootedValue
unboxed(context());
2066 if (!Unbox(context(), obj
, &unboxed
)) {
2069 return out
.writePair(SCTAG_BOOLEAN_OBJECT
, unboxed
.toBoolean());
2071 case ESClass::RegExp
: {
2072 RegExpShared
* re
= RegExpToShared(context(), obj
);
2076 return out
.writePair(SCTAG_REGEXP_OBJECT
, re
->getFlags().value()) &&
2077 writeString(SCTAG_STRING
, re
->getSource());
2079 case ESClass::ArrayBuffer
: {
2080 if (JS::IsArrayBufferObject(obj
) && JS::ArrayBufferHasData(obj
)) {
2081 return writeArrayBuffer(obj
);
2085 case ESClass::SharedArrayBuffer
:
2086 if (JS::IsSharedArrayBufferObject(obj
)) {
2087 return writeSharedArrayBuffer(obj
);
2090 case ESClass::Date
: {
2091 RootedValue
unboxed(context());
2092 if (!Unbox(context(), obj
, &unboxed
)) {
2095 return out
.writePair(SCTAG_DATE_OBJECT
, 0) &&
2096 out
.writeDouble(unboxed
.toNumber());
2099 return traverseSet(obj
);
2101 return traverseMap(obj
);
2102 case ESClass::Error
:
2103 return traverseError(obj
);
2104 case ESClass::BigInt
: {
2105 RootedValue
unboxed(context());
2106 if (!Unbox(context(), obj
, &unboxed
)) {
2109 return writeBigInt(SCTAG_BIGINT_OBJECT
, unboxed
.toBigInt());
2111 case ESClass::Promise
:
2112 case ESClass::MapIterator
:
2113 case ESClass::SetIterator
:
2114 case ESClass::Arguments
:
2115 case ESClass::Function
:
2118 #ifdef ENABLE_RECORD_TUPLE
2119 case ESClass::Record
:
2120 case ESClass::Tuple
:
2121 MOZ_CRASH("Record and Tuple are not supported");
2124 case ESClass::Other
: {
2125 if (obj
->canUnwrapAs
<FixedLengthTypedArrayObject
>()) {
2126 return writeTypedArray(obj
);
2128 if (obj
->canUnwrapAs
<ResizableTypedArrayObject
>()) {
2129 // TODO(anba): support resizable.
2130 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2132 if (obj
->canUnwrapAs
<FixedLengthDataViewObject
>()) {
2133 return writeDataView(obj
);
2135 if (obj
->canUnwrapAs
<ResizableDataViewObject
>()) {
2136 // TODO(anba): support resizable.
2137 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2139 if (wasm::IsSharedWasmMemoryObject(obj
)) {
2140 return writeSharedWasmMemory(obj
);
2142 if (obj
->canUnwrapAs
<SavedFrame
>()) {
2143 return traverseSavedFrame(obj
);
2149 if (out
.buf
.callbacks_
&& out
.buf
.callbacks_
->write
) {
2150 bool sameProcessScopeRequired
= false;
2151 if (!out
.buf
.callbacks_
->write(context(), this, obj
,
2152 &sameProcessScopeRequired
,
2153 out
.buf
.closure_
)) {
2157 if (sameProcessScopeRequired
) {
2158 output().sameProcessScopeRequired();
2164 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2167 bool JSStructuredCloneWriter::writeHeader() {
2168 return out
.writePair(SCTAG_HEADER
, (uint32_t)output().scope());
2171 bool JSStructuredCloneWriter::writeTransferMap() {
2172 if (transferableObjects
.empty()) {
2176 if (!out
.writePair(SCTAG_TRANSFER_MAP_HEADER
, (uint32_t)SCTAG_TM_UNREAD
)) {
2180 if (!out
.write(transferableObjects
.length())) {
2184 RootedObject
obj(context());
2185 for (auto* o
: transferableObjects
) {
2187 if (!memory
.put(obj
, memory
.count())) {
2188 ReportOutOfMemory(context());
2192 // Emit a placeholder pointer. We defer stealing the data until later
2193 // (and, if necessary, detaching this object if it's an ArrayBuffer).
2194 if (!out
.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY
,
2195 JS::SCTAG_TMO_UNFILLED
)) {
2198 if (!out
.write(0)) { // Pointer to ArrayBuffer contents.
2201 if (!out
.write(0)) { // extraData
2209 bool JSStructuredCloneWriter::transferOwnership() {
2210 if (transferableObjects
.empty()) {
2214 // Walk along the transferables and the transfer map at the same time,
2215 // grabbing out pointers from the transferables and stuffing them into the
2217 auto point
= out
.iter();
2218 MOZ_RELEASE_ASSERT(point
.canPeek());
2219 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
2222 MOZ_RELEASE_ASSERT(point
.canPeek());
2223 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
2224 SCTAG_TRANSFER_MAP_HEADER
);
2226 MOZ_RELEASE_ASSERT(point
.canPeek());
2227 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point
.peek()) ==
2228 transferableObjects
.length());
2231 JSContext
* cx
= context();
2232 RootedObject
obj(cx
);
2233 JS::StructuredCloneScope scope
= output().scope();
2234 for (auto* o
: transferableObjects
) {
2238 JS::TransferableOwnership ownership
;
2243 SCInput::getPair(point
.peek(), &tag
, (uint32_t*)&ownership
);
2244 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2245 MOZ_ASSERT(ownership
== JS::SCTAG_TMO_UNFILLED
);
2249 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
2253 if (cls
== ESClass::ArrayBuffer
) {
2254 tag
= SCTAG_TRANSFER_MAP_ARRAY_BUFFER
;
2256 // The current setup of the array buffer inheritance hierarchy doesn't
2257 // lend itself well to generic manipulation via proxies.
2258 Rooted
<ArrayBufferObject
*> arrayBuffer(
2259 cx
, obj
->maybeUnwrapAs
<ArrayBufferObject
>());
2260 JSAutoRealm
ar(cx
, arrayBuffer
);
2262 if (arrayBuffer
->isDetached()) {
2263 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED
);
2267 if (arrayBuffer
->isPreparedForAsmJS()) {
2268 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER
);
2272 // FIXME: Support structured cloning for resizable ArrayBuffers.
2273 if (arrayBuffer
->isResizable()) {
2274 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2278 if (scope
== JS::StructuredCloneScope::DifferentProcess
||
2279 scope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
2280 // Write Transferred ArrayBuffers in DifferentProcess scope at
2281 // the end of the clone buffer, and store the offset within the
2282 // buffer to where the ArrayBuffer was written. Note that this
2283 // will invalidate the current position iterator.
2285 size_t pointOffset
= out
.offset(point
);
2286 tag
= SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
;
2287 ownership
= JS::SCTAG_TMO_UNOWNED
;
2289 extraData
= out
.tell() -
2290 pointOffset
; // Offset from tag to current end of buffer
2291 if (!writeArrayBuffer(arrayBuffer
)) {
2292 ReportOutOfMemory(cx
);
2296 // Must refresh the point iterator after its collection has
2299 point
+= pointOffset
;
2301 if (!JS::DetachArrayBuffer(cx
, arrayBuffer
)) {
2305 size_t nbytes
= arrayBuffer
->byteLength();
2307 using BufferContents
= ArrayBufferObject::BufferContents
;
2309 BufferContents bufContents
=
2310 ArrayBufferObject::extractStructuredCloneContents(cx
, arrayBuffer
);
2312 return false; // out of memory
2315 content
= bufContents
.data();
2316 if (bufContents
.kind() == ArrayBufferObject::MAPPED
) {
2317 ownership
= JS::SCTAG_TMO_MAPPED_DATA
;
2320 bufContents
.kind() ==
2321 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
||
2322 bufContents
.kind() ==
2323 ArrayBufferObject::MALLOCED_UNKNOWN_ARENA
,
2324 "failing to handle new ArrayBuffer kind?");
2325 ownership
= JS::SCTAG_TMO_ALLOC_DATA
;
2330 if (!out
.buf
.callbacks_
|| !out
.buf
.callbacks_
->writeTransfer
) {
2331 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
2333 if (!out
.buf
.callbacks_
->writeTransfer(cx
, obj
, out
.buf
.closure_
, &tag
,
2334 &ownership
, &content
,
2338 MOZ_ASSERT(tag
> SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2341 point
.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag
, ownership
)));
2342 MOZ_ALWAYS_TRUE(point
.advance());
2344 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content
)));
2345 MOZ_ALWAYS_TRUE(point
.advance());
2346 point
.write(NativeEndian::swapToLittleEndian(extraData
));
2347 MOZ_ALWAYS_TRUE(point
.advance());
2351 // Make sure there aren't any more transfer map entries after the expected
2352 // number we read out.
2353 if (!point
.done()) {
2355 SCInput::getPair(point
.peek(), &tag
, &data
);
2356 MOZ_ASSERT(tag
< SCTAG_TRANSFER_MAP_HEADER
||
2357 tag
>= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES
);
2363 bool JSStructuredCloneWriter::write(HandleValue v
) {
2364 if (!startWrite(v
)) {
2368 RootedObject
obj(context());
2369 RootedValue
key(context());
2370 RootedValue
val(context());
2371 RootedId
id(context());
2373 RootedValue
cause(context());
2374 RootedValue
errors(context());
2375 RootedValue
stack(context());
2377 while (!counts
.empty()) {
2378 obj
= &objs
.back().toObject();
2379 context()->check(obj
);
2380 if (counts
.back()) {
2384 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2388 if (cls
== ESClass::Map
) {
2389 key
= otherEntries
.popCopy();
2393 val
= otherEntries
.popCopy();
2396 if (!startWrite(key
) || !startWrite(val
)) {
2399 } else if (cls
== ESClass::Set
|| obj
->canUnwrapAs
<SavedFrame
>()) {
2400 key
= otherEntries
.popCopy();
2403 if (!startWrite(key
)) {
2406 } else if (cls
== ESClass::Error
) {
2407 cause
= otherEntries
.popCopy();
2411 errors
= otherEntries
.popCopy();
2415 stack
= otherEntries
.popCopy();
2418 if (!startWrite(cause
) || !startWrite(errors
) || !startWrite(stack
)) {
2422 id
= objectEntries
.popCopy();
2423 key
= IdToValue(id
);
2426 // If obj still has an own property named id, write it out.
2428 if (GetOwnPropertyPure(context(), obj
, id
, val
.address(), &found
)) {
2430 if (!writePrimitive(key
) || !startWrite(val
)) {
2437 if (!HasOwnProperty(context(), obj
, id
, &found
)) {
2442 #if FUZZING_JS_FUZZILLI
2443 // supress calls into user code
2444 if (js::SupportDifferentialTesting()) {
2445 fprintf(stderr
, "Differential testing: cannot call GetProperty\n");
2450 if (!writePrimitive(key
) ||
2451 !GetProperty(context(), obj
, obj
, id
, &val
) || !startWrite(val
)) {
2457 if (!out
.writePair(SCTAG_END_OF_KEYS
, 0)) {
2466 return transferOwnership();
2469 JSStructuredCloneReader::JSStructuredCloneReader(
2470 SCInput
& in
, JS::StructuredCloneScope scope
,
2471 const JS::CloneDataPolicy
& cloneDataPolicy
,
2472 const JSStructuredCloneCallbacks
* cb
, void* cbClosure
)
2474 allowedScope(scope
),
2475 cloneDataPolicy(cloneDataPolicy
),
2477 objState(in
.context(), in
.context()),
2478 allObjs(in
.context()),
2482 gcHeap(in
.context()) {
2483 // Avoid the need to bounds check by keeping a never-matching element at the
2484 // base of the `objState` stack. This append() will always succeed because
2485 // the objState vector has a nonzero MinInlineCapacity.
2486 MOZ_ALWAYS_TRUE(objState
.append(std::make_pair(nullptr, true)));
2489 template <typename CharT
>
2490 JSString
* JSStructuredCloneReader::readStringImpl(
2491 uint32_t nchars
, ShouldAtomizeStrings atomize
) {
2492 InlineCharBuffer
<CharT
> chars
;
2493 if (!chars
.maybeAlloc(context(), nchars
) ||
2494 !in
.readChars(chars
.get(), nchars
)) {
2499 return chars
.toAtom(context(), nchars
);
2502 return chars
.toStringDontDeflate(context(), nchars
, gcHeap
);
2505 JSString
* JSStructuredCloneReader::readString(uint32_t data
,
2506 ShouldAtomizeStrings atomize
) {
2507 uint32_t nchars
= data
& BitMask(31);
2508 bool latin1
= data
& (1 << 31);
2510 if (nchars
> JSString::MAX_LENGTH
) {
2511 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2512 JSMSG_SC_BAD_SERIALIZED_DATA
, "string length");
2516 return latin1
? readStringImpl
<Latin1Char
>(nchars
, atomize
)
2517 : readStringImpl
<char16_t
>(nchars
, atomize
);
2520 [[nodiscard
]] bool JSStructuredCloneReader::readUint32(uint32_t* num
) {
2521 Rooted
<Value
> lineVal(context());
2522 if (!startRead(&lineVal
)) {
2525 if (!lineVal
.isInt32()) {
2526 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2527 JSMSG_SC_BAD_SERIALIZED_DATA
, "integer required");
2530 *num
= uint32_t(lineVal
.toInt32());
2534 BigInt
* JSStructuredCloneReader::readBigInt(uint32_t data
) {
2535 size_t length
= data
& BitMask(31);
2536 bool isNegative
= data
& (1 << 31);
2538 return BigInt::zero(context());
2540 RootedBigInt
result(context(), BigInt::createUninitialized(
2541 context(), length
, isNegative
, gcHeap
));
2545 if (!in
.readArray(result
->digits().data(), length
)) {
2551 static uint32_t TagToV1ArrayType(uint32_t tag
) {
2552 MOZ_ASSERT(tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&&
2553 tag
<= SCTAG_TYPED_ARRAY_V1_MAX
);
2554 return tag
- SCTAG_TYPED_ARRAY_V1_MIN
;
2557 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType
,
2559 MutableHandleValue vp
,
2561 if (arrayType
> (v1Read
? Scalar::Uint8Clamped
: Scalar::BigUint64
)) {
2562 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2563 JSMSG_SC_BAD_SERIALIZED_DATA
,
2564 "unhandled typed array element type");
2568 // Push a placeholder onto the allObjs list to stand in for the typed array.
2569 uint32_t placeholderIndex
= allObjs
.length();
2570 Value dummy
= UndefinedValue();
2571 if (!allObjs
.append(dummy
)) {
2575 // Read the ArrayBuffer object and its contents (but no properties)
2576 RootedValue
v(context());
2577 uint64_t byteOffset
;
2579 if (!readV1ArrayBuffer(arrayType
, nelems
, &v
)) {
2584 if (!startRead(&v
)) {
2587 if (!in
.read(&byteOffset
)) {
2592 // Ensure invalid 64-bit values won't be truncated below.
2593 if (nelems
> ArrayBufferObject::MaxByteLength
||
2594 byteOffset
> ArrayBufferObject::MaxByteLength
) {
2595 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2596 JSMSG_SC_BAD_SERIALIZED_DATA
,
2597 "invalid typed array length or offset");
2601 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2602 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2603 JSMSG_SC_BAD_SERIALIZED_DATA
,
2604 "typed array must be backed by an ArrayBuffer");
2608 RootedObject
buffer(context(), &v
.toObject());
2609 RootedObject
obj(context(), nullptr);
2611 switch (arrayType
) {
2612 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2613 case Scalar::Name: \
2614 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2615 byteOffset, nelems) \
2619 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER
)
2620 #undef CREATE_FROM_BUFFER
2623 MOZ_CRASH("Can't happen: arrayType range checked above");
2631 allObjs
[placeholderIndex
].set(vp
);
2636 bool JSStructuredCloneReader::readDataView(uint64_t byteLength
,
2637 MutableHandleValue vp
) {
2638 // Push a placeholder onto the allObjs list to stand in for the DataView.
2639 uint32_t placeholderIndex
= allObjs
.length();
2640 Value dummy
= UndefinedValue();
2641 if (!allObjs
.append(dummy
)) {
2645 // Read the ArrayBuffer object and its contents (but no properties).
2646 RootedValue
v(context());
2647 if (!startRead(&v
)) {
2650 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2651 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2652 JSMSG_SC_BAD_SERIALIZED_DATA
,
2653 "DataView must be backed by an ArrayBuffer");
2658 uint64_t byteOffset
;
2659 if (!in
.read(&byteOffset
)) {
2663 // Ensure invalid 64-bit values won't be truncated below.
2664 if (byteLength
> ArrayBufferObject::MaxByteLength
||
2665 byteOffset
> ArrayBufferObject::MaxByteLength
) {
2666 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2667 JSMSG_SC_BAD_SERIALIZED_DATA
,
2668 "invalid DataView length or offset");
2672 RootedObject
buffer(context(), &v
.toObject());
2673 RootedObject
obj(context(),
2674 JS_NewDataView(context(), buffer
, byteOffset
, byteLength
));
2680 allObjs
[placeholderIndex
].set(vp
);
2685 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type
,
2687 MutableHandleValue vp
) {
2688 // V2 stores the length in |data|. The current version stores the
2689 // length separately to allow larger length values.
2690 uint64_t nbytes
= 0;
2691 if (type
== SCTAG_ARRAY_BUFFER_OBJECT
) {
2692 if (!in
.read(&nbytes
)) {
2696 MOZ_ASSERT(type
== SCTAG_ARRAY_BUFFER_OBJECT_V2
);
2700 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2701 // below, so we have to check this here.
2702 if (nbytes
> ArrayBufferObject::MaxByteLength
) {
2703 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2704 JSMSG_BAD_ARRAY_LENGTH
);
2708 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), size_t(nbytes
));
2713 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2714 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2715 return in
.readArray(buffer
.dataPointer(), nbytes
);
2718 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp
) {
2719 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2720 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2721 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2722 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2723 : JS_SCERR_NOT_CLONABLE
;
2724 ReportDataCloneError(context(), callbacks
, error
, closure
,
2725 "SharedArrayBuffer");
2729 uint64_t byteLength
;
2730 if (!in
.readBytes(&byteLength
, sizeof(byteLength
))) {
2731 return in
.reportTruncated();
2734 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2735 // below, so we have to check this here.
2736 if (byteLength
> ArrayBufferObject::MaxByteLength
) {
2737 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2738 JSMSG_BAD_ARRAY_LENGTH
);
2743 if (!in
.readBytes(&p
, sizeof(p
))) {
2744 return in
.reportTruncated();
2747 SharedArrayRawBuffer
* rawbuf
= reinterpret_cast<SharedArrayRawBuffer
*>(p
);
2749 // There's no guarantee that the receiving agent has enabled shared memory
2750 // even if the transmitting agent has done so. Ideally we'd check at the
2751 // transmission point, but that's tricky, and it will be a very rare problem
2752 // in any case. Just fail at the receiving end if we can't handle it.
2757 .getSharedMemoryAndAtomicsEnabled()) {
2758 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2759 JSMSG_SC_SAB_DISABLED
);
2763 // The new object will have a new reference to the rawbuf.
2765 if (!rawbuf
->addReference()) {
2766 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2767 JSMSG_SC_SAB_REFCNT_OFLO
);
2771 RootedObject
obj(context(),
2772 SharedArrayBufferObject::New(context(), rawbuf
, byteLength
));
2774 rawbuf
->dropReference();
2778 // `rawbuf` is now owned by `obj`.
2780 if (callbacks
&& callbacks
->sabCloned
&&
2781 !callbacks
->sabCloned(context(), /*receiving=*/true, closure
)) {
2789 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes
,
2790 MutableHandleValue vp
) {
2791 JSContext
* cx
= context();
2793 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2794 JSMSG_SC_BAD_SERIALIZED_DATA
,
2795 "invalid shared wasm memory tag");
2799 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2800 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2801 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2802 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2803 : JS_SCERR_NOT_CLONABLE
;
2804 ReportDataCloneError(cx
, callbacks
, error
, closure
, "WebAssembly.Memory");
2808 // Read the isHuge flag
2809 RootedValue
isHuge(cx
);
2810 if (!startRead(&isHuge
)) {
2814 // Read the SharedArrayBuffer object.
2815 RootedValue
payload(cx
);
2816 if (!startRead(&payload
)) {
2819 if (!payload
.isObject() ||
2820 !payload
.toObject().is
<SharedArrayBufferObject
>()) {
2821 JS_ReportErrorNumberASCII(
2822 context(), GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
2823 "shared wasm memory must be backed by a SharedArrayBuffer");
2827 Rooted
<ArrayBufferObjectMaybeShared
*> sab(
2828 cx
, &payload
.toObject().as
<SharedArrayBufferObject
>());
2830 // Construct the memory.
2832 cx
, GlobalObject::getOrCreatePrototype(cx
, JSProto_WasmMemory
));
2836 RootedObject
memory(
2837 cx
, WasmMemoryObject::create(cx
, sab
, isHuge
.toBoolean(), proto
));
2842 vp
.setObject(*memory
);
2847 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2848 * endianness-conversion while reading.
2850 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType
,
2852 MutableHandleValue vp
) {
2853 if (arrayType
> Scalar::Uint8Clamped
) {
2854 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2855 JSMSG_SC_BAD_SERIALIZED_DATA
,
2856 "invalid TypedArray type");
2860 mozilla::CheckedInt
<size_t> nbytes
=
2861 mozilla::CheckedInt
<size_t>(nelems
) *
2862 TypedArrayElemSize(static_cast<Scalar::Type
>(arrayType
));
2863 if (!nbytes
.isValid() || nbytes
.value() > UINT32_MAX
) {
2864 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2865 JSMSG_SC_BAD_SERIALIZED_DATA
,
2866 "invalid typed array size");
2870 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), nbytes
.value());
2875 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2876 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2878 switch (arrayType
) {
2881 case Scalar::Uint8Clamped
:
2882 return in
.readArray((uint8_t*)buffer
.dataPointer(), nelems
);
2884 case Scalar::Uint16
:
2885 return in
.readArray((uint16_t*)buffer
.dataPointer(), nelems
);
2887 case Scalar::Uint32
:
2888 case Scalar::Float32
:
2889 return in
.readArray((uint32_t*)buffer
.dataPointer(), nelems
);
2890 case Scalar::Float64
:
2891 case Scalar::BigInt64
:
2892 case Scalar::BigUint64
:
2893 return in
.readArray((uint64_t*)buffer
.dataPointer(), nelems
);
2895 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2899 static bool PrimitiveToObject(JSContext
* cx
, MutableHandleValue vp
) {
2900 JSObject
* obj
= js::PrimitiveToObject(cx
, vp
);
2909 bool JSStructuredCloneReader::startRead(MutableHandleValue vp
,
2910 ShouldAtomizeStrings atomizeStrings
) {
2912 bool alreadAppended
= false;
2914 if (!in
.readPair(&tag
, &data
)) {
2925 case SCTAG_UNDEFINED
:
2934 case SCTAG_BOOLEAN_OBJECT
:
2935 vp
.setBoolean(!!data
);
2936 if (tag
== SCTAG_BOOLEAN_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2942 case SCTAG_STRING_OBJECT
: {
2943 JSString
* str
= readString(data
, atomizeStrings
);
2948 if (tag
== SCTAG_STRING_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2954 case SCTAG_NUMBER_OBJECT
: {
2956 if (!in
.readDouble(&d
)) {
2959 vp
.setDouble(CanonicalizeNaN(d
));
2960 if (!PrimitiveToObject(context(), vp
)) {
2967 case SCTAG_BIGINT_OBJECT
: {
2968 RootedBigInt
bi(context(), readBigInt(data
));
2973 if (tag
== SCTAG_BIGINT_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2979 case SCTAG_DATE_OBJECT
: {
2981 if (!in
.readDouble(&d
)) {
2984 JS::ClippedTime t
= JS::TimeClip(d
);
2985 if (!NumbersAreIdentical(d
, t
.toDouble())) {
2986 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2987 JSMSG_SC_BAD_SERIALIZED_DATA
, "date");
2990 JSObject
* obj
= NewDateObjectMsec(context(), t
);
2998 case SCTAG_REGEXP_OBJECT
: {
2999 if ((data
& RegExpFlag::AllFlags
) != data
) {
3000 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3001 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
3005 RegExpFlags
flags(AssertedCast
<uint8_t>(data
));
3007 uint32_t tag2
, stringData
;
3008 if (!in
.readPair(&tag2
, &stringData
)) {
3011 if (tag2
!= SCTAG_STRING
) {
3012 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3013 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
3017 JSString
* str
= readString(stringData
, AtomizeStrings
);
3022 Rooted
<JSAtom
*> atom(context(), &str
->asAtom());
3024 NewObjectKind kind
=
3025 gcHeap
== gc::Heap::Tenured
? TenuredObject
: GenericObject
;
3026 RegExpObject
* reobj
= RegExpObject::create(context(), atom
, flags
, kind
);
3030 vp
.setObject(*reobj
);
3034 case SCTAG_ARRAY_OBJECT
:
3035 case SCTAG_OBJECT_OBJECT
: {
3036 NewObjectKind kind
=
3037 gcHeap
== gc::Heap::Tenured
? TenuredObject
: GenericObject
;
3039 if (tag
== SCTAG_ARRAY_OBJECT
) {
3040 obj
= NewDenseUnallocatedArray(
3041 context(), NativeEndian::swapFromLittleEndian(data
), kind
);
3043 obj
= NewPlainObject(context(), kind
);
3045 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3053 case SCTAG_BACK_REFERENCE_OBJECT
: {
3054 if (data
>= allObjs
.length() || !allObjs
[data
].isObject()) {
3055 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3056 JSMSG_SC_BAD_SERIALIZED_DATA
,
3057 "invalid back reference in input");
3060 vp
.set(allObjs
[data
]);
3064 case SCTAG_TRANSFER_MAP_HEADER
:
3065 case SCTAG_TRANSFER_MAP_PENDING_ENTRY
:
3066 // We should be past all the transfer map tags.
3067 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3068 JSMSG_SC_BAD_SERIALIZED_DATA
, "invalid input");
3071 case SCTAG_ARRAY_BUFFER_OBJECT_V2
:
3072 case SCTAG_ARRAY_BUFFER_OBJECT
:
3073 if (!readArrayBuffer(StructuredDataType(tag
), data
, vp
)) {
3078 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT
:
3079 if (!readSharedArrayBuffer(vp
)) {
3084 case SCTAG_SHARED_WASM_MEMORY_OBJECT
:
3085 if (!readSharedWasmMemory(data
, vp
)) {
3090 case SCTAG_TYPED_ARRAY_OBJECT_V2
: {
3091 // readTypedArray adds the array to allObjs.
3092 // V2 stores the length (nelems) in |data| and the arrayType separately.
3094 if (!in
.read(&arrayType
)) {
3097 uint64_t nelems
= data
;
3098 return readTypedArray(arrayType
, nelems
, vp
);
3101 case SCTAG_TYPED_ARRAY_OBJECT
: {
3102 // readTypedArray adds the array to allObjs.
3103 // The current version stores the array type in |data| and the length
3104 // (nelems) separately to support large TypedArrays.
3105 uint32_t arrayType
= data
;
3107 if (!in
.read(&nelems
)) {
3110 return readTypedArray(arrayType
, nelems
, vp
);
3113 case SCTAG_DATA_VIEW_OBJECT_V2
: {
3114 // readDataView adds the array to allObjs.
3115 uint64_t byteLength
= data
;
3116 return readDataView(byteLength
, vp
);
3119 case SCTAG_DATA_VIEW_OBJECT
: {
3120 // readDataView adds the array to allObjs.
3121 uint64_t byteLength
;
3122 if (!in
.read(&byteLength
)) {
3125 return readDataView(byteLength
, vp
);
3128 case SCTAG_MAP_OBJECT
: {
3129 JSObject
* obj
= MapObject::create(context());
3130 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3137 case SCTAG_SET_OBJECT
: {
3138 JSObject
* obj
= SetObject::create(context());
3139 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3146 case SCTAG_SAVED_FRAME_OBJECT
: {
3147 auto* obj
= readSavedFrameHeader(data
);
3148 if (!obj
|| !objs
.append(ObjectValue(*obj
)) ||
3149 !objState
.append(std::make_pair(obj
, false))) {
3156 case SCTAG_ERROR_OBJECT
: {
3157 auto* obj
= readErrorHeader(data
);
3158 if (!obj
|| !objs
.append(ObjectValue(*obj
)) ||
3159 !objState
.append(std::make_pair(obj
, false))) {
3166 case SCTAG_END_OF_KEYS
:
3167 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3168 JSMSG_SC_BAD_SERIALIZED_DATA
,
3174 if (tag
<= SCTAG_FLOAT_MAX
) {
3175 double d
= ReinterpretPairAsDouble(tag
, data
);
3176 vp
.setNumber(CanonicalizeNaN(d
));
3180 if (SCTAG_TYPED_ARRAY_V1_MIN
<= tag
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
3181 // A v1-format typed array
3182 // readTypedArray adds the array to allObjs
3183 return readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
3186 if (!callbacks
|| !callbacks
->read
) {
3187 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3188 JSMSG_SC_BAD_SERIALIZED_DATA
,
3189 "unsupported type");
3193 // callbacks->read() might read other objects from the buffer.
3194 // In startWrite we always write the object itself before calling
3195 // the custom function. We should do the same here to keep
3196 // indexing consistent.
3197 uint32_t placeholderIndex
= allObjs
.length();
3198 Value dummy
= UndefinedValue();
3199 if (!allObjs
.append(dummy
)) {
3203 callbacks
->read(context(), this, cloneDataPolicy
, tag
, data
, closure
);
3208 allObjs
[placeholderIndex
].set(vp
);
3209 alreadAppended
= true;
3213 if (!alreadAppended
&& vp
.isObject() && !allObjs
.append(vp
)) {
3220 bool JSStructuredCloneReader::readHeader() {
3222 if (!in
.getPair(&tag
, &data
)) {
3223 return in
.reportTruncated();
3226 JS::StructuredCloneScope storedScope
;
3227 if (tag
== SCTAG_HEADER
) {
3228 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3229 storedScope
= JS::StructuredCloneScope(data
);
3231 // Old structured clone buffer. We must have read it from disk.
3232 storedScope
= JS::StructuredCloneScope::DifferentProcessForIndexedDB
;
3235 // Backward compatibility with old structured clone buffers. Value '0' was
3236 // used for SameProcessSameThread scope.
3237 if ((int)storedScope
== 0) {
3238 storedScope
= JS::StructuredCloneScope::SameProcess
;
3241 if (storedScope
< JS::StructuredCloneScope::SameProcess
||
3242 storedScope
> JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3243 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3244 JSMSG_SC_BAD_SERIALIZED_DATA
,
3245 "invalid structured clone scope");
3249 if (allowedScope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3250 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
3251 // clones are incorrect. Treat them as if they were DifferentProcess.
3252 allowedScope
= JS::StructuredCloneScope::DifferentProcess
;
3256 if (storedScope
< allowedScope
) {
3257 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3258 JSMSG_SC_BAD_SERIALIZED_DATA
,
3259 "incompatible structured clone scope");
3266 bool JSStructuredCloneReader::readTransferMap() {
3267 JSContext
* cx
= context();
3268 auto headerPos
= in
.tell();
3271 if (!in
.getPair(&tag
, &data
)) {
3272 return in
.reportTruncated();
3275 if (tag
!= SCTAG_TRANSFER_MAP_HEADER
||
3276 TransferableMapHeader(data
) == SCTAG_TM_TRANSFERRED
) {
3280 uint64_t numTransferables
;
3281 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3282 if (!in
.read(&numTransferables
)) {
3286 for (uint64_t i
= 0; i
< numTransferables
; i
++) {
3287 auto pos
= in
.tell();
3289 if (!in
.readPair(&tag
, &data
)) {
3293 if (tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
) {
3294 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3298 RootedObject
obj(cx
);
3301 if (!in
.readPtr(&content
)) {
3306 if (!in
.read(&extraData
)) {
3310 if (tag
== SCTAG_TRANSFER_MAP_ARRAY_BUFFER
) {
3311 if (allowedScope
== JS::StructuredCloneScope::DifferentProcess
||
3313 JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3314 // Transferred ArrayBuffers in a DifferentProcess clone buffer
3315 // are treated as if they weren't Transferred at all. We should
3316 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
3317 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3321 MOZ_RELEASE_ASSERT(extraData
<= ArrayBufferObject::MaxByteLength
);
3322 size_t nbytes
= extraData
;
3324 MOZ_ASSERT(data
== JS::SCTAG_TMO_ALLOC_DATA
||
3325 data
== JS::SCTAG_TMO_MAPPED_DATA
);
3326 if (data
== JS::SCTAG_TMO_ALLOC_DATA
) {
3327 // When the ArrayBuffer can't be allocated, |content| will be free'ed
3328 // in `JSStructuredCloneData::discardTransferables()`.
3329 obj
= JS::NewArrayBufferWithContents(
3330 cx
, nbytes
, content
,
3331 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory
);
3332 } else if (data
== JS::SCTAG_TMO_MAPPED_DATA
) {
3333 obj
= JS::NewMappedArrayBufferWithContents(cx
, nbytes
, content
);
3335 } else if (tag
== SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
) {
3336 auto savedPos
= in
.tell();
3337 auto guard
= mozilla::MakeScopeExit([&] { in
.seekTo(savedPos
); });
3339 if (!in
.seekBy(static_cast<size_t>(extraData
))) {
3343 if (tailStartPos
.isNothing()) {
3344 tailStartPos
= mozilla::Some(in
.tell());
3348 if (!in
.readPair(&tag
, &data
)) {
3351 if (tag
!= SCTAG_ARRAY_BUFFER_OBJECT_V2
&&
3352 tag
!= SCTAG_ARRAY_BUFFER_OBJECT
) {
3353 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3356 RootedValue
val(cx
);
3357 if (!readArrayBuffer(StructuredDataType(tag
), data
, &val
)) {
3360 obj
= &val
.toObject();
3361 tailEndPos
= mozilla::Some(in
.tell());
3363 if (!callbacks
|| !callbacks
->readTransfer
) {
3364 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3367 if (!callbacks
->readTransfer(cx
, this, cloneDataPolicy
, tag
, content
,
3368 extraData
, closure
, &obj
)) {
3369 if (!cx
->isExceptionPending()) {
3370 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3375 MOZ_ASSERT(!cx
->isExceptionPending());
3378 // On failure, the buffer will still own the data (since its ownership
3379 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3380 // DiscardTransferables.
3385 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3387 pos
.write(PairToUInt64(tag
, JS::SCTAG_TMO_UNOWNED
));
3388 MOZ_ASSERT(!pos
.done());
3390 if (!allObjs
.append(ObjectValue(*obj
))) {
3395 // Mark the whole transfer map as consumed.
3397 SCInput::getPair(headerPos
.peek(), &tag
, &data
);
3398 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_HEADER
);
3399 MOZ_ASSERT(TransferableMapHeader(data
) != SCTAG_TM_TRANSFERRED
);
3402 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER
, SCTAG_TM_TRANSFERRED
));
3407 JSObject
* JSStructuredCloneReader::readSavedFrameHeader(
3408 uint32_t principalsTag
) {
3409 Rooted
<SavedFrame
*> savedFrame(context(), SavedFrame::create(context()));
3414 JSPrincipals
* principals
;
3415 if (principalsTag
== SCTAG_JSPRINCIPALS
) {
3416 if (!context()->runtime()->readPrincipals
) {
3417 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3418 JSMSG_SC_UNSUPPORTED_TYPE
);
3422 if (!context()->runtime()->readPrincipals(context(), this, &principals
)) {
3425 } else if (principalsTag
==
3426 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
) {
3427 principals
= &ReconstructedSavedFramePrincipals::IsSystem
;
3428 principals
->refcount
++;
3429 } else if (principalsTag
==
3430 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
) {
3431 principals
= &ReconstructedSavedFramePrincipals::IsNotSystem
;
3432 principals
->refcount
++;
3433 } else if (principalsTag
== SCTAG_NULL_JSPRINCIPALS
) {
3434 principals
= nullptr;
3436 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3437 JSMSG_SC_BAD_SERIALIZED_DATA
,
3438 "bad SavedFrame principals");
3442 RootedValue
mutedErrors(context());
3443 RootedValue
source(context());
3445 // Read a |mutedErrors| boolean followed by a |source| string.
3446 // The |mutedErrors| boolean is present in all new structured-clone data,
3447 // but in older data it will be absent and only the |source| string will be
3449 if (!startRead(&mutedErrors
, AtomizeStrings
)) {
3453 if (mutedErrors
.isBoolean()) {
3454 if (!startRead(&source
, AtomizeStrings
) || !source
.isString()) {
3457 } else if (mutedErrors
.isString()) {
3458 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3459 // this is actually just a |source| string.
3460 source
= mutedErrors
;
3461 mutedErrors
.setBoolean(true); // Safe default value.
3468 savedFrame
->initPrincipalsAlreadyHeldAndMutedErrors(principals
,
3469 mutedErrors
.toBoolean());
3471 savedFrame
->initSource(&source
.toString()->asAtom());
3474 if (!readUint32(&line
)) {
3477 savedFrame
->initLine(line
);
3479 JS::TaggedColumnNumberOneOrigin column
;
3480 if (!readUint32(column
.addressOfValueForTranscode())) {
3483 savedFrame
->initColumn(column
);
3485 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3486 // are only valid within a specific process.
3487 savedFrame
->initSourceId(0);
3489 RootedValue
name(context());
3490 if (!startRead(&name
, AtomizeStrings
)) {
3493 if (!(name
.isString() || name
.isNull())) {
3494 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3495 JSMSG_SC_BAD_SERIALIZED_DATA
,
3496 "invalid saved frame cause");
3499 JSAtom
* atomName
= nullptr;
3500 if (name
.isString()) {
3501 atomName
= &name
.toString()->asAtom();
3504 savedFrame
->initFunctionDisplayName(atomName
);
3506 RootedValue
cause(context());
3507 if (!startRead(&cause
, AtomizeStrings
)) {
3510 if (!(cause
.isString() || cause
.isNull())) {
3511 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3512 JSMSG_SC_BAD_SERIALIZED_DATA
,
3513 "invalid saved frame cause");
3516 JSAtom
* atomCause
= nullptr;
3517 if (cause
.isString()) {
3518 atomCause
= &cause
.toString()->asAtom();
3520 savedFrame
->initAsyncCause(atomCause
);
3525 // SavedFrame object: there is one child value, the parent SavedFrame,
3526 // which is either null or another SavedFrame object.
3527 bool JSStructuredCloneReader::readSavedFrameFields(Handle
<SavedFrame
*> frameObj
,
3531 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3532 JSMSG_SC_BAD_SERIALIZED_DATA
,
3533 "multiple SavedFrame parents");
3537 SavedFrame
* parentFrame
;
3538 if (parent
.isNull()) {
3539 parentFrame
= nullptr;
3540 } else if (parent
.isObject() && parent
.toObject().is
<SavedFrame
>()) {
3541 parentFrame
= &parent
.toObject().as
<SavedFrame
>();
3543 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3544 JSMSG_SC_BAD_SERIALIZED_DATA
,
3545 "invalid SavedFrame parent");
3549 frameObj
->initParent(parentFrame
);
3554 JSObject
* JSStructuredCloneReader::readErrorHeader(uint32_t type
) {
3555 JSContext
* cx
= context();
3560 case JSEXN_RANGEERR
:
3561 case JSEXN_REFERENCEERR
:
3562 case JSEXN_SYNTAXERR
:
3565 case JSEXN_AGGREGATEERR
:
3568 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3569 JSMSG_SC_BAD_SERIALIZED_DATA
,
3570 "invalid error type");
3574 RootedString
message(cx
);
3576 RootedValue
messageVal(cx
);
3577 if (!startRead(&messageVal
)) {
3580 if (messageVal
.isString()) {
3581 message
= messageVal
.toString();
3582 } else if (!messageVal
.isNull()) {
3583 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3584 JSMSG_SC_BAD_SERIALIZED_DATA
,
3585 "invalid 'message' field for Error object");
3590 // We have to set |cause| to something if it exists, otherwise the shape
3591 // would be wrong. The actual value will be overwritten later.
3592 RootedValue
val(cx
);
3593 if (!startRead(&val
)) {
3596 bool hasCause
= ToBoolean(val
);
3597 Rooted
<Maybe
<Value
>> cause(cx
, mozilla::Nothing());
3599 cause
= mozilla::Some(BooleanValue(true));
3602 if (!startRead(&val
)) {
3605 if (!val
.isString()) {
3606 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3607 JSMSG_SC_BAD_SERIALIZED_DATA
,
3608 "invalid 'fileName' field for Error object");
3611 RootedString
fileName(cx
, val
.toString());
3613 uint32_t lineNumber
;
3614 JS::ColumnNumberOneOrigin columnNumber
;
3615 if (!readUint32(&lineNumber
) ||
3616 !readUint32(columnNumber
.addressOfValueForTranscode())) {
3620 // The |cause| and |stack| slots of the objects might be overwritten later.
3621 // For AggregateErrors the |errors| property will be added.
3622 RootedObject
errorObj(
3623 cx
, ErrorObject::create(cx
, static_cast<JSExnType
>(type
), nullptr,
3624 fileName
, 0, lineNumber
, columnNumber
, nullptr,
3633 // Error objects have 3 fields, some or all of them null: cause,
3634 // errors, and stack.
3635 bool JSStructuredCloneReader::readErrorFields(Handle
<ErrorObject
*> errorObj
,
3636 HandleValue cause
, bool* state
) {
3637 JSContext
* cx
= context();
3639 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3640 JSMSG_SC_BAD_SERIALIZED_DATA
,
3641 "unexpected child value seen for Error object");
3645 RootedValue
errors(cx
);
3646 RootedValue
stack(cx
);
3647 if (!startRead(&errors
) || !startRead(&stack
)) {
3651 bool hasCause
= errorObj
->getCause().isSome();
3653 errorObj
->setCauseSlot(cause
);
3654 } else if (!cause
.isNull()) {
3655 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3656 JSMSG_SC_BAD_SERIALIZED_DATA
,
3657 "invalid 'cause' field for Error object");
3661 if (errorObj
->type() == JSEXN_AGGREGATEERR
) {
3662 if (!DefineDataProperty(context(), errorObj
, cx
->names().errors
, errors
,
3666 } else if (!errors
.isNull()) {
3667 JS_ReportErrorNumberASCII(
3668 cx
, GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
3669 "unexpected 'errors' field seen for non-AggregateError");
3673 if (stack
.isObject()) {
3674 RootedObject
stackObj(cx
, &stack
.toObject());
3675 if (!stackObj
->is
<SavedFrame
>()) {
3676 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3677 JSMSG_SC_BAD_SERIALIZED_DATA
,
3678 "invalid 'stack' field for Error object");
3681 errorObj
->setStackSlot(stack
);
3682 } else if (!stack
.isNull()) {
3683 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3684 JSMSG_SC_BAD_SERIALIZED_DATA
,
3685 "invalid 'stack' field for Error object");
3693 // Read a value and treat as a key,value pair.
3694 bool JSStructuredCloneReader::readMapField(Handle
<MapObject
*> mapObj
,
3696 RootedValue
val(context());
3697 if (!startRead(&val
)) {
3700 return MapObject::set(context(), mapObj
, key
, val
);
3703 // Read a value and treat as a key,value pair. Interpret as a plain property
3705 bool JSStructuredCloneReader::readObjectField(HandleObject obj
,
3707 if (!key
.isString() && !key
.isInt32()) {
3708 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3709 JSMSG_SC_BAD_SERIALIZED_DATA
,
3710 "property key expected");
3714 RootedValue
val(context());
3715 if (!startRead(&val
)) {
3719 RootedId
id(context());
3720 if (!PrimitiveValueToId
<CanGC
>(context(), key
, &id
)) {
3724 // Fast path for adding a new property to a plain object. The property names
3725 // we see here should be unique, but we check for duplicates to guard against
3726 // corrupt or malicious data.
3727 if (id
.isString() && obj
->is
<PlainObject
>() &&
3728 MOZ_LIKELY(!obj
->as
<PlainObject
>().contains(context(), id
))) {
3729 return AddDataPropertyToPlainObject(context(), obj
.as
<PlainObject
>(), id
,
3733 // Fast path for adding an array element. The index shouldn't exceed the
3734 // array's length, but we check for this in `addDenseElementNoLengthChange` to
3735 // guard against corrupt or malicious data.
3736 if (id
.isInt() && obj
->is
<ArrayObject
>()) {
3737 ArrayObject
* arr
= &obj
->as
<ArrayObject
>();
3738 switch (arr
->addDenseElementNoLengthChange(context(), id
.toInt(), val
)) {
3739 case DenseElementResult::Failure
:
3741 case DenseElementResult::Success
:
3743 case DenseElementResult::Incomplete
:
3744 // Fall-through to slow path.
3749 return DefineDataProperty(context(), obj
, id
, val
);
3752 // Perform the whole recursive reading procedure.
3753 bool JSStructuredCloneReader::read(MutableHandleValue vp
, size_t nbytes
) {
3754 auto startTime
= mozilla::TimeStamp::Now();
3756 if (!readHeader()) {
3760 if (!readTransferMap()) {
3764 MOZ_ASSERT(objs
.length() == 0);
3765 MOZ_ASSERT(objState
.length() == 1);
3767 // Start out by reading in the main object and pushing it onto the 'objs'
3768 // stack. The data related to this object and its descendants extends from
3769 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3770 if (!startRead(vp
)) {
3774 // Stop when the stack shows that all objects have been read.
3775 while (objs
.length() != 0) {
3776 // What happens depends on the top obj on the objs stack.
3777 RootedObject
obj(context(), &objs
.back().toObject());
3780 if (!in
.getPair(&tag
, &data
)) {
3784 if (tag
== SCTAG_END_OF_KEYS
) {
3785 // Pop the current obj off the stack, since we are done with it and
3787 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3789 if (objState
.back().first
== obj
) {
3795 // Remember the index of the current top of the state stack, which will
3796 // correspond to the state for `obj` iff `obj` is a type that uses state.
3797 // startRead() may push additional entries before the state is accessed and
3798 // updated while filling in the object's data.
3799 size_t objStateIdx
= objState
.length() - 1;
3801 // The input stream contains a sequence of "child" values, whose
3802 // interpretation depends on the type of obj. These values can be
3803 // anything, and startRead() will push onto 'objs' for any non-leaf
3804 // value (i.e., anything that may contain children).
3806 // startRead() will allocate the (empty) object, but note that when
3807 // startRead() returns, 'key' is not yet initialized with any of its
3808 // properties. Those will be filled in by returning to the head of this
3809 // loop, processing the first child obj, and continuing until all
3810 // children have been fully created.
3812 // Note that this means the ordering in the stream is a little funky for
3813 // things like Map. See the comment above traverseMap() for an example.
3815 bool expectKeyValuePairs
=
3816 !(obj
->is
<MapObject
>() || obj
->is
<SetObject
>() ||
3817 obj
->is
<SavedFrame
>() || obj
->is
<ErrorObject
>());
3819 RootedValue
key(context());
3820 ShouldAtomizeStrings atomize
=
3821 expectKeyValuePairs
? AtomizeStrings
: DontAtomizeStrings
;
3822 if (!startRead(&key
, atomize
)) {
3826 if (key
.isNull() && expectKeyValuePairs
) {
3827 // Backwards compatibility: Null formerly indicated the end of
3828 // object properties.
3830 // No legacy objects used the state stack.
3831 MOZ_ASSERT(objState
[objStateIdx
].first() != obj
);
3837 context()->check(key
);
3839 if (obj
->is
<SetObject
>()) {
3840 // Set object: the values between obj header (from startRead()) and
3841 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3842 if (!SetObject::add(context(), obj
, key
)) {
3845 } else if (obj
->is
<MapObject
>()) {
3846 Rooted
<MapObject
*> mapObj(context(), &obj
->as
<MapObject
>());
3847 if (!readMapField(mapObj
, key
)) {
3850 } else if (obj
->is
<SavedFrame
>()) {
3851 Rooted
<SavedFrame
*> frameObj(context(), &obj
->as
<SavedFrame
>());
3852 MOZ_ASSERT(objState
[objStateIdx
].first() == obj
);
3853 bool state
= objState
[objStateIdx
].second();
3854 if (!readSavedFrameFields(frameObj
, key
, &state
)) {
3857 objState
[objStateIdx
].second() = state
;
3858 } else if (obj
->is
<ErrorObject
>()) {
3859 Rooted
<ErrorObject
*> errorObj(context(), &obj
->as
<ErrorObject
>());
3860 MOZ_ASSERT(objState
[objStateIdx
].first() == obj
);
3861 bool state
= objState
[objStateIdx
].second();
3862 if (!readErrorFields(errorObj
, key
, &state
)) {
3865 objState
[objStateIdx
].second() = state
;
3867 MOZ_ASSERT(expectKeyValuePairs
);
3868 // Everything else uses a series of key,value,key,value,... Value
3870 if (!readObjectField(obj
, key
)) {
3878 // For fuzzing, it is convenient to allow extra data at the end
3879 // of the input buffer so that more possible inputs are considered
3883 if (tailStartPos
.isSome()) {
3884 // in.tell() is the end of the main data. If "tail" data was consumed,
3885 // then check whether there's any data between the main data and the
3886 // beginning of the tail, or after the last read point in the tail.
3887 extraData
= (in
.tell() != *tailStartPos
|| !tailEndPos
->done());
3889 extraData
= !in
.tell().done();
3892 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3893 JSMSG_SC_BAD_SERIALIZED_DATA
,
3894 "extra data after end");
3899 JSRuntime
* rt
= context()->runtime();
3900 rt
->metrics().DESERIALIZE_BYTES(nbytes
);
3901 rt
->metrics().DESERIALIZE_ITEMS(numItemsRead
);
3902 mozilla::TimeDuration elapsed
= mozilla::TimeStamp::Now() - startTime
;
3903 rt
->metrics().DESERIALIZE_US(elapsed
);
3908 JS_PUBLIC_API
bool JS_ReadStructuredClone(
3909 JSContext
* cx
, const JSStructuredCloneData
& buf
, uint32_t version
,
3910 JS::StructuredCloneScope scope
, MutableHandleValue vp
,
3911 const JS::CloneDataPolicy
& cloneDataPolicy
,
3912 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3916 if (version
> JS_STRUCTURED_CLONE_VERSION
) {
3917 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3918 JSMSG_SC_BAD_CLONE_VERSION
);
3921 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3922 return ReadStructuredClone(cx
, buf
, scope
, vp
, cloneDataPolicy
, callbacks
,
3926 JS_PUBLIC_API
bool JS_WriteStructuredClone(
3927 JSContext
* cx
, HandleValue value
, JSStructuredCloneData
* bufp
,
3928 JS::StructuredCloneScope scope
, const JS::CloneDataPolicy
& cloneDataPolicy
,
3929 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
,
3930 HandleValue transferable
) {
3935 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3936 return WriteStructuredClone(cx
, value
, bufp
, scope
, cloneDataPolicy
,
3937 callbacks
, closure
, transferable
);
3940 JS_PUBLIC_API
bool JS_StructuredCloneHasTransferables(
3941 JSStructuredCloneData
& data
, bool* hasTransferable
) {
3942 *hasTransferable
= StructuredCloneHasTransferObjects(data
);
3946 JS_PUBLIC_API
bool JS_StructuredClone(
3947 JSContext
* cx
, HandleValue value
, MutableHandleValue vp
,
3948 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3952 // Strings are associated with zones, not compartments,
3953 // so we copy the string by wrapping it.
3954 if (value
.isString()) {
3955 RootedString
strValue(cx
, value
.toString());
3956 if (!cx
->compartment()->wrap(cx
, &strValue
)) {
3959 vp
.setString(strValue
);
3963 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3965 JSAutoStructuredCloneBuffer
buf(JS::StructuredCloneScope::SameProcess
,
3966 callbacks
, closure
);
3968 if (value
.isObject()) {
3969 RootedObject
obj(cx
, &value
.toObject());
3970 obj
= CheckedUnwrapStatic(obj
);
3972 ReportAccessDenied(cx
);
3975 AutoRealm
ar(cx
, obj
);
3976 RootedValue
unwrappedVal(cx
, ObjectValue(*obj
));
3977 if (!buf
.write(cx
, unwrappedVal
, callbacks
, closure
)) {
3981 if (!buf
.write(cx
, value
, callbacks
, closure
)) {
3987 return buf
.read(cx
, vp
, JS::CloneDataPolicy(), callbacks
, closure
);
3990 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3991 JSAutoStructuredCloneBuffer
&& other
)
3992 : data_(other
.scope()) {
3993 version_
= other
.version_
;
3994 other
.giveTo(&data_
);
3997 JSAutoStructuredCloneBuffer
& JSAutoStructuredCloneBuffer::operator=(
3998 JSAutoStructuredCloneBuffer
&& other
) {
3999 MOZ_ASSERT(&other
!= this);
4000 MOZ_ASSERT(scope() == other
.scope());
4002 version_
= other
.version_
;
4003 other
.giveTo(&data_
);
4007 void JSAutoStructuredCloneBuffer::clear() {
4008 data_
.discardTransferables();
4009 data_
.ownTransferables_
= OwnTransferablePolicy::NoTransferables
;
4010 data_
.refsHeld_
.releaseAll();
4015 void JSAutoStructuredCloneBuffer::adopt(
4016 JSStructuredCloneData
&& data
, uint32_t version
,
4017 const JSStructuredCloneCallbacks
* callbacks
, void* closure
) {
4019 data_
= std::move(data
);
4021 data_
.setCallbacks(callbacks
, closure
,
4022 OwnTransferablePolicy::OwnsTransferablesIfAny
);
4025 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData
* data
) {
4026 *data
= std::move(data_
);
4028 data_
.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables
);
4032 bool JSAutoStructuredCloneBuffer::read(
4033 JSContext
* cx
, MutableHandleValue vp
,
4034 const JS::CloneDataPolicy
& cloneDataPolicy
,
4035 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4037 return !!JS_ReadStructuredClone(
4038 cx
, data_
, version_
, data_
.scope(), vp
, cloneDataPolicy
,
4039 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4040 optionalCallbacks
? closure
: data_
.closure_
);
4043 bool JSAutoStructuredCloneBuffer::write(
4044 JSContext
* cx
, HandleValue value
,
4045 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4046 HandleValue transferable
= UndefinedHandleValue
;
4047 return write(cx
, value
, transferable
, JS::CloneDataPolicy(),
4048 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4049 optionalCallbacks
? closure
: data_
.closure_
);
4052 bool JSAutoStructuredCloneBuffer::write(
4053 JSContext
* cx
, HandleValue value
, HandleValue transferable
,
4054 const JS::CloneDataPolicy
& cloneDataPolicy
,
4055 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4057 bool ok
= JS_WriteStructuredClone(
4058 cx
, value
, &data_
, data_
.scopeForInternalWriting(), cloneDataPolicy
,
4059 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4060 optionalCallbacks
? closure
: data_
.closure_
, transferable
);
4062 version_
= JS_STRUCTURED_CLONE_VERSION
;
4067 JS_PUBLIC_API
bool JS_ReadUint32Pair(JSStructuredCloneReader
* r
, uint32_t* p1
,
4069 return r
->input().readPair((uint32_t*)p1
, (uint32_t*)p2
);
4072 JS_PUBLIC_API
bool JS_ReadBytes(JSStructuredCloneReader
* r
, void* p
,
4074 return r
->input().readBytes(p
, len
);
4077 JS_PUBLIC_API
bool JS_ReadString(JSStructuredCloneReader
* r
,
4078 MutableHandleString str
) {
4080 if (!r
->input().readPair(&tag
, &data
)) {
4084 if (tag
== SCTAG_STRING
) {
4086 r
->readString(data
, JSStructuredCloneReader::DontAtomizeStrings
)) {
4093 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
4094 JSMSG_SC_BAD_SERIALIZED_DATA
, "expected string");
4098 JS_PUBLIC_API
bool JS_ReadDouble(JSStructuredCloneReader
* r
, double* v
) {
4099 return r
->input().readDouble(v
);
4102 JS_PUBLIC_API
bool JS_ReadTypedArray(JSStructuredCloneReader
* r
,
4103 MutableHandleValue vp
) {
4105 if (!r
->input().readPair(&tag
, &data
)) {
4109 if (tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
4110 return r
->readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
4113 if (tag
== SCTAG_TYPED_ARRAY_OBJECT_V2
) {
4114 // V2 stores the length (nelems) in |data| and the arrayType separately.
4116 if (!r
->input().read(&arrayType
)) {
4119 uint64_t nelems
= data
;
4120 return r
->readTypedArray(arrayType
, nelems
, vp
);
4123 if (tag
== SCTAG_TYPED_ARRAY_OBJECT
) {
4124 // The current version stores the array type in |data| and the length
4125 // (nelems) separately to support large TypedArrays.
4126 uint32_t arrayType
= data
;
4128 if (!r
->input().read(&nelems
)) {
4131 return r
->readTypedArray(arrayType
, nelems
, vp
);
4134 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
4135 JSMSG_SC_BAD_SERIALIZED_DATA
,
4136 "expected type array");
4140 JS_PUBLIC_API
bool JS_WriteUint32Pair(JSStructuredCloneWriter
* w
, uint32_t tag
,
4142 return w
->output().writePair(tag
, data
);
4145 JS_PUBLIC_API
bool JS_WriteBytes(JSStructuredCloneWriter
* w
, const void* p
,
4147 return w
->output().writeBytes(p
, len
);
4150 JS_PUBLIC_API
bool JS_WriteString(JSStructuredCloneWriter
* w
,
4152 return w
->writeString(SCTAG_STRING
, str
);
4155 JS_PUBLIC_API
bool JS_WriteDouble(JSStructuredCloneWriter
* w
, double v
) {
4156 return w
->output().writeDouble(v
);
4159 JS_PUBLIC_API
bool JS_WriteTypedArray(JSStructuredCloneWriter
* w
,
4161 MOZ_ASSERT(v
.isObject());
4162 w
->context()->check(v
);
4163 RootedObject
obj(w
->context(), &v
.toObject());
4165 // startWrite can write everything, thus we should check here
4166 // and report error if the user passes a wrong type.
4167 if (!obj
->canUnwrapAs
<TypedArrayObject
>()) {
4168 ReportAccessDenied(w
->context());
4172 // We should use startWrite instead of writeTypedArray, because
4173 // typed array is an object, we should add it to the |memory|
4174 // (allObjs) list. Directly calling writeTypedArray won't add it.
4175 return w
->startWrite(v
);
4178 JS_PUBLIC_API
bool JS_ObjectNotWritten(JSStructuredCloneWriter
* w
,
4180 w
->memory
.remove(w
->memory
.lookup(obj
));
4185 JS_PUBLIC_API
JS::StructuredCloneScope
JS_GetStructuredCloneScope(
4186 JSStructuredCloneWriter
* w
) {
4187 return w
->output().scope();