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/RangedPtr.h"
36 #include "mozilla/ScopeExit.h"
44 #include "builtin/DataViewObject.h"
45 #include "builtin/MapObject.h"
46 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
47 #include "js/ArrayBuffer.h" // JS::{ArrayBufferHasData,DetachArrayBuffer,IsArrayBufferObject,New{,Mapped}ArrayBufferWithContents,ReleaseMappedArrayBufferContents}
49 #include "js/experimental/TypedData.h" // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer
50 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
51 #include "js/GCHashTable.h"
52 #include "js/Object.h" // JS::GetBuiltinClass
53 #include "js/PropertyAndElement.h" // JS_GetElement
54 #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
55 #include "js/ScalarType.h" // js::Scalar::Type
56 #include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject
57 #include "js/Wrapper.h"
58 #include "vm/BigIntType.h"
59 #include "vm/JSContext.h"
60 #include "vm/PlainObject.h" // js::PlainObject
61 #include "vm/RegExpObject.h"
62 #include "vm/SavedFrame.h"
63 #include "vm/SharedArrayObject.h"
64 #include "vm/TypedArrayObject.h"
65 #include "vm/WrapperObject.h"
66 #include "wasm/WasmJS.h"
68 #include "vm/InlineCharBuffer-inl.h"
69 #include "vm/JSContext-inl.h"
70 #include "vm/JSObject-inl.h"
74 using JS::CanonicalizeNaN
;
75 using JS::GetBuiltinClass
;
77 using JS::RegExpFlags
;
78 using JS::RootedValueVector
;
79 using mozilla::AssertedCast
;
80 using mozilla::BitwiseCast
;
81 using mozilla::NativeEndian
;
82 using mozilla::NumbersAreIdentical
;
83 using mozilla::RangedPtr
;
85 // When you make updates here, make sure you consider whether you need to bump
86 // the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You
87 // will likely need to increment the version if anything at all changes in the
88 // serialization format.
90 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should
91 // have a stable ID, it need not be at the end of the list and should not be
92 // used for sizing data structures.
94 enum StructuredDataType
: uint32_t {
95 // Structured data types provided by the engine
96 SCTAG_FLOAT_MAX
= 0xFFF00000,
97 SCTAG_HEADER
= 0xFFF10000,
98 SCTAG_NULL
= 0xFFFF0000,
107 SCTAG_ARRAY_BUFFER_OBJECT_V2
, // Old version, for backwards compatibility.
108 SCTAG_BOOLEAN_OBJECT
,
111 SCTAG_BACK_REFERENCE_OBJECT
,
112 SCTAG_DO_NOT_USE_1
, // Required for backwards compatibility
113 SCTAG_DO_NOT_USE_2
, // Required for backwards compatibility
114 SCTAG_TYPED_ARRAY_OBJECT_V2
, // Old version, for backwards compatibility.
118 SCTAG_DO_NOT_USE_3
, // Required for backwards compatibility
119 SCTAG_DATA_VIEW_OBJECT_V2
, // Old version, for backwards compatibility.
120 SCTAG_SAVED_FRAME_OBJECT
,
122 // No new tags before principals.
124 SCTAG_NULL_JSPRINCIPALS
,
125 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
,
126 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
,
128 SCTAG_SHARED_ARRAY_BUFFER_OBJECT
,
129 SCTAG_SHARED_WASM_MEMORY_OBJECT
,
134 SCTAG_ARRAY_BUFFER_OBJECT
,
135 SCTAG_TYPED_ARRAY_OBJECT
,
136 SCTAG_DATA_VIEW_OBJECT
,
138 SCTAG_TYPED_ARRAY_V1_MIN
= 0xFFFF0100,
139 SCTAG_TYPED_ARRAY_V1_INT8
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Int8
,
140 SCTAG_TYPED_ARRAY_V1_UINT8
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint8
,
141 SCTAG_TYPED_ARRAY_V1_INT16
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Int16
,
142 SCTAG_TYPED_ARRAY_V1_UINT16
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint16
,
143 SCTAG_TYPED_ARRAY_V1_INT32
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Int32
,
144 SCTAG_TYPED_ARRAY_V1_UINT32
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint32
,
145 SCTAG_TYPED_ARRAY_V1_FLOAT32
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Float32
,
146 SCTAG_TYPED_ARRAY_V1_FLOAT64
= SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Float64
,
147 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED
=
148 SCTAG_TYPED_ARRAY_V1_MIN
+ Scalar::Uint8Clamped
,
149 // BigInt64 and BigUint64 are not supported in the v1 format.
150 SCTAG_TYPED_ARRAY_V1_MAX
= SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED
,
152 // Define a separate range of numbers for Transferable-only tags, since
153 // they are not used for persistent clone buffers and therefore do not
154 // require bumping JS_STRUCTURED_CLONE_VERSION.
155 SCTAG_TRANSFER_MAP_HEADER
= 0xFFFF0200,
156 SCTAG_TRANSFER_MAP_PENDING_ENTRY
,
157 SCTAG_TRANSFER_MAP_ARRAY_BUFFER
,
158 SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
,
159 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES
,
161 SCTAG_END_OF_BUILTIN_TYPES
165 * Format of transfer map:
166 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
167 * numTransferables (64 bits)
169 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
171 * extraData (64 bits), eg byte length for ArrayBuffers
174 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
175 // contents have been read out yet or not.
176 enum TransferableMapHeader
{ SCTAG_TM_UNREAD
= 0, SCTAG_TM_TRANSFERRED
};
178 static inline uint64_t PairToUInt64(uint32_t tag
, uint32_t data
) {
179 return uint64_t(data
) | (uint64_t(tag
) << 32);
184 template <typename T
, typename AllocPolicy
>
185 struct BufferIterator
{
186 using BufferList
= mozilla::BufferList
<AllocPolicy
>;
188 explicit BufferIterator(const BufferList
& buffer
)
189 : mBuffer(buffer
), mIter(buffer
.Iter()) {
190 static_assert(8 % sizeof(T
) == 0);
193 explicit BufferIterator(const JSStructuredCloneData
& data
)
194 : mBuffer(data
.bufList_
), mIter(data
.Start()) {}
196 BufferIterator
& operator=(const BufferIterator
& other
) {
197 MOZ_ASSERT(&mBuffer
== &other
.mBuffer
);
202 [[nodiscard
]] bool advance(size_t size
= sizeof(T
)) {
203 return mIter
.AdvanceAcrossSegments(mBuffer
, size
);
206 BufferIterator
operator++(int) {
207 BufferIterator ret
= *this;
208 if (!advance(sizeof(T
))) {
209 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
214 BufferIterator
& operator+=(size_t size
) {
215 if (!advance(size
)) {
216 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
221 size_t operator-(const BufferIterator
& other
) const {
222 MOZ_ASSERT(&mBuffer
== &other
.mBuffer
);
223 return mBuffer
.RangeLength(other
.mIter
, mIter
);
226 bool done() const { return mIter
.Done(); }
228 [[nodiscard
]] bool readBytes(char* outData
, size_t size
) {
229 return mBuffer
.ReadBytes(mIter
, outData
, size
);
232 void write(const T
& data
) {
233 MOZ_ASSERT(mIter
.HasRoomFor(sizeof(T
)));
234 *reinterpret_cast<T
*>(mIter
.Data()) = data
;
238 MOZ_ASSERT(mIter
.HasRoomFor(sizeof(T
)));
239 return *reinterpret_cast<T
*>(mIter
.Data());
242 bool canPeek() const { return mIter
.HasRoomFor(sizeof(T
)); }
244 const BufferList
& mBuffer
;
245 typename
BufferList::IterImpl mIter
;
248 SharedArrayRawBufferRefs
& SharedArrayRawBufferRefs::operator=(
249 SharedArrayRawBufferRefs
&& other
) {
250 takeOwnership(std::move(other
));
254 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }
256 bool SharedArrayRawBufferRefs::acquire(JSContext
* cx
,
257 SharedArrayRawBuffer
* rawbuf
) {
258 if (!refs_
.append(rawbuf
)) {
259 ReportOutOfMemory(cx
);
263 if (!rawbuf
->addReference()) {
265 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
266 JSMSG_SC_SAB_REFCNT_OFLO
);
273 bool SharedArrayRawBufferRefs::acquireAll(
274 JSContext
* cx
, const SharedArrayRawBufferRefs
& that
) {
275 if (!refs_
.reserve(refs_
.length() + that
.refs_
.length())) {
276 ReportOutOfMemory(cx
);
280 for (auto ref
: that
.refs_
) {
281 if (!ref
->addReference()) {
282 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
283 JSMSG_SC_SAB_REFCNT_OFLO
);
286 MOZ_ALWAYS_TRUE(refs_
.append(ref
));
292 void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs
&& other
) {
293 MOZ_ASSERT(refs_
.empty());
294 refs_
= std::move(other
.refs_
);
297 void SharedArrayRawBufferRefs::releaseAll() {
298 for (auto ref
: refs_
) {
299 ref
->dropReference();
304 // SCOutput provides an interface to write raw data -- eg uint64_ts, doubles,
305 // arrays of bytes -- into a structured clone data output stream. It also knows
306 // how to free any transferable data within that stream.
308 // Note that it contains a full JSStructuredCloneData object, which holds the
309 // callbacks necessary to read/write/transfer/free the data. For the purpose of
310 // this class, only the freeTransfer callback is relevant; the rest of the
311 // callbacks are used by the higher-level JSStructuredCloneWriter interface.
314 using Iter
= BufferIterator
<uint64_t, SystemAllocPolicy
>;
316 SCOutput(JSContext
* cx
, JS::StructuredCloneScope scope
);
318 JSContext
* context() const { return cx
; }
319 JS::StructuredCloneScope
scope() const { return buf
.scope(); }
320 void sameProcessScopeRequired() { buf
.sameProcessScopeRequired(); }
322 [[nodiscard
]] bool write(uint64_t u
);
323 [[nodiscard
]] bool writePair(uint32_t tag
, uint32_t data
);
324 [[nodiscard
]] bool writeDouble(double d
);
325 [[nodiscard
]] bool writeBytes(const void* p
, size_t nbytes
);
326 [[nodiscard
]] bool writeChars(const Latin1Char
* p
, size_t nchars
);
327 [[nodiscard
]] bool writeChars(const char16_t
* p
, size_t nchars
);
330 [[nodiscard
]] bool writeArray(const T
* p
, size_t nelems
);
332 void setCallbacks(const JSStructuredCloneCallbacks
* callbacks
, void* closure
,
333 OwnTransferablePolicy policy
) {
334 buf
.setCallbacks(callbacks
, closure
, policy
);
336 void extractBuffer(JSStructuredCloneData
* data
) { *data
= std::move(buf
); }
337 void discardTransferables();
339 uint64_t tell() const { return buf
.Size(); }
340 uint64_t count() const { return buf
.Size() / sizeof(uint64_t); }
341 Iter
iter() { return Iter(buf
); }
343 size_t offset(Iter dest
) { return dest
- iter(); }
346 JSStructuredCloneData buf
;
350 typedef js::BufferIterator
<uint64_t, SystemAllocPolicy
> BufferIterator
;
353 SCInput(JSContext
* cx
, const JSStructuredCloneData
& data
);
355 JSContext
* context() const { return cx
; }
357 static void getPtr(uint64_t data
, void** ptr
);
358 static void getPair(uint64_t data
, uint32_t* tagp
, uint32_t* datap
);
360 [[nodiscard
]] bool read(uint64_t* p
);
361 [[nodiscard
]] bool readPair(uint32_t* tagp
, uint32_t* datap
);
362 [[nodiscard
]] bool readDouble(double* p
);
363 [[nodiscard
]] bool readBytes(void* p
, size_t nbytes
);
364 [[nodiscard
]] bool readChars(Latin1Char
* p
, size_t nchars
);
365 [[nodiscard
]] bool readChars(char16_t
* p
, size_t nchars
);
366 [[nodiscard
]] bool readPtr(void**);
368 [[nodiscard
]] bool get(uint64_t* p
);
369 [[nodiscard
]] bool getPair(uint32_t* tagp
, uint32_t* datap
);
371 const BufferIterator
& tell() const { return point
; }
372 void seekTo(const BufferIterator
& pos
) { point
= pos
; }
373 [[nodiscard
]] bool seekBy(size_t pos
) {
374 if (!point
.advance(pos
)) {
382 [[nodiscard
]] bool readArray(T
* p
, size_t nelems
);
384 bool reportTruncated() {
385 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
386 JSMSG_SC_BAD_SERIALIZED_DATA
, "truncated");
391 void staticAssertions() {
392 static_assert(sizeof(char16_t
) == 2);
393 static_assert(sizeof(uint32_t) == 4);
397 BufferIterator point
;
402 struct JSStructuredCloneReader
{
404 explicit JSStructuredCloneReader(SCInput
& in
, JS::StructuredCloneScope scope
,
405 const JS::CloneDataPolicy
& cloneDataPolicy
,
406 const JSStructuredCloneCallbacks
* cb
,
410 cloneDataPolicy(cloneDataPolicy
),
412 allObjs(in
.context()),
415 closure(cbClosure
) {}
417 SCInput
& input() { return in
; }
418 bool read(MutableHandleValue vp
, size_t nbytes
);
421 JSContext
* context() { return in
.context(); }
424 bool readTransferMap();
426 template <typename CharT
>
427 JSString
* readStringImpl(uint32_t nchars
, gc::InitialHeap heap
);
428 JSString
* readString(uint32_t data
, gc::InitialHeap heap
= gc::DefaultHeap
);
430 BigInt
* readBigInt(uint32_t data
);
432 [[nodiscard
]] bool readTypedArray(uint32_t arrayType
, uint64_t nelems
,
433 MutableHandleValue vp
, bool v1Read
= false);
434 [[nodiscard
]] bool readDataView(uint64_t byteLength
, MutableHandleValue vp
);
435 [[nodiscard
]] bool readArrayBuffer(StructuredDataType type
, uint32_t data
,
436 MutableHandleValue vp
);
437 [[nodiscard
]] bool readSharedArrayBuffer(MutableHandleValue vp
);
438 [[nodiscard
]] bool readSharedWasmMemory(uint32_t nbytes
,
439 MutableHandleValue vp
);
440 [[nodiscard
]] bool readV1ArrayBuffer(uint32_t arrayType
, uint32_t nelems
,
441 MutableHandleValue vp
);
442 JSObject
* readSavedFrame(uint32_t principalsTag
);
443 [[nodiscard
]] bool startRead(MutableHandleValue vp
,
444 gc::InitialHeap strHeap
= gc::DefaultHeap
);
448 // The widest scope that the caller will accept, where
449 // SameProcess is the widest (it can store anything it wants)
450 // and DifferentProcess is the narrowest (it cannot contain pointers and must
451 // be valid cross-process.)
452 JS::StructuredCloneScope allowedScope
;
454 const JS::CloneDataPolicy cloneDataPolicy
;
456 // Stack of objects with properties remaining to be read.
457 RootedValueVector objs
;
459 // Array of all objects read during this deserialization, for resolving
462 // For backreferences to work correctly, objects must be added to this
463 // array in exactly the order expected by the version of the Writer that
464 // created the serialized data, even across years and format versions. This
465 // is usually no problem, since both algorithms do a single linear pass
466 // over the serialized data. There is one hitch; see readTypedArray.
468 // The values in this vector are objects, except it can temporarily have
469 // one `undefined` placeholder value (the readTypedArray hack).
470 RootedValueVector allObjs
;
474 // The user defined callbacks that will be used for cloning.
475 const JSStructuredCloneCallbacks
* callbacks
;
477 // Any value passed to JS_ReadStructuredClone.
480 friend bool JS_ReadTypedArray(JSStructuredCloneReader
* r
,
481 MutableHandleValue vp
);
484 struct JSStructuredCloneWriter
{
486 explicit JSStructuredCloneWriter(JSContext
* cx
,
487 JS::StructuredCloneScope scope
,
488 const JS::CloneDataPolicy
& cloneDataPolicy
,
489 const JSStructuredCloneCallbacks
* cb
,
490 void* cbClosure
, const Value
& tVal
)
499 transferable(cx
, tVal
),
500 transferableObjects(cx
, TransferableObjectsList(cx
)),
501 cloneDataPolicy(cloneDataPolicy
) {
502 out
.setCallbacks(cb
, cbClosure
, OwnTransferablePolicy::NoTransferables
);
505 ~JSStructuredCloneWriter();
508 return parseTransferable() && writeHeader() && writeTransferMap();
511 bool write(HandleValue v
);
513 SCOutput
& output() { return out
; }
515 void extractBuffer(JSStructuredCloneData
* newData
) {
516 out
.extractBuffer(newData
);
520 JSStructuredCloneWriter() = delete;
521 JSStructuredCloneWriter(const JSStructuredCloneWriter
&) = delete;
523 JSContext
* context() { return out
.context(); }
526 bool writeTransferMap();
528 bool writeString(uint32_t tag
, JSString
* str
);
529 bool writeBigInt(uint32_t tag
, BigInt
* bi
);
530 bool writeArrayBuffer(HandleObject obj
);
531 bool writeTypedArray(HandleObject obj
);
532 bool writeDataView(HandleObject obj
);
533 bool writeSharedArrayBuffer(HandleObject obj
);
534 bool writeSharedWasmMemory(HandleObject obj
);
535 bool startObject(HandleObject obj
, bool* backref
);
536 bool startWrite(HandleValue v
);
537 bool traverseObject(HandleObject obj
, ESClass cls
);
538 bool traverseMap(HandleObject obj
);
539 bool traverseSet(HandleObject obj
);
540 bool traverseSavedFrame(HandleObject obj
);
542 template <typename
... Args
>
543 bool reportDataCloneError(uint32_t errorId
, Args
&&... aArgs
);
545 bool parseTransferable();
546 bool transferOwnership();
548 inline void checkStack();
552 // The user defined callbacks that will be used to signal cloning, in some
554 const JSStructuredCloneCallbacks
* callbacks
;
556 // Any value passed to the callbacks.
559 // Vector of objects with properties remaining to be written.
561 // NB: These can span multiple compartments, so the compartment must be
562 // entered before any manipulation is performed.
563 RootedValueVector objs
;
565 // counts[i] is the number of entries of objs[i] remaining to be written.
566 // counts.length() == objs.length() and sum(counts) == entries.length().
567 Vector
<size_t> counts
;
569 // For JSObject: Property IDs as value
570 RootedIdVector objectEntries
;
572 // For Map: Key followed by value
574 // For SavedFrame: parent SavedFrame
575 RootedValueVector otherEntries
;
577 // The "memory" list described in the HTML5 internal structured cloning
578 // algorithm. memory is a superset of objs; items are never removed from
579 // Memory until a serialization operation is finished
581 GCHashMap
<JSObject
*, uint32_t, MovableCellHasher
<JSObject
*>,
583 Rooted
<CloneMemory
> memory
;
585 // Set of transferable objects
586 RootedValue transferable
;
587 using TransferableObjectsList
= GCVector
<JSObject
*>;
588 Rooted
<TransferableObjectsList
> transferableObjects
;
590 const JS::CloneDataPolicy cloneDataPolicy
;
592 friend bool JS_WriteString(JSStructuredCloneWriter
* w
, HandleString str
);
593 friend bool JS_WriteTypedArray(JSStructuredCloneWriter
* w
, HandleValue v
);
594 friend bool JS_ObjectNotWritten(JSStructuredCloneWriter
* w
, HandleObject obj
);
597 JS_PUBLIC_API
uint64_t js::GetSCOffset(JSStructuredCloneWriter
* writer
) {
599 return writer
->output().count() * sizeof(uint64_t);
602 static_assert(SCTAG_END_OF_BUILTIN_TYPES
<= JS_SCTAG_USER_MIN
);
603 static_assert(JS_SCTAG_USER_MIN
<= JS_SCTAG_USER_MAX
);
604 static_assert(Scalar::Int8
== 0);
606 template <typename
... Args
>
607 static void ReportDataCloneError(JSContext
* cx
,
608 const JSStructuredCloneCallbacks
* callbacks
,
609 uint32_t errorId
, void* closure
,
611 unsigned errorNumber
;
613 case JS_SCERR_DUP_TRANSFERABLE
:
614 errorNumber
= JSMSG_SC_DUP_TRANSFERABLE
;
617 case JS_SCERR_TRANSFERABLE
:
618 errorNumber
= JSMSG_SC_NOT_TRANSFERABLE
;
621 case JS_SCERR_UNSUPPORTED_TYPE
:
622 errorNumber
= JSMSG_SC_UNSUPPORTED_TYPE
;
625 case JS_SCERR_SHMEM_TRANSFERABLE
:
626 errorNumber
= JSMSG_SC_SHMEM_TRANSFERABLE
;
629 case JS_SCERR_TYPED_ARRAY_DETACHED
:
630 errorNumber
= JSMSG_TYPED_ARRAY_DETACHED
;
633 case JS_SCERR_WASM_NO_TRANSFER
:
634 errorNumber
= JSMSG_WASM_NO_TRANSFER
;
637 case JS_SCERR_NOT_CLONABLE
:
638 errorNumber
= JSMSG_SC_NOT_CLONABLE
;
641 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
:
642 errorNumber
= JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP
;
646 MOZ_CRASH("Unkown errorId");
650 if (callbacks
&& callbacks
->reportError
) {
651 MOZ_RELEASE_ASSERT(!cx
->isExceptionPending());
653 JSErrorReport report
;
654 report
.errorNumber
= errorNumber
;
655 // Get js error message if it's possible and propagate it through callback.
656 if (JS_ExpandErrorArgumentsASCII(cx
, GetErrorMessage
, errorNumber
, &report
,
657 std::forward
<Args
>(aArgs
)...) &&
659 callbacks
->reportError(cx
, errorId
, closure
, report
.message().c_str());
661 ReportOutOfMemory(cx
);
663 callbacks
->reportError(cx
, errorId
, closure
, "");
669 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
,
670 std::forward
<Args
>(aArgs
)...);
673 bool WriteStructuredClone(JSContext
* cx
, HandleValue v
,
674 JSStructuredCloneData
* bufp
,
675 JS::StructuredCloneScope scope
,
676 const JS::CloneDataPolicy
& cloneDataPolicy
,
677 const JSStructuredCloneCallbacks
* cb
, void* cbClosure
,
678 const Value
& transferable
) {
679 JSStructuredCloneWriter
w(cx
, scope
, cloneDataPolicy
, cb
, cbClosure
,
687 w
.extractBuffer(bufp
);
691 bool ReadStructuredClone(JSContext
* cx
, const JSStructuredCloneData
& data
,
692 JS::StructuredCloneScope scope
, MutableHandleValue vp
,
693 const JS::CloneDataPolicy
& cloneDataPolicy
,
694 const JSStructuredCloneCallbacks
* cb
,
696 if (data
.Size() % 8) {
697 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
698 JSMSG_SC_BAD_SERIALIZED_DATA
, "misaligned");
701 SCInput
in(cx
, data
);
702 JSStructuredCloneReader
r(in
, scope
, cloneDataPolicy
, cb
, cbClosure
);
703 return r
.read(vp
, data
.Size());
706 static bool StructuredCloneHasTransferObjects(
707 const JSStructuredCloneData
& data
) {
708 if (data
.Size() < sizeof(uint64_t)) {
713 BufferIterator
<uint64_t, SystemAllocPolicy
> iter(data
);
714 MOZ_ALWAYS_TRUE(iter
.readBytes(reinterpret_cast<char*>(&u
), sizeof(u
)));
715 uint32_t tag
= uint32_t(u
>> 32);
716 return (tag
== SCTAG_TRANSFER_MAP_HEADER
);
721 SCInput::SCInput(JSContext
* cx
, const JSStructuredCloneData
& data
)
722 : cx(cx
), point(data
) {
723 static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment
% 8 == 0,
724 "structured clone buffer reads should be aligned");
725 MOZ_ASSERT(data
.Size() % 8 == 0);
728 bool SCInput::read(uint64_t* p
) {
729 if (!point
.canPeek()) {
730 *p
= 0; // initialize to shut GCC up
731 return reportTruncated();
733 *p
= NativeEndian::swapFromLittleEndian(point
.peek());
734 MOZ_ALWAYS_TRUE(point
.advance());
738 bool SCInput::readPair(uint32_t* tagp
, uint32_t* datap
) {
742 *tagp
= uint32_t(u
>> 32);
743 *datap
= uint32_t(u
);
748 bool SCInput::get(uint64_t* p
) {
749 if (!point
.canPeek()) {
750 return reportTruncated();
752 *p
= NativeEndian::swapFromLittleEndian(point
.peek());
756 bool SCInput::getPair(uint32_t* tagp
, uint32_t* datap
) {
762 *tagp
= uint32_t(u
>> 32);
763 *datap
= uint32_t(u
);
767 void SCInput::getPair(uint64_t data
, uint32_t* tagp
, uint32_t* datap
) {
768 uint64_t u
= NativeEndian::swapFromLittleEndian(data
);
769 *tagp
= uint32_t(u
>> 32);
770 *datap
= uint32_t(u
);
773 bool SCInput::readDouble(double* p
) {
778 *p
= CanonicalizeNaN(mozilla::BitwiseCast
<double>(u
));
782 template <typename T
>
783 static void swapFromLittleEndianInPlace(T
* ptr
, size_t nelems
) {
785 NativeEndian::swapFromLittleEndianInPlace(ptr
, nelems
);
790 void swapFromLittleEndianInPlace(uint8_t* ptr
, size_t nelems
) {}
792 // Data is packed into an integral number of uint64_t words. Compute the
793 // padding required to finish off the final word.
794 static size_t ComputePadding(size_t nelems
, size_t elemSize
) {
795 // We want total length mod 8, where total length is nelems * sizeof(T),
796 // but that might overflow. So reduce nelems to nelems mod 8, since we are
797 // going to be doing a mod 8 later anyway.
798 size_t leftoverLength
= (nelems
% sizeof(uint64_t)) * elemSize
;
799 return (-leftoverLength
) & (sizeof(uint64_t) - 1);
803 bool SCInput::readArray(T
* p
, size_t nelems
) {
808 static_assert(sizeof(uint64_t) % sizeof(T
) == 0);
810 // Fail if nelems is so huge that computing the full size will overflow.
811 mozilla::CheckedInt
<size_t> size
=
812 mozilla::CheckedInt
<size_t>(nelems
) * sizeof(T
);
813 if (!size
.isValid()) {
814 return reportTruncated();
817 if (!point
.readBytes(reinterpret_cast<char*>(p
), size
.value())) {
818 // To avoid any way in which uninitialized data could escape, zero the array
819 // if filling it failed.
820 std::uninitialized_fill_n(p
, nelems
, 0);
824 swapFromLittleEndianInPlace(p
, nelems
);
826 point
+= ComputePadding(nelems
, sizeof(T
));
831 bool SCInput::readBytes(void* p
, size_t nbytes
) {
832 return readArray((uint8_t*)p
, nbytes
);
835 bool SCInput::readChars(Latin1Char
* p
, size_t nchars
) {
836 static_assert(sizeof(Latin1Char
) == sizeof(uint8_t),
837 "Latin1Char must fit in 1 byte");
838 return readBytes(p
, nchars
);
841 bool SCInput::readChars(char16_t
* p
, size_t nchars
) {
842 MOZ_ASSERT(sizeof(char16_t
) == sizeof(uint16_t));
843 return readArray((uint16_t*)p
, nchars
);
846 void SCInput::getPtr(uint64_t data
, void** ptr
) {
847 *ptr
= reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data
));
850 bool SCInput::readPtr(void** p
) {
855 *p
= reinterpret_cast<void*>(u
);
859 SCOutput::SCOutput(JSContext
* cx
, JS::StructuredCloneScope scope
)
860 : cx(cx
), buf(scope
) {}
862 bool SCOutput::write(uint64_t u
) {
863 uint64_t v
= NativeEndian::swapToLittleEndian(u
);
864 if (!buf
.AppendBytes(reinterpret_cast<char*>(&v
), sizeof(u
))) {
865 ReportOutOfMemory(context());
871 bool SCOutput::writePair(uint32_t tag
, uint32_t data
) {
872 // As it happens, the tag word appears after the data word in the output.
873 // This is because exponents occupy the last 2 bytes of doubles on the
874 // little-endian platforms we care most about.
876 // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
877 // PairToUInt64 produces the number 0xFFFF000200000001.
878 // That is written out as the bytes 01 00 00 00 02 00 FF FF.
879 return write(PairToUInt64(tag
, data
));
882 static inline double ReinterpretPairAsDouble(uint32_t tag
, uint32_t data
) {
883 return BitwiseCast
<double>(PairToUInt64(tag
, data
));
886 bool SCOutput::writeDouble(double d
) {
887 return write(BitwiseCast
<uint64_t>(CanonicalizeNaN(d
)));
891 bool SCOutput::writeArray(const T
* p
, size_t nelems
) {
892 static_assert(8 % sizeof(T
) == 0);
893 static_assert(sizeof(uint64_t) % sizeof(T
) == 0);
899 for (size_t i
= 0; i
< nelems
; i
++) {
900 T value
= NativeEndian::swapToLittleEndian(p
[i
]);
901 if (!buf
.AppendBytes(reinterpret_cast<char*>(&value
), sizeof(value
))) {
906 // Zero-pad to 8 bytes boundary.
907 size_t padbytes
= ComputePadding(nelems
, sizeof(T
));
908 char zeroes
[sizeof(uint64_t)] = {0};
909 if (!buf
.AppendBytes(zeroes
, padbytes
)) {
917 bool SCOutput::writeArray
<uint8_t>(const uint8_t* p
, size_t nelems
) {
922 if (!buf
.AppendBytes(reinterpret_cast<const char*>(p
), nelems
)) {
926 // zero-pad to 8 bytes boundary
927 size_t padbytes
= ComputePadding(nelems
, 1);
928 char zeroes
[sizeof(uint64_t)] = {0};
929 if (!buf
.AppendBytes(zeroes
, padbytes
)) {
936 bool SCOutput::writeBytes(const void* p
, size_t nbytes
) {
937 return writeArray((const uint8_t*)p
, nbytes
);
940 bool SCOutput::writeChars(const char16_t
* p
, size_t nchars
) {
941 static_assert(sizeof(char16_t
) == sizeof(uint16_t),
942 "required so that treating char16_t[] memory as uint16_t[] "
943 "memory is permissible");
944 return writeArray((const uint16_t*)p
, nchars
);
947 bool SCOutput::writeChars(const Latin1Char
* p
, size_t nchars
) {
948 static_assert(sizeof(Latin1Char
) == sizeof(uint8_t),
949 "Latin1Char must fit in 1 byte");
950 return writeBytes(p
, nchars
);
953 void SCOutput::discardTransferables() { buf
.discardTransferables(); }
957 JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); }
959 // If the buffer contains Transferables, free them. Note that custom
960 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
961 // delete their transferables.
962 void JSStructuredCloneData::discardTransferables() {
967 if (ownTransferables_
!= OwnTransferablePolicy::OwnsTransferablesIfAny
) {
971 // DifferentProcess clones cannot contain pointers, so nothing needs to be
973 if (scope() == JS::StructuredCloneScope::DifferentProcess
) {
977 FreeTransferStructuredCloneOp freeTransfer
= nullptr;
979 freeTransfer
= callbacks_
->freeTransfer
;
982 auto point
= BufferIterator
<uint64_t, SystemAllocPolicy
>(*this);
984 return; // Empty buffer
988 MOZ_RELEASE_ASSERT(point
.canPeek());
989 SCInput::getPair(point
.peek(), &tag
, &data
);
990 MOZ_ALWAYS_TRUE(point
.advance());
992 if (tag
== SCTAG_HEADER
) {
997 MOZ_RELEASE_ASSERT(point
.canPeek());
998 SCInput::getPair(point
.peek(), &tag
, &data
);
999 MOZ_ALWAYS_TRUE(point
.advance());
1002 if (tag
!= SCTAG_TRANSFER_MAP_HEADER
) {
1006 if (TransferableMapHeader(data
) == SCTAG_TM_TRANSFERRED
) {
1010 // freeTransfer should not GC
1011 JS::AutoSuppressGCAnalysis nogc
;
1017 MOZ_RELEASE_ASSERT(point
.canPeek());
1018 uint64_t numTransferables
= NativeEndian::swapFromLittleEndian(point
.peek());
1019 MOZ_ALWAYS_TRUE(point
.advance());
1020 while (numTransferables
--) {
1021 if (!point
.canPeek()) {
1026 SCInput::getPair(point
.peek(), &tag
, &ownership
);
1027 MOZ_ALWAYS_TRUE(point
.advance());
1028 MOZ_ASSERT(tag
>= SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
1029 if (!point
.canPeek()) {
1034 SCInput::getPtr(point
.peek(), &content
);
1035 MOZ_ALWAYS_TRUE(point
.advance());
1036 if (!point
.canPeek()) {
1040 uint64_t extraData
= NativeEndian::swapFromLittleEndian(point
.peek());
1041 MOZ_ALWAYS_TRUE(point
.advance());
1043 if (ownership
< JS::SCTAG_TMO_FIRST_OWNED
) {
1047 if (ownership
== JS::SCTAG_TMO_ALLOC_DATA
) {
1049 } else if (ownership
== JS::SCTAG_TMO_MAPPED_DATA
) {
1050 JS::ReleaseMappedArrayBufferContents(content
, extraData
);
1051 } else if (freeTransfer
) {
1052 freeTransfer(tag
, JS::TransferableOwnership(ownership
), content
,
1053 extraData
, closure_
);
1055 MOZ_ASSERT(false, "unknown ownership");
1060 static_assert(JSString::MAX_LENGTH
< UINT32_MAX
);
1062 JSStructuredCloneWriter::~JSStructuredCloneWriter() {
1063 // Free any transferable data left lying around in the buffer
1065 out
.discardTransferables();
1069 bool JSStructuredCloneWriter::parseTransferable() {
1070 // NOTE: The transferables set is tested for non-emptiness at various
1071 // junctures in structured cloning, so this set must be initialized
1072 // by this method in all non-error cases.
1073 MOZ_ASSERT(transferableObjects
.empty(),
1074 "parseTransferable called with stale data");
1076 if (transferable
.isNull() || transferable
.isUndefined()) {
1080 if (!transferable
.isObject()) {
1081 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1084 JSContext
* cx
= context();
1085 RootedObject
array(cx
, &transferable
.toObject());
1087 if (!JS::IsArrayObject(cx
, array
, &isArray
)) {
1091 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1095 if (!JS::GetArrayLength(cx
, array
, &length
)) {
1099 // Initialize the set for the provided array's length.
1100 if (!transferableObjects
.reserve(length
)) {
1108 RootedValue
v(context());
1109 RootedObject
tObj(context());
1111 for (uint32_t i
= 0; i
< length
; ++i
) {
1112 if (!CheckForInterrupt(cx
)) {
1116 if (!JS_GetElement(cx
, array
, i
, &v
)) {
1120 if (!v
.isObject()) {
1121 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1123 tObj
= &v
.toObject();
1125 RootedObject
unwrappedObj(cx
, CheckedUnwrapStatic(tObj
));
1126 if (!unwrappedObj
) {
1127 ReportAccessDenied(cx
);
1131 // Shared memory cannot be transferred because it is not possible (nor
1132 // desirable) to detach the memory in agents that already hold a
1135 if (unwrappedObj
->is
<SharedArrayBufferObject
>()) {
1136 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE
);
1139 else if (unwrappedObj
->is
<WasmMemoryObject
>()) {
1140 if (unwrappedObj
->as
<WasmMemoryObject
>().isShared()) {
1141 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE
);
1145 // External array buffers may be able to be transferred in the future,
1146 // but that is not currently implemented.
1148 else if (unwrappedObj
->is
<ArrayBufferObject
>()) {
1149 if (unwrappedObj
->as
<ArrayBufferObject
>().isExternal()) {
1150 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1155 if (!out
.buf
.callbacks_
|| !out
.buf
.callbacks_
->canTransfer
) {
1156 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
1159 JSAutoRealm
ar(cx
, unwrappedObj
);
1160 bool sameProcessScopeRequired
= false;
1161 if (!out
.buf
.callbacks_
->canTransfer(
1162 cx
, unwrappedObj
, &sameProcessScopeRequired
, out
.buf
.closure_
)) {
1166 if (sameProcessScopeRequired
) {
1167 output().sameProcessScopeRequired();
1171 // No duplicates allowed
1172 if (std::find(transferableObjects
.begin(), transferableObjects
.end(),
1173 tObj
) != transferableObjects
.end()) {
1174 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE
);
1177 if (!transferableObjects
.append(tObj
)) {
1185 template <typename
... Args
>
1186 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId
,
1188 ReportDataCloneError(context(), out
.buf
.callbacks_
, errorId
, out
.buf
.closure_
,
1189 std::forward
<Args
>(aArgs
)...);
1193 bool JSStructuredCloneWriter::writeString(uint32_t tag
, JSString
* str
) {
1194 JSLinearString
* linear
= str
->ensureLinear(context());
1199 static_assert(JSString::MAX_LENGTH
<= INT32_MAX
,
1200 "String length must fit in 31 bits");
1202 uint32_t length
= linear
->length();
1203 uint32_t lengthAndEncoding
=
1204 length
| (uint32_t(linear
->hasLatin1Chars()) << 31);
1205 if (!out
.writePair(tag
, lengthAndEncoding
)) {
1209 JS::AutoCheckCannotGC nogc
;
1210 return linear
->hasLatin1Chars()
1211 ? out
.writeChars(linear
->latin1Chars(nogc
), length
)
1212 : out
.writeChars(linear
->twoByteChars(nogc
), length
);
1215 bool JSStructuredCloneWriter::writeBigInt(uint32_t tag
, BigInt
* bi
) {
1216 bool signBit
= bi
->isNegative();
1217 size_t length
= bi
->digitLength();
1218 // The length must fit in 31 bits to leave room for a sign bit.
1219 if (length
> size_t(INT32_MAX
)) {
1222 uint32_t lengthAndSign
= length
| (static_cast<uint32_t>(signBit
) << 31);
1224 if (!out
.writePair(tag
, lengthAndSign
)) {
1227 return out
.writeArray(bi
->digits().data(), length
);
1230 inline void JSStructuredCloneWriter::checkStack() {
1232 // To avoid making serialization O(n^2), limit stack-checking at 10.
1233 const size_t MAX
= 10;
1235 size_t limit
= std::min(counts
.length(), MAX
);
1236 MOZ_ASSERT(objs
.length() == counts
.length());
1238 for (size_t i
= 0; i
< limit
; i
++) {
1239 MOZ_ASSERT(total
+ counts
[i
] >= total
);
1242 if (counts
.length() <= MAX
) {
1243 MOZ_ASSERT(total
== objectEntries
.length() + otherEntries
.length());
1245 MOZ_ASSERT(total
<= objectEntries
.length() + otherEntries
.length());
1248 size_t j
= objs
.length();
1249 for (size_t i
= 0; i
< limit
; i
++) {
1251 MOZ_ASSERT(memory
.has(&objs
[j
].toObject()));
1257 * Write out a typed array. Note that post-v1 structured clone buffers do not
1258 * perform endianness conversion on stored data, so multibyte typed arrays
1259 * cannot be deserialized into a different endianness machine. Endianness
1260 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
1261 * Int16Array views of the same ArrayBuffer, should the data bytes be
1262 * byte-swapped when writing or not? The Int8Array requires them to not be
1263 * swapped; the Int16Array requires that they are.
1265 bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj
) {
1266 Rooted
<TypedArrayObject
*> tarr(context(),
1267 obj
->maybeUnwrapAs
<TypedArrayObject
>());
1268 JSAutoRealm
ar(context(), tarr
);
1270 if (!TypedArrayObject::ensureHasBuffer(context(), tarr
)) {
1274 if (!out
.writePair(SCTAG_TYPED_ARRAY_OBJECT
, uint32_t(tarr
->type()))) {
1278 uint64_t nelems
= tarr
->length();
1279 if (!out
.write(nelems
)) {
1283 // Write out the ArrayBuffer tag and contents
1284 RootedValue
val(context(), tarr
->bufferValue());
1285 if (!startWrite(val
)) {
1289 uint64_t byteOffset
= tarr
->byteOffset();
1290 return out
.write(byteOffset
);
1293 bool JSStructuredCloneWriter::writeDataView(HandleObject obj
) {
1294 Rooted
<DataViewObject
*> view(context(), obj
->maybeUnwrapAs
<DataViewObject
>());
1295 JSAutoRealm
ar(context(), view
);
1297 if (!out
.writePair(SCTAG_DATA_VIEW_OBJECT
, 0)) {
1301 uint64_t byteLength
= view
->byteLength();
1302 if (!out
.write(byteLength
)) {
1306 // Write out the ArrayBuffer tag and contents
1307 RootedValue
val(context(), view
->bufferValue());
1308 if (!startWrite(val
)) {
1312 uint64_t byteOffset
= view
->byteOffset();
1313 return out
.write(byteOffset
);
1316 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj
) {
1317 Rooted
<ArrayBufferObject
*> buffer(context(),
1318 obj
->maybeUnwrapAs
<ArrayBufferObject
>());
1319 JSAutoRealm
ar(context(), buffer
);
1321 if (!out
.writePair(SCTAG_ARRAY_BUFFER_OBJECT
, 0)) {
1325 uint64_t byteLength
= buffer
->byteLength();
1326 if (!out
.write(byteLength
)) {
1330 return out
.writeBytes(buffer
->dataPointer(), byteLength
);
1333 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj
) {
1334 MOZ_ASSERT(obj
->canUnwrapAs
<SharedArrayBufferObject
>());
1336 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1337 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1338 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1339 : JS_SCERR_NOT_CLONABLE
;
1340 reportDataCloneError(error
, "SharedArrayBuffer");
1344 output().sameProcessScopeRequired();
1346 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1347 // cross-process. The cloneDataPolicy should have guarded against this;
1348 // since it did not then throw, with a very explicit message.
1350 if (output().scope() > JS::StructuredCloneScope::SameProcess
) {
1351 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1352 JSMSG_SC_SHMEM_POLICY
);
1356 Rooted
<SharedArrayBufferObject
*> sharedArrayBuffer(
1357 context(), obj
->maybeUnwrapAs
<SharedArrayBufferObject
>());
1358 SharedArrayRawBuffer
* rawbuf
= sharedArrayBuffer
->rawBufferObject();
1360 if (!out
.buf
.refsHeld_
.acquire(context(), rawbuf
)) {
1364 // We must serialize the length so that the buffer object arrives in the
1365 // receiver with the same length, and not with the length read from the
1366 // rawbuf - that length can be different, and it can change at any time.
1368 intptr_t p
= reinterpret_cast<intptr_t>(rawbuf
);
1369 uint64_t byteLength
= sharedArrayBuffer
->byteLength();
1370 if (!(out
.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT
,
1371 static_cast<uint32_t>(sizeof(p
))) &&
1372 out
.writeBytes(&byteLength
, sizeof(byteLength
)) &&
1373 out
.writeBytes(&p
, sizeof(p
)))) {
1377 if (callbacks
&& callbacks
->sabCloned
&&
1378 !callbacks
->sabCloned(context(), /*receiving=*/false, closure
)) {
1385 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj
) {
1386 MOZ_ASSERT(obj
->canUnwrapAs
<WasmMemoryObject
>());
1388 // Check the policy here so that we can report a sane error.
1389 if (!cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
1390 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
1391 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1392 : JS_SCERR_NOT_CLONABLE
;
1393 reportDataCloneError(error
, "WebAssembly.Memory");
1397 // If this changes, might need to change what we write.
1398 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS
== 3);
1400 Rooted
<WasmMemoryObject
*> memoryObj(context(),
1401 &obj
->unwrapAs
<WasmMemoryObject
>());
1402 Rooted
<SharedArrayBufferObject
*> sab(
1403 context(), &memoryObj
->buffer().as
<SharedArrayBufferObject
>());
1405 return out
.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT
, 0) &&
1406 out
.writePair(SCTAG_BOOLEAN
, memoryObj
->isHuge()) &&
1407 writeSharedArrayBuffer(sab
);
1410 bool JSStructuredCloneWriter::startObject(HandleObject obj
, bool* backref
) {
1411 // Handle cycles in the object graph.
1412 CloneMemory::AddPtr p
= memory
.lookupForAdd(obj
);
1413 if ((*backref
= p
.found())) {
1414 return out
.writePair(SCTAG_BACK_REFERENCE_OBJECT
, p
->value());
1416 if (!memory
.add(p
, obj
, memory
.count())) {
1417 ReportOutOfMemory(context());
1421 if (memory
.count() == UINT32_MAX
) {
1422 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
1423 JSMSG_NEED_DIET
, "object graph to serialize");
1430 static bool TryAppendNativeProperties(JSContext
* cx
, HandleObject obj
,
1431 MutableHandleIdVector entries
,
1432 size_t* properties
, bool* optimized
) {
1435 if (!obj
->is
<NativeObject
>()) {
1439 HandleNativeObject nobj
= obj
.as
<NativeObject
>();
1440 if (nobj
->isIndexed() || nobj
->is
<TypedArrayObject
>() ||
1441 nobj
->getClass()->getNewEnumerate() || nobj
->getClass()->getEnumerate()) {
1448 // We iterate from the last to the first property, so the property names
1449 // are already in reverse order.
1450 for (ShapePropertyIter
<NoGC
> iter(nobj
->shape()); !iter
.done(); iter
++) {
1451 jsid id
= iter
->key();
1453 // Ignore symbols and non-enumerable properties.
1454 if (!iter
->enumerable() || id
.isSymbol()) {
1458 MOZ_ASSERT(JSID_IS_STRING(id
));
1459 if (!entries
.append(id
)) {
1466 // Add dense element ids in reverse order.
1467 for (uint32_t i
= nobj
->getDenseInitializedLength(); i
> 0; --i
) {
1468 if (nobj
->getDenseElement(i
- 1).isMagic(JS_ELEMENTS_HOLE
)) {
1472 if (!entries
.append(INT_TO_JSID(i
- 1))) {
1479 *properties
= count
;
1483 // Objects are written as a "preorder" traversal of the object graph: object
1484 // "headers" (the class tag and any data needed for initial construction) are
1485 // visited first, then the children are recursed through (where children are
1486 // properties, Set or Map entries, etc.). So for example
1488 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1490 // would be stored as:
1492 // <Object tag for obj1>
1494 // <Object tag for key1's value>
1499 // <end-of-children marker for key1's value>
1501 // <Object tag for key2's value>
1502 // <end-of-children marker for key2's value>
1503 // <end-of-children marker for obj1>
1505 // This nests nicely (ie, an entire recursive value starts with its tag and
1506 // ends with its end-of-children marker) and so it can be presented indented.
1507 // But see traverseMap below for how this looks different for Maps.
1508 bool JSStructuredCloneWriter::traverseObject(HandleObject obj
, ESClass cls
) {
1510 bool optimized
= false;
1511 if (!TryAppendNativeProperties(context(), obj
, &objectEntries
, &count
,
1517 // Get enumerable property ids and put them in reverse order so that they
1518 // will come off the stack in forward order.
1519 RootedIdVector
properties(context());
1520 if (!GetPropertyKeys(context(), obj
, JSITER_OWNONLY
, &properties
)) {
1524 for (size_t i
= properties
.length(); i
> 0; --i
) {
1525 jsid id
= properties
[i
- 1];
1527 MOZ_ASSERT(JSID_IS_STRING(id
) || JSID_IS_INT(id
));
1528 if (!objectEntries
.append(id
)) {
1533 count
= properties
.length();
1536 // Push obj and count to the stack.
1537 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(count
)) {
1545 if (!GetBuiltinClass(context(), obj
, &cls2
)) {
1548 MOZ_ASSERT(cls2
== cls
);
1551 // Write the header for obj.
1552 if (cls
== ESClass::Array
) {
1553 uint32_t length
= 0;
1554 if (!JS::GetArrayLength(context(), obj
, &length
)) {
1558 return out
.writePair(SCTAG_ARRAY_OBJECT
,
1559 NativeEndian::swapToLittleEndian(length
));
1562 return out
.writePair(SCTAG_OBJECT_OBJECT
, 0);
1565 // Use the same basic setup as for traverseObject, but now keys can themselves
1566 // be complex objects. Keys and values are visited first via startWrite(), then
1567 // the key's children (if any) are handled, then the value's children.
1570 // m.set(key1 = ..., value1 = ...)
1572 // where key1 and value2 are both objects would be stored as
1576 // <value1 class tag>
1578 // <end-of-children marker for key1>
1579 // ...value1 data...
1580 // <end-of-children marker for value1>
1581 // <end-of-children marker for Map>
1583 // Notice how the end-of-children marker for key1 is sandwiched between the
1584 // value1 beginning and end.
1585 bool JSStructuredCloneWriter::traverseMap(HandleObject obj
) {
1586 Rooted
<GCVector
<Value
>> newEntries(context(), GCVector
<Value
>(context()));
1588 // If there is no wrapper, the compartment munging is a no-op.
1589 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<MapObject
>());
1590 MOZ_ASSERT(unwrapped
);
1591 JSAutoRealm
ar(context(), unwrapped
);
1592 if (!MapObject::getKeysAndValuesInterleaved(unwrapped
, &newEntries
)) {
1596 if (!context()->compartment()->wrap(context(), &newEntries
)) {
1600 for (size_t i
= newEntries
.length(); i
> 0; --i
) {
1601 if (!otherEntries
.append(newEntries
[i
- 1])) {
1606 // Push obj and count to the stack.
1607 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(newEntries
.length())) {
1613 // Write the header for obj.
1614 return out
.writePair(SCTAG_MAP_OBJECT
, 0);
1617 // Similar to traverseMap, only there is a single value instead of a key and
1618 // value, and thus no interleaving is possible: a value will be fully emitted
1619 // before the next value is begun.
1620 bool JSStructuredCloneWriter::traverseSet(HandleObject obj
) {
1621 Rooted
<GCVector
<Value
>> keys(context(), GCVector
<Value
>(context()));
1623 // If there is no wrapper, the compartment munging is a no-op.
1624 RootedObject
unwrapped(context(), obj
->maybeUnwrapAs
<SetObject
>());
1625 MOZ_ASSERT(unwrapped
);
1626 JSAutoRealm
ar(context(), unwrapped
);
1627 if (!SetObject::keys(context(), unwrapped
, &keys
)) {
1631 if (!context()->compartment()->wrap(context(), &keys
)) {
1635 for (size_t i
= keys
.length(); i
> 0; --i
) {
1636 if (!otherEntries
.append(keys
[i
- 1])) {
1641 // Push obj and count to the stack.
1642 if (!objs
.append(ObjectValue(*obj
)) || !counts
.append(keys
.length())) {
1648 // Write the header for obj.
1649 return out
.writePair(SCTAG_SET_OBJECT
, 0);
1652 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj
) {
1653 RootedSavedFrame
savedFrame(context(), obj
->maybeUnwrapAs
<SavedFrame
>());
1654 MOZ_ASSERT(savedFrame
);
1656 RootedObject
parent(context(), savedFrame
->getParent());
1657 if (!context()->compartment()->wrap(context(), &parent
)) {
1661 if (!objs
.append(ObjectValue(*obj
)) ||
1662 !otherEntries
.append(parent
? ObjectValue(*parent
) : NullValue()) ||
1663 !counts
.append(1)) {
1669 // Write the SavedFrame tag and the SavedFrame's principals.
1671 if (savedFrame
->getPrincipals() ==
1672 &ReconstructedSavedFramePrincipals::IsSystem
) {
1673 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
,
1674 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
)) {
1677 } else if (savedFrame
->getPrincipals() ==
1678 &ReconstructedSavedFramePrincipals::IsNotSystem
) {
1680 SCTAG_SAVED_FRAME_OBJECT
,
1681 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
)) {
1685 if (auto principals
= savedFrame
->getPrincipals()) {
1686 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_JSPRINCIPALS
) ||
1687 !principals
->write(context(), this)) {
1691 if (!out
.writePair(SCTAG_SAVED_FRAME_OBJECT
, SCTAG_NULL_JSPRINCIPALS
)) {
1697 // Write the SavedFrame's reserved slots, except for the parent, which is
1698 // queued on objs for further traversal.
1700 RootedValue
val(context());
1702 val
= BooleanValue(savedFrame
->getMutedErrors());
1703 if (!startWrite(val
)) {
1707 context()->markAtom(savedFrame
->getSource());
1708 val
= StringValue(savedFrame
->getSource());
1709 if (!startWrite(val
)) {
1713 val
= NumberValue(savedFrame
->getLine());
1714 if (!startWrite(val
)) {
1718 val
= NumberValue(savedFrame
->getColumn());
1719 if (!startWrite(val
)) {
1723 auto name
= savedFrame
->getFunctionDisplayName();
1725 context()->markAtom(name
);
1727 val
= name
? StringValue(name
) : NullValue();
1728 if (!startWrite(val
)) {
1732 auto cause
= savedFrame
->getAsyncCause();
1734 context()->markAtom(cause
);
1736 val
= cause
? StringValue(cause
) : NullValue();
1737 if (!startWrite(val
)) {
1744 bool JSStructuredCloneWriter::startWrite(HandleValue v
) {
1745 context()->check(v
);
1748 return writeString(SCTAG_STRING
, v
.toString());
1749 } else if (v
.isInt32()) {
1750 return out
.writePair(SCTAG_INT32
, v
.toInt32());
1751 } else if (v
.isDouble()) {
1752 return out
.writeDouble(v
.toDouble());
1753 } else if (v
.isBoolean()) {
1754 return out
.writePair(SCTAG_BOOLEAN
, v
.toBoolean());
1755 } else if (v
.isNull()) {
1756 return out
.writePair(SCTAG_NULL
, 0);
1757 } else if (v
.isUndefined()) {
1758 return out
.writePair(SCTAG_UNDEFINED
, 0);
1759 } else if (v
.isBigInt()) {
1760 return writeBigInt(SCTAG_BIGINT
, v
.toBigInt());
1761 } else if (v
.isObject()) {
1762 RootedObject
obj(context(), &v
.toObject());
1765 if (!startObject(obj
, &backref
)) {
1773 if (!GetBuiltinClass(context(), obj
, &cls
)) {
1778 case ESClass::Object
:
1779 case ESClass::Array
:
1780 return traverseObject(obj
, cls
);
1781 case ESClass::Number
: {
1782 RootedValue
unboxed(context());
1783 if (!Unbox(context(), obj
, &unboxed
)) {
1786 return out
.writePair(SCTAG_NUMBER_OBJECT
, 0) &&
1787 out
.writeDouble(unboxed
.toNumber());
1789 case ESClass::String
: {
1790 RootedValue
unboxed(context());
1791 if (!Unbox(context(), obj
, &unboxed
)) {
1794 return writeString(SCTAG_STRING_OBJECT
, unboxed
.toString());
1796 case ESClass::Boolean
: {
1797 RootedValue
unboxed(context());
1798 if (!Unbox(context(), obj
, &unboxed
)) {
1801 return out
.writePair(SCTAG_BOOLEAN_OBJECT
, unboxed
.toBoolean());
1803 case ESClass::RegExp
: {
1804 RegExpShared
* re
= RegExpToShared(context(), obj
);
1808 return out
.writePair(SCTAG_REGEXP_OBJECT
, re
->getFlags().value()) &&
1809 writeString(SCTAG_STRING
, re
->getSource());
1811 case ESClass::ArrayBuffer
: {
1812 if (JS::IsArrayBufferObject(obj
) && JS::ArrayBufferHasData(obj
)) {
1813 return writeArrayBuffer(obj
);
1817 case ESClass::SharedArrayBuffer
:
1818 if (JS::IsSharedArrayBufferObject(obj
)) {
1819 return writeSharedArrayBuffer(obj
);
1822 case ESClass::Date
: {
1823 RootedValue
unboxed(context());
1824 if (!Unbox(context(), obj
, &unboxed
)) {
1827 return out
.writePair(SCTAG_DATE_OBJECT
, 0) &&
1828 out
.writeDouble(unboxed
.toNumber());
1831 return traverseSet(obj
);
1833 return traverseMap(obj
);
1834 case ESClass::BigInt
: {
1835 RootedValue
unboxed(context());
1836 if (!Unbox(context(), obj
, &unboxed
)) {
1839 return writeBigInt(SCTAG_BIGINT_OBJECT
, unboxed
.toBigInt());
1841 case ESClass::Promise
:
1842 case ESClass::MapIterator
:
1843 case ESClass::SetIterator
:
1844 case ESClass::Arguments
:
1845 case ESClass::Error
:
1846 case ESClass::Function
:
1849 #ifdef ENABLE_RECORD_TUPLE
1850 case ESClass::Record
:
1851 case ESClass::Tuple
:
1852 MOZ_CRASH("Record and Tuple are not supported");
1855 case ESClass::Other
: {
1856 if (obj
->canUnwrapAs
<TypedArrayObject
>()) {
1857 return writeTypedArray(obj
);
1859 if (obj
->canUnwrapAs
<DataViewObject
>()) {
1860 return writeDataView(obj
);
1862 if (wasm::IsSharedWasmMemoryObject(obj
)) {
1863 return writeSharedWasmMemory(obj
);
1865 if (obj
->canUnwrapAs
<SavedFrame
>()) {
1866 return traverseSavedFrame(obj
);
1872 if (out
.buf
.callbacks_
&& out
.buf
.callbacks_
->write
) {
1873 bool sameProcessScopeRequired
= false;
1874 if (!out
.buf
.callbacks_
->write(context(), this, obj
,
1875 &sameProcessScopeRequired
,
1876 out
.buf
.closure_
)) {
1880 if (sameProcessScopeRequired
) {
1881 output().sameProcessScopeRequired();
1886 // else fall through
1889 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE
);
1892 bool JSStructuredCloneWriter::writeHeader() {
1893 return out
.writePair(SCTAG_HEADER
, (uint32_t)output().scope());
1896 bool JSStructuredCloneWriter::writeTransferMap() {
1897 if (transferableObjects
.empty()) {
1901 if (!out
.writePair(SCTAG_TRANSFER_MAP_HEADER
, (uint32_t)SCTAG_TM_UNREAD
)) {
1905 if (!out
.write(transferableObjects
.length())) {
1909 RootedObject
obj(context());
1910 for (auto* o
: transferableObjects
) {
1912 if (!memory
.put(obj
, memory
.count())) {
1913 ReportOutOfMemory(context());
1917 // Emit a placeholder pointer. We defer stealing the data until later
1918 // (and, if necessary, detaching this object if it's an ArrayBuffer).
1919 if (!out
.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY
,
1920 JS::SCTAG_TMO_UNFILLED
)) {
1923 if (!out
.write(0)) { // Pointer to ArrayBuffer contents.
1926 if (!out
.write(0)) { // extraData
1934 bool JSStructuredCloneWriter::transferOwnership() {
1935 if (transferableObjects
.empty()) {
1939 // Walk along the transferables and the transfer map at the same time,
1940 // grabbing out pointers from the transferables and stuffing them into the
1942 auto point
= out
.iter();
1943 MOZ_RELEASE_ASSERT(point
.canPeek());
1944 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
1947 MOZ_RELEASE_ASSERT(point
.canPeek());
1948 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point
.peek()) >> 32) ==
1949 SCTAG_TRANSFER_MAP_HEADER
);
1951 MOZ_RELEASE_ASSERT(point
.canPeek());
1952 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point
.peek()) ==
1953 transferableObjects
.length());
1956 JSContext
* cx
= context();
1957 RootedObject
obj(cx
);
1958 JS::StructuredCloneScope scope
= output().scope();
1959 for (auto* o
: transferableObjects
) {
1963 JS::TransferableOwnership ownership
;
1968 SCInput::getPair(point
.peek(), &tag
, (uint32_t*)&ownership
);
1969 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
1970 MOZ_ASSERT(ownership
== JS::SCTAG_TMO_UNFILLED
);
1974 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
1978 if (cls
== ESClass::ArrayBuffer
) {
1979 tag
= SCTAG_TRANSFER_MAP_ARRAY_BUFFER
;
1981 // The current setup of the array buffer inheritance hierarchy doesn't
1982 // lend itself well to generic manipulation via proxies.
1983 Rooted
<ArrayBufferObject
*> arrayBuffer(
1984 cx
, obj
->maybeUnwrapAs
<ArrayBufferObject
>());
1985 JSAutoRealm
ar(cx
, arrayBuffer
);
1987 if (arrayBuffer
->isDetached()) {
1988 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED
);
1992 if (arrayBuffer
->isPreparedForAsmJS()) {
1993 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER
);
1997 if (scope
== JS::StructuredCloneScope::DifferentProcess
||
1998 scope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
1999 // Write Transferred ArrayBuffers in DifferentProcess scope at
2000 // the end of the clone buffer, and store the offset within the
2001 // buffer to where the ArrayBuffer was written. Note that this
2002 // will invalidate the current position iterator.
2004 size_t pointOffset
= out
.offset(point
);
2005 tag
= SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
;
2006 ownership
= JS::SCTAG_TMO_UNOWNED
;
2008 extraData
= out
.tell() -
2009 pointOffset
; // Offset from tag to current end of buffer
2010 if (!writeArrayBuffer(arrayBuffer
)) {
2011 ReportOutOfMemory(cx
);
2015 // Must refresh the point iterator after its collection has
2018 point
+= pointOffset
;
2020 if (!JS::DetachArrayBuffer(cx
, arrayBuffer
)) {
2024 size_t nbytes
= arrayBuffer
->byteLength();
2026 using BufferContents
= ArrayBufferObject::BufferContents
;
2028 BufferContents bufContents
=
2029 ArrayBufferObject::extractStructuredCloneContents(cx
, arrayBuffer
);
2031 return false; // out of memory
2034 content
= bufContents
.data();
2035 if (bufContents
.kind() == ArrayBufferObject::MAPPED
) {
2036 ownership
= JS::SCTAG_TMO_MAPPED_DATA
;
2038 MOZ_ASSERT(bufContents
.kind() == ArrayBufferObject::MALLOCED
,
2039 "failing to handle new ArrayBuffer kind?");
2040 ownership
= JS::SCTAG_TMO_ALLOC_DATA
;
2045 if (!out
.buf
.callbacks_
|| !out
.buf
.callbacks_
->writeTransfer
) {
2046 return reportDataCloneError(JS_SCERR_TRANSFERABLE
);
2048 if (!out
.buf
.callbacks_
->writeTransfer(cx
, obj
, out
.buf
.closure_
, &tag
,
2049 &ownership
, &content
,
2053 MOZ_ASSERT(tag
> SCTAG_TRANSFER_MAP_PENDING_ENTRY
);
2056 point
.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag
, ownership
)));
2057 MOZ_ALWAYS_TRUE(point
.advance());
2059 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content
)));
2060 MOZ_ALWAYS_TRUE(point
.advance());
2061 point
.write(NativeEndian::swapToLittleEndian(extraData
));
2062 MOZ_ALWAYS_TRUE(point
.advance());
2066 // Make sure there aren't any more transfer map entries after the expected
2067 // number we read out.
2068 if (!point
.done()) {
2070 SCInput::getPair(point
.peek(), &tag
, &data
);
2071 MOZ_ASSERT(tag
< SCTAG_TRANSFER_MAP_HEADER
||
2072 tag
>= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES
);
2078 bool JSStructuredCloneWriter::write(HandleValue v
) {
2079 if (!startWrite(v
)) {
2083 RootedObject
obj(context());
2084 RootedValue
key(context());
2085 RootedValue
val(context());
2086 RootedId
id(context());
2088 while (!counts
.empty()) {
2089 obj
= &objs
.back().toObject();
2090 context()->check(obj
);
2091 if (counts
.back()) {
2095 if (!GetBuiltinClass(context(), obj
, &cls
)) {
2099 if (cls
== ESClass::Map
) {
2100 key
= otherEntries
.popCopy();
2104 val
= otherEntries
.popCopy();
2107 if (!startWrite(key
) || !startWrite(val
)) {
2110 } else if (cls
== ESClass::Set
|| obj
->canUnwrapAs
<SavedFrame
>()) {
2111 key
= otherEntries
.popCopy();
2114 if (!startWrite(key
)) {
2118 id
= objectEntries
.popCopy();
2119 key
= IdToValue(id
);
2122 // If obj still has an own property named id, write it out.
2124 if (GetOwnPropertyPure(context(), obj
, id
, val
.address(), &found
)) {
2126 if (!startWrite(key
) || !startWrite(val
)) {
2133 if (!HasOwnProperty(context(), obj
, id
, &found
)) {
2138 if (!startWrite(key
) || !GetProperty(context(), obj
, obj
, id
, &val
) ||
2145 if (!out
.writePair(SCTAG_END_OF_KEYS
, 0)) {
2154 return transferOwnership();
2157 template <typename CharT
>
2158 JSString
* JSStructuredCloneReader::readStringImpl(uint32_t nchars
,
2159 gc::InitialHeap heap
) {
2160 if (nchars
> JSString::MAX_LENGTH
) {
2161 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2162 JSMSG_SC_BAD_SERIALIZED_DATA
, "string length");
2166 InlineCharBuffer
<CharT
> chars
;
2167 if (!chars
.maybeAlloc(context(), nchars
) ||
2168 !in
.readChars(chars
.get(), nchars
)) {
2171 return chars
.toStringDontDeflate(context(), nchars
, heap
);
2174 JSString
* JSStructuredCloneReader::readString(uint32_t data
,
2175 gc::InitialHeap heap
) {
2176 uint32_t nchars
= data
& BitMask(31);
2177 bool latin1
= data
& (1 << 31);
2178 return latin1
? readStringImpl
<Latin1Char
>(nchars
, heap
)
2179 : readStringImpl
<char16_t
>(nchars
, heap
);
2182 BigInt
* JSStructuredCloneReader::readBigInt(uint32_t data
) {
2183 size_t length
= data
& BitMask(31);
2184 bool isNegative
= data
& (1 << 31);
2186 return BigInt::zero(context());
2188 RootedBigInt
result(
2189 context(), BigInt::createUninitialized(context(), length
, isNegative
));
2193 if (!in
.readArray(result
->digits().data(), length
)) {
2199 static uint32_t TagToV1ArrayType(uint32_t tag
) {
2200 MOZ_ASSERT(tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&&
2201 tag
<= SCTAG_TYPED_ARRAY_V1_MAX
);
2202 return tag
- SCTAG_TYPED_ARRAY_V1_MIN
;
2205 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType
,
2207 MutableHandleValue vp
,
2209 if (arrayType
> (v1Read
? Scalar::Uint8Clamped
: Scalar::BigUint64
)) {
2210 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2211 JSMSG_SC_BAD_SERIALIZED_DATA
,
2212 "unhandled typed array element type");
2216 // Push a placeholder onto the allObjs list to stand in for the typed array.
2217 uint32_t placeholderIndex
= allObjs
.length();
2218 Value dummy
= UndefinedValue();
2219 if (!allObjs
.append(dummy
)) {
2223 // Read the ArrayBuffer object and its contents (but no properties)
2224 RootedValue
v(context());
2225 uint64_t byteOffset
;
2227 if (!readV1ArrayBuffer(arrayType
, nelems
, &v
)) {
2232 if (!startRead(&v
)) {
2235 if (!in
.read(&byteOffset
)) {
2240 // Ensure invalid 64-bit values won't be truncated below.
2241 if (nelems
> ArrayBufferObject::maxBufferByteLength() ||
2242 byteOffset
> ArrayBufferObject::maxBufferByteLength()) {
2243 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2244 JSMSG_SC_BAD_SERIALIZED_DATA
,
2245 "invalid typed array length or offset");
2249 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2250 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2251 JSMSG_SC_BAD_SERIALIZED_DATA
,
2252 "typed array must be backed by an ArrayBuffer");
2256 RootedObject
buffer(context(), &v
.toObject());
2257 RootedObject
obj(context(), nullptr);
2259 switch (arrayType
) {
2260 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2261 case Scalar::Name: \
2262 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2263 byteOffset, nelems) \
2267 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER
)
2268 #undef CREATE_FROM_BUFFER
2271 MOZ_CRASH("Can't happen: arrayType range checked above");
2279 allObjs
[placeholderIndex
].set(vp
);
2284 bool JSStructuredCloneReader::readDataView(uint64_t byteLength
,
2285 MutableHandleValue vp
) {
2286 // Push a placeholder onto the allObjs list to stand in for the DataView.
2287 uint32_t placeholderIndex
= allObjs
.length();
2288 Value dummy
= UndefinedValue();
2289 if (!allObjs
.append(dummy
)) {
2293 // Read the ArrayBuffer object and its contents (but no properties).
2294 RootedValue
v(context());
2295 if (!startRead(&v
)) {
2298 if (!v
.isObject() || !v
.toObject().is
<ArrayBufferObjectMaybeShared
>()) {
2299 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2300 JSMSG_SC_BAD_SERIALIZED_DATA
,
2301 "DataView must be backed by an ArrayBuffer");
2306 uint64_t byteOffset
;
2307 if (!in
.read(&byteOffset
)) {
2311 // Ensure invalid 64-bit values won't be truncated below.
2312 if (byteLength
> ArrayBufferObject::maxBufferByteLength() ||
2313 byteOffset
> ArrayBufferObject::maxBufferByteLength()) {
2314 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2315 JSMSG_SC_BAD_SERIALIZED_DATA
,
2316 "invalid DataView length or offset");
2320 RootedObject
buffer(context(), &v
.toObject());
2321 RootedObject
obj(context(),
2322 JS_NewDataView(context(), buffer
, byteOffset
, byteLength
));
2328 allObjs
[placeholderIndex
].set(vp
);
2333 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type
,
2335 MutableHandleValue vp
) {
2336 // V2 stores the length in |data|. The current version stores the
2337 // length separately to allow larger length values.
2338 uint64_t nbytes
= 0;
2339 if (type
== SCTAG_ARRAY_BUFFER_OBJECT
) {
2340 if (!in
.read(&nbytes
)) {
2344 MOZ_ASSERT(type
== SCTAG_ARRAY_BUFFER_OBJECT_V2
);
2348 // The maximum ArrayBuffer size depends on the platform and prefs, and we cast
2349 // to size_t below, so we have to check this here.
2350 if (nbytes
> ArrayBufferObject::maxBufferByteLength()) {
2351 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2352 JSMSG_BAD_ARRAY_LENGTH
);
2356 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), size_t(nbytes
));
2361 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2362 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2363 return in
.readArray(buffer
.dataPointer(), nbytes
);
2366 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp
) {
2367 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2368 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2369 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2370 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2371 : JS_SCERR_NOT_CLONABLE
;
2372 ReportDataCloneError(context(), callbacks
, error
, closure
,
2373 "SharedArrayBuffer");
2377 uint64_t byteLength
;
2378 if (!in
.readBytes(&byteLength
, sizeof(byteLength
))) {
2379 return in
.reportTruncated();
2382 // The maximum ArrayBuffer size depends on the platform and prefs, and we cast
2383 // to size_t below, so we have to check this here.
2384 if (byteLength
> ArrayBufferObject::maxBufferByteLength()) {
2385 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2386 JSMSG_BAD_ARRAY_LENGTH
);
2391 if (!in
.readBytes(&p
, sizeof(p
))) {
2392 return in
.reportTruncated();
2395 SharedArrayRawBuffer
* rawbuf
= reinterpret_cast<SharedArrayRawBuffer
*>(p
);
2397 // There's no guarantee that the receiving agent has enabled shared memory
2398 // even if the transmitting agent has done so. Ideally we'd check at the
2399 // transmission point, but that's tricky, and it will be a very rare problem
2400 // in any case. Just fail at the receiving end if we can't handle it.
2405 .getSharedMemoryAndAtomicsEnabled()) {
2406 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2407 JSMSG_SC_SAB_DISABLED
);
2411 // The new object will have a new reference to the rawbuf.
2413 if (!rawbuf
->addReference()) {
2414 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2415 JSMSG_SC_SAB_REFCNT_OFLO
);
2419 RootedObject
obj(context(),
2420 SharedArrayBufferObject::New(context(), rawbuf
, byteLength
));
2422 rawbuf
->dropReference();
2426 // `rawbuf` is now owned by `obj`.
2428 if (callbacks
&& callbacks
->sabCloned
&&
2429 !callbacks
->sabCloned(context(), /*receiving=*/true, closure
)) {
2437 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes
,
2438 MutableHandleValue vp
) {
2439 JSContext
* cx
= context();
2441 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2442 JSMSG_SC_BAD_SERIALIZED_DATA
,
2443 "invalid shared wasm memory tag");
2447 if (!cloneDataPolicy
.areIntraClusterClonableSharedObjectsAllowed() ||
2448 !cloneDataPolicy
.areSharedMemoryObjectsAllowed()) {
2449 auto error
= context()->realm()->creationOptions().getCoopAndCoepEnabled()
2450 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2451 : JS_SCERR_NOT_CLONABLE
;
2452 ReportDataCloneError(cx
, callbacks
, error
, closure
, "WebAssembly.Memory");
2456 // Read the isHuge flag
2457 RootedValue
isHuge(cx
);
2458 if (!startRead(&isHuge
)) {
2462 // Read the SharedArrayBuffer object.
2463 RootedValue
payload(cx
);
2464 if (!startRead(&payload
)) {
2467 if (!payload
.isObject() ||
2468 !payload
.toObject().is
<SharedArrayBufferObject
>()) {
2469 JS_ReportErrorNumberASCII(
2470 context(), GetErrorMessage
, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA
,
2471 "shared wasm memory must be backed by a SharedArrayBuffer");
2475 Rooted
<ArrayBufferObjectMaybeShared
*> sab(
2476 cx
, &payload
.toObject().as
<SharedArrayBufferObject
>());
2478 // Construct the memory.
2479 RootedObject
proto(cx
, &cx
->global()->getPrototype(JSProto_WasmMemory
));
2480 RootedObject
memory(
2481 cx
, WasmMemoryObject::create(cx
, sab
, isHuge
.toBoolean(), proto
));
2486 vp
.setObject(*memory
);
2491 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2492 * endianness-conversion while reading.
2494 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType
,
2496 MutableHandleValue vp
) {
2497 if (arrayType
> Scalar::Uint8Clamped
) {
2498 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2499 JSMSG_SC_BAD_SERIALIZED_DATA
,
2500 "invalid TypedArray type");
2504 mozilla::CheckedInt
<size_t> nbytes
=
2505 mozilla::CheckedInt
<size_t>(nelems
) *
2506 TypedArrayElemSize(static_cast<Scalar::Type
>(arrayType
));
2507 if (!nbytes
.isValid() || nbytes
.value() > UINT32_MAX
) {
2508 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2509 JSMSG_SC_BAD_SERIALIZED_DATA
,
2510 "invalid typed array size");
2514 JSObject
* obj
= ArrayBufferObject::createZeroed(context(), nbytes
.value());
2519 ArrayBufferObject
& buffer
= obj
->as
<ArrayBufferObject
>();
2520 MOZ_ASSERT(buffer
.byteLength() == nbytes
);
2522 switch (arrayType
) {
2525 case Scalar::Uint8Clamped
:
2526 return in
.readArray((uint8_t*)buffer
.dataPointer(), nelems
);
2528 case Scalar::Uint16
:
2529 return in
.readArray((uint16_t*)buffer
.dataPointer(), nelems
);
2531 case Scalar::Uint32
:
2532 case Scalar::Float32
:
2533 return in
.readArray((uint32_t*)buffer
.dataPointer(), nelems
);
2534 case Scalar::Float64
:
2535 case Scalar::BigInt64
:
2536 case Scalar::BigUint64
:
2537 return in
.readArray((uint64_t*)buffer
.dataPointer(), nelems
);
2539 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2543 static bool PrimitiveToObject(JSContext
* cx
, MutableHandleValue vp
) {
2544 JSObject
* obj
= js::PrimitiveToObject(cx
, vp
);
2553 bool JSStructuredCloneReader::startRead(MutableHandleValue vp
,
2554 gc::InitialHeap strHeap
) {
2556 bool alreadAppended
= false;
2558 if (!in
.readPair(&tag
, &data
)) {
2569 case SCTAG_UNDEFINED
:
2578 case SCTAG_BOOLEAN_OBJECT
:
2579 vp
.setBoolean(!!data
);
2580 if (tag
== SCTAG_BOOLEAN_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2586 case SCTAG_STRING_OBJECT
: {
2587 JSString
* str
= readString(data
, strHeap
);
2592 if (tag
== SCTAG_STRING_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2598 case SCTAG_NUMBER_OBJECT
: {
2600 if (!in
.readDouble(&d
)) {
2603 vp
.setDouble(CanonicalizeNaN(d
));
2604 if (!PrimitiveToObject(context(), vp
)) {
2611 case SCTAG_BIGINT_OBJECT
: {
2612 RootedBigInt
bi(context(), readBigInt(data
));
2617 if (tag
== SCTAG_BIGINT_OBJECT
&& !PrimitiveToObject(context(), vp
)) {
2623 case SCTAG_DATE_OBJECT
: {
2625 if (!in
.readDouble(&d
)) {
2628 JS::ClippedTime t
= JS::TimeClip(d
);
2629 if (!NumbersAreIdentical(d
, t
.toDouble())) {
2630 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2631 JSMSG_SC_BAD_SERIALIZED_DATA
, "date");
2634 JSObject
* obj
= NewDateObjectMsec(context(), t
);
2642 case SCTAG_REGEXP_OBJECT
: {
2643 if ((data
& RegExpFlag::AllFlags
) != data
) {
2644 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2645 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
2649 RegExpFlags
flags(AssertedCast
<uint8_t>(data
));
2651 uint32_t tag2
, stringData
;
2652 if (!in
.readPair(&tag2
, &stringData
)) {
2655 if (tag2
!= SCTAG_STRING
) {
2656 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2657 JSMSG_SC_BAD_SERIALIZED_DATA
, "regexp");
2661 JSString
* str
= readString(stringData
, gc::TenuredHeap
);
2666 RootedAtom
atom(context(), AtomizeString(context(), str
));
2671 RegExpObject
* reobj
=
2672 RegExpObject::create(context(), atom
, flags
, GenericObject
);
2676 vp
.setObject(*reobj
);
2680 case SCTAG_ARRAY_OBJECT
:
2681 case SCTAG_OBJECT_OBJECT
: {
2683 (tag
== SCTAG_ARRAY_OBJECT
)
2684 ? (JSObject
*)NewDenseUnallocatedArray(
2685 context(), NativeEndian::swapFromLittleEndian(data
))
2686 : (JSObject
*)NewPlainObject(context());
2687 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
2694 case SCTAG_BACK_REFERENCE_OBJECT
: {
2695 if (data
>= allObjs
.length() || !allObjs
[data
].isObject()) {
2696 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2697 JSMSG_SC_BAD_SERIALIZED_DATA
,
2698 "invalid back reference in input");
2701 vp
.set(allObjs
[data
]);
2705 case SCTAG_TRANSFER_MAP_HEADER
:
2706 case SCTAG_TRANSFER_MAP_PENDING_ENTRY
:
2707 // We should be past all the transfer map tags.
2708 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2709 JSMSG_SC_BAD_SERIALIZED_DATA
, "invalid input");
2712 case SCTAG_ARRAY_BUFFER_OBJECT_V2
:
2713 case SCTAG_ARRAY_BUFFER_OBJECT
:
2714 if (!readArrayBuffer(StructuredDataType(tag
), data
, vp
)) {
2719 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT
:
2720 if (!readSharedArrayBuffer(vp
)) {
2725 case SCTAG_SHARED_WASM_MEMORY_OBJECT
:
2726 if (!readSharedWasmMemory(data
, vp
)) {
2731 case SCTAG_TYPED_ARRAY_OBJECT_V2
: {
2732 // readTypedArray adds the array to allObjs.
2733 // V2 stores the length (nelems) in |data| and the arrayType separately.
2735 if (!in
.read(&arrayType
)) {
2738 uint64_t nelems
= data
;
2739 return readTypedArray(arrayType
, nelems
, vp
);
2742 case SCTAG_TYPED_ARRAY_OBJECT
: {
2743 // readTypedArray adds the array to allObjs.
2744 // The current version stores the array type in |data| and the length
2745 // (nelems) separately to support large TypedArrays.
2746 uint32_t arrayType
= data
;
2748 if (!in
.read(&nelems
)) {
2751 return readTypedArray(arrayType
, nelems
, vp
);
2754 case SCTAG_DATA_VIEW_OBJECT_V2
: {
2755 // readDataView adds the array to allObjs.
2756 uint64_t byteLength
= data
;
2757 return readDataView(byteLength
, vp
);
2760 case SCTAG_DATA_VIEW_OBJECT
: {
2761 // readDataView adds the array to allObjs.
2762 uint64_t byteLength
;
2763 if (!in
.read(&byteLength
)) {
2766 return readDataView(byteLength
, vp
);
2769 case SCTAG_MAP_OBJECT
: {
2770 JSObject
* obj
= MapObject::create(context());
2771 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
2778 case SCTAG_SET_OBJECT
: {
2779 JSObject
* obj
= SetObject::create(context());
2780 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
2787 case SCTAG_SAVED_FRAME_OBJECT
: {
2788 auto obj
= readSavedFrame(data
);
2789 if (!obj
|| !objs
.append(ObjectValue(*obj
))) {
2797 if (tag
<= SCTAG_FLOAT_MAX
) {
2798 double d
= ReinterpretPairAsDouble(tag
, data
);
2799 vp
.setNumber(CanonicalizeNaN(d
));
2803 if (SCTAG_TYPED_ARRAY_V1_MIN
<= tag
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
2804 // A v1-format typed array
2805 // readTypedArray adds the array to allObjs
2806 return readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
2809 if (!callbacks
|| !callbacks
->read
) {
2810 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2811 JSMSG_SC_BAD_SERIALIZED_DATA
,
2812 "unsupported type");
2816 // callbacks->read() might read other objects from the buffer.
2817 // In startWrite we always write the object itself before calling
2818 // the custom function. We should do the same here to keep
2819 // indexing consistent.
2820 uint32_t placeholderIndex
= allObjs
.length();
2821 Value dummy
= UndefinedValue();
2822 if (!allObjs
.append(dummy
)) {
2826 callbacks
->read(context(), this, cloneDataPolicy
, tag
, data
, closure
);
2831 allObjs
[placeholderIndex
].set(vp
);
2832 alreadAppended
= true;
2836 if (!alreadAppended
&& vp
.isObject() && !allObjs
.append(vp
)) {
2843 bool JSStructuredCloneReader::readHeader() {
2845 if (!in
.getPair(&tag
, &data
)) {
2846 return in
.reportTruncated();
2849 JS::StructuredCloneScope storedScope
;
2850 if (tag
== SCTAG_HEADER
) {
2851 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
2852 storedScope
= JS::StructuredCloneScope(data
);
2854 // Old structured clone buffer. We must have read it from disk.
2855 storedScope
= JS::StructuredCloneScope::DifferentProcessForIndexedDB
;
2858 // Backward compatibility with old structured clone buffers. Value '0' was
2859 // used for SameProcessSameThread scope.
2860 if ((int)storedScope
== 0) {
2861 storedScope
= JS::StructuredCloneScope::SameProcess
;
2864 if (storedScope
< JS::StructuredCloneScope::SameProcess
||
2865 storedScope
> JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
2866 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2867 JSMSG_SC_BAD_SERIALIZED_DATA
,
2868 "invalid structured clone scope");
2872 if (allowedScope
== JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
2873 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
2874 // clones are incorrect. Treat them as if they were DifferentProcess.
2875 allowedScope
= JS::StructuredCloneScope::DifferentProcess
;
2879 if (storedScope
< allowedScope
) {
2880 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
2881 JSMSG_SC_BAD_SERIALIZED_DATA
,
2882 "incompatible structured clone scope");
2889 bool JSStructuredCloneReader::readTransferMap() {
2890 JSContext
* cx
= context();
2891 auto headerPos
= in
.tell();
2894 if (!in
.getPair(&tag
, &data
)) {
2895 return in
.reportTruncated();
2898 if (tag
!= SCTAG_TRANSFER_MAP_HEADER
||
2899 TransferableMapHeader(data
) == SCTAG_TM_TRANSFERRED
) {
2903 uint64_t numTransferables
;
2904 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
2905 if (!in
.read(&numTransferables
)) {
2909 for (uint64_t i
= 0; i
< numTransferables
; i
++) {
2910 auto pos
= in
.tell();
2912 if (!in
.readPair(&tag
, &data
)) {
2916 if (tag
== SCTAG_TRANSFER_MAP_PENDING_ENTRY
) {
2917 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
2921 RootedObject
obj(cx
);
2924 if (!in
.readPtr(&content
)) {
2929 if (!in
.read(&extraData
)) {
2933 if (tag
== SCTAG_TRANSFER_MAP_ARRAY_BUFFER
) {
2934 if (allowedScope
== JS::StructuredCloneScope::DifferentProcess
||
2936 JS::StructuredCloneScope::DifferentProcessForIndexedDB
) {
2937 // Transferred ArrayBuffers in a DifferentProcess clone buffer
2938 // are treated as if they weren't Transferred at all. We should
2939 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
2940 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
2944 MOZ_RELEASE_ASSERT(extraData
<= ArrayBufferObject::maxBufferByteLength());
2945 size_t nbytes
= extraData
;
2947 MOZ_ASSERT(data
== JS::SCTAG_TMO_ALLOC_DATA
||
2948 data
== JS::SCTAG_TMO_MAPPED_DATA
);
2949 if (data
== JS::SCTAG_TMO_ALLOC_DATA
) {
2950 obj
= JS::NewArrayBufferWithContents(cx
, nbytes
, content
);
2951 } else if (data
== JS::SCTAG_TMO_MAPPED_DATA
) {
2952 obj
= JS::NewMappedArrayBufferWithContents(cx
, nbytes
, content
);
2954 } else if (tag
== SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER
) {
2955 auto savedPos
= in
.tell();
2956 auto guard
= mozilla::MakeScopeExit([&] { in
.seekTo(savedPos
); });
2958 if (!in
.seekBy(static_cast<size_t>(extraData
))) {
2963 if (!in
.readPair(&tag
, &data
)) {
2966 if (tag
!= SCTAG_ARRAY_BUFFER_OBJECT_V2
&&
2967 tag
!= SCTAG_ARRAY_BUFFER_OBJECT
) {
2968 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
2971 RootedValue
val(cx
);
2972 if (!readArrayBuffer(StructuredDataType(tag
), data
, &val
)) {
2975 obj
= &val
.toObject();
2977 if (!callbacks
|| !callbacks
->readTransfer
) {
2978 ReportDataCloneError(cx
, callbacks
, JS_SCERR_TRANSFERABLE
, closure
);
2981 if (!callbacks
->readTransfer(cx
, this, tag
, content
, extraData
, closure
,
2986 MOZ_ASSERT(!cx
->isExceptionPending());
2989 // On failure, the buffer will still own the data (since its ownership
2990 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
2991 // DiscardTransferables.
2996 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
2998 pos
.write(PairToUInt64(tag
, JS::SCTAG_TMO_UNOWNED
));
2999 MOZ_ASSERT(!pos
.done());
3001 if (!allObjs
.append(ObjectValue(*obj
))) {
3006 // Mark the whole transfer map as consumed.
3008 SCInput::getPair(headerPos
.peek(), &tag
, &data
);
3009 MOZ_ASSERT(tag
== SCTAG_TRANSFER_MAP_HEADER
);
3010 MOZ_ASSERT(TransferableMapHeader(data
) != SCTAG_TM_TRANSFERRED
);
3013 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER
, SCTAG_TM_TRANSFERRED
));
3018 JSObject
* JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag
) {
3019 RootedSavedFrame
savedFrame(context(), SavedFrame::create(context()));
3024 JSPrincipals
* principals
;
3025 if (principalsTag
== SCTAG_JSPRINCIPALS
) {
3026 if (!context()->runtime()->readPrincipals
) {
3027 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3028 JSMSG_SC_UNSUPPORTED_TYPE
);
3032 if (!context()->runtime()->readPrincipals(context(), this, &principals
)) {
3035 } else if (principalsTag
==
3036 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM
) {
3037 principals
= &ReconstructedSavedFramePrincipals::IsSystem
;
3038 principals
->refcount
++;
3039 } else if (principalsTag
==
3040 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM
) {
3041 principals
= &ReconstructedSavedFramePrincipals::IsNotSystem
;
3042 principals
->refcount
++;
3043 } else if (principalsTag
== SCTAG_NULL_JSPRINCIPALS
) {
3044 principals
= nullptr;
3046 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3047 JSMSG_SC_BAD_SERIALIZED_DATA
,
3048 "bad SavedFrame principals");
3052 RootedValue
mutedErrors(context());
3053 RootedValue
source(context());
3055 // Read a |mutedErrors| boolean followed by a |source| string.
3056 // The |mutedErrors| boolean is present in all new structured-clone data,
3057 // but in older data it will be absent and only the |source| string will be
3059 if (!startRead(&mutedErrors
)) {
3063 if (mutedErrors
.isBoolean()) {
3064 if (!startRead(&source
, gc::TenuredHeap
) || !source
.isString()) {
3067 } else if (mutedErrors
.isString()) {
3068 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3069 // this is actually just a |source| string.
3070 source
= mutedErrors
;
3071 mutedErrors
.setBoolean(true); // Safe default value.
3078 savedFrame
->initPrincipalsAlreadyHeldAndMutedErrors(principals
,
3079 mutedErrors
.toBoolean());
3081 auto atomSource
= AtomizeString(context(), source
.toString());
3085 savedFrame
->initSource(atomSource
);
3087 RootedValue
lineVal(context());
3089 if (!startRead(&lineVal
) || !lineVal
.isNumber() ||
3090 !ToUint32(context(), lineVal
, &line
)) {
3093 savedFrame
->initLine(line
);
3095 RootedValue
columnVal(context());
3097 if (!startRead(&columnVal
) || !columnVal
.isNumber() ||
3098 !ToUint32(context(), columnVal
, &column
)) {
3101 savedFrame
->initColumn(column
);
3103 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3104 // are only valid within a specific process.
3105 savedFrame
->initSourceId(0);
3107 RootedValue
name(context());
3108 if (!startRead(&name
, gc::TenuredHeap
)) {
3111 if (!(name
.isString() || name
.isNull())) {
3112 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3113 JSMSG_SC_BAD_SERIALIZED_DATA
,
3114 "invalid saved frame cause");
3117 JSAtom
* atomName
= nullptr;
3118 if (name
.isString()) {
3119 atomName
= AtomizeString(context(), name
.toString());
3125 savedFrame
->initFunctionDisplayName(atomName
);
3127 RootedValue
cause(context());
3128 if (!startRead(&cause
, gc::TenuredHeap
)) {
3131 if (!(cause
.isString() || cause
.isNull())) {
3132 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3133 JSMSG_SC_BAD_SERIALIZED_DATA
,
3134 "invalid saved frame cause");
3137 JSAtom
* atomCause
= nullptr;
3138 if (cause
.isString()) {
3139 atomCause
= AtomizeString(context(), cause
.toString());
3144 savedFrame
->initAsyncCause(atomCause
);
3149 // Class for counting "children" (actually parent frames) of the SavedFrames on
3150 // the `objs` stack. When a SavedFrame is complete, it should have exactly 1
3153 // This class must be notified after every startRead() call.
3155 // If we add other types with restrictions on the number of children, this
3156 // should be expanded to handle those types as well.
3158 class ChildCounter
{
3160 Vector
<size_t> counts
;
3162 size_t objCountsIndex
;
3165 explicit ChildCounter(JSContext
* cx
) : cx(cx
), counts(cx
), objsLength(0) {}
3167 void noteObjIsOnTopOfStack() { objCountsIndex
= counts
.length() - 1; }
3169 // startRead() will have pushed any newly seen object onto the `objs` stack.
3170 // If it did not read an object, or if the object it read was a backreference
3171 // to an earlier object, the stack will be unchanged.
3172 bool postStartRead(HandleValueVector objs
) {
3173 if (objs
.length() == objsLength
) {
3174 // No new object pushed.
3178 // Push a new child counter (initialized to zero) for the new object.
3179 MOZ_ASSERT(objs
.length() == objsLength
+ 1);
3180 objsLength
= objs
.length();
3181 if (objs
.back().toObject().is
<SavedFrame
>()) {
3182 return counts
.append(0);
3185 // Not a SavedFrame; do nothing.
3189 // Reading has reached the end of the children for an object. Check whether
3190 // we saw the right number of children.
3191 bool handleEndOfChildren(HandleValueVector objs
) {
3192 MOZ_ASSERT(objsLength
> 0);
3195 if (objs
.back().toObject().is
<SavedFrame
>()) {
3196 if (counts
.back() != 1) {
3197 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3198 JSMSG_SC_BAD_SERIALIZED_DATA
,
3199 "must have single SavedFrame parent");
3208 // While we are reading children, we need to know whether this is the first
3209 // child seen or not, in order to avoid double-initializing in the error
3211 bool checkSingleParentFrame() {
3212 // We are checking at a point where we have read 0 or more parent frames,
3213 // in which case `obj` may not be on top of the `objs` stack anymore and
3214 // the count on top of the `counts` stack will correspond to the most
3215 // recently read frame, not `obj`. Use the remembered `counts` index from
3216 // when `obj` *was* on top of the stack.
3217 return ++counts
[objCountsIndex
] == 1;
3221 // Perform the whole recursive reading procedure.
3222 bool JSStructuredCloneReader::read(MutableHandleValue vp
, size_t nbytes
) {
3223 auto startTime
= mozilla::TimeStamp::Now();
3225 if (!readHeader()) {
3229 if (!readTransferMap()) {
3233 ChildCounter
childCounter(context());
3235 // Start out by reading in the main object and pushing it onto the 'objs'
3236 // stack. The data related to this object and its descendants extends from
3237 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3238 if (!startRead(vp
) || !childCounter
.postStartRead(objs
)) {
3242 // Stop when the stack shows that all objects have been read.
3243 while (objs
.length() != 0) {
3244 // What happens depends on the top obj on the objs stack.
3245 RootedObject
obj(context(), &objs
.back().toObject());
3246 childCounter
.noteObjIsOnTopOfStack();
3249 if (!in
.getPair(&tag
, &data
)) {
3253 if (tag
== SCTAG_END_OF_KEYS
) {
3254 if (!childCounter
.handleEndOfChildren(objs
)) {
3258 // Pop the current obj off the stack, since we are done with it and
3260 MOZ_ALWAYS_TRUE(in
.readPair(&tag
, &data
));
3265 // The input stream contains a sequence of "child" values, whose
3266 // interpretation depends on the type of obj. These values can be
3267 // anything, and startRead() will push onto 'objs' for any non-leaf
3268 // value (i.e., anything that may contain children).
3270 // startRead() will allocate the (empty) object, but note that when
3271 // startRead() returns, 'key' is not yet initialized with any of its
3272 // properties. Those will be filled in by returning to the head of this
3273 // loop, processing the first child obj, and continuing until all
3274 // children have been fully created.
3276 // Note that this means the ordering in the stream is a little funky for
3277 // things like Map. See the comment above traverseMap() for an example.
3278 RootedValue
key(context());
3279 if (!startRead(&key
) || !childCounter
.postStartRead(objs
)) {
3283 if (key
.isNull() && !(obj
->is
<MapObject
>() || obj
->is
<SetObject
>() ||
3284 obj
->is
<SavedFrame
>())) {
3285 // Backwards compatibility: Null formerly indicated the end of
3286 // object properties.
3287 if (!childCounter
.handleEndOfChildren(objs
)) {
3294 // Set object: the values between obj header (from startRead()) and
3295 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3296 if (obj
->is
<SetObject
>()) {
3297 if (!SetObject::add(context(), obj
, key
)) {
3303 // SavedFrame object: there is one following value, the parent SavedFrame,
3304 // which is either null or another SavedFrame object.
3305 if (obj
->is
<SavedFrame
>()) {
3306 SavedFrame
* parentFrame
;
3308 parentFrame
= nullptr;
3309 } else if (key
.isObject() && key
.toObject().is
<SavedFrame
>()) {
3310 parentFrame
= &key
.toObject().as
<SavedFrame
>();
3312 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3313 JSMSG_SC_BAD_SERIALIZED_DATA
,
3314 "invalid SavedFrame parent");
3318 if (!childCounter
.checkSingleParentFrame()) {
3319 // This is an error (more than one parent given), but it will be
3320 // reported when the SavedFrame is complete so it can be handled along
3321 // with the "no parent given" case.
3323 obj
->as
<SavedFrame
>().initParent(parentFrame
);
3329 // Everything else uses a series of key,value,key,value,... Value
3331 RootedValue
val(context());
3332 if (!startRead(&val
) || !childCounter
.postStartRead(objs
)) {
3336 if (obj
->is
<MapObject
>()) {
3337 // For a Map, store those <key,value> pairs in the contained map
3339 if (!MapObject::set(context(), obj
, key
, val
)) {
3343 // For any other Object, interpret them as plain properties.
3344 RootedId
id(context());
3346 if (!key
.isString() && !key
.isInt32()) {
3347 JS_ReportErrorNumberASCII(context(), GetErrorMessage
, nullptr,
3348 JSMSG_SC_BAD_SERIALIZED_DATA
,
3349 "property key expected");
3353 if (!PrimitiveValueToId
<CanGC
>(context(), key
, &id
)) {
3357 if (!DefineDataProperty(context(), obj
, id
, val
)) {
3365 JSRuntime
* rt
= context()->runtime();
3366 rt
->addTelemetry(JS_TELEMETRY_DESERIALIZE_BYTES
,
3367 static_cast<uint32_t>(std::min(nbytes
, size_t(MAX_UINT32
))));
3369 JS_TELEMETRY_DESERIALIZE_ITEMS
,
3370 static_cast<uint32_t>(std::min(numItemsRead
, size_t(MAX_UINT32
))));
3371 mozilla::TimeDuration elapsed
= mozilla::TimeStamp::Now() - startTime
;
3372 rt
->addTelemetry(JS_TELEMETRY_DESERIALIZE_US
,
3373 static_cast<uint32_t>(elapsed
.ToMicroseconds()));
3380 JS_PUBLIC_API
bool JS_ReadStructuredClone(
3381 JSContext
* cx
, const JSStructuredCloneData
& buf
, uint32_t version
,
3382 JS::StructuredCloneScope scope
, MutableHandleValue vp
,
3383 const JS::CloneDataPolicy
& cloneDataPolicy
,
3384 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3388 if (version
> JS_STRUCTURED_CLONE_VERSION
) {
3389 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3390 JSMSG_SC_BAD_CLONE_VERSION
);
3393 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3394 return ReadStructuredClone(cx
, buf
, scope
, vp
, cloneDataPolicy
, callbacks
,
3398 JS_PUBLIC_API
bool JS_WriteStructuredClone(
3399 JSContext
* cx
, HandleValue value
, JSStructuredCloneData
* bufp
,
3400 JS::StructuredCloneScope scope
, const JS::CloneDataPolicy
& cloneDataPolicy
,
3401 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
,
3402 HandleValue transferable
) {
3407 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3408 return WriteStructuredClone(cx
, value
, bufp
, scope
, cloneDataPolicy
,
3409 callbacks
, closure
, transferable
);
3412 JS_PUBLIC_API
bool JS_StructuredCloneHasTransferables(
3413 JSStructuredCloneData
& data
, bool* hasTransferable
) {
3414 *hasTransferable
= StructuredCloneHasTransferObjects(data
);
3418 JS_PUBLIC_API
bool JS_StructuredClone(
3419 JSContext
* cx
, HandleValue value
, MutableHandleValue vp
,
3420 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3424 // Strings are associated with zones, not compartments,
3425 // so we copy the string by wrapping it.
3426 if (value
.isString()) {
3427 RootedString
strValue(cx
, value
.toString());
3428 if (!cx
->compartment()->wrap(cx
, &strValue
)) {
3431 vp
.setString(strValue
);
3435 const JSStructuredCloneCallbacks
* callbacks
= optionalCallbacks
;
3437 JSAutoStructuredCloneBuffer
buf(JS::StructuredCloneScope::SameProcess
,
3438 callbacks
, closure
);
3440 if (value
.isObject()) {
3441 RootedObject
obj(cx
, &value
.toObject());
3442 obj
= CheckedUnwrapStatic(obj
);
3444 ReportAccessDenied(cx
);
3447 AutoRealm
ar(cx
, obj
);
3448 RootedValue
unwrappedVal(cx
, ObjectValue(*obj
));
3449 if (!buf
.write(cx
, unwrappedVal
, callbacks
, closure
)) {
3453 if (!buf
.write(cx
, value
, callbacks
, closure
)) {
3459 return buf
.read(cx
, vp
, JS::CloneDataPolicy(), callbacks
, closure
);
3462 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3463 JSAutoStructuredCloneBuffer
&& other
)
3464 : data_(other
.scope()) {
3465 data_
.ownTransferables_
= other
.data_
.ownTransferables_
;
3466 other
.steal(&data_
, &version_
, &data_
.callbacks_
, &data_
.closure_
);
3469 JSAutoStructuredCloneBuffer
& JSAutoStructuredCloneBuffer::operator=(
3470 JSAutoStructuredCloneBuffer
&& other
) {
3471 MOZ_ASSERT(&other
!= this);
3472 MOZ_ASSERT(scope() == other
.scope());
3474 data_
.ownTransferables_
= other
.data_
.ownTransferables_
;
3475 other
.steal(&data_
, &version_
, &data_
.callbacks_
, &data_
.closure_
);
3479 void JSAutoStructuredCloneBuffer::clear() {
3480 data_
.discardTransferables();
3481 data_
.ownTransferables_
= OwnTransferablePolicy::NoTransferables
;
3482 data_
.refsHeld_
.releaseAll();
3487 void JSAutoStructuredCloneBuffer::adopt(
3488 JSStructuredCloneData
&& data
, uint32_t version
,
3489 const JSStructuredCloneCallbacks
* callbacks
, void* closure
) {
3491 data_
= std::move(data
);
3493 data_
.setCallbacks(callbacks
, closure
,
3494 OwnTransferablePolicy::OwnsTransferablesIfAny
);
3497 void JSAutoStructuredCloneBuffer::steal(
3498 JSStructuredCloneData
* data
, uint32_t* versionp
,
3499 const JSStructuredCloneCallbacks
** callbacks
, void** closure
) {
3501 *versionp
= version_
;
3504 *callbacks
= data_
.callbacks_
;
3507 *closure
= data_
.closure_
;
3509 *data
= std::move(data_
);
3512 data_
.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables
);
3515 bool JSAutoStructuredCloneBuffer::read(
3516 JSContext
* cx
, MutableHandleValue vp
,
3517 const JS::CloneDataPolicy
& cloneDataPolicy
,
3518 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3520 return !!JS_ReadStructuredClone(
3521 cx
, data_
, version_
, data_
.scope(), vp
, cloneDataPolicy
,
3522 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
3523 optionalCallbacks
? closure
: data_
.closure_
);
3526 bool JSAutoStructuredCloneBuffer::write(
3527 JSContext
* cx
, HandleValue value
,
3528 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3529 HandleValue transferable
= UndefinedHandleValue
;
3530 return write(cx
, value
, transferable
, JS::CloneDataPolicy(),
3531 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
3532 optionalCallbacks
? closure
: data_
.closure_
);
3535 bool JSAutoStructuredCloneBuffer::write(
3536 JSContext
* cx
, HandleValue value
, HandleValue transferable
,
3537 const JS::CloneDataPolicy
& cloneDataPolicy
,
3538 const JSStructuredCloneCallbacks
* optionalCallbacks
, void* closure
) {
3540 bool ok
= JS_WriteStructuredClone(
3541 cx
, value
, &data_
, data_
.scopeForInternalWriting(), cloneDataPolicy
,
3542 optionalCallbacks
? optionalCallbacks
: data_
.callbacks_
,
3543 optionalCallbacks
? closure
: data_
.closure_
, transferable
);
3546 data_
.ownTransferables_
= OwnTransferablePolicy::OwnsTransferablesIfAny
;
3548 version_
= JS_STRUCTURED_CLONE_VERSION
;
3549 data_
.ownTransferables_
= OwnTransferablePolicy::NoTransferables
;
3554 JS_PUBLIC_API
bool JS_ReadUint32Pair(JSStructuredCloneReader
* r
, uint32_t* p1
,
3556 return r
->input().readPair((uint32_t*)p1
, (uint32_t*)p2
);
3559 JS_PUBLIC_API
bool JS_ReadBytes(JSStructuredCloneReader
* r
, void* p
,
3561 return r
->input().readBytes(p
, len
);
3564 JS_PUBLIC_API
bool JS_ReadTypedArray(JSStructuredCloneReader
* r
,
3565 MutableHandleValue vp
) {
3567 if (!r
->input().readPair(&tag
, &data
)) {
3571 if (tag
>= SCTAG_TYPED_ARRAY_V1_MIN
&& tag
<= SCTAG_TYPED_ARRAY_V1_MAX
) {
3572 return r
->readTypedArray(TagToV1ArrayType(tag
), data
, vp
, true);
3575 if (tag
== SCTAG_TYPED_ARRAY_OBJECT_V2
) {
3576 // V2 stores the length (nelems) in |data| and the arrayType separately.
3578 if (!r
->input().read(&arrayType
)) {
3581 uint64_t nelems
= data
;
3582 return r
->readTypedArray(arrayType
, nelems
, vp
);
3585 if (tag
== SCTAG_TYPED_ARRAY_OBJECT
) {
3586 // The current version stores the array type in |data| and the length
3587 // (nelems) separately to support large TypedArrays.
3588 uint32_t arrayType
= data
;
3590 if (!r
->input().read(&nelems
)) {
3593 return r
->readTypedArray(arrayType
, nelems
, vp
);
3596 JS_ReportErrorNumberASCII(r
->context(), GetErrorMessage
, nullptr,
3597 JSMSG_SC_BAD_SERIALIZED_DATA
,
3598 "expected type array");
3602 JS_PUBLIC_API
bool JS_WriteUint32Pair(JSStructuredCloneWriter
* w
, uint32_t tag
,
3604 return w
->output().writePair(tag
, data
);
3607 JS_PUBLIC_API
bool JS_WriteBytes(JSStructuredCloneWriter
* w
, const void* p
,
3609 return w
->output().writeBytes(p
, len
);
3612 JS_PUBLIC_API
bool JS_WriteString(JSStructuredCloneWriter
* w
,
3614 return w
->writeString(SCTAG_STRING
, str
);
3617 JS_PUBLIC_API
bool JS_WriteTypedArray(JSStructuredCloneWriter
* w
,
3619 MOZ_ASSERT(v
.isObject());
3620 w
->context()->check(v
);
3621 RootedObject
obj(w
->context(), &v
.toObject());
3623 // startWrite can write everything, thus we should check here
3624 // and report error if the user passes a wrong type.
3625 if (!obj
->canUnwrapAs
<TypedArrayObject
>()) {
3626 ReportAccessDenied(w
->context());
3630 // We should use startWrite instead of writeTypedArray, because
3631 // typed array is an object, we should add it to the |memory|
3632 // (allObjs) list. Directly calling writeTypedArray won't add it.
3633 return w
->startWrite(v
);
3636 JS_PUBLIC_API
bool JS_ObjectNotWritten(JSStructuredCloneWriter
* w
,
3638 w
->memory
.remove(w
->memory
.lookup(obj
));
3643 JS_PUBLIC_API
JS::StructuredCloneScope
JS_GetStructuredCloneScope(
3644 JSStructuredCloneWriter
* w
) {
3645 return w
->output().scope();