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
<FixedLengthDataViewObject
*> view(
1373 context(), obj
->maybeUnwrapAs
<FixedLengthDataViewObject
>());
1374 JSAutoRealm
ar(context(), view
);
1376 if (!out
.writePair(SCTAG_DATA_VIEW_OBJECT
, 0)) {
1380 uint64_t byteLength
= view
->byteLength();
1381 if (!out
.write(byteLength
)) {
1385 // Write out the ArrayBuffer tag and contents
1386 RootedValue
val(context(), view
->bufferValue());
1387 if (!startWrite(val
)) {
1391 uint64_t byteOffset
= view
->byteOffset();
1392 return out
.write(byteOffset
);
1395 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj
) {
1396 Rooted
<ArrayBufferObject
*> buffer(context(),
1397 obj
->maybeUnwrapAs
<ArrayBufferObject
>());
1398 JSAutoRealm
ar(context(), buffer
);
1400 // FIXME: Support structured cloning for resizable ArrayBuffers.
1401 if (buffer
->isResizable()) {
1402 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
1406 if (!out
.writePair(SCTAG_ARRAY_BUFFER_OBJECT
, 0)) {
1410 uint64_t byteLength
= buffer
->byteLength();
1411 if (!out
.write(byteLength
)) {
1415 return out
.writeBytes(buffer
->dataPointer(), byteLength
);
1418 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj
) {
1419 MOZ_ASSERT(obj
->canUnwrapAs
<SharedArrayBufferObject
>());
1421 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1422 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1423 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1424 : JS_SCERR_NOT_CLONABLE
;
1425 reportDataCloneError(error
, "SharedArrayBuffer");
1429 output().sameProcessScopeRequired();
1431 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1432 // cross-process. The cloneDataPolicy should have guarded against this;
1433 // since it did not then throw, with a very explicit message.
1435 if (output().scope() > JS::StructuredCloneScope::SameProcess
) {
1436 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1437 JSMSG_SC_SHMEM_POLICY
);
1441 Rooted
<SharedArrayBufferObject
*> sharedArrayBuffer(
1442 context(), obj
->maybeUnwrapAs
<SharedArrayBufferObject
>());
1443 SharedArrayRawBuffer
* rawbuf
= sharedArrayBuffer
->rawBufferObject();
1445 if (!out
.buf
.refsHeld_
.acquire(context(), rawbuf
)) {
1449 // We must serialize the length so that the buffer object arrives in the
1450 // receiver with the same length, and not with the length read from the
1451 // rawbuf - that length can be different, and it can change at any time.
1453 intptr_t p
= reinterpret_cast<intptr_t>(rawbuf
);
1454 uint64_t byteLength
= sharedArrayBuffer
->byteLength();
1455 if (!(out
.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT
,
1456 static_cast<uint32_t>(sizeof(p
))) &&
1457 out
.writeBytes(&byteLength
, sizeof(byteLength
)) &&
1458 out
.writeBytes(&p
, sizeof(p
)))) {
1462 if (callbacks
&& callbacks
->sabCloned
&&
1463 !callbacks
->sabCloned(context(), /*receiving=*/false, closure
)) {
1470 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj
) {
1471 MOZ_ASSERT(obj
->canUnwrapAs
<WasmMemoryObject
>());
1473 // Check the policy here so that we can report a sane error.
1474 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1475 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1476 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1477 : JS_SCERR_NOT_CLONABLE
;
1478 reportDataCloneError(error
, "WebAssembly.Memory");
1482 // If this changes, might need to change what we write.
1483 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS
== 3);
1485 Rooted
<WasmMemoryObject
*> memoryObj(context(),
1486 &obj
->unwrapAs
<WasmMemoryObject
>());
1487 Rooted
<SharedArrayBufferObject
*> sab(
1488 context(), &memoryObj
->buffer().as
<SharedArrayBufferObject
>());
1490 return out
.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT
, 0) &&
1491 out
.writePair(SCTAG_BOOLEAN
, memoryObj
->isHuge()) &&
1492 writeSharedArrayBuffer(sab
);
1495 bool JSStructuredCloneWriter::startObject(HandleObject obj
, bool* backref
) {
1496 // Handle cycles in the object graph.
1497 CloneMemory::AddPtr p
= memory
.lookupForAdd(obj
);
1498 if ((*backref
= p
.found())) {
1499 return out
.writePair(SCTAG_BACK_REFERENCE_OBJECT
, p
->value());
1501 if (!memory
.add(p
, obj
, memory
.count())) {
1502 ReportOutOfMemory(context());
1506 if (memory
.count() == UINT32_MAX
) {
1507 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1508 JSMSG_NEED_DIET
, "object graph to serialize");
1515 static bool TryAppendNativeProperties(JSContext
* cx
, HandleObject obj
,
1516 MutableHandleIdVector entries
,
1517 size_t* properties
, bool* optimized
) {
1520 if (!obj
->is
<NativeObject
>()) {
1524 Handle
<NativeObject
*> nobj
= obj
.as
<NativeObject
>();
1525 if (nobj
->isIndexed() || nobj
->is
<TypedArrayObject
>() ||
1526 nobj
->getClass()->getNewEnumerate() || nobj
->getClass()->getEnumerate()) {
1533 // We iterate from the last to the first property, so the property names
1534 // are already in reverse order.
1535 for (ShapePropertyIter
<NoGC
> iter(nobj
->shape()); !iter
.done(); iter
++) {
1536 jsid id
= iter
->key();
1538 // Ignore symbols and non-enumerable properties.
1539 if (!iter
->enumerable() || id
.isSymbol()) {
1543 MOZ_ASSERT(id
.isString());
1544 if (!entries
.append(id
)) {
1551 // Add dense element ids in reverse order.
1552 for (uint32_t i
= nobj
->getDenseInitializedLength(); i
> 0; --i
) {
1553 if (nobj
->getDenseElement(i
- 1).isMagic(JS_ELEMENTS_HOLE
)) {
1557 if (!entries
.append(PropertyKey::Int(i
- 1))) {
1564 *properties
= count
;
1568 // Objects are written as a "preorder" traversal of the object graph: object
1569 // "headers" (the class tag and any data needed for initial construction) are
1570 // visited first, then the children are recursed through (where children are
1571 // properties, Set or Map entries, etc.). So for example
1573 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1575 // would be stored as:
1577 // <Object tag for obj1>
1579 // <Object tag for key1's value>
1584 // <end-of-children marker for key1's value>
1586 // <Object tag for key2's value>
1587 // <end-of-children marker for key2's value>
1588 // <end-of-children marker for obj1>
1590 // This nests nicely (ie, an entire recursive value starts with its tag and
1591 // ends with its end-of-children marker) and so it can be presented indented.
1592 // But see traverseMap below for how this looks different for Maps.
1593 bool JSStructuredCloneWriter::traverseObject(HandleObject obj
, ESClass cls
) {
1595 bool optimized
= false;
1596 if (!js::SupportDifferentialTesting()) {
1597 if (!TryAppendNativeProperties(context(), obj
, &objectEntries
, &count
,
1604 // Get enumerable property ids and put them in reverse order so that they
1605 // will come off the stack in forward order.
1606 RootedIdVector
properties(context());
1607 if (!GetPropertyKeys(context(), obj
, JSITER_OWNONLY
, &properties
)) {
1611 for (size_t i
= properties
.length(); i
> 0; --i
) {
1612 jsid id
= properties
[i
- 1];
1614 MOZ_ASSERT(id
.isString() || id
.isInt());
1615 if (!objectEntries
.append(id
)) {
1620 count
= properties
.length();
1623 // Push obj and count to the stack.
1624 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(count
)) {
1632 if (!GetBuiltinClass(context(), obj
, &cls2
)) {
1635 MOZ_ASSERT(cls2
== cls
);
1638 // Write the header for obj.
1639 if (cls
== ESClass::Array
) {
1640 uint32_t length
= 0;
1641 if (!JS::GetArrayLength(context(), obj
, &length
)) {
1645 return out
.writePair(SCTAG_ARRAY_OBJECT
,
1646 NativeEndian::swapToLittleEndian(length
));
1649 return out
.writePair(SCTAG_OBJECT_OBJECT
, 0);
1652 // Use the same basic setup as for traverseObject, but now keys can themselves
1653 // be complex objects. Keys and values are visited first via startWrite(), then
1654 // the key's children (if any) are handled, then the value's children.
1657 // m.set(key1 = ..., value1 = ...)
1659 // where key1 and value2 are both objects would be stored as
1663 // <value1 class tag>
1664 // ...key1 fields...
1665 // <end-of-children marker for key1>
1666 // ...value1 fields...
1667 // <end-of-children marker for value1>
1668 // <end-of-children marker for Map>
1670 // Notice how the end-of-children marker for key1 is sandwiched between the
1671 // value1 beginning and end.
1672 bool JSStructuredCloneWriter::traverseMap(HandleObject obj
) {
1673 Rooted
<GCVector
<Value
>> newEntries(context(), GCVector
<Value
>(context()));
1675 // If there is no wrapper, the compartment munging is a no-op.
1676 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<MapObject
>());
1677 MOZ_ASSERT(unwrapped
);
1678 JSAutoRealm
ar(context(), unwrapped
);
1679 if (!MapObject::getKeysAndValuesInterleaved(unwrapped
, &newEntries
)) {
1683 if (!context()->compartment()->wrap(context(), &newEntries
)) {
1687 for (size_t i
= newEntries
.length(); i
> 0; --i
) {
1688 if (!otherEntries
.append(newEntries
[i
- 1])) {
1693 // Push obj and count to the stack.
1694 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(newEntries
.length())) {
1700 // Write the header for obj.
1701 return out
.writePair(SCTAG_MAP_OBJECT
, 0);
1704 // Similar to traverseMap, only there is a single value instead of a key and
1705 // value, and thus no interleaving is possible: a value will be fully emitted
1706 // before the next value is begun.
1707 bool JSStructuredCloneWriter::traverseSet(HandleObject obj
) {
1708 Rooted
<GCVector
<Value
>> keys(context(), GCVector
<Value
>(context()));
1710 // If there is no wrapper, the compartment munging is a no-op.
1711 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<SetObject
>());
1712 MOZ_ASSERT(unwrapped
);
1713 JSAutoRealm
ar(context(), unwrapped
);
1714 if (!SetObject::keys(context(), unwrapped
, &keys
)) {
1718 if (!context()->compartment()->wrap(context(), &keys
)) {
1722 for (size_t i
= keys
.length(); i
> 0; --i
) {
1723 if (!otherEntries
.append(keys
[i
- 1])) {
1728 // Push obj and count to the stack.
1729 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(keys
.length())) {
1735 // Write the header for obj.
1736 return out
.writePair(SCTAG_SET_OBJECT
, 0);
1739 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj
) {
1740 Rooted
<SavedFrame
*> savedFrame(context(), obj
->maybeUnwrapAs
<SavedFrame
>());
1741 MOZ_ASSERT(savedFrame
);
1743 RootedObject
parent(context(), savedFrame
->getParent());
1744 if (!context()->compartment()->wrap(context(), &parent
)) {
1748 if (!objs
.append(ObjectValue(*obj
)) ||
1749 !otherEntries
.append(parent
? ObjectValue(*parent
) : NullValue()) ||
1750 !counts
.append(1)) {
1756 // Write the SavedFrame tag and the SavedFrame's principals.
1758 if (savedFrame
->getPrincipals() ==
1759 &ReconstructedSavedFramePrincipals::IsSystem
) {
1760 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
,
1761 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
)) {
1764 } else if (savedFrame
->getPrincipals() ==
1765 &ReconstructedSavedFramePrincipals::IsNotSystem
) {
1767 SCTAG_SAVED_FRAME_OBJECT
,
1768 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
)) {
1772 if (auto principals
= savedFrame
->getPrincipals()) {
1773 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_JSPRINCIPALS
) ||
1774 !principals
->write(context(), this)) {
1778 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_NULL_JSPRINCIPALS
)) {
1784 // Write the SavedFrame's reserved slots, except for the parent, which is
1785 // queued on objs for further traversal.
1787 RootedValue
val(context());
1789 val
= BooleanValue(savedFrame
->getMutedErrors());
1790 if (!writePrimitive(val
)) {
1794 context()->markAtom(savedFrame
->getSource());
1795 val
= StringValue(savedFrame
->getSource());
1796 if (!writePrimitive(val
)) {
1800 val
= NumberValue(savedFrame
->getLine());
1801 if (!writePrimitive(val
)) {
1805 val
= NumberValue(*savedFrame
->getColumn().addressOfValueForTranscode());
1806 if (!writePrimitive(val
)) {
1810 auto name
= savedFrame
->getFunctionDisplayName();
1812 context()->markAtom(name
);
1814 val
= name
? StringValue(name
) : NullValue();
1815 if (!writePrimitive(val
)) {
1819 auto cause
= savedFrame
->getAsyncCause();
1821 context()->markAtom(cause
);
1823 val
= cause
? StringValue(cause
) : NullValue();
1824 if (!writePrimitive(val
)) {
1831 // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
1832 // 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
1834 // Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
1835 // value is not a platform object, then:
1837 // Note: This contains custom extensions for handling non-standard properties.
1838 bool JSStructuredCloneWriter::traverseError(HandleObject obj
) {
1839 JSContext
* cx
= context();
1841 // 1. Let name be ? Get(value, "name").
1842 RootedValue
name(cx
);
1843 if (!GetProperty(cx
, obj
, obj
, cx
->names().name
, &name
)) {
1847 // 2. If name is not one of "Error", "EvalError", "RangeError",
1848 // "ReferenceError", "SyntaxError", "TypeError", or "URIError",
1849 // (not yet specified: or "AggregateError")
1850 // then set name to "Error".
1851 JSExnType type
= JSEXN_ERR
;
1852 if (name
.isString()) {
1853 JSLinearString
* linear
= name
.toString()->ensureLinear(cx
);
1858 if (EqualStrings(linear
, cx
->names().Error
)) {
1860 } else if (EqualStrings(linear
, cx
->names().EvalError
)) {
1861 type
= JSEXN_EVALERR
;
1862 } else if (EqualStrings(linear
, cx
->names().RangeError
)) {
1863 type
= JSEXN_RANGEERR
;
1864 } else if (EqualStrings(linear
, cx
->names().ReferenceError
)) {
1865 type
= JSEXN_REFERENCEERR
;
1866 } else if (EqualStrings(linear
, cx
->names().SyntaxError
)) {
1867 type
= JSEXN_SYNTAXERR
;
1868 } else if (EqualStrings(linear
, cx
->names().TypeError
)) {
1869 type
= JSEXN_TYPEERR
;
1870 } else if (EqualStrings(linear
, cx
->names().URIError
)) {
1871 type
= JSEXN_URIERR
;
1872 } else if (EqualStrings(linear
, cx
->names().AggregateError
)) {
1873 type
= JSEXN_AGGREGATEERR
;
1877 // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
1878 RootedId
messageId(cx
, NameToId(cx
->names().message
));
1879 Rooted
<Maybe
<PropertyDescriptor
>> messageDesc(cx
);
1880 if (!GetOwnPropertyDescriptor(cx
, obj
, messageId
, &messageDesc
)) {
1884 // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
1885 // and ? ToString(valueMessageDesc.[[Value]]) otherwise.
1886 RootedString
message(cx
);
1887 if (messageDesc
.isSome() && messageDesc
->isDataDescriptor()) {
1888 RootedValue
messageVal(cx
, messageDesc
->value());
1889 message
= ToString
<CanGC
>(cx
, messageVal
);
1895 // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
1898 if (!objs
.append(ObjectValue(*obj
))) {
1902 Rooted
<ErrorObject
*> unwrapped(cx
, obj
->maybeUnwrapAs
<ErrorObject
>());
1903 MOZ_ASSERT(unwrapped
);
1905 // Non-standard: Serialize |stack|.
1906 // The Error stack property is saved as SavedFrames.
1907 RootedValue
stack(cx
, NullValue());
1908 RootedObject
stackObj(cx
, unwrapped
->stack());
1909 if (stackObj
&& stackObj
->canUnwrapAs
<SavedFrame
>()) {
1910 stack
.setObject(*stackObj
);
1911 if (!cx
->compartment()->wrap(cx
, &stack
)) {
1915 if (!otherEntries
.append(stack
)) {
1919 // Serialize |errors|
1920 if (type
== JSEXN_AGGREGATEERR
) {
1921 RootedValue
errors(cx
);
1922 if (!GetProperty(cx
, obj
, obj
, cx
->names().errors
, &errors
)) {
1925 if (!otherEntries
.append(errors
)) {
1929 if (!otherEntries
.append(NullValue())) {
1934 // Non-standard: Serialize |cause|. Because this property
1935 // might be missing we also write "hasCause" later.
1936 RootedId
causeId(cx
, NameToId(cx
->names().cause
));
1937 Rooted
<Maybe
<PropertyDescriptor
>> causeDesc(cx
);
1938 if (!GetOwnPropertyDescriptor(cx
, obj
, causeId
, &causeDesc
)) {
1942 Rooted
<Maybe
<Value
>> cause(cx
);
1943 if (causeDesc
.isSome() && causeDesc
->isDataDescriptor()) {
1944 cause
= mozilla::Some(causeDesc
->value());
1946 if (!cx
->compartment()->wrap(cx
, &cause
)) {
1949 if (!otherEntries
.append(cause
.get().valueOr(NullValue()))) {
1953 // |cause| + |errors| + |stack|, pushed in reverse order
1954 if (!counts
.append(3)) {
1960 if (!out
.writePair(SCTAG_ERROR_OBJECT
, type
)) {
1964 RootedValue
val(cx
, message
? StringValue(message
) : NullValue());
1965 if (!writePrimitive(val
)) {
1970 val
= BooleanValue(cause
.isSome());
1971 if (!writePrimitive(val
)) {
1975 // Non-standard: Also serialize fileName, lineNumber and columnNumber.
1977 JSAutoRealm
ar(cx
, unwrapped
);
1978 val
= StringValue(unwrapped
->fileName(cx
));
1980 if (!cx
->compartment()->wrap(cx
, &val
) || !writePrimitive(val
)) {
1984 val
= Int32Value(unwrapped
->lineNumber());
1985 if (!writePrimitive(val
)) {
1989 val
= Int32Value(*unwrapped
->columnNumber().addressOfValueForTranscode());
1990 return writePrimitive(val
);
1993 bool JSStructuredCloneWriter::writePrimitive(HandleValue v
) {
1994 MOZ_ASSERT(v
.isPrimitive());
1995 context()->check(v
);
1998 return writeString(SCTAG_STRING
, v
.toString());
1999 } else if (v
.isInt32()) {
2000 if (js::SupportDifferentialTesting()) {
2001 return out
.writeDouble(v
.toInt32());
2003 return out
.writePair(SCTAG_INT32
, v
.toInt32());
2004 } else if (v
.isDouble()) {
2005 return out
.writeDouble(v
.toDouble());
2006 } else if (v
.isBoolean()) {
2007 return out
.writePair(SCTAG_BOOLEAN
, v
.toBoolean());
2008 } else if (v
.isNull()) {
2009 return out
.writePair(SCTAG_NULL
, 0);
2010 } else if (v
.isUndefined()) {
2011 return out
.writePair(SCTAG_UNDEFINED
, 0);
2012 } else if (v
.isBigInt()) {
2013 return writeBigInt(SCTAG_BIGINT
, v
.toBigInt());
2016 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2019 bool JSStructuredCloneWriter::startWrite(HandleValue v
) {
2020 context()->check(v
);
2022 if (v
.isPrimitive()) {
2023 return writePrimitive(v
);
2026 if (!v
.isObject()) {
2027 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2030 RootedObject
obj(context(), &v
.toObject());
2033 if (!startObject(obj
, &backref
)) {
2041 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2046 case ESClass::Object
:
2047 case ESClass::Array
:
2048 return traverseObject(obj
, cls
);
2049 case ESClass::Number
: {
2050 RootedValue
unboxed(context());
2051 if (!Unbox(context(), obj
, &unboxed
)) {
2054 return out
.writePair(SCTAG_NUMBER_OBJECT
, 0) &&
2055 out
.writeDouble(unboxed
.toNumber());
2057 case ESClass::String
: {
2058 RootedValue
unboxed(context());
2059 if (!Unbox(context(), obj
, &unboxed
)) {
2062 return writeString(SCTAG_STRING_OBJECT
, unboxed
.toString());
2064 case ESClass::Boolean
: {
2065 RootedValue
unboxed(context());
2066 if (!Unbox(context(), obj
, &unboxed
)) {
2069 return out
.writePair(SCTAG_BOOLEAN_OBJECT
, unboxed
.toBoolean());
2071 case ESClass::RegExp
: {
2072 RegExpShared
* re
= RegExpToShared(context(), obj
);
2076 return out
.writePair(SCTAG_REGEXP_OBJECT
, re
->getFlags().value()) &&
2077 writeString(SCTAG_STRING
, re
->getSource());
2079 case ESClass::ArrayBuffer
: {
2080 if (JS::IsArrayBufferObject(obj
) && JS::ArrayBufferHasData(obj
)) {
2081 return writeArrayBuffer(obj
);
2085 case ESClass::SharedArrayBuffer
:
2086 if (JS::IsSharedArrayBufferObject(obj
)) {
2087 return writeSharedArrayBuffer(obj
);
2090 case ESClass::Date
: {
2091 RootedValue
unboxed(context());
2092 if (!Unbox(context(), obj
, &unboxed
)) {
2095 return out
.writePair(SCTAG_DATE_OBJECT
, 0) &&
2096 out
.writeDouble(unboxed
.toNumber());
2099 return traverseSet(obj
);
2101 return traverseMap(obj
);
2102 case ESClass::Error
:
2103 return traverseError(obj
);
2104 case ESClass::BigInt
: {
2105 RootedValue
unboxed(context());
2106 if (!Unbox(context(), obj
, &unboxed
)) {
2109 return writeBigInt(SCTAG_BIGINT_OBJECT
, unboxed
.toBigInt());
2111 case ESClass::Promise
:
2112 case ESClass::MapIterator
:
2113 case ESClass::SetIterator
:
2114 case ESClass::Arguments
:
2115 case ESClass::Function
:
2118 #ifdef ENABLE_RECORD_TUPLE
2119 case ESClass::Record
:
2120 case ESClass::Tuple
:
2121 MOZ_CRASH("Record and Tuple are not supported");
2124 case ESClass::Other
: {
2125 if (obj
->canUnwrapAs
<TypedArrayObject
>()) {
2126 return writeTypedArray(obj
);
2128 if (obj
->canUnwrapAs
<FixedLengthDataViewObject
>()) {
2129 return writeDataView(obj
);
2131 if (obj
->canUnwrapAs
<ResizableDataViewObject
>()) {
2132 // TODO(anba): support resizable.
2133 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2135 if (wasm::IsSharedWasmMemoryObject(obj
)) {
2136 return writeSharedWasmMemory(obj
);
2138 if (obj
->canUnwrapAs
<SavedFrame
>()) {
2139 return traverseSavedFrame(obj
);
2145 if (out
.buf
.callbacks_
&& out
.buf
.callbacks_
->write
) {
2146 bool sameProcessScopeRequired
= false;
2147 if (!out
.buf
.callbacks_
->write(context(), this, obj
,
2148 &sameProcessScopeRequired
,
2149 out
.buf
.closure_
)) {
2153 if (sameProcessScopeRequired
) {
2154 output().sameProcessScopeRequired();
2160 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2163 bool JSStructuredCloneWriter::writeHeader() {
2164 return out
.writePair(SCTAG_HEADER
, (uint32_t)output().scope());
2167 bool JSStructuredCloneWriter::writeTransferMap() {
2168 if (transferableObjects
.empty()) {
2172 if (!out
.writePair(SCTAG_TRANSFER_MAP_HEADER
, (uint32_t)SCTAG_TM_UNREAD
)) {
2176 if (!out
.write(transferableObjects
.length())) {
2180 RootedObject
obj(context());
2181 for (auto* o
: transferableObjects
) {
2183 if (!memory
.put(obj
, memory
.count())) {
2184 ReportOutOfMemory(context());
2188 // Emit a placeholder pointer. We defer stealing the data until later
2189 // (and, if necessary, detaching this object if it's an ArrayBuffer).
2190 if (!out
.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY
,
2191 JS::SCTAG_TMO_UNFILLED
)) {
2194 if (!out
.write(0)) { // Pointer to ArrayBuffer contents.
2197 if (!out
.write(0)) { // extraData
2205 bool JSStructuredCloneWriter::transferOwnership() {
2206 if (transferableObjects
.empty()) {
2210 // Walk along the transferables and the transfer map at the same time,
2211 // grabbing out pointers from the transferables and stuffing them into the
2213 auto point
= out
.iter();
2214 MOZ_RELEASE_ASSERT(point
.canPeek());
2215 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
2218 MOZ_RELEASE_ASSERT(point
.canPeek());
2219 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
2220 SCTAG_TRANSFER_MAP_HEADER
);
2222 MOZ_RELEASE_ASSERT(point
.canPeek());
2223 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point
.peek()) ==
2224 transferableObjects
.length());
2227 JSContext
* cx
= context();
2228 RootedObject
obj(cx
);
2229 JS::StructuredCloneScope scope
= output().scope();
2230 for (auto* o
: transferableObjects
) {
2234 JS::TransferableOwnership ownership
;
2239 SCInput::getPair(point
.peek(), &tag
, (uint32_t*)&ownership
);
2240 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2241 MOZ_ASSERT(ownership
== JS::SCTAG_TMO_UNFILLED
);
2245 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
2249 if (cls
== ESClass::ArrayBuffer
) {
2250 tag
= SCTAG_TRANSFER_MAP_ARRAY_BUFFER
;
2252 // The current setup of the array buffer inheritance hierarchy doesn't
2253 // lend itself well to generic manipulation via proxies.
2254 Rooted
<ArrayBufferObject
*> arrayBuffer(
2255 cx
, obj
->maybeUnwrapAs
<ArrayBufferObject
>());
2256 JSAutoRealm
ar(cx
, arrayBuffer
);
2258 if (arrayBuffer
->isDetached()) {
2259 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED
);
2263 if (arrayBuffer
->isPreparedForAsmJS()) {
2264 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER
);
2268 // FIXME: Support structured cloning for resizable ArrayBuffers.
2269 if (arrayBuffer
->isResizable()) {
2270 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
2274 if (scope
== JS::StructuredCloneScope::DifferentProcess
||
2275 scope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
2276 // Write Transferred ArrayBuffers in DifferentProcess scope at
2277 // the end of the clone buffer, and store the offset within the
2278 // buffer to where the ArrayBuffer was written. Note that this
2279 // will invalidate the current position iterator.
2281 size_t pointOffset
= out
.offset(point
);
2282 tag
= SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
;
2283 ownership
= JS::SCTAG_TMO_UNOWNED
;
2285 extraData
= out
.tell() -
2286 pointOffset
; // Offset from tag to current end of buffer
2287 if (!writeArrayBuffer(arrayBuffer
)) {
2288 ReportOutOfMemory(cx
);
2292 // Must refresh the point iterator after its collection has
2295 point
+= pointOffset
;
2297 if (!JS::DetachArrayBuffer(cx
, arrayBuffer
)) {
2301 size_t nbytes
= arrayBuffer
->byteLength();
2303 using BufferContents
= ArrayBufferObject::BufferContents
;
2305 BufferContents bufContents
=
2306 ArrayBufferObject::extractStructuredCloneContents(cx
, arrayBuffer
);
2308 return false; // out of memory
2311 content
= bufContents
.data();
2312 if (bufContents
.kind() == ArrayBufferObject::MAPPED
) {
2313 ownership
= JS::SCTAG_TMO_MAPPED_DATA
;
2316 bufContents
.kind() ==
2317 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
||
2318 bufContents
.kind() ==
2319 ArrayBufferObject::MALLOCED_UNKNOWN_ARENA
,
2320 "failing to handle new ArrayBuffer kind?");
2321 ownership
= JS::SCTAG_TMO_ALLOC_DATA
;
2326 if (!out
.buf
.callbacks_
|| !out
.buf
.callbacks_
->writeTransfer
) {
2327 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
2329 if (!out
.buf
.callbacks_
->writeTransfer(cx
, obj
, out
.buf
.closure_
, &tag
,
2330 &ownership
, &content
,
2334 MOZ_ASSERT(tag
> SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2337 point
.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag
, ownership
)));
2338 MOZ_ALWAYS_TRUE(point
.advance());
2340 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content
)));
2341 MOZ_ALWAYS_TRUE(point
.advance());
2342 point
.write(NativeEndian::swapToLittleEndian(extraData
));
2343 MOZ_ALWAYS_TRUE(point
.advance());
2347 // Make sure there aren't any more transfer map entries after the expected
2348 // number we read out.
2349 if (!point
.done()) {
2351 SCInput::getPair(point
.peek(), &tag
, &data
);
2352 MOZ_ASSERT(tag
< SCTAG_TRANSFER_MAP_HEADER
||
2353 tag
>= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES
);
2359 bool JSStructuredCloneWriter::write(HandleValue v
) {
2360 if (!startWrite(v
)) {
2364 RootedObject
obj(context());
2365 RootedValue
key(context());
2366 RootedValue
val(context());
2367 RootedId
id(context());
2369 RootedValue
cause(context());
2370 RootedValue
errors(context());
2371 RootedValue
stack(context());
2373 while (!counts
.empty()) {
2374 obj
= &objs
.back().toObject();
2375 context()->check(obj
);
2376 if (counts
.back()) {
2380 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2384 if (cls
== ESClass::Map
) {
2385 key
= otherEntries
.popCopy();
2389 val
= otherEntries
.popCopy();
2392 if (!startWrite(key
) || !startWrite(val
)) {
2395 } else if (cls
== ESClass::Set
|| obj
->canUnwrapAs
<SavedFrame
>()) {
2396 key
= otherEntries
.popCopy();
2399 if (!startWrite(key
)) {
2402 } else if (cls
== ESClass::Error
) {
2403 cause
= otherEntries
.popCopy();
2407 errors
= otherEntries
.popCopy();
2411 stack
= otherEntries
.popCopy();
2414 if (!startWrite(cause
) || !startWrite(errors
) || !startWrite(stack
)) {
2418 id
= objectEntries
.popCopy();
2419 key
= IdToValue(id
);
2422 // If obj still has an own property named id, write it out.
2424 if (GetOwnPropertyPure(context(), obj
, id
, val
.address(), &found
)) {
2426 if (!writePrimitive(key
) || !startWrite(val
)) {
2433 if (!HasOwnProperty(context(), obj
, id
, &found
)) {
2438 #if FUZZING_JS_FUZZILLI
2439 // supress calls into user code
2440 if (js::SupportDifferentialTesting()) {
2441 fprintf(stderr
, "Differential testing: cannot call GetProperty\n");
2446 if (!writePrimitive(key
) ||
2447 !GetProperty(context(), obj
, obj
, id
, &val
) || !startWrite(val
)) {
2453 if (!out
.writePair(SCTAG_END_OF_KEYS
, 0)) {
2462 return transferOwnership();
2465 JSStructuredCloneReader::JSStructuredCloneReader(
2466 SCInput
& in
, JS::StructuredCloneScope scope
,
2467 const JS::CloneDataPolicy
& cloneDataPolicy
,
2468 const JSStructuredCloneCallbacks
* cb
, void* cbClosure
)
2470 allowedScope(scope
),
2471 cloneDataPolicy(cloneDataPolicy
),
2473 objState(in
.context(), in
.context()),
2474 allObjs(in
.context()),
2478 gcHeap(in
.context()) {
2479 // Avoid the need to bounds check by keeping a never-matching element at the
2480 // base of the `objState` stack. This append() will always succeed because
2481 // the objState vector has a nonzero MinInlineCapacity.
2482 MOZ_ALWAYS_TRUE(objState
.append(std::make_pair(nullptr, true)));
2485 template <typename CharT
>
2486 JSString
* JSStructuredCloneReader::readStringImpl(
2487 uint32_t nchars
, ShouldAtomizeStrings atomize
) {
2488 InlineCharBuffer
<CharT
> chars
;
2489 if (!chars
.maybeAlloc(context(), nchars
) ||
2490 !in
.readChars(chars
.get(), nchars
)) {
2495 return chars
.toAtom(context(), nchars
);
2498 return chars
.toStringDontDeflate(context(), nchars
, gcHeap
);
2501 JSString
* JSStructuredCloneReader::readString(uint32_t data
,
2502 ShouldAtomizeStrings atomize
) {
2503 uint32_t nchars
= data
& BitMask(31);
2504 bool latin1
= data
& (1 << 31);
2506 if (nchars
> JSString::MAX_LENGTH
) {
2507 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2508 JSMSG_SC_BAD_SERIALIZED_DATA
, "string length");
2512 return latin1
? readStringImpl
<Latin1Char
>(nchars
, atomize
)
2513 : readStringImpl
<char16_t
>(nchars
, atomize
);
2516 [[nodiscard
]] bool JSStructuredCloneReader::readUint32(uint32_t* num
) {
2517 Rooted
<Value
> lineVal(context());
2518 if (!startRead(&lineVal
)) {
2521 if (!lineVal
.isInt32()) {
2522 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2523 JSMSG_SC_BAD_SERIALIZED_DATA
, "integer required");
2526 *num
= uint32_t(lineVal
.toInt32());
2530 BigInt
* JSStructuredCloneReader::readBigInt(uint32_t data
) {
2531 size_t length
= data
& BitMask(31);
2532 bool isNegative
= data
& (1 << 31);
2534 return BigInt::zero(context());
2536 RootedBigInt
result(context(), BigInt::createUninitialized(
2537 context(), length
, isNegative
, gcHeap
));
2541 if (!in
.readArray(result
->digits().data(), length
)) {
2547 static uint32_t TagToV1ArrayType(uint32_t tag
) {
2548 MOZ_ASSERT(tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&&
2549 tag
<= SCTAG_TYPED_ARRAY_V1_MAX
);
2550 return tag
- SCTAG_TYPED_ARRAY_V1_MIN
;
2553 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType
,
2555 MutableHandleValue vp
,
2557 if (arrayType
> (v1Read
? Scalar::Uint8Clamped
: Scalar::BigUint64
)) {
2558 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2559 JSMSG_SC_BAD_SERIALIZED_DATA
,
2560 "unhandled typed array element type");
2564 // Push a placeholder onto the allObjs list to stand in for the typed array.
2565 uint32_t placeholderIndex
= allObjs
.length();
2566 Value dummy
= UndefinedValue();
2567 if (!allObjs
.append(dummy
)) {
2571 // Read the ArrayBuffer object and its contents (but no properties)
2572 RootedValue
v(context());
2573 uint64_t byteOffset
;
2575 if (!readV1ArrayBuffer(arrayType
, nelems
, &v
)) {
2580 if (!startRead(&v
)) {
2583 if (!in
.read(&byteOffset
)) {
2588 // Ensure invalid 64-bit values won't be truncated below.
2589 if (nelems
> ArrayBufferObject::MaxByteLength
||
2590 byteOffset
> ArrayBufferObject::MaxByteLength
) {
2591 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2592 JSMSG_SC_BAD_SERIALIZED_DATA
,
2593 "invalid typed array length or offset");
2597 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2598 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2599 JSMSG_SC_BAD_SERIALIZED_DATA
,
2600 "typed array must be backed by an ArrayBuffer");
2604 RootedObject
buffer(context(), &v
.toObject());
2605 RootedObject
obj(context(), nullptr);
2607 switch (arrayType
) {
2608 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2609 case Scalar::Name: \
2610 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2611 byteOffset, nelems) \
2615 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER
)
2616 #undef CREATE_FROM_BUFFER
2619 MOZ_CRASH("Can't happen: arrayType range checked above");
2627 allObjs
[placeholderIndex
].set(vp
);
2632 bool JSStructuredCloneReader::readDataView(uint64_t byteLength
,
2633 MutableHandleValue vp
) {
2634 // Push a placeholder onto the allObjs list to stand in for the DataView.
2635 uint32_t placeholderIndex
= allObjs
.length();
2636 Value dummy
= UndefinedValue();
2637 if (!allObjs
.append(dummy
)) {
2641 // Read the ArrayBuffer object and its contents (but no properties).
2642 RootedValue
v(context());
2643 if (!startRead(&v
)) {
2646 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2647 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2648 JSMSG_SC_BAD_SERIALIZED_DATA
,
2649 "DataView must be backed by an ArrayBuffer");
2654 uint64_t byteOffset
;
2655 if (!in
.read(&byteOffset
)) {
2659 // Ensure invalid 64-bit values won't be truncated below.
2660 if (byteLength
> ArrayBufferObject::MaxByteLength
||
2661 byteOffset
> ArrayBufferObject::MaxByteLength
) {
2662 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2663 JSMSG_SC_BAD_SERIALIZED_DATA
,
2664 "invalid DataView length or offset");
2668 RootedObject
buffer(context(), &v
.toObject());
2669 RootedObject
obj(context(),
2670 JS_NewDataView(context(), buffer
, byteOffset
, byteLength
));
2676 allObjs
[placeholderIndex
].set(vp
);
2681 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type
,
2683 MutableHandleValue vp
) {
2684 // V2 stores the length in |data|. The current version stores the
2685 // length separately to allow larger length values.
2686 uint64_t nbytes
= 0;
2687 if (type
== SCTAG_ARRAY_BUFFER_OBJECT
) {
2688 if (!in
.read(&nbytes
)) {
2692 MOZ_ASSERT(type
== SCTAG_ARRAY_BUFFER_OBJECT_V2
);
2696 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2697 // below, so we have to check this here.
2698 if (nbytes
> ArrayBufferObject::MaxByteLength
) {
2699 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2700 JSMSG_BAD_ARRAY_LENGTH
);
2704 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), size_t(nbytes
));
2709 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2710 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2711 return in
.readArray(buffer
.dataPointer(), nbytes
);
2714 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp
) {
2715 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2716 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2717 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2718 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2719 : JS_SCERR_NOT_CLONABLE
;
2720 ReportDataCloneError(context(), callbacks
, error
, closure
,
2721 "SharedArrayBuffer");
2725 uint64_t byteLength
;
2726 if (!in
.readBytes(&byteLength
, sizeof(byteLength
))) {
2727 return in
.reportTruncated();
2730 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2731 // below, so we have to check this here.
2732 if (byteLength
> ArrayBufferObject::MaxByteLength
) {
2733 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2734 JSMSG_BAD_ARRAY_LENGTH
);
2739 if (!in
.readBytes(&p
, sizeof(p
))) {
2740 return in
.reportTruncated();
2743 SharedArrayRawBuffer
* rawbuf
= reinterpret_cast<SharedArrayRawBuffer
*>(p
);
2745 // There's no guarantee that the receiving agent has enabled shared memory
2746 // even if the transmitting agent has done so. Ideally we'd check at the
2747 // transmission point, but that's tricky, and it will be a very rare problem
2748 // in any case. Just fail at the receiving end if we can't handle it.
2753 .getSharedMemoryAndAtomicsEnabled()) {
2754 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2755 JSMSG_SC_SAB_DISABLED
);
2759 // The new object will have a new reference to the rawbuf.
2761 if (!rawbuf
->addReference()) {
2762 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2763 JSMSG_SC_SAB_REFCNT_OFLO
);
2767 RootedObject
obj(context(),
2768 SharedArrayBufferObject::New(context(), rawbuf
, byteLength
));
2770 rawbuf
->dropReference();
2774 // `rawbuf` is now owned by `obj`.
2776 if (callbacks
&& callbacks
->sabCloned
&&
2777 !callbacks
->sabCloned(context(), /*receiving=*/true, closure
)) {
2785 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes
,
2786 MutableHandleValue vp
) {
2787 JSContext
* cx
= context();
2789 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2790 JSMSG_SC_BAD_SERIALIZED_DATA
,
2791 "invalid shared wasm memory tag");
2795 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2796 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2797 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2798 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2799 : JS_SCERR_NOT_CLONABLE
;
2800 ReportDataCloneError(cx
, callbacks
, error
, closure
, "WebAssembly.Memory");
2804 // Read the isHuge flag
2805 RootedValue
isHuge(cx
);
2806 if (!startRead(&isHuge
)) {
2810 // Read the SharedArrayBuffer object.
2811 RootedValue
payload(cx
);
2812 if (!startRead(&payload
)) {
2815 if (!payload
.isObject() ||
2816 !payload
.toObject().is
<SharedArrayBufferObject
>()) {
2817 JS_ReportErrorNumberASCII(
2818 context(), GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
2819 "shared wasm memory must be backed by a SharedArrayBuffer");
2823 Rooted
<ArrayBufferObjectMaybeShared
*> sab(
2824 cx
, &payload
.toObject().as
<SharedArrayBufferObject
>());
2826 // Construct the memory.
2828 cx
, GlobalObject::getOrCreatePrototype(cx
, JSProto_WasmMemory
));
2832 RootedObject
memory(
2833 cx
, WasmMemoryObject::create(cx
, sab
, isHuge
.toBoolean(), proto
));
2838 vp
.setObject(*memory
);
2843 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2844 * endianness-conversion while reading.
2846 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType
,
2848 MutableHandleValue vp
) {
2849 if (arrayType
> Scalar::Uint8Clamped
) {
2850 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2851 JSMSG_SC_BAD_SERIALIZED_DATA
,
2852 "invalid TypedArray type");
2856 mozilla::CheckedInt
<size_t> nbytes
=
2857 mozilla::CheckedInt
<size_t>(nelems
) *
2858 TypedArrayElemSize(static_cast<Scalar::Type
>(arrayType
));
2859 if (!nbytes
.isValid() || nbytes
.value() > UINT32_MAX
) {
2860 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2861 JSMSG_SC_BAD_SERIALIZED_DATA
,
2862 "invalid typed array size");
2866 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), nbytes
.value());
2871 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2872 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2874 switch (arrayType
) {
2877 case Scalar::Uint8Clamped
:
2878 return in
.readArray((uint8_t*)buffer
.dataPointer(), nelems
);
2880 case Scalar::Uint16
:
2881 return in
.readArray((uint16_t*)buffer
.dataPointer(), nelems
);
2883 case Scalar::Uint32
:
2884 case Scalar::Float32
:
2885 return in
.readArray((uint32_t*)buffer
.dataPointer(), nelems
);
2886 case Scalar::Float64
:
2887 case Scalar::BigInt64
:
2888 case Scalar::BigUint64
:
2889 return in
.readArray((uint64_t*)buffer
.dataPointer(), nelems
);
2891 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2895 static bool PrimitiveToObject(JSContext
* cx
, MutableHandleValue vp
) {
2896 JSObject
* obj
= js::PrimitiveToObject(cx
, vp
);
2905 bool JSStructuredCloneReader::startRead(MutableHandleValue vp
,
2906 ShouldAtomizeStrings atomizeStrings
) {
2908 bool alreadAppended
= false;
2910 if (!in
.readPair(&tag
, &data
)) {
2921 case SCTAG_UNDEFINED
:
2930 case SCTAG_BOOLEAN_OBJECT
:
2931 vp
.setBoolean(!!data
);
2932 if (tag
== SCTAG_BOOLEAN_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2938 case SCTAG_STRING_OBJECT
: {
2939 JSString
* str
= readString(data
, atomizeStrings
);
2944 if (tag
== SCTAG_STRING_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2950 case SCTAG_NUMBER_OBJECT
: {
2952 if (!in
.readDouble(&d
)) {
2955 vp
.setDouble(CanonicalizeNaN(d
));
2956 if (!PrimitiveToObject(context(), vp
)) {
2963 case SCTAG_BIGINT_OBJECT
: {
2964 RootedBigInt
bi(context(), readBigInt(data
));
2969 if (tag
== SCTAG_BIGINT_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2975 case SCTAG_DATE_OBJECT
: {
2977 if (!in
.readDouble(&d
)) {
2980 JS::ClippedTime t
= JS::TimeClip(d
);
2981 if (!NumbersAreIdentical(d
, t
.toDouble())) {
2982 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2983 JSMSG_SC_BAD_SERIALIZED_DATA
, "date");
2986 JSObject
* obj
= NewDateObjectMsec(context(), t
);
2994 case SCTAG_REGEXP_OBJECT
: {
2995 if ((data
& RegExpFlag::AllFlags
) != data
) {
2996 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2997 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
3001 RegExpFlags
flags(AssertedCast
<uint8_t>(data
));
3003 uint32_t tag2
, stringData
;
3004 if (!in
.readPair(&tag2
, &stringData
)) {
3007 if (tag2
!= SCTAG_STRING
) {
3008 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3009 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
3013 JSString
* str
= readString(stringData
, AtomizeStrings
);
3018 Rooted
<JSAtom
*> atom(context(), &str
->asAtom());
3020 NewObjectKind kind
=
3021 gcHeap
== gc::Heap::Tenured
? TenuredObject
: GenericObject
;
3022 RegExpObject
* reobj
= RegExpObject::create(context(), atom
, flags
, kind
);
3026 vp
.setObject(*reobj
);
3030 case SCTAG_ARRAY_OBJECT
:
3031 case SCTAG_OBJECT_OBJECT
: {
3032 NewObjectKind kind
=
3033 gcHeap
== gc::Heap::Tenured
? TenuredObject
: GenericObject
;
3035 if (tag
== SCTAG_ARRAY_OBJECT
) {
3036 obj
= NewDenseUnallocatedArray(
3037 context(), NativeEndian::swapFromLittleEndian(data
), kind
);
3039 obj
= NewPlainObject(context(), kind
);
3041 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3049 case SCTAG_BACK_REFERENCE_OBJECT
: {
3050 if (data
>= allObjs
.length() || !allObjs
[data
].isObject()) {
3051 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3052 JSMSG_SC_BAD_SERIALIZED_DATA
,
3053 "invalid back reference in input");
3056 vp
.set(allObjs
[data
]);
3060 case SCTAG_TRANSFER_MAP_HEADER
:
3061 case SCTAG_TRANSFER_MAP_PENDING_ENTRY
:
3062 // We should be past all the transfer map tags.
3063 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3064 JSMSG_SC_BAD_SERIALIZED_DATA
, "invalid input");
3067 case SCTAG_ARRAY_BUFFER_OBJECT_V2
:
3068 case SCTAG_ARRAY_BUFFER_OBJECT
:
3069 if (!readArrayBuffer(StructuredDataType(tag
), data
, vp
)) {
3074 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT
:
3075 if (!readSharedArrayBuffer(vp
)) {
3080 case SCTAG_SHARED_WASM_MEMORY_OBJECT
:
3081 if (!readSharedWasmMemory(data
, vp
)) {
3086 case SCTAG_TYPED_ARRAY_OBJECT_V2
: {
3087 // readTypedArray adds the array to allObjs.
3088 // V2 stores the length (nelems) in |data| and the arrayType separately.
3090 if (!in
.read(&arrayType
)) {
3093 uint64_t nelems
= data
;
3094 return readTypedArray(arrayType
, nelems
, vp
);
3097 case SCTAG_TYPED_ARRAY_OBJECT
: {
3098 // readTypedArray adds the array to allObjs.
3099 // The current version stores the array type in |data| and the length
3100 // (nelems) separately to support large TypedArrays.
3101 uint32_t arrayType
= data
;
3103 if (!in
.read(&nelems
)) {
3106 return readTypedArray(arrayType
, nelems
, vp
);
3109 case SCTAG_DATA_VIEW_OBJECT_V2
: {
3110 // readDataView adds the array to allObjs.
3111 uint64_t byteLength
= data
;
3112 return readDataView(byteLength
, vp
);
3115 case SCTAG_DATA_VIEW_OBJECT
: {
3116 // readDataView adds the array to allObjs.
3117 uint64_t byteLength
;
3118 if (!in
.read(&byteLength
)) {
3121 return readDataView(byteLength
, vp
);
3124 case SCTAG_MAP_OBJECT
: {
3125 JSObject
* obj
= MapObject::create(context());
3126 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3133 case SCTAG_SET_OBJECT
: {
3134 JSObject
* obj
= SetObject::create(context());
3135 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
3142 case SCTAG_SAVED_FRAME_OBJECT
: {
3143 auto* obj
= readSavedFrameHeader(data
);
3144 if (!obj
|| !objs
.append(ObjectValue(*obj
)) ||
3145 !objState
.append(std::make_pair(obj
, false))) {
3152 case SCTAG_ERROR_OBJECT
: {
3153 auto* obj
= readErrorHeader(data
);
3154 if (!obj
|| !objs
.append(ObjectValue(*obj
)) ||
3155 !objState
.append(std::make_pair(obj
, false))) {
3162 case SCTAG_END_OF_KEYS
:
3163 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3164 JSMSG_SC_BAD_SERIALIZED_DATA
,
3170 if (tag
<= SCTAG_FLOAT_MAX
) {
3171 double d
= ReinterpretPairAsDouble(tag
, data
);
3172 vp
.setNumber(CanonicalizeNaN(d
));
3176 if (SCTAG_TYPED_ARRAY_V1_MIN
<= tag
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
3177 // A v1-format typed array
3178 // readTypedArray adds the array to allObjs
3179 return readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
3182 if (!callbacks
|| !callbacks
->read
) {
3183 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3184 JSMSG_SC_BAD_SERIALIZED_DATA
,
3185 "unsupported type");
3189 // callbacks->read() might read other objects from the buffer.
3190 // In startWrite we always write the object itself before calling
3191 // the custom function. We should do the same here to keep
3192 // indexing consistent.
3193 uint32_t placeholderIndex
= allObjs
.length();
3194 Value dummy
= UndefinedValue();
3195 if (!allObjs
.append(dummy
)) {
3199 callbacks
->read(context(), this, cloneDataPolicy
, tag
, data
, closure
);
3204 allObjs
[placeholderIndex
].set(vp
);
3205 alreadAppended
= true;
3209 if (!alreadAppended
&& vp
.isObject() && !allObjs
.append(vp
)) {
3216 bool JSStructuredCloneReader::readHeader() {
3218 if (!in
.getPair(&tag
, &data
)) {
3219 return in
.reportTruncated();
3222 JS::StructuredCloneScope storedScope
;
3223 if (tag
== SCTAG_HEADER
) {
3224 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3225 storedScope
= JS::StructuredCloneScope(data
);
3227 // Old structured clone buffer. We must have read it from disk.
3228 storedScope
= JS::StructuredCloneScope::DifferentProcessForIndexedDB
;
3231 // Backward compatibility with old structured clone buffers. Value '0' was
3232 // used for SameProcessSameThread scope.
3233 if ((int)storedScope
== 0) {
3234 storedScope
= JS::StructuredCloneScope::SameProcess
;
3237 if (storedScope
< JS::StructuredCloneScope::SameProcess
||
3238 storedScope
> JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3239 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3240 JSMSG_SC_BAD_SERIALIZED_DATA
,
3241 "invalid structured clone scope");
3245 if (allowedScope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3246 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
3247 // clones are incorrect. Treat them as if they were DifferentProcess.
3248 allowedScope
= JS::StructuredCloneScope::DifferentProcess
;
3252 if (storedScope
< allowedScope
) {
3253 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3254 JSMSG_SC_BAD_SERIALIZED_DATA
,
3255 "incompatible structured clone scope");
3262 bool JSStructuredCloneReader::readTransferMap() {
3263 JSContext
* cx
= context();
3264 auto headerPos
= in
.tell();
3267 if (!in
.getPair(&tag
, &data
)) {
3268 return in
.reportTruncated();
3271 if (tag
!= SCTAG_TRANSFER_MAP_HEADER
||
3272 TransferableMapHeader(data
) == SCTAG_TM_TRANSFERRED
) {
3276 uint64_t numTransferables
;
3277 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3278 if (!in
.read(&numTransferables
)) {
3282 for (uint64_t i
= 0; i
< numTransferables
; i
++) {
3283 auto pos
= in
.tell();
3285 if (!in
.readPair(&tag
, &data
)) {
3289 if (tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
) {
3290 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3294 RootedObject
obj(cx
);
3297 if (!in
.readPtr(&content
)) {
3302 if (!in
.read(&extraData
)) {
3306 if (tag
== SCTAG_TRANSFER_MAP_ARRAY_BUFFER
) {
3307 if (allowedScope
== JS::StructuredCloneScope::DifferentProcess
||
3309 JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
3310 // Transferred ArrayBuffers in a DifferentProcess clone buffer
3311 // are treated as if they weren't Transferred at all. We should
3312 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
3313 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3317 MOZ_RELEASE_ASSERT(extraData
<= ArrayBufferObject::MaxByteLength
);
3318 size_t nbytes
= extraData
;
3320 MOZ_ASSERT(data
== JS::SCTAG_TMO_ALLOC_DATA
||
3321 data
== JS::SCTAG_TMO_MAPPED_DATA
);
3322 if (data
== JS::SCTAG_TMO_ALLOC_DATA
) {
3323 // When the ArrayBuffer can't be allocated, |content| will be free'ed
3324 // in `JSStructuredCloneData::discardTransferables()`.
3325 obj
= JS::NewArrayBufferWithContents(
3326 cx
, nbytes
, content
,
3327 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory
);
3328 } else if (data
== JS::SCTAG_TMO_MAPPED_DATA
) {
3329 obj
= JS::NewMappedArrayBufferWithContents(cx
, nbytes
, content
);
3331 } else if (tag
== SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
) {
3332 auto savedPos
= in
.tell();
3333 auto guard
= mozilla::MakeScopeExit([&] { in
.seekTo(savedPos
); });
3335 if (!in
.seekBy(static_cast<size_t>(extraData
))) {
3339 if (tailStartPos
.isNothing()) {
3340 tailStartPos
= mozilla::Some(in
.tell());
3344 if (!in
.readPair(&tag
, &data
)) {
3347 if (tag
!= SCTAG_ARRAY_BUFFER_OBJECT_V2
&&
3348 tag
!= SCTAG_ARRAY_BUFFER_OBJECT
) {
3349 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3352 RootedValue
val(cx
);
3353 if (!readArrayBuffer(StructuredDataType(tag
), data
, &val
)) {
3356 obj
= &val
.toObject();
3357 tailEndPos
= mozilla::Some(in
.tell());
3359 if (!callbacks
|| !callbacks
->readTransfer
) {
3360 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3363 if (!callbacks
->readTransfer(cx
, this, cloneDataPolicy
, tag
, content
,
3364 extraData
, closure
, &obj
)) {
3365 if (!cx
->isExceptionPending()) {
3366 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
3371 MOZ_ASSERT(!cx
->isExceptionPending());
3374 // On failure, the buffer will still own the data (since its ownership
3375 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3376 // DiscardTransferables.
3381 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3383 pos
.write(PairToUInt64(tag
, JS::SCTAG_TMO_UNOWNED
));
3384 MOZ_ASSERT(!pos
.done());
3386 if (!allObjs
.append(ObjectValue(*obj
))) {
3391 // Mark the whole transfer map as consumed.
3393 SCInput::getPair(headerPos
.peek(), &tag
, &data
);
3394 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_HEADER
);
3395 MOZ_ASSERT(TransferableMapHeader(data
) != SCTAG_TM_TRANSFERRED
);
3398 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER
, SCTAG_TM_TRANSFERRED
));
3403 JSObject
* JSStructuredCloneReader::readSavedFrameHeader(
3404 uint32_t principalsTag
) {
3405 Rooted
<SavedFrame
*> savedFrame(context(), SavedFrame::create(context()));
3410 JSPrincipals
* principals
;
3411 if (principalsTag
== SCTAG_JSPRINCIPALS
) {
3412 if (!context()->runtime()->readPrincipals
) {
3413 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3414 JSMSG_SC_UNSUPPORTED_TYPE
);
3418 if (!context()->runtime()->readPrincipals(context(), this, &principals
)) {
3421 } else if (principalsTag
==
3422 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
) {
3423 principals
= &ReconstructedSavedFramePrincipals::IsSystem
;
3424 principals
->refcount
++;
3425 } else if (principalsTag
==
3426 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
) {
3427 principals
= &ReconstructedSavedFramePrincipals::IsNotSystem
;
3428 principals
->refcount
++;
3429 } else if (principalsTag
== SCTAG_NULL_JSPRINCIPALS
) {
3430 principals
= nullptr;
3432 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3433 JSMSG_SC_BAD_SERIALIZED_DATA
,
3434 "bad SavedFrame principals");
3438 RootedValue
mutedErrors(context());
3439 RootedValue
source(context());
3441 // Read a |mutedErrors| boolean followed by a |source| string.
3442 // The |mutedErrors| boolean is present in all new structured-clone data,
3443 // but in older data it will be absent and only the |source| string will be
3445 if (!startRead(&mutedErrors
, AtomizeStrings
)) {
3449 if (mutedErrors
.isBoolean()) {
3450 if (!startRead(&source
, AtomizeStrings
) || !source
.isString()) {
3453 } else if (mutedErrors
.isString()) {
3454 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3455 // this is actually just a |source| string.
3456 source
= mutedErrors
;
3457 mutedErrors
.setBoolean(true); // Safe default value.
3464 savedFrame
->initPrincipalsAlreadyHeldAndMutedErrors(principals
,
3465 mutedErrors
.toBoolean());
3467 savedFrame
->initSource(&source
.toString()->asAtom());
3470 if (!readUint32(&line
)) {
3473 savedFrame
->initLine(line
);
3475 JS::TaggedColumnNumberOneOrigin column
;
3476 if (!readUint32(column
.addressOfValueForTranscode())) {
3479 savedFrame
->initColumn(column
);
3481 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3482 // are only valid within a specific process.
3483 savedFrame
->initSourceId(0);
3485 RootedValue
name(context());
3486 if (!startRead(&name
, AtomizeStrings
)) {
3489 if (!(name
.isString() || name
.isNull())) {
3490 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3491 JSMSG_SC_BAD_SERIALIZED_DATA
,
3492 "invalid saved frame cause");
3495 JSAtom
* atomName
= nullptr;
3496 if (name
.isString()) {
3497 atomName
= &name
.toString()->asAtom();
3500 savedFrame
->initFunctionDisplayName(atomName
);
3502 RootedValue
cause(context());
3503 if (!startRead(&cause
, AtomizeStrings
)) {
3506 if (!(cause
.isString() || cause
.isNull())) {
3507 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3508 JSMSG_SC_BAD_SERIALIZED_DATA
,
3509 "invalid saved frame cause");
3512 JSAtom
* atomCause
= nullptr;
3513 if (cause
.isString()) {
3514 atomCause
= &cause
.toString()->asAtom();
3516 savedFrame
->initAsyncCause(atomCause
);
3521 // SavedFrame object: there is one child value, the parent SavedFrame,
3522 // which is either null or another SavedFrame object.
3523 bool JSStructuredCloneReader::readSavedFrameFields(Handle
<SavedFrame
*> frameObj
,
3527 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3528 JSMSG_SC_BAD_SERIALIZED_DATA
,
3529 "multiple SavedFrame parents");
3533 SavedFrame
* parentFrame
;
3534 if (parent
.isNull()) {
3535 parentFrame
= nullptr;
3536 } else if (parent
.isObject() && parent
.toObject().is
<SavedFrame
>()) {
3537 parentFrame
= &parent
.toObject().as
<SavedFrame
>();
3539 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3540 JSMSG_SC_BAD_SERIALIZED_DATA
,
3541 "invalid SavedFrame parent");
3545 frameObj
->initParent(parentFrame
);
3550 JSObject
* JSStructuredCloneReader::readErrorHeader(uint32_t type
) {
3551 JSContext
* cx
= context();
3556 case JSEXN_RANGEERR
:
3557 case JSEXN_REFERENCEERR
:
3558 case JSEXN_SYNTAXERR
:
3561 case JSEXN_AGGREGATEERR
:
3564 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3565 JSMSG_SC_BAD_SERIALIZED_DATA
,
3566 "invalid error type");
3570 RootedString
message(cx
);
3572 RootedValue
messageVal(cx
);
3573 if (!startRead(&messageVal
)) {
3576 if (messageVal
.isString()) {
3577 message
= messageVal
.toString();
3578 } else if (!messageVal
.isNull()) {
3579 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3580 JSMSG_SC_BAD_SERIALIZED_DATA
,
3581 "invalid 'message' field for Error object");
3586 // We have to set |cause| to something if it exists, otherwise the shape
3587 // would be wrong. The actual value will be overwritten later.
3588 RootedValue
val(cx
);
3589 if (!startRead(&val
)) {
3592 bool hasCause
= ToBoolean(val
);
3593 Rooted
<Maybe
<Value
>> cause(cx
, mozilla::Nothing());
3595 cause
= mozilla::Some(BooleanValue(true));
3598 if (!startRead(&val
)) {
3601 if (!val
.isString()) {
3602 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3603 JSMSG_SC_BAD_SERIALIZED_DATA
,
3604 "invalid 'fileName' field for Error object");
3607 RootedString
fileName(cx
, val
.toString());
3609 uint32_t lineNumber
;
3610 JS::ColumnNumberOneOrigin columnNumber
;
3611 if (!readUint32(&lineNumber
) ||
3612 !readUint32(columnNumber
.addressOfValueForTranscode())) {
3616 // The |cause| and |stack| slots of the objects might be overwritten later.
3617 // For AggregateErrors the |errors| property will be added.
3618 RootedObject
errorObj(
3619 cx
, ErrorObject::create(cx
, static_cast<JSExnType
>(type
), nullptr,
3620 fileName
, 0, lineNumber
, columnNumber
, nullptr,
3629 // Error objects have 3 fields, some or all of them null: cause,
3630 // errors, and stack.
3631 bool JSStructuredCloneReader::readErrorFields(Handle
<ErrorObject
*> errorObj
,
3632 HandleValue cause
, bool* state
) {
3633 JSContext
* cx
= context();
3635 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3636 JSMSG_SC_BAD_SERIALIZED_DATA
,
3637 "unexpected child value seen for Error object");
3641 RootedValue
errors(cx
);
3642 RootedValue
stack(cx
);
3643 if (!startRead(&errors
) || !startRead(&stack
)) {
3647 bool hasCause
= errorObj
->getCause().isSome();
3649 errorObj
->setCauseSlot(cause
);
3650 } else if (!cause
.isNull()) {
3651 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3652 JSMSG_SC_BAD_SERIALIZED_DATA
,
3653 "invalid 'cause' field for Error object");
3657 if (errorObj
->type() == JSEXN_AGGREGATEERR
) {
3658 if (!DefineDataProperty(context(), errorObj
, cx
->names().errors
, errors
,
3662 } else if (!errors
.isNull()) {
3663 JS_ReportErrorNumberASCII(
3664 cx
, GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
3665 "unexpected 'errors' field seen for non-AggregateError");
3669 if (stack
.isObject()) {
3670 RootedObject
stackObj(cx
, &stack
.toObject());
3671 if (!stackObj
->is
<SavedFrame
>()) {
3672 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3673 JSMSG_SC_BAD_SERIALIZED_DATA
,
3674 "invalid 'stack' field for Error object");
3677 errorObj
->setStackSlot(stack
);
3678 } else if (!stack
.isNull()) {
3679 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3680 JSMSG_SC_BAD_SERIALIZED_DATA
,
3681 "invalid 'stack' field for Error object");
3689 // Read a value and treat as a key,value pair.
3690 bool JSStructuredCloneReader::readMapField(Handle
<MapObject
*> mapObj
,
3692 RootedValue
val(context());
3693 if (!startRead(&val
)) {
3696 return MapObject::set(context(), mapObj
, key
, val
);
3699 // Read a value and treat as a key,value pair. Interpret as a plain property
3701 bool JSStructuredCloneReader::readObjectField(HandleObject obj
,
3703 if (!key
.isString() && !key
.isInt32()) {
3704 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3705 JSMSG_SC_BAD_SERIALIZED_DATA
,
3706 "property key expected");
3710 RootedValue
val(context());
3711 if (!startRead(&val
)) {
3715 RootedId
id(context());
3716 if (!PrimitiveValueToId
<CanGC
>(context(), key
, &id
)) {
3720 // Fast path for adding a new property to a plain object. The property names
3721 // we see here should be unique, but we check for duplicates to guard against
3722 // corrupt or malicious data.
3723 if (id
.isString() && obj
->is
<PlainObject
>() &&
3724 MOZ_LIKELY(!obj
->as
<PlainObject
>().contains(context(), id
))) {
3725 return AddDataPropertyToPlainObject(context(), obj
.as
<PlainObject
>(), id
,
3729 // Fast path for adding an array element. The index shouldn't exceed the
3730 // array's length, but we check for this in `addDenseElementNoLengthChange` to
3731 // guard against corrupt or malicious data.
3732 if (id
.isInt() && obj
->is
<ArrayObject
>()) {
3733 ArrayObject
* arr
= &obj
->as
<ArrayObject
>();
3734 switch (arr
->addDenseElementNoLengthChange(context(), id
.toInt(), val
)) {
3735 case DenseElementResult::Failure
:
3737 case DenseElementResult::Success
:
3739 case DenseElementResult::Incomplete
:
3740 // Fall-through to slow path.
3745 return DefineDataProperty(context(), obj
, id
, val
);
3748 // Perform the whole recursive reading procedure.
3749 bool JSStructuredCloneReader::read(MutableHandleValue vp
, size_t nbytes
) {
3750 auto startTime
= mozilla::TimeStamp::Now();
3752 if (!readHeader()) {
3756 if (!readTransferMap()) {
3760 MOZ_ASSERT(objs
.length() == 0);
3761 MOZ_ASSERT(objState
.length() == 1);
3763 // Start out by reading in the main object and pushing it onto the 'objs'
3764 // stack. The data related to this object and its descendants extends from
3765 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3766 if (!startRead(vp
)) {
3770 // Stop when the stack shows that all objects have been read.
3771 while (objs
.length() != 0) {
3772 // What happens depends on the top obj on the objs stack.
3773 RootedObject
obj(context(), &objs
.back().toObject());
3776 if (!in
.getPair(&tag
, &data
)) {
3780 if (tag
== SCTAG_END_OF_KEYS
) {
3781 // Pop the current obj off the stack, since we are done with it and
3783 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3785 if (objState
.back().first
== obj
) {
3791 // Remember the index of the current top of the state stack, which will
3792 // correspond to the state for `obj` iff `obj` is a type that uses state.
3793 // startRead() may push additional entries before the state is accessed and
3794 // updated while filling in the object's data.
3795 size_t objStateIdx
= objState
.length() - 1;
3797 // The input stream contains a sequence of "child" values, whose
3798 // interpretation depends on the type of obj. These values can be
3799 // anything, and startRead() will push onto 'objs' for any non-leaf
3800 // value (i.e., anything that may contain children).
3802 // startRead() will allocate the (empty) object, but note that when
3803 // startRead() returns, 'key' is not yet initialized with any of its
3804 // properties. Those will be filled in by returning to the head of this
3805 // loop, processing the first child obj, and continuing until all
3806 // children have been fully created.
3808 // Note that this means the ordering in the stream is a little funky for
3809 // things like Map. See the comment above traverseMap() for an example.
3811 bool expectKeyValuePairs
=
3812 !(obj
->is
<MapObject
>() || obj
->is
<SetObject
>() ||
3813 obj
->is
<SavedFrame
>() || obj
->is
<ErrorObject
>());
3815 RootedValue
key(context());
3816 ShouldAtomizeStrings atomize
=
3817 expectKeyValuePairs
? AtomizeStrings
: DontAtomizeStrings
;
3818 if (!startRead(&key
, atomize
)) {
3822 if (key
.isNull() && expectKeyValuePairs
) {
3823 // Backwards compatibility: Null formerly indicated the end of
3824 // object properties.
3826 // No legacy objects used the state stack.
3827 MOZ_ASSERT(objState
[objStateIdx
].first() != obj
);
3833 context()->check(key
);
3835 if (obj
->is
<SetObject
>()) {
3836 // Set object: the values between obj header (from startRead()) and
3837 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3838 if (!SetObject::add(context(), obj
, key
)) {
3841 } else if (obj
->is
<MapObject
>()) {
3842 Rooted
<MapObject
*> mapObj(context(), &obj
->as
<MapObject
>());
3843 if (!readMapField(mapObj
, key
)) {
3846 } else if (obj
->is
<SavedFrame
>()) {
3847 Rooted
<SavedFrame
*> frameObj(context(), &obj
->as
<SavedFrame
>());
3848 MOZ_ASSERT(objState
[objStateIdx
].first() == obj
);
3849 bool state
= objState
[objStateIdx
].second();
3850 if (!readSavedFrameFields(frameObj
, key
, &state
)) {
3853 objState
[objStateIdx
].second() = state
;
3854 } else if (obj
->is
<ErrorObject
>()) {
3855 Rooted
<ErrorObject
*> errorObj(context(), &obj
->as
<ErrorObject
>());
3856 MOZ_ASSERT(objState
[objStateIdx
].first() == obj
);
3857 bool state
= objState
[objStateIdx
].second();
3858 if (!readErrorFields(errorObj
, key
, &state
)) {
3861 objState
[objStateIdx
].second() = state
;
3863 MOZ_ASSERT(expectKeyValuePairs
);
3864 // Everything else uses a series of key,value,key,value,... Value
3866 if (!readObjectField(obj
, key
)) {
3874 // For fuzzing, it is convenient to allow extra data at the end
3875 // of the input buffer so that more possible inputs are considered
3879 if (tailStartPos
.isSome()) {
3880 // in.tell() is the end of the main data. If "tail" data was consumed,
3881 // then check whether there's any data between the main data and the
3882 // beginning of the tail, or after the last read point in the tail.
3883 extraData
= (in
.tell() != *tailStartPos
|| !tailEndPos
->done());
3885 extraData
= !in
.tell().done();
3888 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3889 JSMSG_SC_BAD_SERIALIZED_DATA
,
3890 "extra data after end");
3895 JSRuntime
* rt
= context()->runtime();
3896 rt
->metrics().DESERIALIZE_BYTES(nbytes
);
3897 rt
->metrics().DESERIALIZE_ITEMS(numItemsRead
);
3898 mozilla::TimeDuration elapsed
= mozilla::TimeStamp::Now() - startTime
;
3899 rt
->metrics().DESERIALIZE_US(elapsed
);
3904 JS_PUBLIC_API
bool JS_ReadStructuredClone(
3905 JSContext
* cx
, const JSStructuredCloneData
& buf
, uint32_t version
,
3906 JS::StructuredCloneScope scope
, MutableHandleValue vp
,
3907 const JS::CloneDataPolicy
& cloneDataPolicy
,
3908 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3912 if (version
> JS_STRUCTURED_CLONE_VERSION
) {
3913 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3914 JSMSG_SC_BAD_CLONE_VERSION
);
3917 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3918 return ReadStructuredClone(cx
, buf
, scope
, vp
, cloneDataPolicy
, callbacks
,
3922 JS_PUBLIC_API
bool JS_WriteStructuredClone(
3923 JSContext
* cx
, HandleValue value
, JSStructuredCloneData
* bufp
,
3924 JS::StructuredCloneScope scope
, const JS::CloneDataPolicy
& cloneDataPolicy
,
3925 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
,
3926 HandleValue transferable
) {
3931 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3932 return WriteStructuredClone(cx
, value
, bufp
, scope
, cloneDataPolicy
,
3933 callbacks
, closure
, transferable
);
3936 JS_PUBLIC_API
bool JS_StructuredCloneHasTransferables(
3937 JSStructuredCloneData
& data
, bool* hasTransferable
) {
3938 *hasTransferable
= StructuredCloneHasTransferObjects(data
);
3942 JS_PUBLIC_API
bool JS_StructuredClone(
3943 JSContext
* cx
, HandleValue value
, MutableHandleValue vp
,
3944 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3948 // Strings are associated with zones, not compartments,
3949 // so we copy the string by wrapping it.
3950 if (value
.isString()) {
3951 RootedString
strValue(cx
, value
.toString());
3952 if (!cx
->compartment()->wrap(cx
, &strValue
)) {
3955 vp
.setString(strValue
);
3959 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3961 JSAutoStructuredCloneBuffer
buf(JS::StructuredCloneScope::SameProcess
,
3962 callbacks
, closure
);
3964 if (value
.isObject()) {
3965 RootedObject
obj(cx
, &value
.toObject());
3966 obj
= CheckedUnwrapStatic(obj
);
3968 ReportAccessDenied(cx
);
3971 AutoRealm
ar(cx
, obj
);
3972 RootedValue
unwrappedVal(cx
, ObjectValue(*obj
));
3973 if (!buf
.write(cx
, unwrappedVal
, callbacks
, closure
)) {
3977 if (!buf
.write(cx
, value
, callbacks
, closure
)) {
3983 return buf
.read(cx
, vp
, JS::CloneDataPolicy(), callbacks
, closure
);
3986 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3987 JSAutoStructuredCloneBuffer
&& other
)
3988 : data_(other
.scope()) {
3989 version_
= other
.version_
;
3990 other
.giveTo(&data_
);
3993 JSAutoStructuredCloneBuffer
& JSAutoStructuredCloneBuffer::operator=(
3994 JSAutoStructuredCloneBuffer
&& other
) {
3995 MOZ_ASSERT(&other
!= this);
3996 MOZ_ASSERT(scope() == other
.scope());
3998 version_
= other
.version_
;
3999 other
.giveTo(&data_
);
4003 void JSAutoStructuredCloneBuffer::clear() {
4004 data_
.discardTransferables();
4005 data_
.ownTransferables_
= OwnTransferablePolicy::NoTransferables
;
4006 data_
.refsHeld_
.releaseAll();
4011 void JSAutoStructuredCloneBuffer::adopt(
4012 JSStructuredCloneData
&& data
, uint32_t version
,
4013 const JSStructuredCloneCallbacks
* callbacks
, void* closure
) {
4015 data_
= std::move(data
);
4017 data_
.setCallbacks(callbacks
, closure
,
4018 OwnTransferablePolicy::OwnsTransferablesIfAny
);
4021 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData
* data
) {
4022 *data
= std::move(data_
);
4024 data_
.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables
);
4028 bool JSAutoStructuredCloneBuffer::read(
4029 JSContext
* cx
, MutableHandleValue vp
,
4030 const JS::CloneDataPolicy
& cloneDataPolicy
,
4031 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4033 return !!JS_ReadStructuredClone(
4034 cx
, data_
, version_
, data_
.scope(), vp
, cloneDataPolicy
,
4035 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4036 optionalCallbacks
? closure
: data_
.closure_
);
4039 bool JSAutoStructuredCloneBuffer::write(
4040 JSContext
* cx
, HandleValue value
,
4041 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4042 HandleValue transferable
= UndefinedHandleValue
;
4043 return write(cx
, value
, transferable
, JS::CloneDataPolicy(),
4044 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4045 optionalCallbacks
? closure
: data_
.closure_
);
4048 bool JSAutoStructuredCloneBuffer::write(
4049 JSContext
* cx
, HandleValue value
, HandleValue transferable
,
4050 const JS::CloneDataPolicy
& cloneDataPolicy
,
4051 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
4053 bool ok
= JS_WriteStructuredClone(
4054 cx
, value
, &data_
, data_
.scopeForInternalWriting(), cloneDataPolicy
,
4055 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
4056 optionalCallbacks
? closure
: data_
.closure_
, transferable
);
4058 version_
= JS_STRUCTURED_CLONE_VERSION
;
4063 JS_PUBLIC_API
bool JS_ReadUint32Pair(JSStructuredCloneReader
* r
, uint32_t* p1
,
4065 return r
->input().readPair((uint32_t*)p1
, (uint32_t*)p2
);
4068 JS_PUBLIC_API
bool JS_ReadBytes(JSStructuredCloneReader
* r
, void* p
,
4070 return r
->input().readBytes(p
, len
);
4073 JS_PUBLIC_API
bool JS_ReadString(JSStructuredCloneReader
* r
,
4074 MutableHandleString str
) {
4076 if (!r
->input().readPair(&tag
, &data
)) {
4080 if (tag
== SCTAG_STRING
) {
4082 r
->readString(data
, JSStructuredCloneReader::DontAtomizeStrings
)) {
4089 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
4090 JSMSG_SC_BAD_SERIALIZED_DATA
, "expected string");
4094 JS_PUBLIC_API
bool JS_ReadDouble(JSStructuredCloneReader
* r
, double* v
) {
4095 return r
->input().readDouble(v
);
4098 JS_PUBLIC_API
bool JS_ReadTypedArray(JSStructuredCloneReader
* r
,
4099 MutableHandleValue vp
) {
4101 if (!r
->input().readPair(&tag
, &data
)) {
4105 if (tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
4106 return r
->readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
4109 if (tag
== SCTAG_TYPED_ARRAY_OBJECT_V2
) {
4110 // V2 stores the length (nelems) in |data| and the arrayType separately.
4112 if (!r
->input().read(&arrayType
)) {
4115 uint64_t nelems
= data
;
4116 return r
->readTypedArray(arrayType
, nelems
, vp
);
4119 if (tag
== SCTAG_TYPED_ARRAY_OBJECT
) {
4120 // The current version stores the array type in |data| and the length
4121 // (nelems) separately to support large TypedArrays.
4122 uint32_t arrayType
= data
;
4124 if (!r
->input().read(&nelems
)) {
4127 return r
->readTypedArray(arrayType
, nelems
, vp
);
4130 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
4131 JSMSG_SC_BAD_SERIALIZED_DATA
,
4132 "expected type array");
4136 JS_PUBLIC_API
bool JS_WriteUint32Pair(JSStructuredCloneWriter
* w
, uint32_t tag
,
4138 return w
->output().writePair(tag
, data
);
4141 JS_PUBLIC_API
bool JS_WriteBytes(JSStructuredCloneWriter
* w
, const void* p
,
4143 return w
->output().writeBytes(p
, len
);
4146 JS_PUBLIC_API
bool JS_WriteString(JSStructuredCloneWriter
* w
,
4148 return w
->writeString(SCTAG_STRING
, str
);
4151 JS_PUBLIC_API
bool JS_WriteDouble(JSStructuredCloneWriter
* w
, double v
) {
4152 return w
->output().writeDouble(v
);
4155 JS_PUBLIC_API
bool JS_WriteTypedArray(JSStructuredCloneWriter
* w
,
4157 MOZ_ASSERT(v
.isObject());
4158 w
->context()->check(v
);
4159 RootedObject
obj(w
->context(), &v
.toObject());
4161 // startWrite can write everything, thus we should check here
4162 // and report error if the user passes a wrong type.
4163 if (!obj
->canUnwrapAs
<TypedArrayObject
>()) {
4164 ReportAccessDenied(w
->context());
4168 // We should use startWrite instead of writeTypedArray, because
4169 // typed array is an object, we should add it to the |memory|
4170 // (allObjs) list. Directly calling writeTypedArray won't add it.
4171 return w
->startWrite(v
);
4174 JS_PUBLIC_API
bool JS_ObjectNotWritten(JSStructuredCloneWriter
* w
,
4176 w
->memory
.remove(w
->memory
.lookup(obj
));
4181 JS_PUBLIC_API
JS::StructuredCloneScope
JS_GetStructuredCloneScope(
4182 JSStructuredCloneWriter
* w
) {
4183 return w
->output().scope();