Bug 1753131 - Dispatch devicechange events even without an actively capturing MediaSt...
[gecko.git] / js / src / vm / StructuredClone.cpp
blobe7ac5ba620d8113cd4a9d96e670e451063f2a57e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
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
21 * of "Records".
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"
38 #include <algorithm>
39 #include <memory>
40 #include <utility>
42 #include "jsdate.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}
48 #include "js/Date.h"
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"
72 using namespace js;
74 using JS::CanonicalizeNaN;
75 using JS::GetBuiltinClass;
76 using JS::RegExpFlag;
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,
99 SCTAG_UNDEFINED,
100 SCTAG_BOOLEAN,
101 SCTAG_INT32,
102 SCTAG_STRING,
103 SCTAG_DATE_OBJECT,
104 SCTAG_REGEXP_OBJECT,
105 SCTAG_ARRAY_OBJECT,
106 SCTAG_OBJECT_OBJECT,
107 SCTAG_ARRAY_BUFFER_OBJECT_V2, // Old version, for backwards compatibility.
108 SCTAG_BOOLEAN_OBJECT,
109 SCTAG_STRING_OBJECT,
110 SCTAG_NUMBER_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.
115 SCTAG_MAP_OBJECT,
116 SCTAG_SET_OBJECT,
117 SCTAG_END_OF_KEYS,
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.
123 SCTAG_JSPRINCIPALS,
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,
131 SCTAG_BIGINT,
132 SCTAG_BIGINT_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)
168 * array of:
169 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
170 * pointer (64 bits)
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);
182 namespace js {
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);
198 mIter = other.mIter;
199 return *this;
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");
211 return ret;
214 BufferIterator& operator+=(size_t size) {
215 if (!advance(size)) {
216 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
218 return *this;
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;
237 T peek() const {
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));
251 return *this;
254 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }
256 bool SharedArrayRawBufferRefs::acquire(JSContext* cx,
257 SharedArrayRawBuffer* rawbuf) {
258 if (!refs_.append(rawbuf)) {
259 ReportOutOfMemory(cx);
260 return false;
263 if (!rawbuf->addReference()) {
264 refs_.popBack();
265 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
266 JSMSG_SC_SAB_REFCNT_OFLO);
267 return false;
270 return true;
273 bool SharedArrayRawBufferRefs::acquireAll(
274 JSContext* cx, const SharedArrayRawBufferRefs& that) {
275 if (!refs_.reserve(refs_.length() + that.refs_.length())) {
276 ReportOutOfMemory(cx);
277 return false;
280 for (auto ref : that.refs_) {
281 if (!ref->addReference()) {
282 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
283 JSMSG_SC_SAB_REFCNT_OFLO);
284 return false;
286 MOZ_ALWAYS_TRUE(refs_.append(ref));
289 return true;
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();
301 refs_.clear();
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.
312 struct SCOutput {
313 public:
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);
329 template <class T>
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(); }
345 JSContext* cx;
346 JSStructuredCloneData buf;
349 class SCInput {
350 typedef js::BufferIterator<uint64_t, SystemAllocPolicy> BufferIterator;
352 public:
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)) {
375 reportTruncated();
376 return false;
378 return true;
381 template <class T>
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");
387 return false;
390 private:
391 void staticAssertions() {
392 static_assert(sizeof(char16_t) == 2);
393 static_assert(sizeof(uint32_t) == 4);
396 JSContext* cx;
397 BufferIterator point;
400 } // namespace js
402 struct JSStructuredCloneReader {
403 public:
404 explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope,
405 const JS::CloneDataPolicy& cloneDataPolicy,
406 const JSStructuredCloneCallbacks* cb,
407 void* cbClosure)
408 : in(in),
409 allowedScope(scope),
410 cloneDataPolicy(cloneDataPolicy),
411 objs(in.context()),
412 allObjs(in.context()),
413 numItemsRead(0),
414 callbacks(cb),
415 closure(cbClosure) {}
417 SCInput& input() { return in; }
418 bool read(MutableHandleValue vp, size_t nbytes);
420 private:
421 JSContext* context() { return in.context(); }
423 bool readHeader();
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);
446 SCInput& in;
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
460 // backreferences.
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;
472 size_t numItemsRead;
474 // The user defined callbacks that will be used for cloning.
475 const JSStructuredCloneCallbacks* callbacks;
477 // Any value passed to JS_ReadStructuredClone.
478 void* closure;
480 friend bool JS_ReadTypedArray(JSStructuredCloneReader* r,
481 MutableHandleValue vp);
484 struct JSStructuredCloneWriter {
485 public:
486 explicit JSStructuredCloneWriter(JSContext* cx,
487 JS::StructuredCloneScope scope,
488 const JS::CloneDataPolicy& cloneDataPolicy,
489 const JSStructuredCloneCallbacks* cb,
490 void* cbClosure, const Value& tVal)
491 : out(cx, scope),
492 callbacks(cb),
493 closure(cbClosure),
494 objs(cx),
495 counts(cx),
496 objectEntries(cx),
497 otherEntries(cx),
498 memory(cx),
499 transferable(cx, tVal),
500 transferableObjects(cx, TransferableObjectsList(cx)),
501 cloneDataPolicy(cloneDataPolicy) {
502 out.setCallbacks(cb, cbClosure, OwnTransferablePolicy::NoTransferables);
505 ~JSStructuredCloneWriter();
507 bool init() {
508 return parseTransferable() && writeHeader() && writeTransferMap();
511 bool write(HandleValue v);
513 SCOutput& output() { return out; }
515 void extractBuffer(JSStructuredCloneData* newData) {
516 out.extractBuffer(newData);
519 private:
520 JSStructuredCloneWriter() = delete;
521 JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
523 JSContext* context() { return out.context(); }
525 bool writeHeader();
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();
550 SCOutput out;
552 // The user defined callbacks that will be used to signal cloning, in some
553 // cases.
554 const JSStructuredCloneCallbacks* callbacks;
556 // Any value passed to the callbacks.
557 void* closure;
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
573 // For Set: Key
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
580 using CloneMemory =
581 GCHashMap<JSObject*, uint32_t, MovableCellHasher<JSObject*>,
582 SystemAllocPolicy>;
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) {
598 MOZ_ASSERT(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,
610 Args&&... aArgs) {
611 unsigned errorNumber;
612 switch (errorId) {
613 case JS_SCERR_DUP_TRANSFERABLE:
614 errorNumber = JSMSG_SC_DUP_TRANSFERABLE;
615 break;
617 case JS_SCERR_TRANSFERABLE:
618 errorNumber = JSMSG_SC_NOT_TRANSFERABLE;
619 break;
621 case JS_SCERR_UNSUPPORTED_TYPE:
622 errorNumber = JSMSG_SC_UNSUPPORTED_TYPE;
623 break;
625 case JS_SCERR_SHMEM_TRANSFERABLE:
626 errorNumber = JSMSG_SC_SHMEM_TRANSFERABLE;
627 break;
629 case JS_SCERR_TYPED_ARRAY_DETACHED:
630 errorNumber = JSMSG_TYPED_ARRAY_DETACHED;
631 break;
633 case JS_SCERR_WASM_NO_TRANSFER:
634 errorNumber = JSMSG_WASM_NO_TRANSFER;
635 break;
637 case JS_SCERR_NOT_CLONABLE:
638 errorNumber = JSMSG_SC_NOT_CLONABLE;
639 break;
641 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP:
642 errorNumber = JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP;
643 break;
645 default:
646 MOZ_CRASH("Unkown errorId");
647 break;
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)...) &&
658 report.message()) {
659 callbacks->reportError(cx, errorId, closure, report.message().c_str());
660 } else {
661 ReportOutOfMemory(cx);
663 callbacks->reportError(cx, errorId, closure, "");
666 return;
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,
680 transferable);
681 if (!w.init()) {
682 return false;
684 if (!w.write(v)) {
685 return false;
687 w.extractBuffer(bufp);
688 return true;
691 bool ReadStructuredClone(JSContext* cx, const JSStructuredCloneData& data,
692 JS::StructuredCloneScope scope, MutableHandleValue vp,
693 const JS::CloneDataPolicy& cloneDataPolicy,
694 const JSStructuredCloneCallbacks* cb,
695 void* cbClosure) {
696 if (data.Size() % 8) {
697 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
698 JSMSG_SC_BAD_SERIALIZED_DATA, "misaligned");
699 return false;
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)) {
709 return false;
712 uint64_t u;
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);
719 namespace js {
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());
735 return true;
738 bool SCInput::readPair(uint32_t* tagp, uint32_t* datap) {
739 uint64_t u;
740 bool ok = read(&u);
741 if (ok) {
742 *tagp = uint32_t(u >> 32);
743 *datap = uint32_t(u);
745 return ok;
748 bool SCInput::get(uint64_t* p) {
749 if (!point.canPeek()) {
750 return reportTruncated();
752 *p = NativeEndian::swapFromLittleEndian(point.peek());
753 return true;
756 bool SCInput::getPair(uint32_t* tagp, uint32_t* datap) {
757 uint64_t u = 0;
758 if (!get(&u)) {
759 return false;
762 *tagp = uint32_t(u >> 32);
763 *datap = uint32_t(u);
764 return true;
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) {
774 uint64_t u;
775 if (!read(&u)) {
776 return false;
778 *p = CanonicalizeNaN(mozilla::BitwiseCast<double>(u));
779 return true;
782 template <typename T>
783 static void swapFromLittleEndianInPlace(T* ptr, size_t nelems) {
784 if (nelems > 0) {
785 NativeEndian::swapFromLittleEndianInPlace(ptr, nelems);
789 template <>
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);
802 template <class T>
803 bool SCInput::readArray(T* p, size_t nelems) {
804 if (!nelems) {
805 return true;
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);
821 return false;
824 swapFromLittleEndianInPlace(p, nelems);
826 point += ComputePadding(nelems, sizeof(T));
828 return true;
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) {
851 uint64_t u;
852 if (!read(&u)) {
853 return false;
855 *p = reinterpret_cast<void*>(u);
856 return true;
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());
866 return false;
868 return true;
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)));
890 template <class T>
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);
895 if (nelems == 0) {
896 return true;
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))) {
902 return false;
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)) {
910 return false;
913 return true;
916 template <>
917 bool SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems) {
918 if (nelems == 0) {
919 return true;
922 if (!buf.AppendBytes(reinterpret_cast<const char*>(p), nelems)) {
923 return false;
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)) {
930 return false;
933 return true;
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(); }
955 } // namespace js
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() {
963 if (!Size()) {
964 return;
967 if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) {
968 return;
971 // DifferentProcess clones cannot contain pointers, so nothing needs to be
972 // released.
973 if (scope() == JS::StructuredCloneScope::DifferentProcess) {
974 return;
977 FreeTransferStructuredCloneOp freeTransfer = nullptr;
978 if (callbacks_) {
979 freeTransfer = callbacks_->freeTransfer;
982 auto point = BufferIterator<uint64_t, SystemAllocPolicy>(*this);
983 if (point.done()) {
984 return; // Empty buffer
987 uint32_t tag, data;
988 MOZ_RELEASE_ASSERT(point.canPeek());
989 SCInput::getPair(point.peek(), &tag, &data);
990 MOZ_ALWAYS_TRUE(point.advance());
992 if (tag == SCTAG_HEADER) {
993 if (point.done()) {
994 return;
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) {
1003 return;
1006 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
1007 return;
1010 // freeTransfer should not GC
1011 JS::AutoSuppressGCAnalysis nogc;
1013 if (point.done()) {
1014 return;
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()) {
1022 return;
1025 uint32_t ownership;
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()) {
1030 return;
1033 void* content;
1034 SCInput::getPtr(point.peek(), &content);
1035 MOZ_ALWAYS_TRUE(point.advance());
1036 if (!point.canPeek()) {
1037 return;
1040 uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek());
1041 MOZ_ALWAYS_TRUE(point.advance());
1043 if (ownership < JS::SCTAG_TMO_FIRST_OWNED) {
1044 continue;
1047 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
1048 js_free(content);
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_);
1054 } else {
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
1064 if (out.count()) {
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()) {
1077 return true;
1080 if (!transferable.isObject()) {
1081 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1084 JSContext* cx = context();
1085 RootedObject array(cx, &transferable.toObject());
1086 bool isArray;
1087 if (!JS::IsArrayObject(cx, array, &isArray)) {
1088 return false;
1090 if (!isArray) {
1091 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1094 uint32_t length;
1095 if (!JS::GetArrayLength(cx, array, &length)) {
1096 return false;
1099 // Initialize the set for the provided array's length.
1100 if (!transferableObjects.reserve(length)) {
1101 return false;
1104 if (length == 0) {
1105 return true;
1108 RootedValue v(context());
1109 RootedObject tObj(context());
1111 for (uint32_t i = 0; i < length; ++i) {
1112 if (!CheckForInterrupt(cx)) {
1113 return false;
1116 if (!JS_GetElement(cx, array, i, &v)) {
1117 return false;
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);
1128 return false;
1131 // Shared memory cannot be transferred because it is not possible (nor
1132 // desirable) to detach the memory in agents that already hold a
1133 // reference to it.
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);
1154 else {
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_)) {
1163 return false;
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)) {
1178 return false;
1182 return true;
1185 template <typename... Args>
1186 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId,
1187 Args&&... aArgs) {
1188 ReportDataCloneError(context(), out.buf.callbacks_, errorId, out.buf.closure_,
1189 std::forward<Args>(aArgs)...);
1190 return false;
1193 bool JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) {
1194 JSLinearString* linear = str->ensureLinear(context());
1195 if (!linear) {
1196 return false;
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)) {
1206 return false;
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)) {
1220 return false;
1222 uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31);
1224 if (!out.writePair(tag, lengthAndSign)) {
1225 return false;
1227 return out.writeArray(bi->digits().data(), length);
1230 inline void JSStructuredCloneWriter::checkStack() {
1231 #ifdef DEBUG
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());
1237 size_t total = 0;
1238 for (size_t i = 0; i < limit; i++) {
1239 MOZ_ASSERT(total + counts[i] >= total);
1240 total += counts[i];
1242 if (counts.length() <= MAX) {
1243 MOZ_ASSERT(total == objectEntries.length() + otherEntries.length());
1244 } else {
1245 MOZ_ASSERT(total <= objectEntries.length() + otherEntries.length());
1248 size_t j = objs.length();
1249 for (size_t i = 0; i < limit; i++) {
1250 --j;
1251 MOZ_ASSERT(memory.has(&objs[j].toObject()));
1253 #endif
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)) {
1271 return false;
1274 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) {
1275 return false;
1278 uint64_t nelems = tarr->length();
1279 if (!out.write(nelems)) {
1280 return false;
1283 // Write out the ArrayBuffer tag and contents
1284 RootedValue val(context(), tarr->bufferValue());
1285 if (!startWrite(val)) {
1286 return false;
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)) {
1298 return false;
1301 uint64_t byteLength = view->byteLength();
1302 if (!out.write(byteLength)) {
1303 return false;
1306 // Write out the ArrayBuffer tag and contents
1307 RootedValue val(context(), view->bufferValue());
1308 if (!startWrite(val)) {
1309 return false;
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)) {
1322 return false;
1325 uint64_t byteLength = buffer->byteLength();
1326 if (!out.write(byteLength)) {
1327 return false;
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");
1341 return false;
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);
1353 return false;
1356 Rooted<SharedArrayBufferObject*> sharedArrayBuffer(
1357 context(), obj->maybeUnwrapAs<SharedArrayBufferObject>());
1358 SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
1360 if (!out.buf.refsHeld_.acquire(context(), rawbuf)) {
1361 return false;
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)))) {
1374 return false;
1377 if (callbacks && callbacks->sabCloned &&
1378 !callbacks->sabCloned(context(), /*receiving=*/false, closure)) {
1379 return false;
1382 return true;
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");
1394 return false;
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());
1418 return false;
1421 if (memory.count() == UINT32_MAX) {
1422 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1423 JSMSG_NEED_DIET, "object graph to serialize");
1424 return false;
1427 return true;
1430 static bool TryAppendNativeProperties(JSContext* cx, HandleObject obj,
1431 MutableHandleIdVector entries,
1432 size_t* properties, bool* optimized) {
1433 *optimized = false;
1435 if (!obj->is<NativeObject>()) {
1436 return true;
1439 HandleNativeObject nobj = obj.as<NativeObject>();
1440 if (nobj->isIndexed() || nobj->is<TypedArrayObject>() ||
1441 nobj->getClass()->getNewEnumerate() || nobj->getClass()->getEnumerate()) {
1442 return true;
1445 *optimized = true;
1447 size_t count = 0;
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()) {
1455 continue;
1458 MOZ_ASSERT(JSID_IS_STRING(id));
1459 if (!entries.append(id)) {
1460 return false;
1463 count++;
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)) {
1469 continue;
1472 if (!entries.append(INT_TO_JSID(i - 1))) {
1473 return false;
1476 count++;
1479 *properties = count;
1480 return true;
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>
1493 // <key1 data>
1494 // <Object tag for key1's value>
1495 // <key1.1 data>
1496 // <val1.1 data>
1497 // <key1.2 data>
1498 // <val1.2 data>
1499 // <end-of-children marker for key1's value>
1500 // <key2 data>
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) {
1509 size_t count;
1510 bool optimized = false;
1511 if (!TryAppendNativeProperties(context(), obj, &objectEntries, &count,
1512 &optimized)) {
1513 return false;
1516 if (!optimized) {
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)) {
1521 return false;
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)) {
1529 return false;
1533 count = properties.length();
1536 // Push obj and count to the stack.
1537 if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) {
1538 return false;
1541 checkStack();
1543 #if DEBUG
1544 ESClass cls2;
1545 if (!GetBuiltinClass(context(), obj, &cls2)) {
1546 return false;
1548 MOZ_ASSERT(cls2 == cls);
1549 #endif
1551 // Write the header for obj.
1552 if (cls == ESClass::Array) {
1553 uint32_t length = 0;
1554 if (!JS::GetArrayLength(context(), obj, &length)) {
1555 return false;
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.
1569 // m = new Map();
1570 // m.set(key1 = ..., value1 = ...)
1572 // where key1 and value2 are both objects would be stored as
1574 // <Map tag>
1575 // <key1 class tag>
1576 // <value1 class tag>
1577 // ...key1 data...
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)) {
1593 return false;
1596 if (!context()->compartment()->wrap(context(), &newEntries)) {
1597 return false;
1600 for (size_t i = newEntries.length(); i > 0; --i) {
1601 if (!otherEntries.append(newEntries[i - 1])) {
1602 return false;
1606 // Push obj and count to the stack.
1607 if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length())) {
1608 return false;
1611 checkStack();
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)) {
1628 return false;
1631 if (!context()->compartment()->wrap(context(), &keys)) {
1632 return false;
1635 for (size_t i = keys.length(); i > 0; --i) {
1636 if (!otherEntries.append(keys[i - 1])) {
1637 return false;
1641 // Push obj and count to the stack.
1642 if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length())) {
1643 return false;
1646 checkStack();
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)) {
1658 return false;
1661 if (!objs.append(ObjectValue(*obj)) ||
1662 !otherEntries.append(parent ? ObjectValue(*parent) : NullValue()) ||
1663 !counts.append(1)) {
1664 return false;
1667 checkStack();
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)) {
1675 return false;
1677 } else if (savedFrame->getPrincipals() ==
1678 &ReconstructedSavedFramePrincipals::IsNotSystem) {
1679 if (!out.writePair(
1680 SCTAG_SAVED_FRAME_OBJECT,
1681 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM)) {
1682 return false;
1684 } else {
1685 if (auto principals = savedFrame->getPrincipals()) {
1686 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
1687 !principals->write(context(), this)) {
1688 return false;
1690 } else {
1691 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS)) {
1692 return false;
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)) {
1704 return false;
1707 context()->markAtom(savedFrame->getSource());
1708 val = StringValue(savedFrame->getSource());
1709 if (!startWrite(val)) {
1710 return false;
1713 val = NumberValue(savedFrame->getLine());
1714 if (!startWrite(val)) {
1715 return false;
1718 val = NumberValue(savedFrame->getColumn());
1719 if (!startWrite(val)) {
1720 return false;
1723 auto name = savedFrame->getFunctionDisplayName();
1724 if (name) {
1725 context()->markAtom(name);
1727 val = name ? StringValue(name) : NullValue();
1728 if (!startWrite(val)) {
1729 return false;
1732 auto cause = savedFrame->getAsyncCause();
1733 if (cause) {
1734 context()->markAtom(cause);
1736 val = cause ? StringValue(cause) : NullValue();
1737 if (!startWrite(val)) {
1738 return false;
1741 return true;
1744 bool JSStructuredCloneWriter::startWrite(HandleValue v) {
1745 context()->check(v);
1747 if (v.isString()) {
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());
1764 bool backref;
1765 if (!startObject(obj, &backref)) {
1766 return false;
1768 if (backref) {
1769 return true;
1772 ESClass cls;
1773 if (!GetBuiltinClass(context(), obj, &cls)) {
1774 return false;
1777 switch (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)) {
1784 return false;
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)) {
1792 return false;
1794 return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
1796 case ESClass::Boolean: {
1797 RootedValue unboxed(context());
1798 if (!Unbox(context(), obj, &unboxed)) {
1799 return false;
1801 return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
1803 case ESClass::RegExp: {
1804 RegExpShared* re = RegExpToShared(context(), obj);
1805 if (!re) {
1806 return false;
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);
1815 break;
1817 case ESClass::SharedArrayBuffer:
1818 if (JS::IsSharedArrayBufferObject(obj)) {
1819 return writeSharedArrayBuffer(obj);
1821 break;
1822 case ESClass::Date: {
1823 RootedValue unboxed(context());
1824 if (!Unbox(context(), obj, &unboxed)) {
1825 return false;
1827 return out.writePair(SCTAG_DATE_OBJECT, 0) &&
1828 out.writeDouble(unboxed.toNumber());
1830 case ESClass::Set:
1831 return traverseSet(obj);
1832 case ESClass::Map:
1833 return traverseMap(obj);
1834 case ESClass::BigInt: {
1835 RootedValue unboxed(context());
1836 if (!Unbox(context(), obj, &unboxed)) {
1837 return false;
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:
1847 break;
1849 #ifdef ENABLE_RECORD_TUPLE
1850 case ESClass::Record:
1851 case ESClass::Tuple:
1852 MOZ_CRASH("Record and Tuple are not supported");
1853 #endif
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);
1868 break;
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_)) {
1877 return false;
1880 if (sameProcessScopeRequired) {
1881 output().sameProcessScopeRequired();
1884 return true;
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()) {
1898 return true;
1901 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) {
1902 return false;
1905 if (!out.write(transferableObjects.length())) {
1906 return false;
1909 RootedObject obj(context());
1910 for (auto* o : transferableObjects) {
1911 obj = o;
1912 if (!memory.put(obj, memory.count())) {
1913 ReportOutOfMemory(context());
1914 return false;
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)) {
1921 return false;
1923 if (!out.write(0)) { // Pointer to ArrayBuffer contents.
1924 return false;
1926 if (!out.write(0)) { // extraData
1927 return false;
1931 return true;
1934 bool JSStructuredCloneWriter::transferOwnership() {
1935 if (transferableObjects.empty()) {
1936 return true;
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
1941 // transfer map.
1942 auto point = out.iter();
1943 MOZ_RELEASE_ASSERT(point.canPeek());
1944 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
1945 SCTAG_HEADER);
1946 point++;
1947 MOZ_RELEASE_ASSERT(point.canPeek());
1948 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
1949 SCTAG_TRANSFER_MAP_HEADER);
1950 point++;
1951 MOZ_RELEASE_ASSERT(point.canPeek());
1952 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) ==
1953 transferableObjects.length());
1954 point++;
1956 JSContext* cx = context();
1957 RootedObject obj(cx);
1958 JS::StructuredCloneScope scope = output().scope();
1959 for (auto* o : transferableObjects) {
1960 obj = o;
1962 uint32_t tag;
1963 JS::TransferableOwnership ownership;
1964 void* content;
1965 uint64_t extraData;
1967 #if DEBUG
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);
1971 #endif
1973 ESClass cls;
1974 if (!GetBuiltinClass(cx, obj, &cls)) {
1975 return false;
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);
1989 return false;
1992 if (arrayBuffer->isPreparedForAsmJS()) {
1993 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER);
1994 return false;
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;
2007 content = nullptr;
2008 extraData = out.tell() -
2009 pointOffset; // Offset from tag to current end of buffer
2010 if (!writeArrayBuffer(arrayBuffer)) {
2011 ReportOutOfMemory(cx);
2012 return false;
2015 // Must refresh the point iterator after its collection has
2016 // been modified.
2017 point = out.iter();
2018 point += pointOffset;
2020 if (!JS::DetachArrayBuffer(cx, arrayBuffer)) {
2021 return false;
2023 } else {
2024 size_t nbytes = arrayBuffer->byteLength();
2026 using BufferContents = ArrayBufferObject::BufferContents;
2028 BufferContents bufContents =
2029 ArrayBufferObject::extractStructuredCloneContents(cx, arrayBuffer);
2030 if (!bufContents) {
2031 return false; // out of memory
2034 content = bufContents.data();
2035 if (bufContents.kind() == ArrayBufferObject::MAPPED) {
2036 ownership = JS::SCTAG_TMO_MAPPED_DATA;
2037 } else {
2038 MOZ_ASSERT(bufContents.kind() == ArrayBufferObject::MALLOCED,
2039 "failing to handle new ArrayBuffer kind?");
2040 ownership = JS::SCTAG_TMO_ALLOC_DATA;
2042 extraData = nbytes;
2044 } else {
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,
2050 &extraData)) {
2051 return false;
2053 MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2056 point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
2057 MOZ_ALWAYS_TRUE(point.advance());
2058 point.write(
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());
2065 #if DEBUG
2066 // Make sure there aren't any more transfer map entries after the expected
2067 // number we read out.
2068 if (!point.done()) {
2069 uint32_t tag, data;
2070 SCInput::getPair(point.peek(), &tag, &data);
2071 MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER ||
2072 tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
2074 #endif
2075 return true;
2078 bool JSStructuredCloneWriter::write(HandleValue v) {
2079 if (!startWrite(v)) {
2080 return false;
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()) {
2092 counts.back()--;
2094 ESClass cls;
2095 if (!GetBuiltinClass(context(), obj, &cls)) {
2096 return false;
2099 if (cls == ESClass::Map) {
2100 key = otherEntries.popCopy();
2101 checkStack();
2103 counts.back()--;
2104 val = otherEntries.popCopy();
2105 checkStack();
2107 if (!startWrite(key) || !startWrite(val)) {
2108 return false;
2110 } else if (cls == ESClass::Set || obj->canUnwrapAs<SavedFrame>()) {
2111 key = otherEntries.popCopy();
2112 checkStack();
2114 if (!startWrite(key)) {
2115 return false;
2117 } else {
2118 id = objectEntries.popCopy();
2119 key = IdToValue(id);
2120 checkStack();
2122 // If obj still has an own property named id, write it out.
2123 bool found;
2124 if (GetOwnPropertyPure(context(), obj, id, val.address(), &found)) {
2125 if (found) {
2126 if (!startWrite(key) || !startWrite(val)) {
2127 return false;
2130 continue;
2133 if (!HasOwnProperty(context(), obj, id, &found)) {
2134 return false;
2137 if (found) {
2138 if (!startWrite(key) || !GetProperty(context(), obj, obj, id, &val) ||
2139 !startWrite(val)) {
2140 return false;
2144 } else {
2145 if (!out.writePair(SCTAG_END_OF_KEYS, 0)) {
2146 return false;
2148 objs.popBack();
2149 counts.popBack();
2153 memory.clear();
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");
2163 return nullptr;
2166 InlineCharBuffer<CharT> chars;
2167 if (!chars.maybeAlloc(context(), nchars) ||
2168 !in.readChars(chars.get(), nchars)) {
2169 return nullptr;
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);
2185 if (length == 0) {
2186 return BigInt::zero(context());
2188 RootedBigInt result(
2189 context(), BigInt::createUninitialized(context(), length, isNegative));
2190 if (!result) {
2191 return nullptr;
2193 if (!in.readArray(result->digits().data(), length)) {
2194 return nullptr;
2196 return result;
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,
2206 uint64_t nelems,
2207 MutableHandleValue vp,
2208 bool v1Read) {
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");
2213 return false;
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)) {
2220 return false;
2223 // Read the ArrayBuffer object and its contents (but no properties)
2224 RootedValue v(context());
2225 uint64_t byteOffset;
2226 if (v1Read) {
2227 if (!readV1ArrayBuffer(arrayType, nelems, &v)) {
2228 return false;
2230 byteOffset = 0;
2231 } else {
2232 if (!startRead(&v)) {
2233 return false;
2235 if (!in.read(&byteOffset)) {
2236 return false;
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");
2246 return false;
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");
2253 return false;
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) \
2264 .asObject(); \
2265 break;
2267 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER)
2268 #undef CREATE_FROM_BUFFER
2270 default:
2271 MOZ_CRASH("Can't happen: arrayType range checked above");
2274 if (!obj) {
2275 return false;
2277 vp.setObject(*obj);
2279 allObjs[placeholderIndex].set(vp);
2281 return true;
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)) {
2290 return false;
2293 // Read the ArrayBuffer object and its contents (but no properties).
2294 RootedValue v(context());
2295 if (!startRead(&v)) {
2296 return false;
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");
2302 return false;
2305 // Read byteOffset.
2306 uint64_t byteOffset;
2307 if (!in.read(&byteOffset)) {
2308 return false;
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");
2317 return false;
2320 RootedObject buffer(context(), &v.toObject());
2321 RootedObject obj(context(),
2322 JS_NewDataView(context(), buffer, byteOffset, byteLength));
2323 if (!obj) {
2324 return false;
2326 vp.setObject(*obj);
2328 allObjs[placeholderIndex].set(vp);
2330 return true;
2333 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type,
2334 uint32_t data,
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)) {
2341 return false;
2343 } else {
2344 MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2);
2345 nbytes = data;
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);
2353 return false;
2356 JSObject* obj = ArrayBufferObject::createZeroed(context(), size_t(nbytes));
2357 if (!obj) {
2358 return false;
2360 vp.setObject(*obj);
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");
2374 return false;
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);
2387 return false;
2390 intptr_t p;
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.
2402 if (!context()
2403 ->realm()
2404 ->creationOptions()
2405 .getSharedMemoryAndAtomicsEnabled()) {
2406 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2407 JSMSG_SC_SAB_DISABLED);
2408 return false;
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);
2416 return false;
2419 RootedObject obj(context(),
2420 SharedArrayBufferObject::New(context(), rawbuf, byteLength));
2421 if (!obj) {
2422 rawbuf->dropReference();
2423 return false;
2426 // `rawbuf` is now owned by `obj`.
2428 if (callbacks && callbacks->sabCloned &&
2429 !callbacks->sabCloned(context(), /*receiving=*/true, closure)) {
2430 return false;
2433 vp.setObject(*obj);
2434 return true;
2437 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes,
2438 MutableHandleValue vp) {
2439 JSContext* cx = context();
2440 if (nbytes != 0) {
2441 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2442 JSMSG_SC_BAD_SERIALIZED_DATA,
2443 "invalid shared wasm memory tag");
2444 return false;
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");
2453 return false;
2456 // Read the isHuge flag
2457 RootedValue isHuge(cx);
2458 if (!startRead(&isHuge)) {
2459 return false;
2462 // Read the SharedArrayBuffer object.
2463 RootedValue payload(cx);
2464 if (!startRead(&payload)) {
2465 return false;
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");
2472 return false;
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));
2482 if (!memory) {
2483 return false;
2486 vp.setObject(*memory);
2487 return true;
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,
2495 uint32_t nelems,
2496 MutableHandleValue vp) {
2497 if (arrayType > Scalar::Uint8Clamped) {
2498 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2499 JSMSG_SC_BAD_SERIALIZED_DATA,
2500 "invalid TypedArray type");
2501 return false;
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");
2511 return false;
2514 JSObject* obj = ArrayBufferObject::createZeroed(context(), nbytes.value());
2515 if (!obj) {
2516 return false;
2518 vp.setObject(*obj);
2519 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2520 MOZ_ASSERT(buffer.byteLength() == nbytes);
2522 switch (arrayType) {
2523 case Scalar::Int8:
2524 case Scalar::Uint8:
2525 case Scalar::Uint8Clamped:
2526 return in.readArray((uint8_t*)buffer.dataPointer(), nelems);
2527 case Scalar::Int16:
2528 case Scalar::Uint16:
2529 return in.readArray((uint16_t*)buffer.dataPointer(), nelems);
2530 case Scalar::Int32:
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);
2538 default:
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);
2545 if (!obj) {
2546 return false;
2549 vp.setObject(*obj);
2550 return true;
2553 bool JSStructuredCloneReader::startRead(MutableHandleValue vp,
2554 gc::InitialHeap strHeap) {
2555 uint32_t tag, data;
2556 bool alreadAppended = false;
2558 if (!in.readPair(&tag, &data)) {
2559 return false;
2562 numItemsRead++;
2564 switch (tag) {
2565 case SCTAG_NULL:
2566 vp.setNull();
2567 break;
2569 case SCTAG_UNDEFINED:
2570 vp.setUndefined();
2571 break;
2573 case SCTAG_INT32:
2574 vp.setInt32(data);
2575 break;
2577 case SCTAG_BOOLEAN:
2578 case SCTAG_BOOLEAN_OBJECT:
2579 vp.setBoolean(!!data);
2580 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) {
2581 return false;
2583 break;
2585 case SCTAG_STRING:
2586 case SCTAG_STRING_OBJECT: {
2587 JSString* str = readString(data, strHeap);
2588 if (!str) {
2589 return false;
2591 vp.setString(str);
2592 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) {
2593 return false;
2595 break;
2598 case SCTAG_NUMBER_OBJECT: {
2599 double d;
2600 if (!in.readDouble(&d)) {
2601 return false;
2603 vp.setDouble(CanonicalizeNaN(d));
2604 if (!PrimitiveToObject(context(), vp)) {
2605 return false;
2607 break;
2610 case SCTAG_BIGINT:
2611 case SCTAG_BIGINT_OBJECT: {
2612 RootedBigInt bi(context(), readBigInt(data));
2613 if (!bi) {
2614 return false;
2616 vp.setBigInt(bi);
2617 if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) {
2618 return false;
2620 break;
2623 case SCTAG_DATE_OBJECT: {
2624 double d;
2625 if (!in.readDouble(&d)) {
2626 return false;
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");
2632 return false;
2634 JSObject* obj = NewDateObjectMsec(context(), t);
2635 if (!obj) {
2636 return false;
2638 vp.setObject(*obj);
2639 break;
2642 case SCTAG_REGEXP_OBJECT: {
2643 if ((data & RegExpFlag::AllFlags) != data) {
2644 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2645 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2646 return false;
2649 RegExpFlags flags(AssertedCast<uint8_t>(data));
2651 uint32_t tag2, stringData;
2652 if (!in.readPair(&tag2, &stringData)) {
2653 return false;
2655 if (tag2 != SCTAG_STRING) {
2656 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2657 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2658 return false;
2661 JSString* str = readString(stringData, gc::TenuredHeap);
2662 if (!str) {
2663 return false;
2666 RootedAtom atom(context(), AtomizeString(context(), str));
2667 if (!atom) {
2668 return false;
2671 RegExpObject* reobj =
2672 RegExpObject::create(context(), atom, flags, GenericObject);
2673 if (!reobj) {
2674 return false;
2676 vp.setObject(*reobj);
2677 break;
2680 case SCTAG_ARRAY_OBJECT:
2681 case SCTAG_OBJECT_OBJECT: {
2682 JSObject* obj =
2683 (tag == SCTAG_ARRAY_OBJECT)
2684 ? (JSObject*)NewDenseUnallocatedArray(
2685 context(), NativeEndian::swapFromLittleEndian(data))
2686 : (JSObject*)NewPlainObject(context());
2687 if (!obj || !objs.append(ObjectValue(*obj))) {
2688 return false;
2690 vp.setObject(*obj);
2691 break;
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");
2699 return false;
2701 vp.set(allObjs[data]);
2702 return true;
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");
2710 return false;
2712 case SCTAG_ARRAY_BUFFER_OBJECT_V2:
2713 case SCTAG_ARRAY_BUFFER_OBJECT:
2714 if (!readArrayBuffer(StructuredDataType(tag), data, vp)) {
2715 return false;
2717 break;
2719 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
2720 if (!readSharedArrayBuffer(vp)) {
2721 return false;
2723 break;
2725 case SCTAG_SHARED_WASM_MEMORY_OBJECT:
2726 if (!readSharedWasmMemory(data, vp)) {
2727 return false;
2729 break;
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.
2734 uint64_t arrayType;
2735 if (!in.read(&arrayType)) {
2736 return false;
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;
2747 uint64_t nelems;
2748 if (!in.read(&nelems)) {
2749 return false;
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)) {
2764 return false;
2766 return readDataView(byteLength, vp);
2769 case SCTAG_MAP_OBJECT: {
2770 JSObject* obj = MapObject::create(context());
2771 if (!obj || !objs.append(ObjectValue(*obj))) {
2772 return false;
2774 vp.setObject(*obj);
2775 break;
2778 case SCTAG_SET_OBJECT: {
2779 JSObject* obj = SetObject::create(context());
2780 if (!obj || !objs.append(ObjectValue(*obj))) {
2781 return false;
2783 vp.setObject(*obj);
2784 break;
2787 case SCTAG_SAVED_FRAME_OBJECT: {
2788 auto obj = readSavedFrame(data);
2789 if (!obj || !objs.append(ObjectValue(*obj))) {
2790 return false;
2792 vp.setObject(*obj);
2793 break;
2796 default: {
2797 if (tag <= SCTAG_FLOAT_MAX) {
2798 double d = ReinterpretPairAsDouble(tag, data);
2799 vp.setNumber(CanonicalizeNaN(d));
2800 break;
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");
2813 return false;
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)) {
2823 return false;
2825 JSObject* obj =
2826 callbacks->read(context(), this, cloneDataPolicy, tag, data, closure);
2827 if (!obj) {
2828 return false;
2830 vp.setObject(*obj);
2831 allObjs[placeholderIndex].set(vp);
2832 alreadAppended = true;
2836 if (!alreadAppended && vp.isObject() && !allObjs.append(vp)) {
2837 return false;
2840 return true;
2843 bool JSStructuredCloneReader::readHeader() {
2844 uint32_t tag, data;
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);
2853 } else {
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");
2869 return false;
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;
2876 return true;
2879 if (storedScope < allowedScope) {
2880 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2881 JSMSG_SC_BAD_SERIALIZED_DATA,
2882 "incompatible structured clone scope");
2883 return false;
2886 return true;
2889 bool JSStructuredCloneReader::readTransferMap() {
2890 JSContext* cx = context();
2891 auto headerPos = in.tell();
2893 uint32_t tag, data;
2894 if (!in.getPair(&tag, &data)) {
2895 return in.reportTruncated();
2898 if (tag != SCTAG_TRANSFER_MAP_HEADER ||
2899 TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
2900 return true;
2903 uint64_t numTransferables;
2904 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2905 if (!in.read(&numTransferables)) {
2906 return false;
2909 for (uint64_t i = 0; i < numTransferables; i++) {
2910 auto pos = in.tell();
2912 if (!in.readPair(&tag, &data)) {
2913 return false;
2916 if (tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY) {
2917 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2918 return false;
2921 RootedObject obj(cx);
2923 void* content;
2924 if (!in.readPtr(&content)) {
2925 return false;
2928 uint64_t extraData;
2929 if (!in.read(&extraData)) {
2930 return false;
2933 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
2934 if (allowedScope == JS::StructuredCloneScope::DifferentProcess ||
2935 allowedScope ==
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);
2941 return false;
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); });
2957 in.seekTo(pos);
2958 if (!in.seekBy(static_cast<size_t>(extraData))) {
2959 return false;
2962 uint32_t tag, data;
2963 if (!in.readPair(&tag, &data)) {
2964 return false;
2966 if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 &&
2967 tag != SCTAG_ARRAY_BUFFER_OBJECT) {
2968 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2969 return false;
2971 RootedValue val(cx);
2972 if (!readArrayBuffer(StructuredDataType(tag), data, &val)) {
2973 return false;
2975 obj = &val.toObject();
2976 } else {
2977 if (!callbacks || !callbacks->readTransfer) {
2978 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2979 return false;
2981 if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure,
2982 &obj)) {
2983 return false;
2985 MOZ_ASSERT(obj);
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.
2992 if (!obj) {
2993 return false;
2996 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
2997 // buffer.
2998 pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED));
2999 MOZ_ASSERT(!pos.done());
3001 if (!allObjs.append(ObjectValue(*obj))) {
3002 return false;
3006 // Mark the whole transfer map as consumed.
3007 #ifdef DEBUG
3008 SCInput::getPair(headerPos.peek(), &tag, &data);
3009 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
3010 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
3011 #endif
3012 headerPos.write(
3013 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
3015 return true;
3018 JSObject* JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag) {
3019 RootedSavedFrame savedFrame(context(), SavedFrame::create(context()));
3020 if (!savedFrame) {
3021 return nullptr;
3024 JSPrincipals* principals;
3025 if (principalsTag == SCTAG_JSPRINCIPALS) {
3026 if (!context()->runtime()->readPrincipals) {
3027 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3028 JSMSG_SC_UNSUPPORTED_TYPE);
3029 return nullptr;
3032 if (!context()->runtime()->readPrincipals(context(), this, &principals)) {
3033 return nullptr;
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;
3045 } else {
3046 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3047 JSMSG_SC_BAD_SERIALIZED_DATA,
3048 "bad SavedFrame principals");
3049 return nullptr;
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
3058 // found.
3059 if (!startRead(&mutedErrors)) {
3060 return nullptr;
3063 if (mutedErrors.isBoolean()) {
3064 if (!startRead(&source, gc::TenuredHeap) || !source.isString()) {
3065 return nullptr;
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.
3072 } else {
3073 // Invalid type.
3074 return nullptr;
3078 savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals,
3079 mutedErrors.toBoolean());
3081 auto atomSource = AtomizeString(context(), source.toString());
3082 if (!atomSource) {
3083 return nullptr;
3085 savedFrame->initSource(atomSource);
3087 RootedValue lineVal(context());
3088 uint32_t line;
3089 if (!startRead(&lineVal) || !lineVal.isNumber() ||
3090 !ToUint32(context(), lineVal, &line)) {
3091 return nullptr;
3093 savedFrame->initLine(line);
3095 RootedValue columnVal(context());
3096 uint32_t column;
3097 if (!startRead(&columnVal) || !columnVal.isNumber() ||
3098 !ToUint32(context(), columnVal, &column)) {
3099 return nullptr;
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)) {
3109 return nullptr;
3111 if (!(name.isString() || name.isNull())) {
3112 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3113 JSMSG_SC_BAD_SERIALIZED_DATA,
3114 "invalid saved frame cause");
3115 return nullptr;
3117 JSAtom* atomName = nullptr;
3118 if (name.isString()) {
3119 atomName = AtomizeString(context(), name.toString());
3120 if (!atomName) {
3121 return nullptr;
3125 savedFrame->initFunctionDisplayName(atomName);
3127 RootedValue cause(context());
3128 if (!startRead(&cause, gc::TenuredHeap)) {
3129 return nullptr;
3131 if (!(cause.isString() || cause.isNull())) {
3132 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3133 JSMSG_SC_BAD_SERIALIZED_DATA,
3134 "invalid saved frame cause");
3135 return nullptr;
3137 JSAtom* atomCause = nullptr;
3138 if (cause.isString()) {
3139 atomCause = AtomizeString(context(), cause.toString());
3140 if (!atomCause) {
3141 return nullptr;
3144 savedFrame->initAsyncCause(atomCause);
3146 return savedFrame;
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
3151 // parent frame.
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 {
3159 JSContext* cx;
3160 Vector<size_t> counts;
3161 size_t objsLength;
3162 size_t objCountsIndex;
3164 public:
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.
3175 return true;
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.
3186 return true;
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);
3193 objsLength--;
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");
3200 return false;
3203 counts.popBack();
3205 return true;
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
3210 // case.
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()) {
3226 return false;
3229 if (!readTransferMap()) {
3230 return false;
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)) {
3239 return false;
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();
3248 uint32_t tag, data;
3249 if (!in.getPair(&tag, &data)) {
3250 return false;
3253 if (tag == SCTAG_END_OF_KEYS) {
3254 if (!childCounter.handleEndOfChildren(objs)) {
3255 return false;
3258 // Pop the current obj off the stack, since we are done with it and
3259 // its children.
3260 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3261 objs.popBack();
3262 continue;
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)) {
3280 return false;
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)) {
3288 return false;
3290 objs.popBack();
3291 continue;
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)) {
3298 return false;
3300 continue;
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;
3307 if (key.isNull()) {
3308 parentFrame = nullptr;
3309 } else if (key.isObject() && key.toObject().is<SavedFrame>()) {
3310 parentFrame = &key.toObject().as<SavedFrame>();
3311 } else {
3312 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3313 JSMSG_SC_BAD_SERIALIZED_DATA,
3314 "invalid SavedFrame parent");
3315 return false;
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.
3322 } else {
3323 obj->as<SavedFrame>().initParent(parentFrame);
3326 continue;
3329 // Everything else uses a series of key,value,key,value,... Value
3330 // objects.
3331 RootedValue val(context());
3332 if (!startRead(&val) || !childCounter.postStartRead(objs)) {
3333 return false;
3336 if (obj->is<MapObject>()) {
3337 // For a Map, store those <key,value> pairs in the contained map
3338 // data structure.
3339 if (!MapObject::set(context(), obj, key, val)) {
3340 return false;
3342 } else {
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");
3350 return false;
3353 if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
3354 return false;
3357 if (!DefineDataProperty(context(), obj, id, val)) {
3358 return false;
3363 allObjs.clear();
3365 JSRuntime* rt = context()->runtime();
3366 rt->addTelemetry(JS_TELEMETRY_DESERIALIZE_BYTES,
3367 static_cast<uint32_t>(std::min(nbytes, size_t(MAX_UINT32))));
3368 rt->addTelemetry(
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()));
3375 return true;
3378 using namespace js;
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) {
3385 AssertHeapIsIdle();
3386 CHECK_THREAD(cx);
3388 if (version > JS_STRUCTURED_CLONE_VERSION) {
3389 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3390 JSMSG_SC_BAD_CLONE_VERSION);
3391 return false;
3393 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3394 return ReadStructuredClone(cx, buf, scope, vp, cloneDataPolicy, callbacks,
3395 closure);
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) {
3403 AssertHeapIsIdle();
3404 CHECK_THREAD(cx);
3405 cx->check(value);
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);
3415 return true;
3418 JS_PUBLIC_API bool JS_StructuredClone(
3419 JSContext* cx, HandleValue value, MutableHandleValue vp,
3420 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3421 AssertHeapIsIdle();
3422 CHECK_THREAD(cx);
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)) {
3429 return false;
3431 vp.setString(strValue);
3432 return true;
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);
3443 if (!obj) {
3444 ReportAccessDenied(cx);
3445 return false;
3447 AutoRealm ar(cx, obj);
3448 RootedValue unwrappedVal(cx, ObjectValue(*obj));
3449 if (!buf.write(cx, unwrappedVal, callbacks, closure)) {
3450 return false;
3452 } else {
3453 if (!buf.write(cx, value, callbacks, closure)) {
3454 return false;
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());
3473 clear();
3474 data_.ownTransferables_ = other.data_.ownTransferables_;
3475 other.steal(&data_, &version_, &data_.callbacks_, &data_.closure_);
3476 return *this;
3479 void JSAutoStructuredCloneBuffer::clear() {
3480 data_.discardTransferables();
3481 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
3482 data_.refsHeld_.releaseAll();
3483 data_.Clear();
3484 version_ = 0;
3487 void JSAutoStructuredCloneBuffer::adopt(
3488 JSStructuredCloneData&& data, uint32_t version,
3489 const JSStructuredCloneCallbacks* callbacks, void* closure) {
3490 clear();
3491 data_ = std::move(data);
3492 version_ = version;
3493 data_.setCallbacks(callbacks, closure,
3494 OwnTransferablePolicy::OwnsTransferablesIfAny);
3497 void JSAutoStructuredCloneBuffer::steal(
3498 JSStructuredCloneData* data, uint32_t* versionp,
3499 const JSStructuredCloneCallbacks** callbacks, void** closure) {
3500 if (versionp) {
3501 *versionp = version_;
3503 if (callbacks) {
3504 *callbacks = data_.callbacks_;
3506 if (closure) {
3507 *closure = data_.closure_;
3509 *data = std::move(data_);
3511 version_ = 0;
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) {
3519 MOZ_ASSERT(cx);
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) {
3539 clear();
3540 bool ok = JS_WriteStructuredClone(
3541 cx, value, &data_, data_.scopeForInternalWriting(), cloneDataPolicy,
3542 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
3543 optionalCallbacks ? closure : data_.closure_, transferable);
3545 if (ok) {
3546 data_.ownTransferables_ = OwnTransferablePolicy::OwnsTransferablesIfAny;
3547 } else {
3548 version_ = JS_STRUCTURED_CLONE_VERSION;
3549 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
3551 return ok;
3554 JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1,
3555 uint32_t* p2) {
3556 return r->input().readPair((uint32_t*)p1, (uint32_t*)p2);
3559 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p,
3560 size_t len) {
3561 return r->input().readBytes(p, len);
3564 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r,
3565 MutableHandleValue vp) {
3566 uint32_t tag, data;
3567 if (!r->input().readPair(&tag, &data)) {
3568 return false;
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.
3577 uint64_t arrayType;
3578 if (!r->input().read(&arrayType)) {
3579 return false;
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;
3589 uint64_t nelems;
3590 if (!r->input().read(&nelems)) {
3591 return false;
3593 return r->readTypedArray(arrayType, nelems, vp);
3596 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
3597 JSMSG_SC_BAD_SERIALIZED_DATA,
3598 "expected type array");
3599 return false;
3602 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag,
3603 uint32_t data) {
3604 return w->output().writePair(tag, data);
3607 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p,
3608 size_t len) {
3609 return w->output().writeBytes(p, len);
3612 JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w,
3613 HandleString str) {
3614 return w->writeString(SCTAG_STRING, str);
3617 JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w,
3618 HandleValue v) {
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());
3627 return false;
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,
3637 HandleObject obj) {
3638 w->memory.remove(w->memory.lookup(obj));
3640 return true;
3643 JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope(
3644 JSStructuredCloneWriter* w) {
3645 return w->output().scope();