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
<TypedArrayObject
*> tarr(context(),
1336 obj
->maybeUnwrapAs
<TypedArrayObject
>());
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
<DataViewObject
*> view(context(), obj
->maybeUnwrapAs
<DataViewObject
>());
1373 JSAutoRealm
ar(context(), view
);
1375 if (!out
.writePair(SCTAG_DATA_VIEW_OBJECT
, 0)) {
1379 uint64_t byteLength
= view
->byteLength();
1380 if (!out
.write(byteLength
)) {
1384 // Write out the ArrayBuffer tag and contents
1385 RootedValue
val(context(), view
->bufferValue());
1386 if (!startWrite(val
)) {
1390 uint64_t byteOffset
= view
->byteOffset();
1391 return out
.write(byteOffset
);
1394 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj
) {
1395 Rooted
<ArrayBufferObject
*> buffer(context(),
1396 obj
->maybeUnwrapAs
<ArrayBufferObject
>());
1397 JSAutoRealm
ar(context(), buffer
);
1399 if (!out
.writePair(SCTAG_ARRAY_BUFFER_OBJECT
, 0)) {
1403 uint64_t byteLength
= buffer
->byteLength();
1404 if (!out
.write(byteLength
)) {
1408 return out
.writeBytes(buffer
->dataPointer(), byteLength
);
1411 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj
) {
1412 MOZ_ASSERT(obj
->canUnwrapAs
<SharedArrayBufferObject
>());
1414 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1415 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1416 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1417 : JS_SCERR_NOT_CLONABLE
;
1418 reportDataCloneError(error
, "SharedArrayBuffer");
1422 output().sameProcessScopeRequired();
1424 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1425 // cross-process. The cloneDataPolicy should have guarded against this;
1426 // since it did not then throw, with a very explicit message.
1428 if (output().scope() > JS::StructuredCloneScope::SameProcess
) {
1429 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1430 JSMSG_SC_SHMEM_POLICY
);
1434 Rooted
<SharedArrayBufferObject
*> sharedArrayBuffer(
1435 context(), obj
->maybeUnwrapAs
<SharedArrayBufferObject
>());
1436 SharedArrayRawBuffer
* rawbuf
= sharedArrayBuffer
->rawBufferObject();
1438 if (!out
.buf
.refsHeld_
.acquire(context(), rawbuf
)) {
1442 // We must serialize the length so that the buffer object arrives in the
1443 // receiver with the same length, and not with the length read from the
1444 // rawbuf - that length can be different, and it can change at any time.
1446 intptr_t p
= reinterpret_cast<intptr_t>(rawbuf
);
1447 uint64_t byteLength
= sharedArrayBuffer
->byteLength();
1448 if (!(out
.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT
,
1449 static_cast<uint32_t>(sizeof(p
))) &&
1450 out
.writeBytes(&byteLength
, sizeof(byteLength
)) &&
1451 out
.writeBytes(&p
, sizeof(p
)))) {
1455 if (callbacks
&& callbacks
->sabCloned
&&
1456 !callbacks
->sabCloned(context(), /*receiving=*/false, closure
)) {
1463 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj
) {
1464 MOZ_ASSERT(obj
->canUnwrapAs
<WasmMemoryObject
>());
1466 // Check the policy here so that we can report a sane error.
1467 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1468 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1469 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1470 : JS_SCERR_NOT_CLONABLE
;
1471 reportDataCloneError(error
, "WebAssembly.Memory");
1475 // If this changes, might need to change what we write.
1476 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS
== 3);
1478 Rooted
<WasmMemoryObject
*> memoryObj(context(),
1479 &obj
->unwrapAs
<WasmMemoryObject
>());
1480 Rooted
<SharedArrayBufferObject
*> sab(
1481 context(), &memoryObj
->buffer().as
<SharedArrayBufferObject
>());
1483 return out
.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT
, 0) &&
1484 out
.writePair(SCTAG_BOOLEAN
, memoryObj
->isHuge()) &&
1485 writeSharedArrayBuffer(sab
);
1488 bool JSStructuredCloneWriter::startObject(HandleObject obj
, bool* backref
) {
1489 // Handle cycles in the object graph.
1490 CloneMemory::AddPtr p
= memory
.lookupForAdd(obj
);
1491 if ((*backref
= p
.found())) {
1492 return out
.writePair(SCTAG_BACK_REFERENCE_OBJECT
, p
->value());
1494 if (!memory
.add(p
, obj
, memory
.count())) {
1495 ReportOutOfMemory(context());
1499 if (memory
.count() == UINT32_MAX
) {
1500 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1501 JSMSG_NEED_DIET
, "object graph to serialize");
1508 static bool TryAppendNativeProperties(JSContext
* cx
, HandleObject obj
,
1509 MutableHandleIdVector entries
,
1510 size_t* properties
, bool* optimized
) {
1513 if (!obj
->is
<NativeObject
>()) {
1517 Handle
<NativeObject
*> nobj
= obj
.as
<NativeObject
>();
1518 if (nobj
->isIndexed() || nobj
->is
<TypedArrayObject
>() ||
1519 nobj
->getClass()->getNewEnumerate() || nobj
->getClass()->getEnumerate()) {
1526 // We iterate from the last to the first property, so the property names
1527 // are already in reverse order.
1528 for (ShapePropertyIter
<NoGC
> iter(nobj
->shape()); !iter
.done(); iter
++) {
1529 jsid id
= iter
->key();
1531 // Ignore symbols and non-enumerable properties.
1532 if (!iter
->enumerable() || id
.isSymbol()) {
1536 MOZ_ASSERT(id
.isString());
1537 if (!entries
.append(id
)) {
1544 // Add dense element ids in reverse order.
1545 for (uint32_t i
= nobj
->getDenseInitializedLength(); i
> 0; --i
) {
1546 if (nobj
->getDenseElement(i
- 1).isMagic(JS_ELEMENTS_HOLE
)) {
1550 if (!entries
.append(PropertyKey::Int(i
- 1))) {
1557 *properties
= count
;
1561 // Objects are written as a "preorder" traversal of the object graph: object
1562 // "headers" (the class tag and any data needed for initial construction) are
1563 // visited first, then the children are recursed through (where children are
1564 // properties, Set or Map entries, etc.). So for example
1566 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1568 // would be stored as:
1570 // <Object tag for obj1>
1572 // <Object tag for key1's value>
1577 // <end-of-children marker for key1's value>
1579 // <Object tag for key2's value>
1580 // <end-of-children marker for key2's value>
1581 // <end-of-children marker for obj1>
1583 // This nests nicely (ie, an entire recursive value starts with its tag and
1584 // ends with its end-of-children marker) and so it can be presented indented.
1585 // But see traverseMap below for how this looks different for Maps.
1586 bool JSStructuredCloneWriter::traverseObject(HandleObject obj
, ESClass cls
) {
1588 bool optimized
= false;
1589 if (!js::SupportDifferentialTesting()) {
1590 if (!TryAppendNativeProperties(context(), obj
, &objectEntries
, &count
,
1597 // Get enumerable property ids and put them in reverse order so that they
1598 // will come off the stack in forward order.
1599 RootedIdVector
properties(context());
1600 if (!GetPropertyKeys(context(), obj
, JSITER_OWNONLY
, &properties
)) {
1604 for (size_t i
= properties
.length(); i
> 0; --i
) {
1605 jsid id
= properties
[i
- 1];
1607 MOZ_ASSERT(id
.isString() || id
.isInt());
1608 if (!objectEntries
.append(id
)) {
1613 count
= properties
.length();
1616 // Push obj and count to the stack.
1617 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(count
)) {
1625 if (!GetBuiltinClass(context(), obj
, &cls2
)) {
1628 MOZ_ASSERT(cls2
== cls
);
1631 // Write the header for obj.
1632 if (cls
== ESClass::Array
) {
1633 uint32_t length
= 0;
1634 if (!JS::GetArrayLength(context(), obj
, &length
)) {
1638 return out
.writePair(SCTAG_ARRAY_OBJECT
,
1639 NativeEndian::swapToLittleEndian(length
));
1642 return out
.writePair(SCTAG_OBJECT_OBJECT
, 0);
1645 // Use the same basic setup as for traverseObject, but now keys can themselves
1646 // be complex objects. Keys and values are visited first via startWrite(), then
1647 // the key's children (if any) are handled, then the value's children.
1650 // m.set(key1 = ..., value1 = ...)
1652 // where key1 and value2 are both objects would be stored as
1656 // <value1 class tag>
1657 // ...key1 fields...
1658 // <end-of-children marker for key1>
1659 // ...value1 fields...
1660 // <end-of-children marker for value1>
1661 // <end-of-children marker for Map>
1663 // Notice how the end-of-children marker for key1 is sandwiched between the
1664 // value1 beginning and end.
1665 bool JSStructuredCloneWriter::traverseMap(HandleObject obj
) {
1666 Rooted
<GCVector
<Value
>> newEntries(context(), GCVector
<Value
>(context()));
1668 // If there is no wrapper, the compartment munging is a no-op.
1669 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<MapObject
>());
1670 MOZ_ASSERT(unwrapped
);
1671 JSAutoRealm
ar(context(), unwrapped
);
1672 if (!MapObject::getKeysAndValuesInterleaved(unwrapped
, &newEntries
)) {
1676 if (!context()->compartment()->wrap(context(), &newEntries
)) {
1680 for (size_t i
= newEntries
.length(); i
> 0; --i
) {
1681 if (!otherEntries
.append(newEntries
[i
- 1])) {
1686 // Push obj and count to the stack.
1687 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(newEntries
.length())) {
1693 // Write the header for obj.
1694 return out
.writePair(SCTAG_MAP_OBJECT
, 0);
1697 // Similar to traverseMap, only there is a single value instead of a key and
1698 // value, and thus no interleaving is possible: a value will be fully emitted
1699 // before the next value is begun.
1700 bool JSStructuredCloneWriter::traverseSet(HandleObject obj
) {
1701 Rooted
<GCVector
<Value
>> keys(context(), GCVector
<Value
>(context()));
1703 // If there is no wrapper, the compartment munging is a no-op.
1704 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<SetObject
>());
1705 MOZ_ASSERT(unwrapped
);
1706 JSAutoRealm
ar(context(), unwrapped
);
1707 if (!SetObject::keys(context(), unwrapped
, &keys
)) {
1711 if (!context()->compartment()->wrap(context(), &keys
)) {
1715 for (size_t i
= keys
.length(); i
> 0; --i
) {
1716 if (!otherEntries
.append(keys
[i
- 1])) {
1721 // Push obj and count to the stack.
1722 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(keys
.length())) {
1728 // Write the header for obj.
1729 return out
.writePair(SCTAG_SET_OBJECT
, 0);
1732 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj
) {
1733 Rooted
<SavedFrame
*> savedFrame(context(), obj
->maybeUnwrapAs
<SavedFrame
>());
1734 MOZ_ASSERT(savedFrame
);
1736 RootedObject
parent(context(), savedFrame
->getParent());
1737 if (!context()->compartment()->wrap(context(), &parent
)) {
1741 if (!objs
.append(ObjectValue(*obj
)) ||
1742 !otherEntries
.append(parent
? ObjectValue(*parent
) : NullValue()) ||
1743 !counts
.append(1)) {
1749 // Write the SavedFrame tag and the SavedFrame's principals.
1751 if (savedFrame
->getPrincipals() ==
1752 &ReconstructedSavedFramePrincipals::IsSystem
) {
1753 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
,
1754 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
)) {
1757 } else if (savedFrame
->getPrincipals() ==
1758 &ReconstructedSavedFramePrincipals::IsNotSystem
) {
1760 SCTAG_SAVED_FRAME_OBJECT
,
1761 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
)) {
1765 if (auto principals
= savedFrame
->getPrincipals()) {
1766 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_JSPRINCIPALS
) ||
1767 !principals
->write(context(), this)) {
1771 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_NULL_JSPRINCIPALS
)) {
1777 // Write the SavedFrame's reserved slots, except for the parent, which is
1778 // queued on objs for further traversal.
1780 RootedValue
val(context());
1782 val
= BooleanValue(savedFrame
->getMutedErrors());
1783 if (!writePrimitive(val
)) {
1787 context()->markAtom(savedFrame
->getSource());
1788 val
= StringValue(savedFrame
->getSource());
1789 if (!writePrimitive(val
)) {
1793 val
= NumberValue(savedFrame
->getLine());
1794 if (!writePrimitive(val
)) {
1798 val
= NumberValue(*savedFrame
->getColumn().addressOfValueForTranscode());
1799 if (!writePrimitive(val
)) {
1803 auto name
= savedFrame
->getFunctionDisplayName();
1805 context()->markAtom(name
);
1807 val
= name
? StringValue(name
) : NullValue();
1808 if (!writePrimitive(val
)) {
1812 auto cause
= savedFrame
->getAsyncCause();
1814 context()->markAtom(cause
);
1816 val
= cause
? StringValue(cause
) : NullValue();
1817 if (!writePrimitive(val
)) {
1824 // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
1825 // 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
1827 // Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
1828 // value is not a platform object, then:
1830 // Note: This contains custom extensions for handling non-standard properties.
1831 bool JSStructuredCloneWriter::traverseError(HandleObject obj
) {
1832 JSContext
* cx
= context();
1834 // 1. Let name be ? Get(value, "name").
1835 RootedValue
name(cx
);
1836 if (!GetProperty(cx
, obj
, obj
, cx
->names().name
, &name
)) {
1840 // 2. If name is not one of "Error", "EvalError", "RangeError",
1841 // "ReferenceError", "SyntaxError", "TypeError", or "URIError",
1842 // (not yet specified: or "AggregateError")
1843 // then set name to "Error".
1844 JSExnType type
= JSEXN_ERR
;
1845 if (name
.isString()) {
1846 JSLinearString
* linear
= name
.toString()->ensureLinear(cx
);
1851 if (EqualStrings(linear
, cx
->names().Error
)) {
1853 } else if (EqualStrings(linear
, cx
->names().EvalError
)) {
1854 type
= JSEXN_EVALERR
;
1855 } else if (EqualStrings(linear
, cx
->names().RangeError
)) {
1856 type
= JSEXN_RANGEERR
;
1857 } else if (EqualStrings(linear
, cx
->names().ReferenceError
)) {
1858 type
= JSEXN_REFERENCEERR
;
1859 } else if (EqualStrings(linear
, cx
->names().SyntaxError
)) {
1860 type
= JSEXN_SYNTAXERR
;
1861 } else if (EqualStrings(linear
, cx
->names().TypeError
)) {
1862 type
= JSEXN_TYPEERR
;
1863 } else if (EqualStrings(linear
, cx
->names().URIError
)) {
1864 type
= JSEXN_URIERR
;
1865 } else if (EqualStrings(linear
, cx
->names().AggregateError
)) {
1866 type
= JSEXN_AGGREGATEERR
;
1870 // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
1871 RootedId
messageId(cx
, NameToId(cx
->names().message
));
1872 Rooted
<Maybe
<PropertyDescriptor
>> messageDesc(cx
);
1873 if (!GetOwnPropertyDescriptor(cx
, obj
, messageId
, &messageDesc
)) {
1877 // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
1878 // and ? ToString(valueMessageDesc.[[Value]]) otherwise.
1879 RootedString
message(cx
);
1880 if (messageDesc
.isSome() && messageDesc
->isDataDescriptor()) {
1881 RootedValue
messageVal(cx
, messageDesc
->value());
1882 message
= ToString
<CanGC
>(cx
, messageVal
);
1888 // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
1891 if (!objs
.append(ObjectValue(*obj
))) {
1895 Rooted
<ErrorObject
*> unwrapped(cx
, obj
->maybeUnwrapAs
<ErrorObject
>());
1896 MOZ_ASSERT(unwrapped
);
1898 // Non-standard: Serialize |stack|.
1899 // The Error stack property is saved as SavedFrames.
1900 RootedValue
stack(cx
, NullValue());
1901 RootedObject
stackObj(cx
, unwrapped
->stack());
1902 if (stackObj
&& stackObj
->canUnwrapAs
<SavedFrame
>()) {
1903 stack
.setObject(*stackObj
);
1904 if (!cx
->compartment()->wrap(cx
, &stack
)) {
1908 if (!otherEntries
.append(stack
)) {
1912 // Serialize |errors|
1913 if (type
== JSEXN_AGGREGATEERR
) {
1914 RootedValue
errors(cx
);
1915 if (!GetProperty(cx
, obj
, obj
, cx
->names().errors
, &errors
)) {
1918 if (!otherEntries
.append(errors
)) {
1922 if (!otherEntries
.append(NullValue())) {
1927 // Non-standard: Serialize |cause|. Because this property
1928 // might be missing we also write "hasCause" later.
1929 RootedId
causeId(cx
, NameToId(cx
->names().cause
));
1930 Rooted
<Maybe
<PropertyDescriptor
>> causeDesc(cx
);
1931 if (!GetOwnPropertyDescriptor(cx
, obj
, causeId
, &causeDesc
)) {
1935 Rooted
<Maybe
<Value
>> cause(cx
);
1936 if (causeDesc
.isSome() && causeDesc
->isDataDescriptor()) {
1937 cause
= mozilla::Some(causeDesc
->value());
1939 if (!cx
->compartment()->wrap(cx
, &cause
)) {
1942 if (!otherEntries
.append(cause
.get().valueOr(NullValue()))) {
1946 // |cause| + |errors| + |stack|, pushed in reverse order
1947 if (!counts
.append(3)) {
1953 if (!out
.writePair(SCTAG_ERROR_OBJECT
, type
)) {
1957 RootedValue
val(cx
, message
? StringValue(message
) : NullValue());
1958 if (!writePrimitive(val
)) {
1963 val
= BooleanValue(cause
.isSome());
1964 if (!writePrimitive(val
)) {
1968 // Non-standard: Also serialize fileName, lineNumber and columnNumber.
1970 JSAutoRealm
ar(cx
, unwrapped
);
1971 val
= StringValue(unwrapped
->fileName(cx
));
1973 if (!cx
->compartment()->wrap(cx
, &val
) || !writePrimitive(val
)) {
1977 val
= Int32Value(unwrapped
->lineNumber());
1978 if (!writePrimitive(val
)) {
1982 val
= Int32Value(*unwrapped
->columnNumber().addressOfValueForTranscode());
1983 return writePrimitive(val
);
1986 bool JSStructuredCloneWriter::writePrimitive(HandleValue v
) {
1987 MOZ_ASSERT(v
.isPrimitive());
1988 context()->check(v
);
1991 return writeString(SCTAG_STRING
, v
.toString());
1992 } else if (v
.isInt32()) {
1993 if (js::SupportDifferentialTesting()) {
1994 return out
.writeDouble(v
.toInt32());
1996 return out
.writePair(SCTAG_INT32
, v
.toInt32());
1997 } else if (v
.isDouble()) {
1998 return out
.writeDouble(v
.toDouble());
1999 } else if (v
.isBoolean()) {
2000 return out
.writePair(SCTAG_BOOLEAN
, v
.toBoolean());
2001 } else if (v
.isNull()) {
2002 return out
.writePair(SCTAG_NULL
, 0);
2003 } else if (v
.isUndefined()) {
2004 return out
.writePair(SCTAG_UNDEFINED
, 0);
2005 } else if (v
.isBigInt()) {
2006 return writeBigInt(SCTAG_BIGINT
, v
.toBigInt());
2009 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2012 bool JSStructuredCloneWriter::startWrite(HandleValue v
) {
2013 context()->check(v
);
2015 if (v
.isPrimitive()) {
2016 return writePrimitive(v
);
2019 if (!v
.isObject()) {
2020 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2023 RootedObject
obj(context(), &v
.toObject());
2026 if (!startObject(obj
, &backref
)) {
2034 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2039 case ESClass::Object
:
2040 case ESClass::Array
:
2041 return traverseObject(obj
, cls
);
2042 case ESClass::Number
: {
2043 RootedValue
unboxed(context());
2044 if (!Unbox(context(), obj
, &unboxed
)) {
2047 return out
.writePair(SCTAG_NUMBER_OBJECT
, 0) &&
2048 out
.writeDouble(unboxed
.toNumber());
2050 case ESClass::String
: {
2051 RootedValue
unboxed(context());
2052 if (!Unbox(context(), obj
, &unboxed
)) {
2055 return writeString(SCTAG_STRING_OBJECT
, unboxed
.toString());
2057 case ESClass::Boolean
: {
2058 RootedValue
unboxed(context());
2059 if (!Unbox(context(), obj
, &unboxed
)) {
2062 return out
.writePair(SCTAG_BOOLEAN_OBJECT
, unboxed
.toBoolean());
2064 case ESClass::RegExp
: {
2065 RegExpShared
* re
= RegExpToShared(context(), obj
);
2069 return out
.writePair(SCTAG_REGEXP_OBJECT
, re
->getFlags().value()) &&
2070 writeString(SCTAG_STRING
, re
->getSource());
2072 case ESClass::ArrayBuffer
: {
2073 if (JS::IsArrayBufferObject(obj
) && JS::ArrayBufferHasData(obj
)) {
2074 return writeArrayBuffer(obj
);
2078 case ESClass::SharedArrayBuffer
:
2079 if (JS::IsSharedArrayBufferObject(obj
)) {
2080 return writeSharedArrayBuffer(obj
);
2083 case ESClass::Date
: {
2084 RootedValue
unboxed(context());
2085 if (!Unbox(context(), obj
, &unboxed
)) {
2088 return out
.writePair(SCTAG_DATE_OBJECT
, 0) &&
2089 out
.writeDouble(unboxed
.toNumber());
2092 return traverseSet(obj
);
2094 return traverseMap(obj
);
2095 case ESClass::Error
:
2096 return traverseError(obj
);
2097 case ESClass::BigInt
: {
2098 RootedValue
unboxed(context());
2099 if (!Unbox(context(), obj
, &unboxed
)) {
2102 return writeBigInt(SCTAG_BIGINT_OBJECT
, unboxed
.toBigInt());
2104 case ESClass::Promise
:
2105 case ESClass::MapIterator
:
2106 case ESClass::SetIterator
:
2107 case ESClass::Arguments
:
2108 case ESClass::Function
:
2111 #ifdef ENABLE_RECORD_TUPLE
2112 case ESClass::Record
:
2113 case ESClass::Tuple
:
2114 MOZ_CRASH("Record and Tuple are not supported");
2117 case ESClass::Other
: {
2118 if (obj
->canUnwrapAs
<TypedArrayObject
>()) {
2119 return writeTypedArray(obj
);
2121 if (obj
->canUnwrapAs
<DataViewObject
>()) {
2122 return writeDataView(obj
);
2124 if (wasm::IsSharedWasmMemoryObject(obj
)) {
2125 return writeSharedWasmMemory(obj
);
2127 if (obj
->canUnwrapAs
<SavedFrame
>()) {
2128 return traverseSavedFrame(obj
);
2134 if (out
.buf
.callbacks_
&& out
.buf
.callbacks_
->write
) {
2135 bool sameProcessScopeRequired
= false;
2136 if (!out
.buf
.callbacks_
->write(context(), this, obj
,
2137 &sameProcessScopeRequired
,
2138 out
.buf
.closure_
)) {
2142 if (sameProcessScopeRequired
) {
2143 output().sameProcessScopeRequired();
2149 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2152 bool JSStructuredCloneWriter::writeHeader() {
2153 return out
.writePair(SCTAG_HEADER
, (uint32_t)output().scope());
2156 bool JSStructuredCloneWriter::writeTransferMap() {
2157 if (transferableObjects
.empty()) {
2161 if (!out
.writePair(SCTAG_TRANSFER_MAP_HEADER
, (uint32_t)SCTAG_TM_UNREAD
)) {
2165 if (!out
.write(transferableObjects
.length())) {
2169 RootedObject
obj(context());
2170 for (auto* o
: transferableObjects
) {
2172 if (!memory
.put(obj
, memory
.count())) {
2173 ReportOutOfMemory(context());
2177 // Emit a placeholder pointer. We defer stealing the data until later
2178 // (and, if necessary, detaching this object if it's an ArrayBuffer).
2179 if (!out
.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY
,
2180 JS::SCTAG_TMO_UNFILLED
)) {
2183 if (!out
.write(0)) { // Pointer to ArrayBuffer contents.
2186 if (!out
.write(0)) { // extraData
2194 bool JSStructuredCloneWriter::transferOwnership() {
2195 if (transferableObjects
.empty()) {
2199 // Walk along the transferables and the transfer map at the same time,
2200 // grabbing out pointers from the transferables and stuffing them into the
2202 auto point
= out
.iter();
2203 MOZ_RELEASE_ASSERT(point
.canPeek());
2204 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
2207 MOZ_RELEASE_ASSERT(point
.canPeek());
2208 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
2209 SCTAG_TRANSFER_MAP_HEADER
);
2211 MOZ_RELEASE_ASSERT(point
.canPeek());
2212 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point
.peek()) ==
2213 transferableObjects
.length());
2216 JSContext
* cx
= context();
2217 RootedObject
obj(cx
);
2218 JS::StructuredCloneScope scope
= output().scope();
2219 for (auto* o
: transferableObjects
) {
2223 JS::TransferableOwnership ownership
;
2228 SCInput::getPair(point
.peek(), &tag
, (uint32_t*)&ownership
);
2229 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2230 MOZ_ASSERT(ownership
== JS::SCTAG_TMO_UNFILLED
);
2234 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
2238 if (cls
== ESClass::ArrayBuffer
) {
2239 tag
= SCTAG_TRANSFER_MAP_ARRAY_BUFFER
;
2241 // The current setup of the array buffer inheritance hierarchy doesn't
2242 // lend itself well to generic manipulation via proxies.
2243 Rooted
<ArrayBufferObject
*> arrayBuffer(
2244 cx
, obj
->maybeUnwrapAs
<ArrayBufferObject
>());
2245 JSAutoRealm
ar(cx
, arrayBuffer
);
2247 if (arrayBuffer
->isDetached()) {
2248 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED
);
2252 if (arrayBuffer
->isPreparedForAsmJS()) {
2253 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER
);
2257 if (scope
== JS::StructuredCloneScope::DifferentProcess
||
2258 scope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
2259 // Write Transferred ArrayBuffers in DifferentProcess scope at
2260 // the end of the clone buffer, and store the offset within the
2261 // buffer to where the ArrayBuffer was written. Note that this
2262 // will invalidate the current position iterator.
2264 size_t pointOffset
= out
.offset(point
);
2265 tag
= SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
;
2266 ownership
= JS::SCTAG_TMO_UNOWNED
;
2268 extraData
= out
.tell() -
2269 pointOffset
; // Offset from tag to current end of buffer
2270 if (!writeArrayBuffer(arrayBuffer
)) {
2271 ReportOutOfMemory(cx
);
2275 // Must refresh the point iterator after its collection has
2278 point
+= pointOffset
;
2280 if (!JS::DetachArrayBuffer(cx
, arrayBuffer
)) {
2284 size_t nbytes
= arrayBuffer
->byteLength();
2286 using BufferContents
= ArrayBufferObject::BufferContents
;
2288 BufferContents bufContents
=
2289 ArrayBufferObject::extractStructuredCloneContents(cx
, arrayBuffer
);
2291 return false; // out of memory
2294 content
= bufContents
.data();
2295 if (bufContents
.kind() == ArrayBufferObject::MAPPED
) {
2296 ownership
= JS::SCTAG_TMO_MAPPED_DATA
;
2298 MOZ_ASSERT(bufContents
.kind() == ArrayBufferObject::MALLOCED
,
2299 "failing to handle new ArrayBuffer kind?");
2300 ownership
= JS::SCTAG_TMO_ALLOC_DATA
;
2305 if (!out
.buf
.callbacks_
|| !out
.buf
.callbacks_
->writeTransfer
) {
2306 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
2308 if (!out
.buf
.callbacks_
->writeTransfer(cx
, obj
, out
.buf
.closure_
, &tag
,
2309 &ownership
, &content
,
2313 MOZ_ASSERT(tag
> SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2316 point
.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag
, ownership
)));
2317 MOZ_ALWAYS_TRUE(point
.advance());
2319 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content
)));
2320 MOZ_ALWAYS_TRUE(point
.advance());
2321 point
.write(NativeEndian::swapToLittleEndian(extraData
));
2322 MOZ_ALWAYS_TRUE(point
.advance());
2326 // Make sure there aren't any more transfer map entries after the expected
2327 // number we read out.
2328 if (!point
.done()) {
2330 SCInput::getPair(point
.peek(), &tag
, &data
);
2331 MOZ_ASSERT(tag
< SCTAG_TRANSFER_MAP_HEADER
||
2332 tag
>= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES
);
2338 bool JSStructuredCloneWriter::write(HandleValue v
) {
2339 if (!startWrite(v
)) {
2343 RootedObject
obj(context());
2344 RootedValue
key(context());
2345 RootedValue
val(context());
2346 RootedId
id(context());
2348 RootedValue
cause(context());
2349 RootedValue
errors(context());
2350 RootedValue
stack(context());
2352 while (!counts
.empty()) {
2353 obj
= &objs
.back().toObject();
2354 context()->check(obj
);
2355 if (counts
.back()) {
2359 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2363 if (cls
== ESClass::Map
) {
2364 key
= otherEntries
.popCopy();
2368 val
= otherEntries
.popCopy();
2371 if (!startWrite(key
) || !startWrite(val
)) {
2374 } else if (cls
== ESClass::Set
|| obj
->canUnwrapAs
<SavedFrame
>()) {
2375 key
= otherEntries
.popCopy();
2378 if (!startWrite(key
)) {
2381 } else if (cls
== ESClass::Error
) {
2382 cause
= otherEntries
.popCopy();
2386 errors
= otherEntries
.popCopy();
2390 stack
= otherEntries
.popCopy();
2393 if (!startWrite(cause
) || !startWrite(errors
) || !startWrite(stack
)) {
2397 id
= objectEntries
.popCopy();
2398 key
= IdToValue(id
);
2401 // If obj still has an own property named id, write it out.
2403 if (GetOwnPropertyPure(context(), obj
, id
, val
.address(), &found
)) {
2405 if (!writePrimitive(key
) || !startWrite(val
)) {
2412 if (!HasOwnProperty(context(), obj
, id
, &found
)) {
2417 #if FUZZING_JS_FUZZILLI
2418 // supress calls into user code
2419 if (js::SupportDifferentialTesting()) {
2420 fprintf(stderr
, "Differential testing: cannot call GetProperty\n");
2425 if (!writePrimitive(key
) ||
2426 !GetProperty(context(), obj
, obj
, id
, &val
) || !startWrite(val
)) {
2432 if (!out
.writePair(SCTAG_END_OF_KEYS
, 0)) {
2441 return transferOwnership();
2444 JSStructuredCloneReader::JSStructuredCloneReader(
2445 SCInput
& in
, JS::StructuredCloneScope scope
,
2446 const JS::CloneDataPolicy
& cloneDataPolicy
,
2447 const JSStructuredCloneCallbacks
* cb
, void* cbClosure
)
2449 allowedScope(scope
),
2450 cloneDataPolicy(cloneDataPolicy
),
2452 objState(in
.context(), in
.context()),
2453 allObjs(in
.context()),
2457 gcHeap(in
.context()) {
2458 // Avoid the need to bounds check by keeping a never-matching element at the
2459 // base of the `objState` stack. This append() will always succeed because
2460 // the objState vector has a nonzero MinInlineCapacity.
2461 MOZ_ALWAYS_TRUE(objState
.append(std::make_pair(nullptr, true)));
2464 template <typename CharT
>
2465 JSString
* JSStructuredCloneReader::readStringImpl(
2466 uint32_t nchars
, ShouldAtomizeStrings atomize
) {
2467 InlineCharBuffer
<CharT
> chars
;
2468 if (!chars
.maybeAlloc(context(), nchars
) ||
2469 !in
.readChars(chars
.get(), nchars
)) {
2474 return chars
.toAtom(context(), nchars
);
2477 return chars
.toStringDontDeflate(context(), nchars
, gcHeap
);
2480 JSString
* JSStructuredCloneReader::readString(uint32_t data
,
2481 ShouldAtomizeStrings atomize
) {
2482 uint32_t nchars
= data
& BitMask(31);
2483 bool latin1
= data
& (1 << 31);
2485 if (nchars
> JSString::MAX_LENGTH
) {
2486 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2487 JSMSG_SC_BAD_SERIALIZED_DATA
, "string length");
2491 return latin1
? readStringImpl
<Latin1Char
>(nchars
, atomize
)
2492 : readStringImpl
<char16_t
>(nchars
, atomize
);
2495 [[nodiscard
]] bool JSStructuredCloneReader::readUint32(uint32_t* num
) {
2496 Rooted
<Value
> lineVal(context());
2497 if (!startRead(&lineVal
)) {
2500 if (!lineVal
.isInt32()) {
2501 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2502 JSMSG_SC_BAD_SERIALIZED_DATA
, "integer required");
2505 *num
= uint32_t(lineVal
.toInt32());
2509 BigInt
* JSStructuredCloneReader::readBigInt(uint32_t data
) {
2510 size_t length
= data
& BitMask(31);
2511 bool isNegative
= data
& (1 << 31);
2513 return BigInt::zero(context());
2515 RootedBigInt
result(context(), BigInt::createUninitialized(
2516 context(), length
, isNegative
, gcHeap
));
2520 if (!in
.readArray(result
->digits().data(), length
)) {
2526 static uint32_t TagToV1ArrayType(uint32_t tag
) {
2527 MOZ_ASSERT(tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&&
2528 tag
<= SCTAG_TYPED_ARRAY_V1_MAX
);
2529 return tag
- SCTAG_TYPED_ARRAY_V1_MIN
;
2532 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType
,
2534 MutableHandleValue vp
,
2536 if (arrayType
> (v1Read
? Scalar::Uint8Clamped
: Scalar::BigUint64
)) {
2537 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2538 JSMSG_SC_BAD_SERIALIZED_DATA
,
2539 "unhandled typed array element type");
2543 // Push a placeholder onto the allObjs list to stand in for the typed array.
2544 uint32_t placeholderIndex
= allObjs
.length();
2545 Value dummy
= UndefinedValue();
2546 if (!allObjs
.append(dummy
)) {
2550 // Read the ArrayBuffer object and its contents (but no properties)
2551 RootedValue
v(context());
2552 uint64_t byteOffset
;
2554 if (!readV1ArrayBuffer(arrayType
, nelems
, &v
)) {
2559 if (!startRead(&v
)) {
2562 if (!in
.read(&byteOffset
)) {
2567 // Ensure invalid 64-bit values won't be truncated below.
2568 if (nelems
> ArrayBufferObject::MaxByteLength
||
2569 byteOffset
> ArrayBufferObject::MaxByteLength
) {
2570 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2571 JSMSG_SC_BAD_SERIALIZED_DATA
,
2572 "invalid typed array length or offset");
2576 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2577 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2578 JSMSG_SC_BAD_SERIALIZED_DATA
,
2579 "typed array must be backed by an ArrayBuffer");
2583 RootedObject
buffer(context(), &v
.toObject());
2584 RootedObject
obj(context(), nullptr);
2586 switch (arrayType
) {
2587 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2588 case Scalar::Name: \
2589 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2590 byteOffset, nelems) \
2594 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER
)
2595 #undef CREATE_FROM_BUFFER
2598 MOZ_CRASH("Can't happen: arrayType range checked above");
2606 allObjs
[placeholderIndex
].set(vp
);
2611 bool JSStructuredCloneReader::readDataView(uint64_t byteLength
,
2612 MutableHandleValue vp
) {
2613 // Push a placeholder onto the allObjs list to stand in for the DataView.
2614 uint32_t placeholderIndex
= allObjs
.length();
2615 Value dummy
= UndefinedValue();
2616 if (!allObjs
.append(dummy
)) {
2620 // Read the ArrayBuffer object and its contents (but no properties).
2621 RootedValue
v(context());
2622 if (!startRead(&v
)) {
2625 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2626 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2627 JSMSG_SC_BAD_SERIALIZED_DATA
,
2628 "DataView must be backed by an ArrayBuffer");
2633 uint64_t byteOffset
;
2634 if (!in
.read(&byteOffset
)) {
2638 // Ensure invalid 64-bit values won't be truncated below.
2639 if (byteLength
> ArrayBufferObject::MaxByteLength
||
2640 byteOffset
> ArrayBufferObject::MaxByteLength
) {
2641 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2642 JSMSG_SC_BAD_SERIALIZED_DATA
,
2643 "invalid DataView length or offset");
2647 RootedObject
buffer(context(), &v
.toObject());
2648 RootedObject
obj(context(),
2649 JS_NewDataView(context(), buffer
, byteOffset
, byteLength
));
2655 allObjs
[placeholderIndex
].set(vp
);
2660 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type
,
2662 MutableHandleValue vp
) {
2663 // V2 stores the length in |data|. The current version stores the
2664 // length separately to allow larger length values.
2665 uint64_t nbytes
= 0;
2666 if (type
== SCTAG_ARRAY_BUFFER_OBJECT
) {
2667 if (!in
.read(&nbytes
)) {
2671 MOZ_ASSERT(type
== SCTAG_ARRAY_BUFFER_OBJECT_V2
);
2675 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2676 // below, so we have to check this here.
2677 if (nbytes
> ArrayBufferObject::MaxByteLength
) {
2678 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2679 JSMSG_BAD_ARRAY_LENGTH
);
2683 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), size_t(nbytes
));
2688 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2689 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2690 return in
.readArray(buffer
.dataPointer(), nbytes
);
2693 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp
) {
2694 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2695 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2696 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2697 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2698 : JS_SCERR_NOT_CLONABLE
;
2699 ReportDataCloneError(context(), callbacks
, error
, closure
,
2700 "SharedArrayBuffer");
2704 uint64_t byteLength
;
2705 if (!in
.readBytes(&byteLength
, sizeof(byteLength
))) {
2706 return in
.reportTruncated();
2709 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2710 // below, so we have to check this here.
2711 if (byteLength
> ArrayBufferObject::MaxByteLength
) {
2712 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2713 JSMSG_BAD_ARRAY_LENGTH
);
2718 if (!in
.readBytes(&p
, sizeof(p
))) {
2719 return in
.reportTruncated();
2722 SharedArrayRawBuffer
* rawbuf
= reinterpret_cast<SharedArrayRawBuffer
*>(p
);
2724 // There's no guarantee that the receiving agent has enabled shared memory
2725 // even if the transmitting agent has done so. Ideally we'd check at the
2726 // transmission point, but that's tricky, and it will be a very rare problem
2727 // in any case. Just fail at the receiving end if we can't handle it.
2732 .getSharedMemoryAndAtomicsEnabled()) {
2733 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2734 JSMSG_SC_SAB_DISABLED
);
2738 // The new object will have a new reference to the rawbuf.
2740 if (!rawbuf
->addReference()) {
2741 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2742 JSMSG_SC_SAB_REFCNT_OFLO
);
2746 RootedObject
obj(context(),
2747 SharedArrayBufferObject::New(context(), rawbuf
, byteLength
));
2749 rawbuf
->dropReference();
2753 // `rawbuf` is now owned by `obj`.
2755 if (callbacks
&& callbacks
->sabCloned
&&
2756 !callbacks
->sabCloned(context(), /*receiving=*/true, closure
)) {
2764 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes
,
2765 MutableHandleValue vp
) {
2766 JSContext
* cx
= context();
2768 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2769 JSMSG_SC_BAD_SERIALIZED_DATA
,
2770 "invalid shared wasm memory tag");
2774 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2775 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2776 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2777 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2778 : JS_SCERR_NOT_CLONABLE
;
2779 ReportDataCloneError(cx
, callbacks
, error
, closure
, "WebAssembly.Memory");
2783 // Read the isHuge flag
2784 RootedValue
isHuge(cx
);
2785 if (!startRead(&isHuge
)) {
2789 // Read the SharedArrayBuffer object.
2790 RootedValue
payload(cx
);
2791 if (!startRead(&payload
)) {
2794 if (!payload
.isObject() ||
2795 !payload
.toObject().is
<SharedArrayBufferObject
>()) {
2796 JS_ReportErrorNumberASCII(
2797 context(), GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
2798 "shared wasm memory must be backed by a SharedArrayBuffer");
2802 Rooted
<ArrayBufferObjectMaybeShared
*> sab(
2803 cx
, &payload
.toObject().as
<SharedArrayBufferObject
>());
2805 // Construct the memory.
2806 RootedObject
proto(cx
, &cx
->global()->getPrototype(JSProto_WasmMemory
));
2807 RootedObject
memory(
2808 cx
, WasmMemoryObject::create(cx
, sab
, isHuge
.toBoolean(), proto
));
2813 vp
.setObject(*memory
);
2818 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2819 * endianness-conversion while reading.
2821 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType
,
2823 MutableHandleValue vp
) {
2824 if (arrayType
> Scalar::Uint8Clamped
) {
2825 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2826 JSMSG_SC_BAD_SERIALIZED_DATA
,
2827 "invalid TypedArray type");
2831 mozilla::CheckedInt
<size_t> nbytes
=
2832 mozilla::CheckedInt
<size_t>(nelems
) *
2833 TypedArrayElemSize(static_cast<Scalar::Type
>(arrayType
));
2834 if (!nbytes
.isValid() || nbytes
.value() > UINT32_MAX
) {
2835 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2836 JSMSG_SC_BAD_SERIALIZED_DATA
,
2837 "invalid typed array size");
2841 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), nbytes
.value());
2846 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2847 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2849 switch (arrayType
) {
2852 case Scalar::Uint8Clamped
:
2853 return in
.readArray((uint8_t*)buffer
.dataPointer(), nelems
);
2855 case Scalar::Uint16
:
2856 return in
.readArray((uint16_t*)buffer
.dataPointer(), nelems
);
2858 case Scalar::Uint32
:
2859 case Scalar::Float32
:
2860 return in
.readArray((uint32_t*)buffer
.dataPointer(), nelems
);
2861 case Scalar::Float64
:
2862 case Scalar::BigInt64
:
2863 case Scalar::BigUint64
:
2864 return in
.readArray((uint64_t*)buffer
.dataPointer(), nelems
);
2866 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2870 static bool PrimitiveToObject(JSContext
* cx
, MutableHandleValue vp
) {
2871 JSObject
* obj
= js::PrimitiveToObject(cx
, vp
);
2880 bool JSStructuredCloneReader::startRead(MutableHandleValue vp
,
2881 ShouldAtomizeStrings atomizeStrings
) {
2883 bool alreadAppended
= false;
2885 if (!in
.readPair(&tag
, &data
)) {
2896 case SCTAG_UNDEFINED
:
2905 case SCTAG_BOOLEAN_OBJECT
:
2906 vp
.setBoolean(!!data
);
2907 if (tag
== SCTAG_BOOLEAN_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2913 case SCTAG_STRING_OBJECT
: {
2914 JSString
* str
= readString(data
, atomizeStrings
);
2919 if (tag
== SCTAG_STRING_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2925 case SCTAG_NUMBER_OBJECT
: {
2927 if (!in
.readDouble(&d
)) {
2930 vp
.setDouble(CanonicalizeNaN(d
));
2931 if (!PrimitiveToObject(context(), vp
)) {
2938 case SCTAG_BIGINT_OBJECT
: {
2939 RootedBigInt
bi(context(), readBigInt(data
));
2944 if (tag
== SCTAG_BIGINT_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2950 case SCTAG_DATE_OBJECT
: {
2952 if (!in
.readDouble(&d
)) {
2955 JS::ClippedTime t
= JS::TimeClip(d
);
2956 if (!NumbersAreIdentical(d
, t
.toDouble())) {
2957 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2958 JSMSG_SC_BAD_SERIALIZED_DATA
, "date");
2961 JSObject
* obj
= NewDateObjectMsec(context(), t
);
2969 case SCTAG_REGEXP_OBJECT
: {
2970 if ((data
& RegExpFlag::AllFlags
) != data
) {
2971 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2972 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
2976 RegExpFlags
flags(AssertedCast
<uint8_t>(data
));
2978 uint32_t tag2
, stringData
;
2979 if (!in
.readPair(&tag2
, &stringData
)) {
2982 if (tag2
!= SCTAG_STRING
) {
2983 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2984 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
2988 JSString
* str
= readString(stringData
, AtomizeStrings
);
2993 Rooted
<JSAtom
*> atom(context(), &str
->asAtom());
2995 NewObjectKind kind
=
2996 gcHeap
== gc::Heap::Tenured
? TenuredObject
: GenericObject
;
2997 RegExpObject
* reobj
= RegExpObject::create(context(), atom
, flags
, kind
);
3001 vp
.setObject(*reobj
);
3005 case SCTAG_ARRAY_OBJECT
:
3006 case SCTAG_OBJECT_OBJECT
: {
3007 NewObjectKind kind
=
3008 gcHeap
== gc::Heap::Tenured
? TenuredObject
: GenericObject
;
3010 if (tag
== SCTAG_ARRAY_OBJECT
) {
3011 obj
= NewDenseUnallocatedArray(
3012 context(), NativeEndian::swapFromLittleEndian(data
), kind
);
3014 obj
= NewPlainObject(context(), kind
);
3016 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3024 case SCTAG_BACK_REFERENCE_OBJECT
: {
3025 if (data
>= allObjs
.length() || !allObjs
[data
].isObject()) {
3026 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3027 JSMSG_SC_BAD_SERIALIZED_DATA
,
3028 "invalid back reference in input");
3031 vp
.set(allObjs
[data
]);
3035 case SCTAG_TRANSFER_MAP_HEADER
:
3036 case SCTAG_TRANSFER_MAP_PENDING_ENTRY
:
3037 // We should be past all the transfer map tags.
3038 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3039 JSMSG_SC_BAD_SERIALIZED_DATA
, "invalid input");
3042 case SCTAG_ARRAY_BUFFER_OBJECT_V2
:
3043 case SCTAG_ARRAY_BUFFER_OBJECT
:
3044 if (!readArrayBuffer(StructuredDataType(tag
), data
, vp
)) {
3049 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT
:
3050 if (!readSharedArrayBuffer(vp
)) {
3055 case SCTAG_SHARED_WASM_MEMORY_OBJECT
:
3056 if (!readSharedWasmMemory(data
, vp
)) {
3061 case SCTAG_TYPED_ARRAY_OBJECT_V2
: {
3062 // readTypedArray adds the array to allObjs.
3063 // V2 stores the length (nelems) in |data| and the arrayType separately.
3065 if (!in
.read(&arrayType
)) {
3068 uint64_t nelems
= data
;
3069 return readTypedArray(arrayType
, nelems
, vp
);
3072 case SCTAG_TYPED_ARRAY_OBJECT
: {
3073 // readTypedArray adds the array to allObjs.
3074 // The current version stores the array type in |data| and the length
3075 // (nelems) separately to support large TypedArrays.
3076 uint32_t arrayType
= data
;
3078 if (!in
.read(&nelems
)) {
3081 return readTypedArray(arrayType
, nelems
, vp
);
3084 case SCTAG_DATA_VIEW_OBJECT_V2
: {
3085 // readDataView adds the array to allObjs.
3086 uint64_t byteLength
= data
;
3087 return readDataView(byteLength
, vp
);
3090 case SCTAG_DATA_VIEW_OBJECT
: {
3091 // readDataView adds the array to allObjs.
3092 uint64_t byteLength
;
3093 if (!in
.read(&byteLength
)) {
3096 return readDataView(byteLength
, vp
);
3099 case SCTAG_MAP_OBJECT
: {
3100 JSObject
* obj
= MapObject::create(context());
3101 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3108 case SCTAG_SET_OBJECT
: {
3109 JSObject
* obj
= SetObject::create(context());
3110 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3117 case SCTAG_SAVED_FRAME_OBJECT
: {
3118 auto* obj
= readSavedFrameHeader(data
);
3119 if (!obj
|| !objs
.append(ObjectValue(*obj
)) ||
3120 !objState
.append(std::make_pair(obj
, false))) {
3127 case SCTAG_ERROR_OBJECT
: {
3128 auto* obj
= readErrorHeader(data
);
3129 if (!obj
|| !objs
.append(ObjectValue(*obj
)) ||
3130 !objState
.append(std::make_pair(obj
, false))) {
3137 case SCTAG_END_OF_KEYS
:
3138 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3139 JSMSG_SC_BAD_SERIALIZED_DATA
,
3145 if (tag
<= SCTAG_FLOAT_MAX
) {
3146 double d
= ReinterpretPairAsDouble(tag
, data
);
3147 vp
.setNumber(CanonicalizeNaN(d
));
3151 if (SCTAG_TYPED_ARRAY_V1_MIN
<= tag
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
3152 // A v1-format typed array
3153 // readTypedArray adds the array to allObjs
3154 return readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
3157 if (!callbacks
|| !callbacks
->read
) {
3158 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3159 JSMSG_SC_BAD_SERIALIZED_DATA
,
3160 "unsupported type");
3164 // callbacks->read() might read other objects from the buffer.
3165 // In startWrite we always write the object itself before calling
3166 // the custom function. We should do the same here to keep
3167 // indexing consistent.
3168 uint32_t placeholderIndex
= allObjs
.length();
3169 Value dummy
= UndefinedValue();
3170 if (!allObjs
.append(dummy
)) {
3174 callbacks
->read(context(), this, cloneDataPolicy
, tag
, data
, closure
);
3179 allObjs
[placeholderIndex
].set(vp
);
3180 alreadAppended
= true;
3184 if (!alreadAppended
&& vp
.isObject() && !allObjs
.append(vp
)) {
3191 bool JSStructuredCloneReader::readHeader() {
3193 if (!in
.getPair(&tag
, &data
)) {
3194 return in
.reportTruncated();
3197 JS::StructuredCloneScope storedScope
;
3198 if (tag
== SCTAG_HEADER
) {
3199 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3200 storedScope
= JS::StructuredCloneScope(data
);
3202 // Old structured clone buffer. We must have read it from disk.
3203 storedScope
= JS::StructuredCloneScope::DifferentProcessForIndexedDB
;
3206 // Backward compatibility with old structured clone buffers. Value '0' was
3207 // used for SameProcessSameThread scope.
3208 if ((int)storedScope
== 0) {
3209 storedScope
= JS::StructuredCloneScope::SameProcess
;
3212 if (storedScope
< JS::StructuredCloneScope::SameProcess
||
3213 storedScope
> JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3214 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3215 JSMSG_SC_BAD_SERIALIZED_DATA
,
3216 "invalid structured clone scope");
3220 if (allowedScope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3221 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
3222 // clones are incorrect. Treat them as if they were DifferentProcess.
3223 allowedScope
= JS::StructuredCloneScope::DifferentProcess
;
3227 if (storedScope
< allowedScope
) {
3228 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3229 JSMSG_SC_BAD_SERIALIZED_DATA
,
3230 "incompatible structured clone scope");
3237 bool JSStructuredCloneReader::readTransferMap() {
3238 JSContext
* cx
= context();
3239 auto headerPos
= in
.tell();
3242 if (!in
.getPair(&tag
, &data
)) {
3243 return in
.reportTruncated();
3246 if (tag
!= SCTAG_TRANSFER_MAP_HEADER
||
3247 TransferableMapHeader(data
) == SCTAG_TM_TRANSFERRED
) {
3251 uint64_t numTransferables
;
3252 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3253 if (!in
.read(&numTransferables
)) {
3257 for (uint64_t i
= 0; i
< numTransferables
; i
++) {
3258 auto pos
= in
.tell();
3260 if (!in
.readPair(&tag
, &data
)) {
3264 if (tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
) {
3265 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3269 RootedObject
obj(cx
);
3272 if (!in
.readPtr(&content
)) {
3277 if (!in
.read(&extraData
)) {
3281 if (tag
== SCTAG_TRANSFER_MAP_ARRAY_BUFFER
) {
3282 if (allowedScope
== JS::StructuredCloneScope::DifferentProcess
||
3284 JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3285 // Transferred ArrayBuffers in a DifferentProcess clone buffer
3286 // are treated as if they weren't Transferred at all. We should
3287 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
3288 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3292 MOZ_RELEASE_ASSERT(extraData
<= ArrayBufferObject::MaxByteLength
);
3293 size_t nbytes
= extraData
;
3295 MOZ_ASSERT(data
== JS::SCTAG_TMO_ALLOC_DATA
||
3296 data
== JS::SCTAG_TMO_MAPPED_DATA
);
3297 if (data
== JS::SCTAG_TMO_ALLOC_DATA
) {
3298 // When the ArrayBuffer can't be allocated, |content| will be free'ed
3299 // in `JSStructuredCloneData::discardTransferables()`.
3300 obj
= JS::NewArrayBufferWithContents(
3301 cx
, nbytes
, content
,
3302 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory
);
3303 } else if (data
== JS::SCTAG_TMO_MAPPED_DATA
) {
3304 obj
= JS::NewMappedArrayBufferWithContents(cx
, nbytes
, content
);
3306 } else if (tag
== SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
) {
3307 auto savedPos
= in
.tell();
3308 auto guard
= mozilla::MakeScopeExit([&] { in
.seekTo(savedPos
); });
3310 if (!in
.seekBy(static_cast<size_t>(extraData
))) {
3314 if (tailStartPos
.isNothing()) {
3315 tailStartPos
= mozilla::Some(in
.tell());
3319 if (!in
.readPair(&tag
, &data
)) {
3322 if (tag
!= SCTAG_ARRAY_BUFFER_OBJECT_V2
&&
3323 tag
!= SCTAG_ARRAY_BUFFER_OBJECT
) {
3324 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3327 RootedValue
val(cx
);
3328 if (!readArrayBuffer(StructuredDataType(tag
), data
, &val
)) {
3331 obj
= &val
.toObject();
3332 tailEndPos
= mozilla::Some(in
.tell());
3334 if (!callbacks
|| !callbacks
->readTransfer
) {
3335 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3338 if (!callbacks
->readTransfer(cx
, this, cloneDataPolicy
, tag
, content
,
3339 extraData
, closure
, &obj
)) {
3340 if (!cx
->isExceptionPending()) {
3341 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3346 MOZ_ASSERT(!cx
->isExceptionPending());
3349 // On failure, the buffer will still own the data (since its ownership
3350 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3351 // DiscardTransferables.
3356 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3358 pos
.write(PairToUInt64(tag
, JS::SCTAG_TMO_UNOWNED
));
3359 MOZ_ASSERT(!pos
.done());
3361 if (!allObjs
.append(ObjectValue(*obj
))) {
3366 // Mark the whole transfer map as consumed.
3368 SCInput::getPair(headerPos
.peek(), &tag
, &data
);
3369 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_HEADER
);
3370 MOZ_ASSERT(TransferableMapHeader(data
) != SCTAG_TM_TRANSFERRED
);
3373 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER
, SCTAG_TM_TRANSFERRED
));
3378 JSObject
* JSStructuredCloneReader::readSavedFrameHeader(
3379 uint32_t principalsTag
) {
3380 Rooted
<SavedFrame
*> savedFrame(context(), SavedFrame::create(context()));
3385 JSPrincipals
* principals
;
3386 if (principalsTag
== SCTAG_JSPRINCIPALS
) {
3387 if (!context()->runtime()->readPrincipals
) {
3388 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3389 JSMSG_SC_UNSUPPORTED_TYPE
);
3393 if (!context()->runtime()->readPrincipals(context(), this, &principals
)) {
3396 } else if (principalsTag
==
3397 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
) {
3398 principals
= &ReconstructedSavedFramePrincipals::IsSystem
;
3399 principals
->refcount
++;
3400 } else if (principalsTag
==
3401 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
) {
3402 principals
= &ReconstructedSavedFramePrincipals::IsNotSystem
;
3403 principals
->refcount
++;
3404 } else if (principalsTag
== SCTAG_NULL_JSPRINCIPALS
) {
3405 principals
= nullptr;
3407 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3408 JSMSG_SC_BAD_SERIALIZED_DATA
,
3409 "bad SavedFrame principals");
3413 RootedValue
mutedErrors(context());
3414 RootedValue
source(context());
3416 // Read a |mutedErrors| boolean followed by a |source| string.
3417 // The |mutedErrors| boolean is present in all new structured-clone data,
3418 // but in older data it will be absent and only the |source| string will be
3420 if (!startRead(&mutedErrors
, AtomizeStrings
)) {
3424 if (mutedErrors
.isBoolean()) {
3425 if (!startRead(&source
, AtomizeStrings
) || !source
.isString()) {
3428 } else if (mutedErrors
.isString()) {
3429 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3430 // this is actually just a |source| string.
3431 source
= mutedErrors
;
3432 mutedErrors
.setBoolean(true); // Safe default value.
3439 savedFrame
->initPrincipalsAlreadyHeldAndMutedErrors(principals
,
3440 mutedErrors
.toBoolean());
3442 savedFrame
->initSource(&source
.toString()->asAtom());
3445 if (!readUint32(&line
)) {
3448 savedFrame
->initLine(line
);
3450 JS::TaggedColumnNumberOneOrigin column
;
3451 if (!readUint32(column
.addressOfValueForTranscode())) {
3454 savedFrame
->initColumn(column
);
3456 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3457 // are only valid within a specific process.
3458 savedFrame
->initSourceId(0);
3460 RootedValue
name(context());
3461 if (!startRead(&name
, AtomizeStrings
)) {
3464 if (!(name
.isString() || name
.isNull())) {
3465 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3466 JSMSG_SC_BAD_SERIALIZED_DATA
,
3467 "invalid saved frame cause");
3470 JSAtom
* atomName
= nullptr;
3471 if (name
.isString()) {
3472 atomName
= &name
.toString()->asAtom();
3475 savedFrame
->initFunctionDisplayName(atomName
);
3477 RootedValue
cause(context());
3478 if (!startRead(&cause
, AtomizeStrings
)) {
3481 if (!(cause
.isString() || cause
.isNull())) {
3482 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3483 JSMSG_SC_BAD_SERIALIZED_DATA
,
3484 "invalid saved frame cause");
3487 JSAtom
* atomCause
= nullptr;
3488 if (cause
.isString()) {
3489 atomCause
= &cause
.toString()->asAtom();
3491 savedFrame
->initAsyncCause(atomCause
);
3496 // SavedFrame object: there is one child value, the parent SavedFrame,
3497 // which is either null or another SavedFrame object.
3498 bool JSStructuredCloneReader::readSavedFrameFields(Handle
<SavedFrame
*> frameObj
,
3502 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3503 JSMSG_SC_BAD_SERIALIZED_DATA
,
3504 "multiple SavedFrame parents");
3508 SavedFrame
* parentFrame
;
3509 if (parent
.isNull()) {
3510 parentFrame
= nullptr;
3511 } else if (parent
.isObject() && parent
.toObject().is
<SavedFrame
>()) {
3512 parentFrame
= &parent
.toObject().as
<SavedFrame
>();
3514 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3515 JSMSG_SC_BAD_SERIALIZED_DATA
,
3516 "invalid SavedFrame parent");
3520 frameObj
->initParent(parentFrame
);
3525 JSObject
* JSStructuredCloneReader::readErrorHeader(uint32_t type
) {
3526 JSContext
* cx
= context();
3531 case JSEXN_RANGEERR
:
3532 case JSEXN_REFERENCEERR
:
3533 case JSEXN_SYNTAXERR
:
3536 case JSEXN_AGGREGATEERR
:
3539 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3540 JSMSG_SC_BAD_SERIALIZED_DATA
,
3541 "invalid error type");
3545 RootedString
message(cx
);
3547 RootedValue
messageVal(cx
);
3548 if (!startRead(&messageVal
)) {
3551 if (messageVal
.isString()) {
3552 message
= messageVal
.toString();
3553 } else if (!messageVal
.isNull()) {
3554 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3555 JSMSG_SC_BAD_SERIALIZED_DATA
,
3556 "invalid 'message' field for Error object");
3561 // We have to set |cause| to something if it exists, otherwise the shape
3562 // would be wrong. The actual value will be overwritten later.
3563 RootedValue
val(cx
);
3564 if (!startRead(&val
)) {
3567 bool hasCause
= ToBoolean(val
);
3568 Rooted
<Maybe
<Value
>> cause(cx
, mozilla::Nothing());
3570 cause
= mozilla::Some(BooleanValue(true));
3573 if (!startRead(&val
)) {
3576 if (!val
.isString()) {
3577 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3578 JSMSG_SC_BAD_SERIALIZED_DATA
,
3579 "invalid 'fileName' field for Error object");
3582 RootedString
fileName(cx
, val
.toString());
3584 uint32_t lineNumber
;
3585 JS::ColumnNumberOneOrigin columnNumber
;
3586 if (!readUint32(&lineNumber
) ||
3587 !readUint32(columnNumber
.addressOfValueForTranscode())) {
3591 // The |cause| and |stack| slots of the objects might be overwritten later.
3592 // For AggregateErrors the |errors| property will be added.
3593 RootedObject
errorObj(
3594 cx
, ErrorObject::create(cx
, static_cast<JSExnType
>(type
), nullptr,
3595 fileName
, 0, lineNumber
, columnNumber
, nullptr,
3604 // Error objects have 3 fields, some or all of them null: cause,
3605 // errors, and stack.
3606 bool JSStructuredCloneReader::readErrorFields(Handle
<ErrorObject
*> errorObj
,
3607 HandleValue cause
, bool* state
) {
3608 JSContext
* cx
= context();
3610 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3611 JSMSG_SC_BAD_SERIALIZED_DATA
,
3612 "unexpected child value seen for Error object");
3616 RootedValue
errors(cx
);
3617 RootedValue
stack(cx
);
3618 if (!startRead(&errors
) || !startRead(&stack
)) {
3622 bool hasCause
= errorObj
->getCause().isSome();
3624 errorObj
->setCauseSlot(cause
);
3625 } else if (!cause
.isNull()) {
3626 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3627 JSMSG_SC_BAD_SERIALIZED_DATA
,
3628 "invalid 'cause' field for Error object");
3632 if (errorObj
->type() == JSEXN_AGGREGATEERR
) {
3633 if (!DefineDataProperty(context(), errorObj
, cx
->names().errors
, errors
,
3637 } else if (!errors
.isNull()) {
3638 JS_ReportErrorNumberASCII(
3639 cx
, GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
3640 "unexpected 'errors' field seen for non-AggregateError");
3644 if (stack
.isObject()) {
3645 RootedObject
stackObj(cx
, &stack
.toObject());
3646 if (!stackObj
->is
<SavedFrame
>()) {
3647 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3648 JSMSG_SC_BAD_SERIALIZED_DATA
,
3649 "invalid 'stack' field for Error object");
3652 errorObj
->setStackSlot(stack
);
3653 } else if (!stack
.isNull()) {
3654 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3655 JSMSG_SC_BAD_SERIALIZED_DATA
,
3656 "invalid 'stack' field for Error object");
3664 // Read a value and treat as a key,value pair.
3665 bool JSStructuredCloneReader::readMapField(Handle
<MapObject
*> mapObj
,
3667 RootedValue
val(context());
3668 if (!startRead(&val
)) {
3671 return MapObject::set(context(), mapObj
, key
, val
);
3674 // Read a value and treat as a key,value pair. Interpret as a plain property
3676 bool JSStructuredCloneReader::readObjectField(HandleObject obj
,
3678 if (!key
.isString() && !key
.isInt32()) {
3679 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3680 JSMSG_SC_BAD_SERIALIZED_DATA
,
3681 "property key expected");
3685 RootedValue
val(context());
3686 if (!startRead(&val
)) {
3690 RootedId
id(context());
3691 if (!PrimitiveValueToId
<CanGC
>(context(), key
, &id
)) {
3695 // Fast path for adding a new property to a plain object. The property names
3696 // we see here should be unique, but we check for duplicates to guard against
3697 // corrupt or malicious data.
3698 if (id
.isString() && obj
->is
<PlainObject
>() &&
3699 MOZ_LIKELY(!obj
->as
<PlainObject
>().contains(context(), id
))) {
3700 return AddDataPropertyToPlainObject(context(), obj
.as
<PlainObject
>(), id
,
3704 // Fast path for adding an array element. The index shouldn't exceed the
3705 // array's length, but we check for this in `addDenseElementNoLengthChange` to
3706 // guard against corrupt or malicious data.
3707 if (id
.isInt() && obj
->is
<ArrayObject
>()) {
3708 ArrayObject
* arr
= &obj
->as
<ArrayObject
>();
3709 switch (arr
->addDenseElementNoLengthChange(context(), id
.toInt(), val
)) {
3710 case DenseElementResult::Failure
:
3712 case DenseElementResult::Success
:
3714 case DenseElementResult::Incomplete
:
3715 // Fall-through to slow path.
3720 return DefineDataProperty(context(), obj
, id
, val
);
3723 // Perform the whole recursive reading procedure.
3724 bool JSStructuredCloneReader::read(MutableHandleValue vp
, size_t nbytes
) {
3725 auto startTime
= mozilla::TimeStamp::Now();
3727 if (!readHeader()) {
3731 if (!readTransferMap()) {
3735 MOZ_ASSERT(objs
.length() == 0);
3736 MOZ_ASSERT(objState
.length() == 1);
3738 // Start out by reading in the main object and pushing it onto the 'objs'
3739 // stack. The data related to this object and its descendants extends from
3740 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3741 if (!startRead(vp
)) {
3745 // Stop when the stack shows that all objects have been read.
3746 while (objs
.length() != 0) {
3747 // What happens depends on the top obj on the objs stack.
3748 RootedObject
obj(context(), &objs
.back().toObject());
3751 if (!in
.getPair(&tag
, &data
)) {
3755 if (tag
== SCTAG_END_OF_KEYS
) {
3756 // Pop the current obj off the stack, since we are done with it and
3758 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3760 if (objState
.back().first
== obj
) {
3766 // Remember the index of the current top of the state stack, which will
3767 // correspond to the state for `obj` iff `obj` is a type that uses state.
3768 // startRead() may push additional entries before the state is accessed and
3769 // updated while filling in the object's data.
3770 size_t objStateIdx
= objState
.length() - 1;
3772 // The input stream contains a sequence of "child" values, whose
3773 // interpretation depends on the type of obj. These values can be
3774 // anything, and startRead() will push onto 'objs' for any non-leaf
3775 // value (i.e., anything that may contain children).
3777 // startRead() will allocate the (empty) object, but note that when
3778 // startRead() returns, 'key' is not yet initialized with any of its
3779 // properties. Those will be filled in by returning to the head of this
3780 // loop, processing the first child obj, and continuing until all
3781 // children have been fully created.
3783 // Note that this means the ordering in the stream is a little funky for
3784 // things like Map. See the comment above traverseMap() for an example.
3786 bool expectKeyValuePairs
=
3787 !(obj
->is
<MapObject
>() || obj
->is
<SetObject
>() ||
3788 obj
->is
<SavedFrame
>() || obj
->is
<ErrorObject
>());
3790 RootedValue
key(context());
3791 ShouldAtomizeStrings atomize
=
3792 expectKeyValuePairs
? AtomizeStrings
: DontAtomizeStrings
;
3793 if (!startRead(&key
, atomize
)) {
3797 if (key
.isNull() && expectKeyValuePairs
) {
3798 // Backwards compatibility: Null formerly indicated the end of
3799 // object properties.
3801 // No legacy objects used the state stack.
3802 MOZ_ASSERT(objState
[objStateIdx
].first() != obj
);
3808 context()->check(key
);
3810 if (obj
->is
<SetObject
>()) {
3811 // Set object: the values between obj header (from startRead()) and
3812 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3813 if (!SetObject::add(context(), obj
, key
)) {
3816 } else if (obj
->is
<MapObject
>()) {
3817 Rooted
<MapObject
*> mapObj(context(), &obj
->as
<MapObject
>());
3818 if (!readMapField(mapObj
, key
)) {
3821 } else if (obj
->is
<SavedFrame
>()) {
3822 Rooted
<SavedFrame
*> frameObj(context(), &obj
->as
<SavedFrame
>());
3823 MOZ_ASSERT(objState
[objStateIdx
].first() == obj
);
3824 bool state
= objState
[objStateIdx
].second();
3825 if (!readSavedFrameFields(frameObj
, key
, &state
)) {
3828 objState
[objStateIdx
].second() = state
;
3829 } else if (obj
->is
<ErrorObject
>()) {
3830 Rooted
<ErrorObject
*> errorObj(context(), &obj
->as
<ErrorObject
>());
3831 MOZ_ASSERT(objState
[objStateIdx
].first() == obj
);
3832 bool state
= objState
[objStateIdx
].second();
3833 if (!readErrorFields(errorObj
, key
, &state
)) {
3836 objState
[objStateIdx
].second() = state
;
3838 MOZ_ASSERT(expectKeyValuePairs
);
3839 // Everything else uses a series of key,value,key,value,... Value
3841 if (!readObjectField(obj
, key
)) {
3849 // For fuzzing, it is convenient to allow extra data at the end
3850 // of the input buffer so that more possible inputs are considered
3854 if (tailStartPos
.isSome()) {
3855 // in.tell() is the end of the main data. If "tail" data was consumed,
3856 // then check whether there's any data between the main data and the
3857 // beginning of the tail, or after the last read point in the tail.
3858 extraData
= (in
.tell() != *tailStartPos
|| !tailEndPos
->done());
3860 extraData
= !in
.tell().done();
3863 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3864 JSMSG_SC_BAD_SERIALIZED_DATA
,
3865 "extra data after end");
3870 JSRuntime
* rt
= context()->runtime();
3871 rt
->metrics().DESERIALIZE_BYTES(nbytes
);
3872 rt
->metrics().DESERIALIZE_ITEMS(numItemsRead
);
3873 mozilla::TimeDuration elapsed
= mozilla::TimeStamp::Now() - startTime
;
3874 rt
->metrics().DESERIALIZE_US(elapsed
);
3879 JS_PUBLIC_API
bool JS_ReadStructuredClone(
3880 JSContext
* cx
, const JSStructuredCloneData
& buf
, uint32_t version
,
3881 JS::StructuredCloneScope scope
, MutableHandleValue vp
,
3882 const JS::CloneDataPolicy
& cloneDataPolicy
,
3883 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3887 if (version
> JS_STRUCTURED_CLONE_VERSION
) {
3888 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3889 JSMSG_SC_BAD_CLONE_VERSION
);
3892 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3893 return ReadStructuredClone(cx
, buf
, scope
, vp
, cloneDataPolicy
, callbacks
,
3897 JS_PUBLIC_API
bool JS_WriteStructuredClone(
3898 JSContext
* cx
, HandleValue value
, JSStructuredCloneData
* bufp
,
3899 JS::StructuredCloneScope scope
, const JS::CloneDataPolicy
& cloneDataPolicy
,
3900 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
,
3901 HandleValue transferable
) {
3906 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3907 return WriteStructuredClone(cx
, value
, bufp
, scope
, cloneDataPolicy
,
3908 callbacks
, closure
, transferable
);
3911 JS_PUBLIC_API
bool JS_StructuredCloneHasTransferables(
3912 JSStructuredCloneData
& data
, bool* hasTransferable
) {
3913 *hasTransferable
= StructuredCloneHasTransferObjects(data
);
3917 JS_PUBLIC_API
bool JS_StructuredClone(
3918 JSContext
* cx
, HandleValue value
, MutableHandleValue vp
,
3919 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3923 // Strings are associated with zones, not compartments,
3924 // so we copy the string by wrapping it.
3925 if (value
.isString()) {
3926 RootedString
strValue(cx
, value
.toString());
3927 if (!cx
->compartment()->wrap(cx
, &strValue
)) {
3930 vp
.setString(strValue
);
3934 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3936 JSAutoStructuredCloneBuffer
buf(JS::StructuredCloneScope::SameProcess
,
3937 callbacks
, closure
);
3939 if (value
.isObject()) {
3940 RootedObject
obj(cx
, &value
.toObject());
3941 obj
= CheckedUnwrapStatic(obj
);
3943 ReportAccessDenied(cx
);
3946 AutoRealm
ar(cx
, obj
);
3947 RootedValue
unwrappedVal(cx
, ObjectValue(*obj
));
3948 if (!buf
.write(cx
, unwrappedVal
, callbacks
, closure
)) {
3952 if (!buf
.write(cx
, value
, callbacks
, closure
)) {
3958 return buf
.read(cx
, vp
, JS::CloneDataPolicy(), callbacks
, closure
);
3961 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3962 JSAutoStructuredCloneBuffer
&& other
)
3963 : data_(other
.scope()) {
3964 version_
= other
.version_
;
3965 other
.giveTo(&data_
);
3968 JSAutoStructuredCloneBuffer
& JSAutoStructuredCloneBuffer::operator=(
3969 JSAutoStructuredCloneBuffer
&& other
) {
3970 MOZ_ASSERT(&other
!= this);
3971 MOZ_ASSERT(scope() == other
.scope());
3973 version_
= other
.version_
;
3974 other
.giveTo(&data_
);
3978 void JSAutoStructuredCloneBuffer::clear() {
3979 data_
.discardTransferables();
3980 data_
.ownTransferables_
= OwnTransferablePolicy::NoTransferables
;
3981 data_
.refsHeld_
.releaseAll();
3986 void JSAutoStructuredCloneBuffer::adopt(
3987 JSStructuredCloneData
&& data
, uint32_t version
,
3988 const JSStructuredCloneCallbacks
* callbacks
, void* closure
) {
3990 data_
= std::move(data
);
3992 data_
.setCallbacks(callbacks
, closure
,
3993 OwnTransferablePolicy::OwnsTransferablesIfAny
);
3996 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData
* data
) {
3997 *data
= std::move(data_
);
3999 data_
.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables
);
4003 bool JSAutoStructuredCloneBuffer::read(
4004 JSContext
* cx
, MutableHandleValue vp
,
4005 const JS::CloneDataPolicy
& cloneDataPolicy
,
4006 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4008 return !!JS_ReadStructuredClone(
4009 cx
, data_
, version_
, data_
.scope(), vp
, cloneDataPolicy
,
4010 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4011 optionalCallbacks
? closure
: data_
.closure_
);
4014 bool JSAutoStructuredCloneBuffer::write(
4015 JSContext
* cx
, HandleValue value
,
4016 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4017 HandleValue transferable
= UndefinedHandleValue
;
4018 return write(cx
, value
, transferable
, JS::CloneDataPolicy(),
4019 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4020 optionalCallbacks
? closure
: data_
.closure_
);
4023 bool JSAutoStructuredCloneBuffer::write(
4024 JSContext
* cx
, HandleValue value
, HandleValue transferable
,
4025 const JS::CloneDataPolicy
& cloneDataPolicy
,
4026 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4028 bool ok
= JS_WriteStructuredClone(
4029 cx
, value
, &data_
, data_
.scopeForInternalWriting(), cloneDataPolicy
,
4030 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4031 optionalCallbacks
? closure
: data_
.closure_
, transferable
);
4033 version_
= JS_STRUCTURED_CLONE_VERSION
;
4038 JS_PUBLIC_API
bool JS_ReadUint32Pair(JSStructuredCloneReader
* r
, uint32_t* p1
,
4040 return r
->input().readPair((uint32_t*)p1
, (uint32_t*)p2
);
4043 JS_PUBLIC_API
bool JS_ReadBytes(JSStructuredCloneReader
* r
, void* p
,
4045 return r
->input().readBytes(p
, len
);
4048 JS_PUBLIC_API
bool JS_ReadString(JSStructuredCloneReader
* r
,
4049 MutableHandleString str
) {
4051 if (!r
->input().readPair(&tag
, &data
)) {
4055 if (tag
== SCTAG_STRING
) {
4057 r
->readString(data
, JSStructuredCloneReader::DontAtomizeStrings
)) {
4064 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
4065 JSMSG_SC_BAD_SERIALIZED_DATA
, "expected string");
4069 JS_PUBLIC_API
bool JS_ReadDouble(JSStructuredCloneReader
* r
, double* v
) {
4070 return r
->input().readDouble(v
);
4073 JS_PUBLIC_API
bool JS_ReadTypedArray(JSStructuredCloneReader
* r
,
4074 MutableHandleValue vp
) {
4076 if (!r
->input().readPair(&tag
, &data
)) {
4080 if (tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
4081 return r
->readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
4084 if (tag
== SCTAG_TYPED_ARRAY_OBJECT_V2
) {
4085 // V2 stores the length (nelems) in |data| and the arrayType separately.
4087 if (!r
->input().read(&arrayType
)) {
4090 uint64_t nelems
= data
;
4091 return r
->readTypedArray(arrayType
, nelems
, vp
);
4094 if (tag
== SCTAG_TYPED_ARRAY_OBJECT
) {
4095 // The current version stores the array type in |data| and the length
4096 // (nelems) separately to support large TypedArrays.
4097 uint32_t arrayType
= data
;
4099 if (!r
->input().read(&nelems
)) {
4102 return r
->readTypedArray(arrayType
, nelems
, vp
);
4105 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
4106 JSMSG_SC_BAD_SERIALIZED_DATA
,
4107 "expected type array");
4111 JS_PUBLIC_API
bool JS_WriteUint32Pair(JSStructuredCloneWriter
* w
, uint32_t tag
,
4113 return w
->output().writePair(tag
, data
);
4116 JS_PUBLIC_API
bool JS_WriteBytes(JSStructuredCloneWriter
* w
, const void* p
,
4118 return w
->output().writeBytes(p
, len
);
4121 JS_PUBLIC_API
bool JS_WriteString(JSStructuredCloneWriter
* w
,
4123 return w
->writeString(SCTAG_STRING
, str
);
4126 JS_PUBLIC_API
bool JS_WriteDouble(JSStructuredCloneWriter
* w
, double v
) {
4127 return w
->output().writeDouble(v
);
4130 JS_PUBLIC_API
bool JS_WriteTypedArray(JSStructuredCloneWriter
* w
,
4132 MOZ_ASSERT(v
.isObject());
4133 w
->context()->check(v
);
4134 RootedObject
obj(w
->context(), &v
.toObject());
4136 // startWrite can write everything, thus we should check here
4137 // and report error if the user passes a wrong type.
4138 if (!obj
->canUnwrapAs
<TypedArrayObject
>()) {
4139 ReportAccessDenied(w
->context());
4143 // We should use startWrite instead of writeTypedArray, because
4144 // typed array is an object, we should add it to the |memory|
4145 // (allObjs) list. Directly calling writeTypedArray won't add it.
4146 return w
->startWrite(v
);
4149 JS_PUBLIC_API
bool JS_ObjectNotWritten(JSStructuredCloneWriter
* w
,
4151 w
->memory
.remove(w
->memory
.lookup(obj
));
4156 JS_PUBLIC_API
JS::StructuredCloneScope
JS_GetStructuredCloneScope(
4157 JSStructuredCloneWriter
* w
) {
4158 return w
->output().scope();