Bug 1865597 - Add error checking when initializing parallel marking and disable on...
[gecko.git] / js / src / vm / StructuredClone.cpp
blob8b36e3e42aca789e7c55dd92471bde09b85ce475
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/Maybe.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 "gc/GC.h" // AutoSelectGCHeap
47 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
48 #include "js/ArrayBuffer.h" // JS::{ArrayBufferHasData,DetachArrayBuffer,IsArrayBufferObject,New{,Mapped}ArrayBufferWithContents,ReleaseMappedArrayBufferContents}
49 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
50 #include "js/Date.h"
51 #include "js/experimental/TypedData.h" // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer
52 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
53 #include "js/GCAPI.h"
54 #include "js/GCHashTable.h"
55 #include "js/Object.h" // JS::GetBuiltinClass
56 #include "js/PropertyAndElement.h" // JS_GetElement
57 #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
58 #include "js/ScalarType.h" // js::Scalar::Type
59 #include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject
60 #include "js/Wrapper.h"
61 #include "util/DifferentialTesting.h"
62 #include "vm/BigIntType.h"
63 #include "vm/ErrorObject.h"
64 #include "vm/JSContext.h"
65 #include "vm/PlainObject.h" // js::PlainObject
66 #include "vm/RegExpObject.h"
67 #include "vm/SavedFrame.h"
68 #include "vm/SharedArrayObject.h"
69 #include "vm/TypedArrayObject.h"
70 #include "wasm/WasmJS.h"
72 #include "vm/ArrayObject-inl.h"
73 #include "vm/Compartment-inl.h"
74 #include "vm/ErrorObject-inl.h"
75 #include "vm/InlineCharBuffer-inl.h"
76 #include "vm/JSContext-inl.h"
77 #include "vm/JSObject-inl.h"
78 #include "vm/NativeObject-inl.h"
79 #include "vm/ObjectOperations-inl.h"
80 #include "vm/Realm-inl.h"
82 using namespace js;
84 using JS::CanonicalizeNaN;
85 using JS::GetBuiltinClass;
86 using JS::RegExpFlag;
87 using JS::RegExpFlags;
88 using JS::RootedValueVector;
89 using mozilla::AssertedCast;
90 using mozilla::BitwiseCast;
91 using mozilla::Maybe;
92 using mozilla::NativeEndian;
93 using mozilla::NumbersAreIdentical;
95 // When you make updates here, make sure you consider whether you need to bump
96 // the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You
97 // will likely need to increment the version if anything at all changes in the
98 // serialization format.
100 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should
101 // have a stable ID, it need not be at the end of the list and should not be
102 // used for sizing data structures.
104 enum StructuredDataType : uint32_t {
105 // Structured data types provided by the engine
106 SCTAG_FLOAT_MAX = 0xFFF00000,
107 SCTAG_HEADER = 0xFFF10000,
108 SCTAG_NULL = 0xFFFF0000,
109 SCTAG_UNDEFINED,
110 SCTAG_BOOLEAN,
111 SCTAG_INT32,
112 SCTAG_STRING,
113 SCTAG_DATE_OBJECT,
114 SCTAG_REGEXP_OBJECT,
115 SCTAG_ARRAY_OBJECT,
116 SCTAG_OBJECT_OBJECT,
117 SCTAG_ARRAY_BUFFER_OBJECT_V2, // Old version, for backwards compatibility.
118 SCTAG_BOOLEAN_OBJECT,
119 SCTAG_STRING_OBJECT,
120 SCTAG_NUMBER_OBJECT,
121 SCTAG_BACK_REFERENCE_OBJECT,
122 SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
123 SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
124 SCTAG_TYPED_ARRAY_OBJECT_V2, // Old version, for backwards compatibility.
125 SCTAG_MAP_OBJECT,
126 SCTAG_SET_OBJECT,
127 SCTAG_END_OF_KEYS,
128 SCTAG_DO_NOT_USE_3, // Required for backwards compatibility
129 SCTAG_DATA_VIEW_OBJECT_V2, // Old version, for backwards compatibility.
130 SCTAG_SAVED_FRAME_OBJECT,
132 // No new tags before principals.
133 SCTAG_JSPRINCIPALS,
134 SCTAG_NULL_JSPRINCIPALS,
135 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
136 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
138 SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
139 SCTAG_SHARED_WASM_MEMORY_OBJECT,
141 SCTAG_BIGINT,
142 SCTAG_BIGINT_OBJECT,
144 SCTAG_ARRAY_BUFFER_OBJECT,
145 SCTAG_TYPED_ARRAY_OBJECT,
146 SCTAG_DATA_VIEW_OBJECT,
148 SCTAG_ERROR_OBJECT,
150 SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
151 SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
152 SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
153 SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
154 SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
155 SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
156 SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
157 SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
158 SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
159 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED =
160 SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
161 // BigInt64 and BigUint64 are not supported in the v1 format.
162 SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED,
164 // Define a separate range of numbers for Transferable-only tags, since
165 // they are not used for persistent clone buffers and therefore do not
166 // require bumping JS_STRUCTURED_CLONE_VERSION.
167 SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
168 SCTAG_TRANSFER_MAP_PENDING_ENTRY,
169 SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
170 SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER,
171 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
173 SCTAG_END_OF_BUILTIN_TYPES
177 * Format of transfer map:
178 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
179 * numTransferables (64 bits)
180 * array of:
181 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
182 * pointer (64 bits)
183 * extraData (64 bits), eg byte length for ArrayBuffers
186 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
187 // contents have been read out yet or not.
188 enum TransferableMapHeader { SCTAG_TM_UNREAD = 0, SCTAG_TM_TRANSFERRED };
190 static inline uint64_t PairToUInt64(uint32_t tag, uint32_t data) {
191 return uint64_t(data) | (uint64_t(tag) << 32);
194 namespace js {
196 template <typename T, typename AllocPolicy>
197 struct BufferIterator {
198 using BufferList = mozilla::BufferList<AllocPolicy>;
200 explicit BufferIterator(const BufferList& buffer)
201 : mBuffer(buffer), mIter(buffer.Iter()) {
202 static_assert(8 % sizeof(T) == 0);
205 explicit BufferIterator(const JSStructuredCloneData& data)
206 : mBuffer(data.bufList_), mIter(data.Start()) {}
208 BufferIterator& operator=(const BufferIterator& other) {
209 MOZ_ASSERT(&mBuffer == &other.mBuffer);
210 mIter = other.mIter;
211 return *this;
214 [[nodiscard]] bool advance(size_t size = sizeof(T)) {
215 return mIter.AdvanceAcrossSegments(mBuffer, size);
218 BufferIterator operator++(int) {
219 BufferIterator ret = *this;
220 if (!advance(sizeof(T))) {
221 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
223 return ret;
226 BufferIterator& operator+=(size_t size) {
227 if (!advance(size)) {
228 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
230 return *this;
233 size_t operator-(const BufferIterator& other) const {
234 MOZ_ASSERT(&mBuffer == &other.mBuffer);
235 return mBuffer.RangeLength(other.mIter, mIter);
238 bool operator==(const BufferIterator& other) const {
239 return mBuffer.Start() == other.mBuffer.Start() && mIter == other.mIter;
241 bool operator!=(const BufferIterator& other) const {
242 return !(*this == other);
245 bool done() const { return mIter.Done(); }
247 [[nodiscard]] bool readBytes(char* outData, size_t size) {
248 return mBuffer.ReadBytes(mIter, outData, size);
251 void write(const T& data) {
252 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
253 *reinterpret_cast<T*>(mIter.Data()) = data;
256 T peek() const {
257 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
258 return *reinterpret_cast<T*>(mIter.Data());
261 bool canPeek() const { return mIter.HasRoomFor(sizeof(T)); }
263 const BufferList& mBuffer;
264 typename BufferList::IterImpl mIter;
267 SharedArrayRawBufferRefs& SharedArrayRawBufferRefs::operator=(
268 SharedArrayRawBufferRefs&& other) {
269 takeOwnership(std::move(other));
270 return *this;
273 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }
275 bool SharedArrayRawBufferRefs::acquire(JSContext* cx,
276 SharedArrayRawBuffer* rawbuf) {
277 if (!refs_.append(rawbuf)) {
278 ReportOutOfMemory(cx);
279 return false;
282 if (!rawbuf->addReference()) {
283 refs_.popBack();
284 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
285 JSMSG_SC_SAB_REFCNT_OFLO);
286 return false;
289 return true;
292 bool SharedArrayRawBufferRefs::acquireAll(
293 JSContext* cx, const SharedArrayRawBufferRefs& that) {
294 if (!refs_.reserve(refs_.length() + that.refs_.length())) {
295 ReportOutOfMemory(cx);
296 return false;
299 for (auto ref : that.refs_) {
300 if (!ref->addReference()) {
301 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
302 JSMSG_SC_SAB_REFCNT_OFLO);
303 return false;
305 MOZ_ALWAYS_TRUE(refs_.append(ref));
308 return true;
311 void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs&& other) {
312 MOZ_ASSERT(refs_.empty());
313 refs_ = std::move(other.refs_);
316 void SharedArrayRawBufferRefs::releaseAll() {
317 for (auto ref : refs_) {
318 ref->dropReference();
320 refs_.clear();
323 // SCOutput provides an interface to write raw data -- eg uint64_ts, doubles,
324 // arrays of bytes -- into a structured clone data output stream. It also knows
325 // how to free any transferable data within that stream.
327 // Note that it contains a full JSStructuredCloneData object, which holds the
328 // callbacks necessary to read/write/transfer/free the data. For the purpose of
329 // this class, only the freeTransfer callback is relevant; the rest of the
330 // callbacks are used by the higher-level JSStructuredCloneWriter interface.
331 struct SCOutput {
332 public:
333 using Iter = BufferIterator<uint64_t, SystemAllocPolicy>;
335 SCOutput(JSContext* cx, JS::StructuredCloneScope scope);
337 JSContext* context() const { return cx; }
338 JS::StructuredCloneScope scope() const { return buf.scope(); }
339 void sameProcessScopeRequired() { buf.sameProcessScopeRequired(); }
341 [[nodiscard]] bool write(uint64_t u);
342 [[nodiscard]] bool writePair(uint32_t tag, uint32_t data);
343 [[nodiscard]] bool writeDouble(double d);
344 [[nodiscard]] bool writeBytes(const void* p, size_t nbytes);
345 [[nodiscard]] bool writeChars(const Latin1Char* p, size_t nchars);
346 [[nodiscard]] bool writeChars(const char16_t* p, size_t nchars);
348 template <class T>
349 [[nodiscard]] bool writeArray(const T* p, size_t nelems);
351 void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure,
352 OwnTransferablePolicy policy) {
353 buf.setCallbacks(callbacks, closure, policy);
355 void extractBuffer(JSStructuredCloneData* data) { *data = std::move(buf); }
357 uint64_t tell() const { return buf.Size(); }
358 uint64_t count() const { return buf.Size() / sizeof(uint64_t); }
359 Iter iter() { return Iter(buf); }
361 size_t offset(Iter dest) { return dest - iter(); }
363 JSContext* cx;
364 JSStructuredCloneData buf;
367 class SCInput {
368 public:
369 using BufferIterator = js::BufferIterator<uint64_t, SystemAllocPolicy>;
371 SCInput(JSContext* cx, const JSStructuredCloneData& data);
373 JSContext* context() const { return cx; }
375 static void getPtr(uint64_t data, void** ptr);
376 static void getPair(uint64_t data, uint32_t* tagp, uint32_t* datap);
378 [[nodiscard]] bool read(uint64_t* p);
379 [[nodiscard]] bool readPair(uint32_t* tagp, uint32_t* datap);
380 [[nodiscard]] bool readDouble(double* p);
381 [[nodiscard]] bool readBytes(void* p, size_t nbytes);
382 [[nodiscard]] bool readChars(Latin1Char* p, size_t nchars);
383 [[nodiscard]] bool readChars(char16_t* p, size_t nchars);
384 [[nodiscard]] bool readPtr(void**);
386 [[nodiscard]] bool get(uint64_t* p);
387 [[nodiscard]] bool getPair(uint32_t* tagp, uint32_t* datap);
389 const BufferIterator& tell() const { return point; }
390 void seekTo(const BufferIterator& pos) { point = pos; }
391 [[nodiscard]] bool seekBy(size_t pos) {
392 if (!point.advance(pos)) {
393 reportTruncated();
394 return false;
396 return true;
399 template <class T>
400 [[nodiscard]] bool readArray(T* p, size_t nelems);
402 bool reportTruncated() {
403 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
404 JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
405 return false;
408 private:
409 void staticAssertions() {
410 static_assert(sizeof(char16_t) == 2);
411 static_assert(sizeof(uint32_t) == 4);
414 JSContext* cx;
415 BufferIterator point;
418 } // namespace js
420 struct JSStructuredCloneReader {
421 public:
422 explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope,
423 const JS::CloneDataPolicy& cloneDataPolicy,
424 const JSStructuredCloneCallbacks* cb,
425 void* cbClosure);
427 SCInput& input() { return in; }
428 bool read(MutableHandleValue vp, size_t nbytes);
430 private:
431 JSContext* context() { return in.context(); }
433 bool readHeader();
434 bool readTransferMap();
436 [[nodiscard]] bool readUint32(uint32_t* num);
438 enum ShouldAtomizeStrings : bool {
439 DontAtomizeStrings = false,
440 AtomizeStrings = true
443 template <typename CharT>
444 JSString* readStringImpl(uint32_t nchars, ShouldAtomizeStrings atomize);
445 JSString* readString(uint32_t data, ShouldAtomizeStrings atomize);
447 BigInt* readBigInt(uint32_t data);
449 [[nodiscard]] bool readTypedArray(uint32_t arrayType, uint64_t nelems,
450 MutableHandleValue vp, bool v1Read = false);
452 [[nodiscard]] bool readDataView(uint64_t byteLength, MutableHandleValue vp);
454 [[nodiscard]] bool readArrayBuffer(StructuredDataType type, uint32_t data,
455 MutableHandleValue vp);
456 [[nodiscard]] bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
457 MutableHandleValue vp);
459 [[nodiscard]] bool readSharedArrayBuffer(MutableHandleValue vp);
461 [[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes,
462 MutableHandleValue vp);
464 // A serialized SavedFrame contains primitive values in a header followed by
465 // an optional parent frame that is read recursively.
466 [[nodiscard]] JSObject* readSavedFrameHeader(uint32_t principalsTag);
467 [[nodiscard]] bool readSavedFrameFields(Handle<SavedFrame*> frameObj,
468 HandleValue parent, bool* state);
470 // A serialized Error contains primitive values in a header followed by
471 // 'cause', 'errors', and 'stack' fields that are read recursively.
472 [[nodiscard]] JSObject* readErrorHeader(uint32_t type);
473 [[nodiscard]] bool readErrorFields(Handle<ErrorObject*> errorObj,
474 HandleValue cause, bool* state);
476 [[nodiscard]] bool readMapField(Handle<MapObject*> mapObj, HandleValue key);
478 [[nodiscard]] bool readObjectField(HandleObject obj, HandleValue key);
480 [[nodiscard]] bool startRead(
481 MutableHandleValue vp,
482 ShouldAtomizeStrings atomizeStrings = DontAtomizeStrings);
484 SCInput& in;
486 // The widest scope that the caller will accept, where
487 // SameProcess is the widest (it can store anything it wants)
488 // and DifferentProcess is the narrowest (it cannot contain pointers and must
489 // be valid cross-process.)
490 JS::StructuredCloneScope allowedScope;
492 const JS::CloneDataPolicy cloneDataPolicy;
494 // Stack of objects with properties remaining to be read.
495 RootedValueVector objs;
497 // Maintain a stack of state values for the `objs` stack. Since this is only
498 // needed for a very small subset of objects (those with a known set of
499 // object children), the state information is stored as a stack of
500 // <object, state> pairs where the object determines which element of the
501 // `objs` stack that it corresponds to. So when reading from the `objs` stack,
502 // the state will be retrieved only if the top object on `objState` matches
503 // the top object of `objs`.
505 // Currently, the only state needed is a boolean indicating whether the fields
506 // have been read yet.
507 Rooted<GCVector<std::pair<HeapPtr<JSObject*>, bool>, 8>> objState;
509 // Array of all objects read during this deserialization, for resolving
510 // backreferences.
512 // For backreferences to work correctly, objects must be added to this
513 // array in exactly the order expected by the version of the Writer that
514 // created the serialized data, even across years and format versions. This
515 // is usually no problem, since both algorithms do a single linear pass
516 // over the serialized data. There is one hitch; see readTypedArray.
518 // The values in this vector are objects, except it can temporarily have
519 // one `undefined` placeholder value (the readTypedArray hack).
520 RootedValueVector allObjs;
522 size_t numItemsRead;
524 // The user defined callbacks that will be used for cloning.
525 const JSStructuredCloneCallbacks* callbacks;
527 // Any value passed to JS_ReadStructuredClone.
528 void* closure;
530 // The heap to use for allocating common GC things. This starts out as the
531 // nursery (the default) but may switch to the tenured heap if nursery
532 // collection occurs, as nursery allocation is pointless after the
533 // deserialized root object is tenured.
535 // This is only used for the most common kind, e.g. plain objects, strings
536 // and a couple of others.
537 AutoSelectGCHeap gcHeap;
539 friend bool JS_ReadString(JSStructuredCloneReader* r,
540 JS::MutableHandleString str);
541 friend bool JS_ReadTypedArray(JSStructuredCloneReader* r,
542 MutableHandleValue vp);
544 // Provide a way to detect whether any of the clone data is never used. When
545 // "tail" data (currently, this is only stored data for Transferred
546 // ArrayBuffers in the DifferentProcess scope) is read, record the first and
547 // last positions. At the end of deserialization, make sure there's nothing
548 // between the end of the main data and the beginning of the tail, nor after
549 // the end of the tail.
550 mozilla::Maybe<SCInput::BufferIterator> tailStartPos;
551 mozilla::Maybe<SCInput::BufferIterator> tailEndPos;
554 struct JSStructuredCloneWriter {
555 public:
556 explicit JSStructuredCloneWriter(JSContext* cx,
557 JS::StructuredCloneScope scope,
558 const JS::CloneDataPolicy& cloneDataPolicy,
559 const JSStructuredCloneCallbacks* cb,
560 void* cbClosure, const Value& tVal)
561 : out(cx, scope),
562 callbacks(cb),
563 closure(cbClosure),
564 objs(cx),
565 counts(cx),
566 objectEntries(cx),
567 otherEntries(cx),
568 memory(cx),
569 transferable(cx, tVal),
570 transferableObjects(cx, TransferableObjectsList(cx)),
571 cloneDataPolicy(cloneDataPolicy) {
572 out.setCallbacks(cb, cbClosure,
573 OwnTransferablePolicy::OwnsTransferablesIfAny);
576 bool init() {
577 return parseTransferable() && writeHeader() && writeTransferMap();
580 bool write(HandleValue v);
582 SCOutput& output() { return out; }
584 void extractBuffer(JSStructuredCloneData* newData) {
585 out.extractBuffer(newData);
588 private:
589 JSStructuredCloneWriter() = delete;
590 JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
592 JSContext* context() { return out.context(); }
594 bool writeHeader();
595 bool writeTransferMap();
597 bool writeString(uint32_t tag, JSString* str);
598 bool writeBigInt(uint32_t tag, BigInt* bi);
599 bool writeArrayBuffer(HandleObject obj);
600 bool writeTypedArray(HandleObject obj);
601 bool writeDataView(HandleObject obj);
602 bool writeSharedArrayBuffer(HandleObject obj);
603 bool writeSharedWasmMemory(HandleObject obj);
604 bool startObject(HandleObject obj, bool* backref);
605 bool writePrimitive(HandleValue v);
606 bool startWrite(HandleValue v);
607 bool traverseObject(HandleObject obj, ESClass cls);
608 bool traverseMap(HandleObject obj);
609 bool traverseSet(HandleObject obj);
610 bool traverseSavedFrame(HandleObject obj);
611 bool traverseError(HandleObject obj);
613 template <typename... Args>
614 bool reportDataCloneError(uint32_t errorId, Args&&... aArgs);
616 bool parseTransferable();
617 bool transferOwnership();
619 inline void checkStack();
621 SCOutput out;
623 // The user defined callbacks that will be used to signal cloning, in some
624 // cases.
625 const JSStructuredCloneCallbacks* callbacks;
627 // Any value passed to the callbacks.
628 void* closure;
630 // Vector of objects with properties remaining to be written.
632 // NB: These can span multiple compartments, so the compartment must be
633 // entered before any manipulation is performed.
634 RootedValueVector objs;
636 // counts[i] is the number of entries of objs[i] remaining to be written.
637 // counts.length() == objs.length() and sum(counts) == entries.length().
638 Vector<size_t> counts;
640 // For JSObject: Property IDs as value
641 RootedIdVector objectEntries;
643 // For Map: Key followed by value
644 // For Set: Key
645 // For SavedFrame: parent SavedFrame
646 // For Error: cause, errors, stack
647 RootedValueVector otherEntries;
649 // The "memory" list described in the HTML5 internal structured cloning
650 // algorithm. memory is a superset of objs; items are never removed from
651 // Memory until a serialization operation is finished
652 using CloneMemory = GCHashMap<JSObject*, uint32_t,
653 StableCellHasher<JSObject*>, SystemAllocPolicy>;
654 Rooted<CloneMemory> memory;
656 // Set of transferable objects
657 RootedValue transferable;
658 using TransferableObjectsList = GCVector<JSObject*>;
659 Rooted<TransferableObjectsList> transferableObjects;
661 const JS::CloneDataPolicy cloneDataPolicy;
663 friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
664 friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
665 friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj);
668 JS_PUBLIC_API uint64_t js::GetSCOffset(JSStructuredCloneWriter* writer) {
669 MOZ_ASSERT(writer);
670 return writer->output().count() * sizeof(uint64_t);
673 static_assert(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
674 static_assert(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
675 static_assert(Scalar::Int8 == 0);
677 template <typename... Args>
678 static void ReportDataCloneError(JSContext* cx,
679 const JSStructuredCloneCallbacks* callbacks,
680 uint32_t errorId, void* closure,
681 Args&&... aArgs) {
682 unsigned errorNumber;
683 switch (errorId) {
684 case JS_SCERR_DUP_TRANSFERABLE:
685 errorNumber = JSMSG_SC_DUP_TRANSFERABLE;
686 break;
688 case JS_SCERR_TRANSFERABLE:
689 errorNumber = JSMSG_SC_NOT_TRANSFERABLE;
690 break;
692 case JS_SCERR_UNSUPPORTED_TYPE:
693 errorNumber = JSMSG_SC_UNSUPPORTED_TYPE;
694 break;
696 case JS_SCERR_SHMEM_TRANSFERABLE:
697 errorNumber = JSMSG_SC_SHMEM_TRANSFERABLE;
698 break;
700 case JS_SCERR_TYPED_ARRAY_DETACHED:
701 errorNumber = JSMSG_TYPED_ARRAY_DETACHED;
702 break;
704 case JS_SCERR_WASM_NO_TRANSFER:
705 errorNumber = JSMSG_WASM_NO_TRANSFER;
706 break;
708 case JS_SCERR_NOT_CLONABLE:
709 errorNumber = JSMSG_SC_NOT_CLONABLE;
710 break;
712 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP:
713 errorNumber = JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP;
714 break;
716 default:
717 MOZ_CRASH("Unkown errorId");
718 break;
721 if (callbacks && callbacks->reportError) {
722 MOZ_RELEASE_ASSERT(!cx->isExceptionPending());
724 JSErrorReport report;
725 report.errorNumber = errorNumber;
726 // Get js error message if it's possible and propagate it through callback.
727 if (JS_ExpandErrorArgumentsASCII(cx, GetErrorMessage, errorNumber, &report,
728 std::forward<Args>(aArgs)...) &&
729 report.message()) {
730 callbacks->reportError(cx, errorId, closure, report.message().c_str());
731 } else {
732 ReportOutOfMemory(cx);
734 callbacks->reportError(cx, errorId, closure, "");
737 return;
740 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber,
741 std::forward<Args>(aArgs)...);
744 bool WriteStructuredClone(JSContext* cx, HandleValue v,
745 JSStructuredCloneData* bufp,
746 JS::StructuredCloneScope scope,
747 const JS::CloneDataPolicy& cloneDataPolicy,
748 const JSStructuredCloneCallbacks* cb, void* cbClosure,
749 const Value& transferable) {
750 JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure,
751 transferable);
752 if (!w.init()) {
753 return false;
755 if (!w.write(v)) {
756 return false;
758 w.extractBuffer(bufp);
759 return true;
762 bool ReadStructuredClone(JSContext* cx, const JSStructuredCloneData& data,
763 JS::StructuredCloneScope scope, MutableHandleValue vp,
764 const JS::CloneDataPolicy& cloneDataPolicy,
765 const JSStructuredCloneCallbacks* cb,
766 void* cbClosure) {
767 if (data.Size() % 8) {
768 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
769 JSMSG_SC_BAD_SERIALIZED_DATA, "misaligned");
770 return false;
772 SCInput in(cx, data);
773 JSStructuredCloneReader r(in, scope, cloneDataPolicy, cb, cbClosure);
774 return r.read(vp, data.Size());
777 static bool StructuredCloneHasTransferObjects(
778 const JSStructuredCloneData& data) {
779 if (data.Size() < sizeof(uint64_t)) {
780 return false;
783 uint64_t u;
784 BufferIterator<uint64_t, SystemAllocPolicy> iter(data);
785 MOZ_ALWAYS_TRUE(iter.readBytes(reinterpret_cast<char*>(&u), sizeof(u)));
786 uint32_t tag = uint32_t(u >> 32);
787 return (tag == SCTAG_TRANSFER_MAP_HEADER);
790 namespace js {
792 SCInput::SCInput(JSContext* cx, const JSStructuredCloneData& data)
793 : cx(cx), point(data) {
794 static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment % 8 == 0,
795 "structured clone buffer reads should be aligned");
796 MOZ_ASSERT(data.Size() % 8 == 0);
799 bool SCInput::read(uint64_t* p) {
800 if (!point.canPeek()) {
801 *p = 0; // initialize to shut GCC up
802 return reportTruncated();
804 *p = NativeEndian::swapFromLittleEndian(point.peek());
805 MOZ_ALWAYS_TRUE(point.advance());
806 return true;
809 bool SCInput::readPair(uint32_t* tagp, uint32_t* datap) {
810 uint64_t u;
811 bool ok = read(&u);
812 if (ok) {
813 *tagp = uint32_t(u >> 32);
814 *datap = uint32_t(u);
816 return ok;
819 bool SCInput::get(uint64_t* p) {
820 if (!point.canPeek()) {
821 return reportTruncated();
823 *p = NativeEndian::swapFromLittleEndian(point.peek());
824 return true;
827 bool SCInput::getPair(uint32_t* tagp, uint32_t* datap) {
828 uint64_t u = 0;
829 if (!get(&u)) {
830 return false;
833 *tagp = uint32_t(u >> 32);
834 *datap = uint32_t(u);
835 return true;
838 void SCInput::getPair(uint64_t data, uint32_t* tagp, uint32_t* datap) {
839 uint64_t u = NativeEndian::swapFromLittleEndian(data);
840 *tagp = uint32_t(u >> 32);
841 *datap = uint32_t(u);
844 bool SCInput::readDouble(double* p) {
845 uint64_t u;
846 if (!read(&u)) {
847 return false;
849 *p = CanonicalizeNaN(mozilla::BitwiseCast<double>(u));
850 return true;
853 template <typename T>
854 static void swapFromLittleEndianInPlace(T* ptr, size_t nelems) {
855 if (nelems > 0) {
856 NativeEndian::swapFromLittleEndianInPlace(ptr, nelems);
860 template <>
861 void swapFromLittleEndianInPlace(uint8_t* ptr, size_t nelems) {}
863 // Data is packed into an integral number of uint64_t words. Compute the
864 // padding required to finish off the final word.
865 static size_t ComputePadding(size_t nelems, size_t elemSize) {
866 // We want total length mod 8, where total length is nelems * sizeof(T),
867 // but that might overflow. So reduce nelems to nelems mod 8, since we are
868 // going to be doing a mod 8 later anyway.
869 size_t leftoverLength = (nelems % sizeof(uint64_t)) * elemSize;
870 return (-leftoverLength) & (sizeof(uint64_t) - 1);
873 template <class T>
874 bool SCInput::readArray(T* p, size_t nelems) {
875 if (!nelems) {
876 return true;
879 static_assert(sizeof(uint64_t) % sizeof(T) == 0);
881 // Fail if nelems is so huge that computing the full size will overflow.
882 mozilla::CheckedInt<size_t> size =
883 mozilla::CheckedInt<size_t>(nelems) * sizeof(T);
884 if (!size.isValid()) {
885 return reportTruncated();
888 if (!point.readBytes(reinterpret_cast<char*>(p), size.value())) {
889 // To avoid any way in which uninitialized data could escape, zero the array
890 // if filling it failed.
891 std::uninitialized_fill_n(p, nelems, 0);
892 return false;
895 swapFromLittleEndianInPlace(p, nelems);
897 point += ComputePadding(nelems, sizeof(T));
899 return true;
902 bool SCInput::readBytes(void* p, size_t nbytes) {
903 return readArray((uint8_t*)p, nbytes);
906 bool SCInput::readChars(Latin1Char* p, size_t nchars) {
907 static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
908 "Latin1Char must fit in 1 byte");
909 return readBytes(p, nchars);
912 bool SCInput::readChars(char16_t* p, size_t nchars) {
913 MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
914 return readArray((uint16_t*)p, nchars);
917 void SCInput::getPtr(uint64_t data, void** ptr) {
918 *ptr = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data));
921 bool SCInput::readPtr(void** p) {
922 uint64_t u;
923 if (!read(&u)) {
924 return false;
926 *p = reinterpret_cast<void*>(u);
927 return true;
930 SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope)
931 : cx(cx), buf(scope) {}
933 bool SCOutput::write(uint64_t u) {
934 uint64_t v = NativeEndian::swapToLittleEndian(u);
935 if (!buf.AppendBytes(reinterpret_cast<char*>(&v), sizeof(u))) {
936 ReportOutOfMemory(context());
937 return false;
939 return true;
942 bool SCOutput::writePair(uint32_t tag, uint32_t data) {
943 // As it happens, the tag word appears after the data word in the output.
944 // This is because exponents occupy the last 2 bytes of doubles on the
945 // little-endian platforms we care most about.
947 // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
948 // PairToUInt64 produces the number 0xFFFF000200000001.
949 // That is written out as the bytes 01 00 00 00 02 00 FF FF.
950 return write(PairToUInt64(tag, data));
953 static inline double ReinterpretPairAsDouble(uint32_t tag, uint32_t data) {
954 return BitwiseCast<double>(PairToUInt64(tag, data));
957 bool SCOutput::writeDouble(double d) {
958 return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
961 template <class T>
962 bool SCOutput::writeArray(const T* p, size_t nelems) {
963 static_assert(8 % sizeof(T) == 0);
964 static_assert(sizeof(uint64_t) % sizeof(T) == 0);
966 if (nelems == 0) {
967 return true;
970 for (size_t i = 0; i < nelems; i++) {
971 T value = NativeEndian::swapToLittleEndian(p[i]);
972 if (!buf.AppendBytes(reinterpret_cast<char*>(&value), sizeof(value))) {
973 return false;
977 // Zero-pad to 8 bytes boundary.
978 size_t padbytes = ComputePadding(nelems, sizeof(T));
979 char zeroes[sizeof(uint64_t)] = {0};
980 if (!buf.AppendBytes(zeroes, padbytes)) {
981 return false;
984 return true;
987 template <>
988 bool SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems) {
989 if (nelems == 0) {
990 return true;
993 if (!buf.AppendBytes(reinterpret_cast<const char*>(p), nelems)) {
994 return false;
997 // zero-pad to 8 bytes boundary
998 size_t padbytes = ComputePadding(nelems, 1);
999 char zeroes[sizeof(uint64_t)] = {0};
1000 if (!buf.AppendBytes(zeroes, padbytes)) {
1001 return false;
1004 return true;
1007 bool SCOutput::writeBytes(const void* p, size_t nbytes) {
1008 return writeArray((const uint8_t*)p, nbytes);
1011 bool SCOutput::writeChars(const char16_t* p, size_t nchars) {
1012 static_assert(sizeof(char16_t) == sizeof(uint16_t),
1013 "required so that treating char16_t[] memory as uint16_t[] "
1014 "memory is permissible");
1015 return writeArray((const uint16_t*)p, nchars);
1018 bool SCOutput::writeChars(const Latin1Char* p, size_t nchars) {
1019 static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
1020 "Latin1Char must fit in 1 byte");
1021 return writeBytes(p, nchars);
1024 } // namespace js
1026 JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); }
1028 // If the buffer contains Transferables, free them. Note that custom
1029 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
1030 // delete their transferables.
1031 void JSStructuredCloneData::discardTransferables() {
1032 if (!Size()) {
1033 return;
1036 if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) {
1037 return;
1040 // DifferentProcess clones cannot contain pointers, so nothing needs to be
1041 // released.
1042 if (scope() == JS::StructuredCloneScope::DifferentProcess) {
1043 return;
1046 FreeTransferStructuredCloneOp freeTransfer = nullptr;
1047 if (callbacks_) {
1048 freeTransfer = callbacks_->freeTransfer;
1051 auto point = BufferIterator<uint64_t, SystemAllocPolicy>(*this);
1052 if (point.done()) {
1053 return; // Empty buffer
1056 uint32_t tag, data;
1057 MOZ_RELEASE_ASSERT(point.canPeek());
1058 SCInput::getPair(point.peek(), &tag, &data);
1059 MOZ_ALWAYS_TRUE(point.advance());
1061 if (tag == SCTAG_HEADER) {
1062 if (point.done()) {
1063 return;
1066 MOZ_RELEASE_ASSERT(point.canPeek());
1067 SCInput::getPair(point.peek(), &tag, &data);
1068 MOZ_ALWAYS_TRUE(point.advance());
1071 if (tag != SCTAG_TRANSFER_MAP_HEADER) {
1072 return;
1075 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
1076 return;
1079 // freeTransfer should not GC
1080 JS::AutoSuppressGCAnalysis nogc;
1082 if (point.done()) {
1083 return;
1086 MOZ_RELEASE_ASSERT(point.canPeek());
1087 uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek());
1088 MOZ_ALWAYS_TRUE(point.advance());
1089 while (numTransferables--) {
1090 if (!point.canPeek()) {
1091 return;
1094 uint32_t ownership;
1095 SCInput::getPair(point.peek(), &tag, &ownership);
1096 MOZ_ALWAYS_TRUE(point.advance());
1097 MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1098 if (!point.canPeek()) {
1099 return;
1102 void* content;
1103 SCInput::getPtr(point.peek(), &content);
1104 MOZ_ALWAYS_TRUE(point.advance());
1105 if (!point.canPeek()) {
1106 return;
1109 uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek());
1110 MOZ_ALWAYS_TRUE(point.advance());
1112 if (ownership < JS::SCTAG_TMO_FIRST_OWNED) {
1113 continue;
1116 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
1117 js_free(content);
1118 } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
1119 JS::ReleaseMappedArrayBufferContents(content, extraData);
1120 } else if (freeTransfer) {
1121 freeTransfer(tag, JS::TransferableOwnership(ownership), content,
1122 extraData, closure_);
1123 } else {
1124 MOZ_ASSERT(false, "unknown ownership");
1129 static_assert(JSString::MAX_LENGTH < UINT32_MAX);
1131 bool JSStructuredCloneWriter::parseTransferable() {
1132 // NOTE: The transferables set is tested for non-emptiness at various
1133 // junctures in structured cloning, so this set must be initialized
1134 // by this method in all non-error cases.
1135 MOZ_ASSERT(transferableObjects.empty(),
1136 "parseTransferable called with stale data");
1138 if (transferable.isNull() || transferable.isUndefined()) {
1139 return true;
1142 if (!transferable.isObject()) {
1143 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1146 JSContext* cx = context();
1147 RootedObject array(cx, &transferable.toObject());
1148 bool isArray;
1149 if (!JS::IsArrayObject(cx, array, &isArray)) {
1150 return false;
1152 if (!isArray) {
1153 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1156 uint32_t length;
1157 if (!JS::GetArrayLength(cx, array, &length)) {
1158 return false;
1161 // Initialize the set for the provided array's length.
1162 if (!transferableObjects.reserve(length)) {
1163 return false;
1166 if (length == 0) {
1167 return true;
1170 RootedValue v(context());
1171 RootedObject tObj(context());
1173 for (uint32_t i = 0; i < length; ++i) {
1174 if (!CheckForInterrupt(cx)) {
1175 return false;
1178 if (!JS_GetElement(cx, array, i, &v)) {
1179 return false;
1182 if (!v.isObject()) {
1183 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1185 tObj = &v.toObject();
1187 RootedObject unwrappedObj(cx, CheckedUnwrapStatic(tObj));
1188 if (!unwrappedObj) {
1189 ReportAccessDenied(cx);
1190 return false;
1193 // Shared memory cannot be transferred because it is not possible (nor
1194 // desirable) to detach the memory in agents that already hold a
1195 // reference to it.
1197 if (unwrappedObj->is<SharedArrayBufferObject>()) {
1198 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
1201 else if (unwrappedObj->is<WasmMemoryObject>()) {
1202 if (unwrappedObj->as<WasmMemoryObject>().isShared()) {
1203 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
1207 // External array buffers may be able to be transferred in the future,
1208 // but that is not currently implemented.
1210 else if (unwrappedObj->is<ArrayBufferObject>()) {
1211 if (unwrappedObj->as<ArrayBufferObject>().isExternal()) {
1212 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1216 else {
1217 if (!out.buf.callbacks_ || !out.buf.callbacks_->canTransfer) {
1218 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1221 JSAutoRealm ar(cx, unwrappedObj);
1222 bool sameProcessScopeRequired = false;
1223 if (!out.buf.callbacks_->canTransfer(
1224 cx, unwrappedObj, &sameProcessScopeRequired, out.buf.closure_)) {
1225 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1228 if (sameProcessScopeRequired) {
1229 output().sameProcessScopeRequired();
1233 // No duplicates allowed
1234 if (std::find(transferableObjects.begin(), transferableObjects.end(),
1235 tObj) != transferableObjects.end()) {
1236 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
1239 if (!transferableObjects.append(tObj)) {
1240 return false;
1244 return true;
1247 template <typename... Args>
1248 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId,
1249 Args&&... aArgs) {
1250 ReportDataCloneError(context(), out.buf.callbacks_, errorId, out.buf.closure_,
1251 std::forward<Args>(aArgs)...);
1252 return false;
1255 bool JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) {
1256 JSLinearString* linear = str->ensureLinear(context());
1257 if (!linear) {
1258 return false;
1261 #if FUZZING_JS_FUZZILLI
1262 if (js::SupportDifferentialTesting()) {
1263 // TODO we could always output a twoByteChar string
1264 return true;
1266 #endif
1268 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
1269 "String length must fit in 31 bits");
1271 uint32_t length = linear->length();
1272 uint32_t lengthAndEncoding =
1273 length | (uint32_t(linear->hasLatin1Chars()) << 31);
1274 if (!out.writePair(tag, lengthAndEncoding)) {
1275 return false;
1278 JS::AutoCheckCannotGC nogc;
1279 return linear->hasLatin1Chars()
1280 ? out.writeChars(linear->latin1Chars(nogc), length)
1281 : out.writeChars(linear->twoByteChars(nogc), length);
1284 bool JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) {
1285 bool signBit = bi->isNegative();
1286 size_t length = bi->digitLength();
1287 // The length must fit in 31 bits to leave room for a sign bit.
1288 if (length > size_t(INT32_MAX)) {
1289 return false;
1291 uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31);
1293 if (!out.writePair(tag, lengthAndSign)) {
1294 return false;
1296 return out.writeArray(bi->digits().data(), length);
1299 inline void JSStructuredCloneWriter::checkStack() {
1300 #ifdef DEBUG
1301 // To avoid making serialization O(n^2), limit stack-checking at 10.
1302 const size_t MAX = 10;
1304 size_t limit = std::min(counts.length(), MAX);
1305 MOZ_ASSERT(objs.length() == counts.length());
1306 size_t total = 0;
1307 for (size_t i = 0; i < limit; i++) {
1308 MOZ_ASSERT(total + counts[i] >= total);
1309 total += counts[i];
1311 if (counts.length() <= MAX) {
1312 MOZ_ASSERT(total == objectEntries.length() + otherEntries.length());
1313 } else {
1314 MOZ_ASSERT(total <= objectEntries.length() + otherEntries.length());
1317 size_t j = objs.length();
1318 for (size_t i = 0; i < limit; i++) {
1319 --j;
1320 MOZ_ASSERT(memory.has(&objs[j].toObject()));
1322 #endif
1326 * Write out a typed array. Note that post-v1 structured clone buffers do not
1327 * perform endianness conversion on stored data, so multibyte typed arrays
1328 * cannot be deserialized into a different endianness machine. Endianness
1329 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
1330 * Int16Array views of the same ArrayBuffer, should the data bytes be
1331 * byte-swapped when writing or not? The Int8Array requires them to not be
1332 * swapped; the Int16Array requires that they are.
1334 bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj) {
1335 Rooted<TypedArrayObject*> tarr(context(),
1336 obj->maybeUnwrapAs<TypedArrayObject>());
1337 JSAutoRealm ar(context(), tarr);
1339 #ifdef FUZZING_JS_FUZZILLI
1340 if (js::SupportDifferentialTesting() && !tarr->hasBuffer()) {
1341 // fake oom because differential testing will fail
1342 fprintf(stderr, "[unhandlable oom]");
1343 _exit(-1);
1344 return false;
1346 #endif
1348 if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) {
1349 return false;
1352 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) {
1353 return false;
1356 uint64_t nelems = tarr->length();
1357 if (!out.write(nelems)) {
1358 return false;
1361 // Write out the ArrayBuffer tag and contents
1362 RootedValue val(context(), tarr->bufferValue());
1363 if (!startWrite(val)) {
1364 return false;
1367 uint64_t byteOffset = tarr->byteOffset();
1368 return out.write(byteOffset);
1371 bool JSStructuredCloneWriter::writeDataView(HandleObject obj) {
1372 Rooted<DataViewObject*> view(context(), obj->maybeUnwrapAs<DataViewObject>());
1373 JSAutoRealm ar(context(), view);
1375 if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, 0)) {
1376 return false;
1379 uint64_t byteLength = view->byteLength();
1380 if (!out.write(byteLength)) {
1381 return false;
1384 // Write out the ArrayBuffer tag and contents
1385 RootedValue val(context(), view->bufferValue());
1386 if (!startWrite(val)) {
1387 return false;
1390 uint64_t byteOffset = view->byteOffset();
1391 return out.write(byteOffset);
1394 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) {
1395 Rooted<ArrayBufferObject*> buffer(context(),
1396 obj->maybeUnwrapAs<ArrayBufferObject>());
1397 JSAutoRealm ar(context(), buffer);
1399 if (!out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, 0)) {
1400 return false;
1403 uint64_t byteLength = buffer->byteLength();
1404 if (!out.write(byteLength)) {
1405 return false;
1408 return out.writeBytes(buffer->dataPointer(), byteLength);
1411 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj) {
1412 MOZ_ASSERT(obj->canUnwrapAs<SharedArrayBufferObject>());
1414 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
1415 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
1416 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1417 : JS_SCERR_NOT_CLONABLE;
1418 reportDataCloneError(error, "SharedArrayBuffer");
1419 return false;
1422 output().sameProcessScopeRequired();
1424 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1425 // cross-process. The cloneDataPolicy should have guarded against this;
1426 // since it did not then throw, with a very explicit message.
1428 if (output().scope() > JS::StructuredCloneScope::SameProcess) {
1429 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1430 JSMSG_SC_SHMEM_POLICY);
1431 return false;
1434 Rooted<SharedArrayBufferObject*> sharedArrayBuffer(
1435 context(), obj->maybeUnwrapAs<SharedArrayBufferObject>());
1436 SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
1438 if (!out.buf.refsHeld_.acquire(context(), rawbuf)) {
1439 return false;
1442 // We must serialize the length so that the buffer object arrives in the
1443 // receiver with the same length, and not with the length read from the
1444 // rawbuf - that length can be different, and it can change at any time.
1446 intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
1447 uint64_t byteLength = sharedArrayBuffer->byteLength();
1448 if (!(out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
1449 static_cast<uint32_t>(sizeof(p))) &&
1450 out.writeBytes(&byteLength, sizeof(byteLength)) &&
1451 out.writeBytes(&p, sizeof(p)))) {
1452 return false;
1455 if (callbacks && callbacks->sabCloned &&
1456 !callbacks->sabCloned(context(), /*receiving=*/false, closure)) {
1457 return false;
1460 return true;
1463 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj) {
1464 MOZ_ASSERT(obj->canUnwrapAs<WasmMemoryObject>());
1466 // Check the policy here so that we can report a sane error.
1467 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
1468 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
1469 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1470 : JS_SCERR_NOT_CLONABLE;
1471 reportDataCloneError(error, "WebAssembly.Memory");
1472 return false;
1475 // If this changes, might need to change what we write.
1476 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS == 3);
1478 Rooted<WasmMemoryObject*> memoryObj(context(),
1479 &obj->unwrapAs<WasmMemoryObject>());
1480 Rooted<SharedArrayBufferObject*> sab(
1481 context(), &memoryObj->buffer().as<SharedArrayBufferObject>());
1483 return out.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT, 0) &&
1484 out.writePair(SCTAG_BOOLEAN, memoryObj->isHuge()) &&
1485 writeSharedArrayBuffer(sab);
1488 bool JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref) {
1489 // Handle cycles in the object graph.
1490 CloneMemory::AddPtr p = memory.lookupForAdd(obj);
1491 if ((*backref = p.found())) {
1492 return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
1494 if (!memory.add(p, obj, memory.count())) {
1495 ReportOutOfMemory(context());
1496 return false;
1499 if (memory.count() == UINT32_MAX) {
1500 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1501 JSMSG_NEED_DIET, "object graph to serialize");
1502 return false;
1505 return true;
1508 static bool TryAppendNativeProperties(JSContext* cx, HandleObject obj,
1509 MutableHandleIdVector entries,
1510 size_t* properties, bool* optimized) {
1511 *optimized = false;
1513 if (!obj->is<NativeObject>()) {
1514 return true;
1517 Handle<NativeObject*> nobj = obj.as<NativeObject>();
1518 if (nobj->isIndexed() || nobj->is<TypedArrayObject>() ||
1519 nobj->getClass()->getNewEnumerate() || nobj->getClass()->getEnumerate()) {
1520 return true;
1523 *optimized = true;
1525 size_t count = 0;
1526 // We iterate from the last to the first property, so the property names
1527 // are already in reverse order.
1528 for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
1529 jsid id = iter->key();
1531 // Ignore symbols and non-enumerable properties.
1532 if (!iter->enumerable() || id.isSymbol()) {
1533 continue;
1536 MOZ_ASSERT(id.isString());
1537 if (!entries.append(id)) {
1538 return false;
1541 count++;
1544 // Add dense element ids in reverse order.
1545 for (uint32_t i = nobj->getDenseInitializedLength(); i > 0; --i) {
1546 if (nobj->getDenseElement(i - 1).isMagic(JS_ELEMENTS_HOLE)) {
1547 continue;
1550 if (!entries.append(PropertyKey::Int(i - 1))) {
1551 return false;
1554 count++;
1557 *properties = count;
1558 return true;
1561 // Objects are written as a "preorder" traversal of the object graph: object
1562 // "headers" (the class tag and any data needed for initial construction) are
1563 // visited first, then the children are recursed through (where children are
1564 // properties, Set or Map entries, etc.). So for example
1566 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1568 // would be stored as:
1570 // <Object tag for obj1>
1571 // <key1 data>
1572 // <Object tag for key1's value>
1573 // <key1.1 data>
1574 // <val1.1 data>
1575 // <key1.2 data>
1576 // <val1.2 data>
1577 // <end-of-children marker for key1's value>
1578 // <key2 data>
1579 // <Object tag for key2's value>
1580 // <end-of-children marker for key2's value>
1581 // <end-of-children marker for obj1>
1583 // This nests nicely (ie, an entire recursive value starts with its tag and
1584 // ends with its end-of-children marker) and so it can be presented indented.
1585 // But see traverseMap below for how this looks different for Maps.
1586 bool JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls) {
1587 size_t count;
1588 bool optimized = false;
1589 if (!js::SupportDifferentialTesting()) {
1590 if (!TryAppendNativeProperties(context(), obj, &objectEntries, &count,
1591 &optimized)) {
1592 return false;
1596 if (!optimized) {
1597 // Get enumerable property ids and put them in reverse order so that they
1598 // will come off the stack in forward order.
1599 RootedIdVector properties(context());
1600 if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties)) {
1601 return false;
1604 for (size_t i = properties.length(); i > 0; --i) {
1605 jsid id = properties[i - 1];
1607 MOZ_ASSERT(id.isString() || id.isInt());
1608 if (!objectEntries.append(id)) {
1609 return false;
1613 count = properties.length();
1616 // Push obj and count to the stack.
1617 if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) {
1618 return false;
1621 checkStack();
1623 #if DEBUG
1624 ESClass cls2;
1625 if (!GetBuiltinClass(context(), obj, &cls2)) {
1626 return false;
1628 MOZ_ASSERT(cls2 == cls);
1629 #endif
1631 // Write the header for obj.
1632 if (cls == ESClass::Array) {
1633 uint32_t length = 0;
1634 if (!JS::GetArrayLength(context(), obj, &length)) {
1635 return false;
1638 return out.writePair(SCTAG_ARRAY_OBJECT,
1639 NativeEndian::swapToLittleEndian(length));
1642 return out.writePair(SCTAG_OBJECT_OBJECT, 0);
1645 // Use the same basic setup as for traverseObject, but now keys can themselves
1646 // be complex objects. Keys and values are visited first via startWrite(), then
1647 // the key's children (if any) are handled, then the value's children.
1649 // m = new Map();
1650 // m.set(key1 = ..., value1 = ...)
1652 // where key1 and value2 are both objects would be stored as
1654 // <Map tag>
1655 // <key1 class tag>
1656 // <value1 class tag>
1657 // ...key1 fields...
1658 // <end-of-children marker for key1>
1659 // ...value1 fields...
1660 // <end-of-children marker for value1>
1661 // <end-of-children marker for Map>
1663 // Notice how the end-of-children marker for key1 is sandwiched between the
1664 // value1 beginning and end.
1665 bool JSStructuredCloneWriter::traverseMap(HandleObject obj) {
1666 Rooted<GCVector<Value>> newEntries(context(), GCVector<Value>(context()));
1668 // If there is no wrapper, the compartment munging is a no-op.
1669 RootedObject unwrapped(context(), obj->maybeUnwrapAs<MapObject>());
1670 MOZ_ASSERT(unwrapped);
1671 JSAutoRealm ar(context(), unwrapped);
1672 if (!MapObject::getKeysAndValuesInterleaved(unwrapped, &newEntries)) {
1673 return false;
1676 if (!context()->compartment()->wrap(context(), &newEntries)) {
1677 return false;
1680 for (size_t i = newEntries.length(); i > 0; --i) {
1681 if (!otherEntries.append(newEntries[i - 1])) {
1682 return false;
1686 // Push obj and count to the stack.
1687 if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length())) {
1688 return false;
1691 checkStack();
1693 // Write the header for obj.
1694 return out.writePair(SCTAG_MAP_OBJECT, 0);
1697 // Similar to traverseMap, only there is a single value instead of a key and
1698 // value, and thus no interleaving is possible: a value will be fully emitted
1699 // before the next value is begun.
1700 bool JSStructuredCloneWriter::traverseSet(HandleObject obj) {
1701 Rooted<GCVector<Value>> keys(context(), GCVector<Value>(context()));
1703 // If there is no wrapper, the compartment munging is a no-op.
1704 RootedObject unwrapped(context(), obj->maybeUnwrapAs<SetObject>());
1705 MOZ_ASSERT(unwrapped);
1706 JSAutoRealm ar(context(), unwrapped);
1707 if (!SetObject::keys(context(), unwrapped, &keys)) {
1708 return false;
1711 if (!context()->compartment()->wrap(context(), &keys)) {
1712 return false;
1715 for (size_t i = keys.length(); i > 0; --i) {
1716 if (!otherEntries.append(keys[i - 1])) {
1717 return false;
1721 // Push obj and count to the stack.
1722 if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length())) {
1723 return false;
1726 checkStack();
1728 // Write the header for obj.
1729 return out.writePair(SCTAG_SET_OBJECT, 0);
1732 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj) {
1733 Rooted<SavedFrame*> savedFrame(context(), obj->maybeUnwrapAs<SavedFrame>());
1734 MOZ_ASSERT(savedFrame);
1736 RootedObject parent(context(), savedFrame->getParent());
1737 if (!context()->compartment()->wrap(context(), &parent)) {
1738 return false;
1741 if (!objs.append(ObjectValue(*obj)) ||
1742 !otherEntries.append(parent ? ObjectValue(*parent) : NullValue()) ||
1743 !counts.append(1)) {
1744 return false;
1747 checkStack();
1749 // Write the SavedFrame tag and the SavedFrame's principals.
1751 if (savedFrame->getPrincipals() ==
1752 &ReconstructedSavedFramePrincipals::IsSystem) {
1753 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1754 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM)) {
1755 return false;
1757 } else if (savedFrame->getPrincipals() ==
1758 &ReconstructedSavedFramePrincipals::IsNotSystem) {
1759 if (!out.writePair(
1760 SCTAG_SAVED_FRAME_OBJECT,
1761 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM)) {
1762 return false;
1764 } else {
1765 if (auto principals = savedFrame->getPrincipals()) {
1766 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
1767 !principals->write(context(), this)) {
1768 return false;
1770 } else {
1771 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS)) {
1772 return false;
1777 // Write the SavedFrame's reserved slots, except for the parent, which is
1778 // queued on objs for further traversal.
1780 RootedValue val(context());
1782 val = BooleanValue(savedFrame->getMutedErrors());
1783 if (!writePrimitive(val)) {
1784 return false;
1787 context()->markAtom(savedFrame->getSource());
1788 val = StringValue(savedFrame->getSource());
1789 if (!writePrimitive(val)) {
1790 return false;
1793 val = NumberValue(savedFrame->getLine());
1794 if (!writePrimitive(val)) {
1795 return false;
1798 val = NumberValue(*savedFrame->getColumn().addressOfValueForTranscode());
1799 if (!writePrimitive(val)) {
1800 return false;
1803 auto name = savedFrame->getFunctionDisplayName();
1804 if (name) {
1805 context()->markAtom(name);
1807 val = name ? StringValue(name) : NullValue();
1808 if (!writePrimitive(val)) {
1809 return false;
1812 auto cause = savedFrame->getAsyncCause();
1813 if (cause) {
1814 context()->markAtom(cause);
1816 val = cause ? StringValue(cause) : NullValue();
1817 if (!writePrimitive(val)) {
1818 return false;
1821 return true;
1824 // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
1825 // 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
1827 // Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
1828 // value is not a platform object, then:
1830 // Note: This contains custom extensions for handling non-standard properties.
1831 bool JSStructuredCloneWriter::traverseError(HandleObject obj) {
1832 JSContext* cx = context();
1834 // 1. Let name be ? Get(value, "name").
1835 RootedValue name(cx);
1836 if (!GetProperty(cx, obj, obj, cx->names().name, &name)) {
1837 return false;
1840 // 2. If name is not one of "Error", "EvalError", "RangeError",
1841 // "ReferenceError", "SyntaxError", "TypeError", or "URIError",
1842 // (not yet specified: or "AggregateError")
1843 // then set name to "Error".
1844 JSExnType type = JSEXN_ERR;
1845 if (name.isString()) {
1846 JSLinearString* linear = name.toString()->ensureLinear(cx);
1847 if (!linear) {
1848 return false;
1851 if (EqualStrings(linear, cx->names().Error)) {
1852 type = JSEXN_ERR;
1853 } else if (EqualStrings(linear, cx->names().EvalError)) {
1854 type = JSEXN_EVALERR;
1855 } else if (EqualStrings(linear, cx->names().RangeError)) {
1856 type = JSEXN_RANGEERR;
1857 } else if (EqualStrings(linear, cx->names().ReferenceError)) {
1858 type = JSEXN_REFERENCEERR;
1859 } else if (EqualStrings(linear, cx->names().SyntaxError)) {
1860 type = JSEXN_SYNTAXERR;
1861 } else if (EqualStrings(linear, cx->names().TypeError)) {
1862 type = JSEXN_TYPEERR;
1863 } else if (EqualStrings(linear, cx->names().URIError)) {
1864 type = JSEXN_URIERR;
1865 } else if (EqualStrings(linear, cx->names().AggregateError)) {
1866 type = JSEXN_AGGREGATEERR;
1870 // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
1871 RootedId messageId(cx, NameToId(cx->names().message));
1872 Rooted<Maybe<PropertyDescriptor>> messageDesc(cx);
1873 if (!GetOwnPropertyDescriptor(cx, obj, messageId, &messageDesc)) {
1874 return false;
1877 // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
1878 // and ? ToString(valueMessageDesc.[[Value]]) otherwise.
1879 RootedString message(cx);
1880 if (messageDesc.isSome() && messageDesc->isDataDescriptor()) {
1881 RootedValue messageVal(cx, messageDesc->value());
1882 message = ToString<CanGC>(cx, messageVal);
1883 if (!message) {
1884 return false;
1888 // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
1889 // message }.
1891 if (!objs.append(ObjectValue(*obj))) {
1892 return false;
1895 Rooted<ErrorObject*> unwrapped(cx, obj->maybeUnwrapAs<ErrorObject>());
1896 MOZ_ASSERT(unwrapped);
1898 // Non-standard: Serialize |stack|.
1899 // The Error stack property is saved as SavedFrames.
1900 RootedValue stack(cx, NullValue());
1901 RootedObject stackObj(cx, unwrapped->stack());
1902 if (stackObj && stackObj->canUnwrapAs<SavedFrame>()) {
1903 stack.setObject(*stackObj);
1904 if (!cx->compartment()->wrap(cx, &stack)) {
1905 return false;
1908 if (!otherEntries.append(stack)) {
1909 return false;
1912 // Serialize |errors|
1913 if (type == JSEXN_AGGREGATEERR) {
1914 RootedValue errors(cx);
1915 if (!GetProperty(cx, obj, obj, cx->names().errors, &errors)) {
1916 return false;
1918 if (!otherEntries.append(errors)) {
1919 return false;
1921 } else {
1922 if (!otherEntries.append(NullValue())) {
1923 return false;
1927 // Non-standard: Serialize |cause|. Because this property
1928 // might be missing we also write "hasCause" later.
1929 RootedId causeId(cx, NameToId(cx->names().cause));
1930 Rooted<Maybe<PropertyDescriptor>> causeDesc(cx);
1931 if (!GetOwnPropertyDescriptor(cx, obj, causeId, &causeDesc)) {
1932 return false;
1935 Rooted<Maybe<Value>> cause(cx);
1936 if (causeDesc.isSome() && causeDesc->isDataDescriptor()) {
1937 cause = mozilla::Some(causeDesc->value());
1939 if (!cx->compartment()->wrap(cx, &cause)) {
1940 return false;
1942 if (!otherEntries.append(cause.get().valueOr(NullValue()))) {
1943 return false;
1946 // |cause| + |errors| + |stack|, pushed in reverse order
1947 if (!counts.append(3)) {
1948 return false;
1951 checkStack();
1953 if (!out.writePair(SCTAG_ERROR_OBJECT, type)) {
1954 return false;
1957 RootedValue val(cx, message ? StringValue(message) : NullValue());
1958 if (!writePrimitive(val)) {
1959 return false;
1962 // hasCause
1963 val = BooleanValue(cause.isSome());
1964 if (!writePrimitive(val)) {
1965 return false;
1968 // Non-standard: Also serialize fileName, lineNumber and columnNumber.
1970 JSAutoRealm ar(cx, unwrapped);
1971 val = StringValue(unwrapped->fileName(cx));
1973 if (!cx->compartment()->wrap(cx, &val) || !writePrimitive(val)) {
1974 return false;
1977 val = Int32Value(unwrapped->lineNumber());
1978 if (!writePrimitive(val)) {
1979 return false;
1982 val = Int32Value(*unwrapped->columnNumber().addressOfValueForTranscode());
1983 return writePrimitive(val);
1986 bool JSStructuredCloneWriter::writePrimitive(HandleValue v) {
1987 MOZ_ASSERT(v.isPrimitive());
1988 context()->check(v);
1990 if (v.isString()) {
1991 return writeString(SCTAG_STRING, v.toString());
1992 } else if (v.isInt32()) {
1993 if (js::SupportDifferentialTesting()) {
1994 return out.writeDouble(v.toInt32());
1996 return out.writePair(SCTAG_INT32, v.toInt32());
1997 } else if (v.isDouble()) {
1998 return out.writeDouble(v.toDouble());
1999 } else if (v.isBoolean()) {
2000 return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
2001 } else if (v.isNull()) {
2002 return out.writePair(SCTAG_NULL, 0);
2003 } else if (v.isUndefined()) {
2004 return out.writePair(SCTAG_UNDEFINED, 0);
2005 } else if (v.isBigInt()) {
2006 return writeBigInt(SCTAG_BIGINT, v.toBigInt());
2009 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2012 bool JSStructuredCloneWriter::startWrite(HandleValue v) {
2013 context()->check(v);
2015 if (v.isPrimitive()) {
2016 return writePrimitive(v);
2019 if (!v.isObject()) {
2020 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2023 RootedObject obj(context(), &v.toObject());
2025 bool backref;
2026 if (!startObject(obj, &backref)) {
2027 return false;
2029 if (backref) {
2030 return true;
2033 ESClass cls;
2034 if (!GetBuiltinClass(context(), obj, &cls)) {
2035 return false;
2038 switch (cls) {
2039 case ESClass::Object:
2040 case ESClass::Array:
2041 return traverseObject(obj, cls);
2042 case ESClass::Number: {
2043 RootedValue unboxed(context());
2044 if (!Unbox(context(), obj, &unboxed)) {
2045 return false;
2047 return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
2048 out.writeDouble(unboxed.toNumber());
2050 case ESClass::String: {
2051 RootedValue unboxed(context());
2052 if (!Unbox(context(), obj, &unboxed)) {
2053 return false;
2055 return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
2057 case ESClass::Boolean: {
2058 RootedValue unboxed(context());
2059 if (!Unbox(context(), obj, &unboxed)) {
2060 return false;
2062 return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
2064 case ESClass::RegExp: {
2065 RegExpShared* re = RegExpToShared(context(), obj);
2066 if (!re) {
2067 return false;
2069 return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags().value()) &&
2070 writeString(SCTAG_STRING, re->getSource());
2072 case ESClass::ArrayBuffer: {
2073 if (JS::IsArrayBufferObject(obj) && JS::ArrayBufferHasData(obj)) {
2074 return writeArrayBuffer(obj);
2076 break;
2078 case ESClass::SharedArrayBuffer:
2079 if (JS::IsSharedArrayBufferObject(obj)) {
2080 return writeSharedArrayBuffer(obj);
2082 break;
2083 case ESClass::Date: {
2084 RootedValue unboxed(context());
2085 if (!Unbox(context(), obj, &unboxed)) {
2086 return false;
2088 return out.writePair(SCTAG_DATE_OBJECT, 0) &&
2089 out.writeDouble(unboxed.toNumber());
2091 case ESClass::Set:
2092 return traverseSet(obj);
2093 case ESClass::Map:
2094 return traverseMap(obj);
2095 case ESClass::Error:
2096 return traverseError(obj);
2097 case ESClass::BigInt: {
2098 RootedValue unboxed(context());
2099 if (!Unbox(context(), obj, &unboxed)) {
2100 return false;
2102 return writeBigInt(SCTAG_BIGINT_OBJECT, unboxed.toBigInt());
2104 case ESClass::Promise:
2105 case ESClass::MapIterator:
2106 case ESClass::SetIterator:
2107 case ESClass::Arguments:
2108 case ESClass::Function:
2109 break;
2111 #ifdef ENABLE_RECORD_TUPLE
2112 case ESClass::Record:
2113 case ESClass::Tuple:
2114 MOZ_CRASH("Record and Tuple are not supported");
2115 #endif
2117 case ESClass::Other: {
2118 if (obj->canUnwrapAs<TypedArrayObject>()) {
2119 return writeTypedArray(obj);
2121 if (obj->canUnwrapAs<DataViewObject>()) {
2122 return writeDataView(obj);
2124 if (wasm::IsSharedWasmMemoryObject(obj)) {
2125 return writeSharedWasmMemory(obj);
2127 if (obj->canUnwrapAs<SavedFrame>()) {
2128 return traverseSavedFrame(obj);
2130 break;
2134 if (out.buf.callbacks_ && out.buf.callbacks_->write) {
2135 bool sameProcessScopeRequired = false;
2136 if (!out.buf.callbacks_->write(context(), this, obj,
2137 &sameProcessScopeRequired,
2138 out.buf.closure_)) {
2139 return false;
2142 if (sameProcessScopeRequired) {
2143 output().sameProcessScopeRequired();
2146 return true;
2149 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2152 bool JSStructuredCloneWriter::writeHeader() {
2153 return out.writePair(SCTAG_HEADER, (uint32_t)output().scope());
2156 bool JSStructuredCloneWriter::writeTransferMap() {
2157 if (transferableObjects.empty()) {
2158 return true;
2161 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) {
2162 return false;
2165 if (!out.write(transferableObjects.length())) {
2166 return false;
2169 RootedObject obj(context());
2170 for (auto* o : transferableObjects) {
2171 obj = o;
2172 if (!memory.put(obj, memory.count())) {
2173 ReportOutOfMemory(context());
2174 return false;
2177 // Emit a placeholder pointer. We defer stealing the data until later
2178 // (and, if necessary, detaching this object if it's an ArrayBuffer).
2179 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY,
2180 JS::SCTAG_TMO_UNFILLED)) {
2181 return false;
2183 if (!out.write(0)) { // Pointer to ArrayBuffer contents.
2184 return false;
2186 if (!out.write(0)) { // extraData
2187 return false;
2191 return true;
2194 bool JSStructuredCloneWriter::transferOwnership() {
2195 if (transferableObjects.empty()) {
2196 return true;
2199 // Walk along the transferables and the transfer map at the same time,
2200 // grabbing out pointers from the transferables and stuffing them into the
2201 // transfer map.
2202 auto point = out.iter();
2203 MOZ_RELEASE_ASSERT(point.canPeek());
2204 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
2205 SCTAG_HEADER);
2206 point++;
2207 MOZ_RELEASE_ASSERT(point.canPeek());
2208 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
2209 SCTAG_TRANSFER_MAP_HEADER);
2210 point++;
2211 MOZ_RELEASE_ASSERT(point.canPeek());
2212 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) ==
2213 transferableObjects.length());
2214 point++;
2216 JSContext* cx = context();
2217 RootedObject obj(cx);
2218 JS::StructuredCloneScope scope = output().scope();
2219 for (auto* o : transferableObjects) {
2220 obj = o;
2222 uint32_t tag;
2223 JS::TransferableOwnership ownership;
2224 void* content;
2225 uint64_t extraData;
2227 #if DEBUG
2228 SCInput::getPair(point.peek(), &tag, (uint32_t*)&ownership);
2229 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2230 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
2231 #endif
2233 ESClass cls;
2234 if (!GetBuiltinClass(cx, obj, &cls)) {
2235 return false;
2238 if (cls == ESClass::ArrayBuffer) {
2239 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
2241 // The current setup of the array buffer inheritance hierarchy doesn't
2242 // lend itself well to generic manipulation via proxies.
2243 Rooted<ArrayBufferObject*> arrayBuffer(
2244 cx, obj->maybeUnwrapAs<ArrayBufferObject>());
2245 JSAutoRealm ar(cx, arrayBuffer);
2247 if (arrayBuffer->isDetached()) {
2248 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED);
2249 return false;
2252 if (arrayBuffer->isPreparedForAsmJS()) {
2253 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER);
2254 return false;
2257 if (scope == JS::StructuredCloneScope::DifferentProcess ||
2258 scope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
2259 // Write Transferred ArrayBuffers in DifferentProcess scope at
2260 // the end of the clone buffer, and store the offset within the
2261 // buffer to where the ArrayBuffer was written. Note that this
2262 // will invalidate the current position iterator.
2264 size_t pointOffset = out.offset(point);
2265 tag = SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER;
2266 ownership = JS::SCTAG_TMO_UNOWNED;
2267 content = nullptr;
2268 extraData = out.tell() -
2269 pointOffset; // Offset from tag to current end of buffer
2270 if (!writeArrayBuffer(arrayBuffer)) {
2271 ReportOutOfMemory(cx);
2272 return false;
2275 // Must refresh the point iterator after its collection has
2276 // been modified.
2277 point = out.iter();
2278 point += pointOffset;
2280 if (!JS::DetachArrayBuffer(cx, arrayBuffer)) {
2281 return false;
2283 } else {
2284 size_t nbytes = arrayBuffer->byteLength();
2286 using BufferContents = ArrayBufferObject::BufferContents;
2288 BufferContents bufContents =
2289 ArrayBufferObject::extractStructuredCloneContents(cx, arrayBuffer);
2290 if (!bufContents) {
2291 return false; // out of memory
2294 content = bufContents.data();
2295 if (bufContents.kind() == ArrayBufferObject::MAPPED) {
2296 ownership = JS::SCTAG_TMO_MAPPED_DATA;
2297 } else {
2298 MOZ_ASSERT(bufContents.kind() == ArrayBufferObject::MALLOCED,
2299 "failing to handle new ArrayBuffer kind?");
2300 ownership = JS::SCTAG_TMO_ALLOC_DATA;
2302 extraData = nbytes;
2304 } else {
2305 if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) {
2306 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
2308 if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag,
2309 &ownership, &content,
2310 &extraData)) {
2311 return false;
2313 MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2316 point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
2317 MOZ_ALWAYS_TRUE(point.advance());
2318 point.write(
2319 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content)));
2320 MOZ_ALWAYS_TRUE(point.advance());
2321 point.write(NativeEndian::swapToLittleEndian(extraData));
2322 MOZ_ALWAYS_TRUE(point.advance());
2325 #if DEBUG
2326 // Make sure there aren't any more transfer map entries after the expected
2327 // number we read out.
2328 if (!point.done()) {
2329 uint32_t tag, data;
2330 SCInput::getPair(point.peek(), &tag, &data);
2331 MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER ||
2332 tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
2334 #endif
2335 return true;
2338 bool JSStructuredCloneWriter::write(HandleValue v) {
2339 if (!startWrite(v)) {
2340 return false;
2343 RootedObject obj(context());
2344 RootedValue key(context());
2345 RootedValue val(context());
2346 RootedId id(context());
2348 RootedValue cause(context());
2349 RootedValue errors(context());
2350 RootedValue stack(context());
2352 while (!counts.empty()) {
2353 obj = &objs.back().toObject();
2354 context()->check(obj);
2355 if (counts.back()) {
2356 counts.back()--;
2358 ESClass cls;
2359 if (!GetBuiltinClass(context(), obj, &cls)) {
2360 return false;
2363 if (cls == ESClass::Map) {
2364 key = otherEntries.popCopy();
2365 checkStack();
2367 counts.back()--;
2368 val = otherEntries.popCopy();
2369 checkStack();
2371 if (!startWrite(key) || !startWrite(val)) {
2372 return false;
2374 } else if (cls == ESClass::Set || obj->canUnwrapAs<SavedFrame>()) {
2375 key = otherEntries.popCopy();
2376 checkStack();
2378 if (!startWrite(key)) {
2379 return false;
2381 } else if (cls == ESClass::Error) {
2382 cause = otherEntries.popCopy();
2383 checkStack();
2385 counts.back()--;
2386 errors = otherEntries.popCopy();
2387 checkStack();
2389 counts.back()--;
2390 stack = otherEntries.popCopy();
2391 checkStack();
2393 if (!startWrite(cause) || !startWrite(errors) || !startWrite(stack)) {
2394 return false;
2396 } else {
2397 id = objectEntries.popCopy();
2398 key = IdToValue(id);
2399 checkStack();
2401 // If obj still has an own property named id, write it out.
2402 bool found;
2403 if (GetOwnPropertyPure(context(), obj, id, val.address(), &found)) {
2404 if (found) {
2405 if (!writePrimitive(key) || !startWrite(val)) {
2406 return false;
2409 continue;
2412 if (!HasOwnProperty(context(), obj, id, &found)) {
2413 return false;
2416 if (found) {
2417 #if FUZZING_JS_FUZZILLI
2418 // supress calls into user code
2419 if (js::SupportDifferentialTesting()) {
2420 fprintf(stderr, "Differential testing: cannot call GetProperty\n");
2421 return false;
2423 #endif
2425 if (!writePrimitive(key) ||
2426 !GetProperty(context(), obj, obj, id, &val) || !startWrite(val)) {
2427 return false;
2431 } else {
2432 if (!out.writePair(SCTAG_END_OF_KEYS, 0)) {
2433 return false;
2435 objs.popBack();
2436 counts.popBack();
2440 memory.clear();
2441 return transferOwnership();
2444 JSStructuredCloneReader::JSStructuredCloneReader(
2445 SCInput& in, JS::StructuredCloneScope scope,
2446 const JS::CloneDataPolicy& cloneDataPolicy,
2447 const JSStructuredCloneCallbacks* cb, void* cbClosure)
2448 : in(in),
2449 allowedScope(scope),
2450 cloneDataPolicy(cloneDataPolicy),
2451 objs(in.context()),
2452 objState(in.context(), in.context()),
2453 allObjs(in.context()),
2454 numItemsRead(0),
2455 callbacks(cb),
2456 closure(cbClosure),
2457 gcHeap(in.context()) {
2458 // Avoid the need to bounds check by keeping a never-matching element at the
2459 // base of the `objState` stack. This append() will always succeed because
2460 // the objState vector has a nonzero MinInlineCapacity.
2461 MOZ_ALWAYS_TRUE(objState.append(std::make_pair(nullptr, true)));
2464 template <typename CharT>
2465 JSString* JSStructuredCloneReader::readStringImpl(
2466 uint32_t nchars, ShouldAtomizeStrings atomize) {
2467 InlineCharBuffer<CharT> chars;
2468 if (!chars.maybeAlloc(context(), nchars) ||
2469 !in.readChars(chars.get(), nchars)) {
2470 return nullptr;
2473 if (atomize) {
2474 return chars.toAtom(context(), nchars);
2477 return chars.toStringDontDeflate(context(), nchars, gcHeap);
2480 JSString* JSStructuredCloneReader::readString(uint32_t data,
2481 ShouldAtomizeStrings atomize) {
2482 uint32_t nchars = data & BitMask(31);
2483 bool latin1 = data & (1 << 31);
2485 if (nchars > JSString::MAX_LENGTH) {
2486 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2487 JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
2488 return nullptr;
2491 return latin1 ? readStringImpl<Latin1Char>(nchars, atomize)
2492 : readStringImpl<char16_t>(nchars, atomize);
2495 [[nodiscard]] bool JSStructuredCloneReader::readUint32(uint32_t* num) {
2496 Rooted<Value> lineVal(context());
2497 if (!startRead(&lineVal)) {
2498 return false;
2500 if (!lineVal.isInt32()) {
2501 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2502 JSMSG_SC_BAD_SERIALIZED_DATA, "integer required");
2503 return false;
2505 *num = uint32_t(lineVal.toInt32());
2506 return true;
2509 BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) {
2510 size_t length = data & BitMask(31);
2511 bool isNegative = data & (1 << 31);
2512 if (length == 0) {
2513 return BigInt::zero(context());
2515 RootedBigInt result(context(), BigInt::createUninitialized(
2516 context(), length, isNegative, gcHeap));
2517 if (!result) {
2518 return nullptr;
2520 if (!in.readArray(result->digits().data(), length)) {
2521 return nullptr;
2523 return result;
2526 static uint32_t TagToV1ArrayType(uint32_t tag) {
2527 MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN &&
2528 tag <= SCTAG_TYPED_ARRAY_V1_MAX);
2529 return tag - SCTAG_TYPED_ARRAY_V1_MIN;
2532 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType,
2533 uint64_t nelems,
2534 MutableHandleValue vp,
2535 bool v1Read) {
2536 if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) {
2537 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2538 JSMSG_SC_BAD_SERIALIZED_DATA,
2539 "unhandled typed array element type");
2540 return false;
2543 // Push a placeholder onto the allObjs list to stand in for the typed array.
2544 uint32_t placeholderIndex = allObjs.length();
2545 Value dummy = UndefinedValue();
2546 if (!allObjs.append(dummy)) {
2547 return false;
2550 // Read the ArrayBuffer object and its contents (but no properties)
2551 RootedValue v(context());
2552 uint64_t byteOffset;
2553 if (v1Read) {
2554 if (!readV1ArrayBuffer(arrayType, nelems, &v)) {
2555 return false;
2557 byteOffset = 0;
2558 } else {
2559 if (!startRead(&v)) {
2560 return false;
2562 if (!in.read(&byteOffset)) {
2563 return false;
2567 // Ensure invalid 64-bit values won't be truncated below.
2568 if (nelems > ArrayBufferObject::MaxByteLength ||
2569 byteOffset > ArrayBufferObject::MaxByteLength) {
2570 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2571 JSMSG_SC_BAD_SERIALIZED_DATA,
2572 "invalid typed array length or offset");
2573 return false;
2576 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2577 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2578 JSMSG_SC_BAD_SERIALIZED_DATA,
2579 "typed array must be backed by an ArrayBuffer");
2580 return false;
2583 RootedObject buffer(context(), &v.toObject());
2584 RootedObject obj(context(), nullptr);
2586 switch (arrayType) {
2587 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2588 case Scalar::Name: \
2589 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2590 byteOffset, nelems) \
2591 .asObject(); \
2592 break;
2594 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER)
2595 #undef CREATE_FROM_BUFFER
2597 default:
2598 MOZ_CRASH("Can't happen: arrayType range checked above");
2601 if (!obj) {
2602 return false;
2604 vp.setObject(*obj);
2606 allObjs[placeholderIndex].set(vp);
2608 return true;
2611 bool JSStructuredCloneReader::readDataView(uint64_t byteLength,
2612 MutableHandleValue vp) {
2613 // Push a placeholder onto the allObjs list to stand in for the DataView.
2614 uint32_t placeholderIndex = allObjs.length();
2615 Value dummy = UndefinedValue();
2616 if (!allObjs.append(dummy)) {
2617 return false;
2620 // Read the ArrayBuffer object and its contents (but no properties).
2621 RootedValue v(context());
2622 if (!startRead(&v)) {
2623 return false;
2625 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2626 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2627 JSMSG_SC_BAD_SERIALIZED_DATA,
2628 "DataView must be backed by an ArrayBuffer");
2629 return false;
2632 // Read byteOffset.
2633 uint64_t byteOffset;
2634 if (!in.read(&byteOffset)) {
2635 return false;
2638 // Ensure invalid 64-bit values won't be truncated below.
2639 if (byteLength > ArrayBufferObject::MaxByteLength ||
2640 byteOffset > ArrayBufferObject::MaxByteLength) {
2641 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2642 JSMSG_SC_BAD_SERIALIZED_DATA,
2643 "invalid DataView length or offset");
2644 return false;
2647 RootedObject buffer(context(), &v.toObject());
2648 RootedObject obj(context(),
2649 JS_NewDataView(context(), buffer, byteOffset, byteLength));
2650 if (!obj) {
2651 return false;
2653 vp.setObject(*obj);
2655 allObjs[placeholderIndex].set(vp);
2657 return true;
2660 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type,
2661 uint32_t data,
2662 MutableHandleValue vp) {
2663 // V2 stores the length in |data|. The current version stores the
2664 // length separately to allow larger length values.
2665 uint64_t nbytes = 0;
2666 if (type == SCTAG_ARRAY_BUFFER_OBJECT) {
2667 if (!in.read(&nbytes)) {
2668 return false;
2670 } else {
2671 MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2);
2672 nbytes = data;
2675 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2676 // below, so we have to check this here.
2677 if (nbytes > ArrayBufferObject::MaxByteLength) {
2678 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2679 JSMSG_BAD_ARRAY_LENGTH);
2680 return false;
2683 JSObject* obj = ArrayBufferObject::createZeroed(context(), size_t(nbytes));
2684 if (!obj) {
2685 return false;
2687 vp.setObject(*obj);
2688 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2689 MOZ_ASSERT(buffer.byteLength() == nbytes);
2690 return in.readArray(buffer.dataPointer(), nbytes);
2693 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp) {
2694 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2695 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2696 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2697 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2698 : JS_SCERR_NOT_CLONABLE;
2699 ReportDataCloneError(context(), callbacks, error, closure,
2700 "SharedArrayBuffer");
2701 return false;
2704 uint64_t byteLength;
2705 if (!in.readBytes(&byteLength, sizeof(byteLength))) {
2706 return in.reportTruncated();
2709 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2710 // below, so we have to check this here.
2711 if (byteLength > ArrayBufferObject::MaxByteLength) {
2712 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2713 JSMSG_BAD_ARRAY_LENGTH);
2714 return false;
2717 intptr_t p;
2718 if (!in.readBytes(&p, sizeof(p))) {
2719 return in.reportTruncated();
2722 SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p);
2724 // There's no guarantee that the receiving agent has enabled shared memory
2725 // even if the transmitting agent has done so. Ideally we'd check at the
2726 // transmission point, but that's tricky, and it will be a very rare problem
2727 // in any case. Just fail at the receiving end if we can't handle it.
2729 if (!context()
2730 ->realm()
2731 ->creationOptions()
2732 .getSharedMemoryAndAtomicsEnabled()) {
2733 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2734 JSMSG_SC_SAB_DISABLED);
2735 return false;
2738 // The new object will have a new reference to the rawbuf.
2740 if (!rawbuf->addReference()) {
2741 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2742 JSMSG_SC_SAB_REFCNT_OFLO);
2743 return false;
2746 RootedObject obj(context(),
2747 SharedArrayBufferObject::New(context(), rawbuf, byteLength));
2748 if (!obj) {
2749 rawbuf->dropReference();
2750 return false;
2753 // `rawbuf` is now owned by `obj`.
2755 if (callbacks && callbacks->sabCloned &&
2756 !callbacks->sabCloned(context(), /*receiving=*/true, closure)) {
2757 return false;
2760 vp.setObject(*obj);
2761 return true;
2764 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes,
2765 MutableHandleValue vp) {
2766 JSContext* cx = context();
2767 if (nbytes != 0) {
2768 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2769 JSMSG_SC_BAD_SERIALIZED_DATA,
2770 "invalid shared wasm memory tag");
2771 return false;
2774 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2775 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2776 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2777 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2778 : JS_SCERR_NOT_CLONABLE;
2779 ReportDataCloneError(cx, callbacks, error, closure, "WebAssembly.Memory");
2780 return false;
2783 // Read the isHuge flag
2784 RootedValue isHuge(cx);
2785 if (!startRead(&isHuge)) {
2786 return false;
2789 // Read the SharedArrayBuffer object.
2790 RootedValue payload(cx);
2791 if (!startRead(&payload)) {
2792 return false;
2794 if (!payload.isObject() ||
2795 !payload.toObject().is<SharedArrayBufferObject>()) {
2796 JS_ReportErrorNumberASCII(
2797 context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
2798 "shared wasm memory must be backed by a SharedArrayBuffer");
2799 return false;
2802 Rooted<ArrayBufferObjectMaybeShared*> sab(
2803 cx, &payload.toObject().as<SharedArrayBufferObject>());
2805 // Construct the memory.
2806 RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory));
2807 RootedObject memory(
2808 cx, WasmMemoryObject::create(cx, sab, isHuge.toBoolean(), proto));
2809 if (!memory) {
2810 return false;
2813 vp.setObject(*memory);
2814 return true;
2818 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2819 * endianness-conversion while reading.
2821 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType,
2822 uint32_t nelems,
2823 MutableHandleValue vp) {
2824 if (arrayType > Scalar::Uint8Clamped) {
2825 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2826 JSMSG_SC_BAD_SERIALIZED_DATA,
2827 "invalid TypedArray type");
2828 return false;
2831 mozilla::CheckedInt<size_t> nbytes =
2832 mozilla::CheckedInt<size_t>(nelems) *
2833 TypedArrayElemSize(static_cast<Scalar::Type>(arrayType));
2834 if (!nbytes.isValid() || nbytes.value() > UINT32_MAX) {
2835 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2836 JSMSG_SC_BAD_SERIALIZED_DATA,
2837 "invalid typed array size");
2838 return false;
2841 JSObject* obj = ArrayBufferObject::createZeroed(context(), nbytes.value());
2842 if (!obj) {
2843 return false;
2845 vp.setObject(*obj);
2846 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2847 MOZ_ASSERT(buffer.byteLength() == nbytes);
2849 switch (arrayType) {
2850 case Scalar::Int8:
2851 case Scalar::Uint8:
2852 case Scalar::Uint8Clamped:
2853 return in.readArray((uint8_t*)buffer.dataPointer(), nelems);
2854 case Scalar::Int16:
2855 case Scalar::Uint16:
2856 return in.readArray((uint16_t*)buffer.dataPointer(), nelems);
2857 case Scalar::Int32:
2858 case Scalar::Uint32:
2859 case Scalar::Float32:
2860 return in.readArray((uint32_t*)buffer.dataPointer(), nelems);
2861 case Scalar::Float64:
2862 case Scalar::BigInt64:
2863 case Scalar::BigUint64:
2864 return in.readArray((uint64_t*)buffer.dataPointer(), nelems);
2865 default:
2866 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2870 static bool PrimitiveToObject(JSContext* cx, MutableHandleValue vp) {
2871 JSObject* obj = js::PrimitiveToObject(cx, vp);
2872 if (!obj) {
2873 return false;
2876 vp.setObject(*obj);
2877 return true;
2880 bool JSStructuredCloneReader::startRead(MutableHandleValue vp,
2881 ShouldAtomizeStrings atomizeStrings) {
2882 uint32_t tag, data;
2883 bool alreadAppended = false;
2885 if (!in.readPair(&tag, &data)) {
2886 return false;
2889 numItemsRead++;
2891 switch (tag) {
2892 case SCTAG_NULL:
2893 vp.setNull();
2894 break;
2896 case SCTAG_UNDEFINED:
2897 vp.setUndefined();
2898 break;
2900 case SCTAG_INT32:
2901 vp.setInt32(data);
2902 break;
2904 case SCTAG_BOOLEAN:
2905 case SCTAG_BOOLEAN_OBJECT:
2906 vp.setBoolean(!!data);
2907 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) {
2908 return false;
2910 break;
2912 case SCTAG_STRING:
2913 case SCTAG_STRING_OBJECT: {
2914 JSString* str = readString(data, atomizeStrings);
2915 if (!str) {
2916 return false;
2918 vp.setString(str);
2919 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) {
2920 return false;
2922 break;
2925 case SCTAG_NUMBER_OBJECT: {
2926 double d;
2927 if (!in.readDouble(&d)) {
2928 return false;
2930 vp.setDouble(CanonicalizeNaN(d));
2931 if (!PrimitiveToObject(context(), vp)) {
2932 return false;
2934 break;
2937 case SCTAG_BIGINT:
2938 case SCTAG_BIGINT_OBJECT: {
2939 RootedBigInt bi(context(), readBigInt(data));
2940 if (!bi) {
2941 return false;
2943 vp.setBigInt(bi);
2944 if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) {
2945 return false;
2947 break;
2950 case SCTAG_DATE_OBJECT: {
2951 double d;
2952 if (!in.readDouble(&d)) {
2953 return false;
2955 JS::ClippedTime t = JS::TimeClip(d);
2956 if (!NumbersAreIdentical(d, t.toDouble())) {
2957 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2958 JSMSG_SC_BAD_SERIALIZED_DATA, "date");
2959 return false;
2961 JSObject* obj = NewDateObjectMsec(context(), t);
2962 if (!obj) {
2963 return false;
2965 vp.setObject(*obj);
2966 break;
2969 case SCTAG_REGEXP_OBJECT: {
2970 if ((data & RegExpFlag::AllFlags) != data) {
2971 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2972 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2973 return false;
2976 RegExpFlags flags(AssertedCast<uint8_t>(data));
2978 uint32_t tag2, stringData;
2979 if (!in.readPair(&tag2, &stringData)) {
2980 return false;
2982 if (tag2 != SCTAG_STRING) {
2983 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2984 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2985 return false;
2988 JSString* str = readString(stringData, AtomizeStrings);
2989 if (!str) {
2990 return false;
2993 Rooted<JSAtom*> atom(context(), &str->asAtom());
2995 NewObjectKind kind =
2996 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject;
2997 RegExpObject* reobj = RegExpObject::create(context(), atom, flags, kind);
2998 if (!reobj) {
2999 return false;
3001 vp.setObject(*reobj);
3002 break;
3005 case SCTAG_ARRAY_OBJECT:
3006 case SCTAG_OBJECT_OBJECT: {
3007 NewObjectKind kind =
3008 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject;
3009 JSObject* obj;
3010 if (tag == SCTAG_ARRAY_OBJECT) {
3011 obj = NewDenseUnallocatedArray(
3012 context(), NativeEndian::swapFromLittleEndian(data), kind);
3013 } else {
3014 obj = NewPlainObject(context(), kind);
3016 if (!obj || !objs.append(ObjectValue(*obj))) {
3017 return false;
3020 vp.setObject(*obj);
3021 break;
3024 case SCTAG_BACK_REFERENCE_OBJECT: {
3025 if (data >= allObjs.length() || !allObjs[data].isObject()) {
3026 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3027 JSMSG_SC_BAD_SERIALIZED_DATA,
3028 "invalid back reference in input");
3029 return false;
3031 vp.set(allObjs[data]);
3032 return true;
3035 case SCTAG_TRANSFER_MAP_HEADER:
3036 case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
3037 // We should be past all the transfer map tags.
3038 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3039 JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input");
3040 return false;
3042 case SCTAG_ARRAY_BUFFER_OBJECT_V2:
3043 case SCTAG_ARRAY_BUFFER_OBJECT:
3044 if (!readArrayBuffer(StructuredDataType(tag), data, vp)) {
3045 return false;
3047 break;
3049 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
3050 if (!readSharedArrayBuffer(vp)) {
3051 return false;
3053 break;
3055 case SCTAG_SHARED_WASM_MEMORY_OBJECT:
3056 if (!readSharedWasmMemory(data, vp)) {
3057 return false;
3059 break;
3061 case SCTAG_TYPED_ARRAY_OBJECT_V2: {
3062 // readTypedArray adds the array to allObjs.
3063 // V2 stores the length (nelems) in |data| and the arrayType separately.
3064 uint64_t arrayType;
3065 if (!in.read(&arrayType)) {
3066 return false;
3068 uint64_t nelems = data;
3069 return readTypedArray(arrayType, nelems, vp);
3072 case SCTAG_TYPED_ARRAY_OBJECT: {
3073 // readTypedArray adds the array to allObjs.
3074 // The current version stores the array type in |data| and the length
3075 // (nelems) separately to support large TypedArrays.
3076 uint32_t arrayType = data;
3077 uint64_t nelems;
3078 if (!in.read(&nelems)) {
3079 return false;
3081 return readTypedArray(arrayType, nelems, vp);
3084 case SCTAG_DATA_VIEW_OBJECT_V2: {
3085 // readDataView adds the array to allObjs.
3086 uint64_t byteLength = data;
3087 return readDataView(byteLength, vp);
3090 case SCTAG_DATA_VIEW_OBJECT: {
3091 // readDataView adds the array to allObjs.
3092 uint64_t byteLength;
3093 if (!in.read(&byteLength)) {
3094 return false;
3096 return readDataView(byteLength, vp);
3099 case SCTAG_MAP_OBJECT: {
3100 JSObject* obj = MapObject::create(context());
3101 if (!obj || !objs.append(ObjectValue(*obj))) {
3102 return false;
3104 vp.setObject(*obj);
3105 break;
3108 case SCTAG_SET_OBJECT: {
3109 JSObject* obj = SetObject::create(context());
3110 if (!obj || !objs.append(ObjectValue(*obj))) {
3111 return false;
3113 vp.setObject(*obj);
3114 break;
3117 case SCTAG_SAVED_FRAME_OBJECT: {
3118 auto* obj = readSavedFrameHeader(data);
3119 if (!obj || !objs.append(ObjectValue(*obj)) ||
3120 !objState.append(std::make_pair(obj, false))) {
3121 return false;
3123 vp.setObject(*obj);
3124 break;
3127 case SCTAG_ERROR_OBJECT: {
3128 auto* obj = readErrorHeader(data);
3129 if (!obj || !objs.append(ObjectValue(*obj)) ||
3130 !objState.append(std::make_pair(obj, false))) {
3131 return false;
3133 vp.setObject(*obj);
3134 break;
3137 case SCTAG_END_OF_KEYS:
3138 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3139 JSMSG_SC_BAD_SERIALIZED_DATA,
3140 "truncated input");
3141 return false;
3142 break;
3144 default: {
3145 if (tag <= SCTAG_FLOAT_MAX) {
3146 double d = ReinterpretPairAsDouble(tag, data);
3147 vp.setNumber(CanonicalizeNaN(d));
3148 break;
3151 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
3152 // A v1-format typed array
3153 // readTypedArray adds the array to allObjs
3154 return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
3157 if (!callbacks || !callbacks->read) {
3158 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3159 JSMSG_SC_BAD_SERIALIZED_DATA,
3160 "unsupported type");
3161 return false;
3164 // callbacks->read() might read other objects from the buffer.
3165 // In startWrite we always write the object itself before calling
3166 // the custom function. We should do the same here to keep
3167 // indexing consistent.
3168 uint32_t placeholderIndex = allObjs.length();
3169 Value dummy = UndefinedValue();
3170 if (!allObjs.append(dummy)) {
3171 return false;
3173 JSObject* obj =
3174 callbacks->read(context(), this, cloneDataPolicy, tag, data, closure);
3175 if (!obj) {
3176 return false;
3178 vp.setObject(*obj);
3179 allObjs[placeholderIndex].set(vp);
3180 alreadAppended = true;
3184 if (!alreadAppended && vp.isObject() && !allObjs.append(vp)) {
3185 return false;
3188 return true;
3191 bool JSStructuredCloneReader::readHeader() {
3192 uint32_t tag, data;
3193 if (!in.getPair(&tag, &data)) {
3194 return in.reportTruncated();
3197 JS::StructuredCloneScope storedScope;
3198 if (tag == SCTAG_HEADER) {
3199 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3200 storedScope = JS::StructuredCloneScope(data);
3201 } else {
3202 // Old structured clone buffer. We must have read it from disk.
3203 storedScope = JS::StructuredCloneScope::DifferentProcessForIndexedDB;
3206 // Backward compatibility with old structured clone buffers. Value '0' was
3207 // used for SameProcessSameThread scope.
3208 if ((int)storedScope == 0) {
3209 storedScope = JS::StructuredCloneScope::SameProcess;
3212 if (storedScope < JS::StructuredCloneScope::SameProcess ||
3213 storedScope > JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3214 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3215 JSMSG_SC_BAD_SERIALIZED_DATA,
3216 "invalid structured clone scope");
3217 return false;
3220 if (allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3221 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
3222 // clones are incorrect. Treat them as if they were DifferentProcess.
3223 allowedScope = JS::StructuredCloneScope::DifferentProcess;
3224 return true;
3227 if (storedScope < allowedScope) {
3228 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3229 JSMSG_SC_BAD_SERIALIZED_DATA,
3230 "incompatible structured clone scope");
3231 return false;
3234 return true;
3237 bool JSStructuredCloneReader::readTransferMap() {
3238 JSContext* cx = context();
3239 auto headerPos = in.tell();
3241 uint32_t tag, data;
3242 if (!in.getPair(&tag, &data)) {
3243 return in.reportTruncated();
3246 if (tag != SCTAG_TRANSFER_MAP_HEADER ||
3247 TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
3248 return true;
3251 uint64_t numTransferables;
3252 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3253 if (!in.read(&numTransferables)) {
3254 return false;
3257 for (uint64_t i = 0; i < numTransferables; i++) {
3258 auto pos = in.tell();
3260 if (!in.readPair(&tag, &data)) {
3261 return false;
3264 if (tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY) {
3265 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3266 return false;
3269 RootedObject obj(cx);
3271 void* content;
3272 if (!in.readPtr(&content)) {
3273 return false;
3276 uint64_t extraData;
3277 if (!in.read(&extraData)) {
3278 return false;
3281 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
3282 if (allowedScope == JS::StructuredCloneScope::DifferentProcess ||
3283 allowedScope ==
3284 JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3285 // Transferred ArrayBuffers in a DifferentProcess clone buffer
3286 // are treated as if they weren't Transferred at all. We should
3287 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
3288 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3289 return false;
3292 MOZ_RELEASE_ASSERT(extraData <= ArrayBufferObject::MaxByteLength);
3293 size_t nbytes = extraData;
3295 MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
3296 data == JS::SCTAG_TMO_MAPPED_DATA);
3297 if (data == JS::SCTAG_TMO_ALLOC_DATA) {
3298 // When the ArrayBuffer can't be allocated, |content| will be free'ed
3299 // in `JSStructuredCloneData::discardTransferables()`.
3300 obj = JS::NewArrayBufferWithContents(
3301 cx, nbytes, content,
3302 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
3303 } else if (data == JS::SCTAG_TMO_MAPPED_DATA) {
3304 obj = JS::NewMappedArrayBufferWithContents(cx, nbytes, content);
3306 } else if (tag == SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER) {
3307 auto savedPos = in.tell();
3308 auto guard = mozilla::MakeScopeExit([&] { in.seekTo(savedPos); });
3309 in.seekTo(pos);
3310 if (!in.seekBy(static_cast<size_t>(extraData))) {
3311 return false;
3314 if (tailStartPos.isNothing()) {
3315 tailStartPos = mozilla::Some(in.tell());
3318 uint32_t tag, data;
3319 if (!in.readPair(&tag, &data)) {
3320 return false;
3322 if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 &&
3323 tag != SCTAG_ARRAY_BUFFER_OBJECT) {
3324 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3325 return false;
3327 RootedValue val(cx);
3328 if (!readArrayBuffer(StructuredDataType(tag), data, &val)) {
3329 return false;
3331 obj = &val.toObject();
3332 tailEndPos = mozilla::Some(in.tell());
3333 } else {
3334 if (!callbacks || !callbacks->readTransfer) {
3335 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3336 return false;
3338 if (!callbacks->readTransfer(cx, this, cloneDataPolicy, tag, content,
3339 extraData, closure, &obj)) {
3340 if (!cx->isExceptionPending()) {
3341 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3343 return false;
3345 MOZ_ASSERT(obj);
3346 MOZ_ASSERT(!cx->isExceptionPending());
3349 // On failure, the buffer will still own the data (since its ownership
3350 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3351 // DiscardTransferables.
3352 if (!obj) {
3353 return false;
3356 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3357 // buffer.
3358 pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED));
3359 MOZ_ASSERT(!pos.done());
3361 if (!allObjs.append(ObjectValue(*obj))) {
3362 return false;
3366 // Mark the whole transfer map as consumed.
3367 #ifdef DEBUG
3368 SCInput::getPair(headerPos.peek(), &tag, &data);
3369 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
3370 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
3371 #endif
3372 headerPos.write(
3373 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
3375 return true;
3378 JSObject* JSStructuredCloneReader::readSavedFrameHeader(
3379 uint32_t principalsTag) {
3380 Rooted<SavedFrame*> savedFrame(context(), SavedFrame::create(context()));
3381 if (!savedFrame) {
3382 return nullptr;
3385 JSPrincipals* principals;
3386 if (principalsTag == SCTAG_JSPRINCIPALS) {
3387 if (!context()->runtime()->readPrincipals) {
3388 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3389 JSMSG_SC_UNSUPPORTED_TYPE);
3390 return nullptr;
3393 if (!context()->runtime()->readPrincipals(context(), this, &principals)) {
3394 return nullptr;
3396 } else if (principalsTag ==
3397 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
3398 principals = &ReconstructedSavedFramePrincipals::IsSystem;
3399 principals->refcount++;
3400 } else if (principalsTag ==
3401 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
3402 principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
3403 principals->refcount++;
3404 } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
3405 principals = nullptr;
3406 } else {
3407 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3408 JSMSG_SC_BAD_SERIALIZED_DATA,
3409 "bad SavedFrame principals");
3410 return nullptr;
3413 RootedValue mutedErrors(context());
3414 RootedValue source(context());
3416 // Read a |mutedErrors| boolean followed by a |source| string.
3417 // The |mutedErrors| boolean is present in all new structured-clone data,
3418 // but in older data it will be absent and only the |source| string will be
3419 // found.
3420 if (!startRead(&mutedErrors, AtomizeStrings)) {
3421 return nullptr;
3424 if (mutedErrors.isBoolean()) {
3425 if (!startRead(&source, AtomizeStrings) || !source.isString()) {
3426 return nullptr;
3428 } else if (mutedErrors.isString()) {
3429 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3430 // this is actually just a |source| string.
3431 source = mutedErrors;
3432 mutedErrors.setBoolean(true); // Safe default value.
3433 } else {
3434 // Invalid type.
3435 return nullptr;
3439 savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals,
3440 mutedErrors.toBoolean());
3442 savedFrame->initSource(&source.toString()->asAtom());
3444 uint32_t line;
3445 if (!readUint32(&line)) {
3446 return nullptr;
3448 savedFrame->initLine(line);
3450 JS::TaggedColumnNumberOneOrigin column;
3451 if (!readUint32(column.addressOfValueForTranscode())) {
3452 return nullptr;
3454 savedFrame->initColumn(column);
3456 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3457 // are only valid within a specific process.
3458 savedFrame->initSourceId(0);
3460 RootedValue name(context());
3461 if (!startRead(&name, AtomizeStrings)) {
3462 return nullptr;
3464 if (!(name.isString() || name.isNull())) {
3465 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3466 JSMSG_SC_BAD_SERIALIZED_DATA,
3467 "invalid saved frame cause");
3468 return nullptr;
3470 JSAtom* atomName = nullptr;
3471 if (name.isString()) {
3472 atomName = &name.toString()->asAtom();
3475 savedFrame->initFunctionDisplayName(atomName);
3477 RootedValue cause(context());
3478 if (!startRead(&cause, AtomizeStrings)) {
3479 return nullptr;
3481 if (!(cause.isString() || cause.isNull())) {
3482 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3483 JSMSG_SC_BAD_SERIALIZED_DATA,
3484 "invalid saved frame cause");
3485 return nullptr;
3487 JSAtom* atomCause = nullptr;
3488 if (cause.isString()) {
3489 atomCause = &cause.toString()->asAtom();
3491 savedFrame->initAsyncCause(atomCause);
3493 return savedFrame;
3496 // SavedFrame object: there is one child value, the parent SavedFrame,
3497 // which is either null or another SavedFrame object.
3498 bool JSStructuredCloneReader::readSavedFrameFields(Handle<SavedFrame*> frameObj,
3499 HandleValue parent,
3500 bool* state) {
3501 if (*state) {
3502 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3503 JSMSG_SC_BAD_SERIALIZED_DATA,
3504 "multiple SavedFrame parents");
3505 return false;
3508 SavedFrame* parentFrame;
3509 if (parent.isNull()) {
3510 parentFrame = nullptr;
3511 } else if (parent.isObject() && parent.toObject().is<SavedFrame>()) {
3512 parentFrame = &parent.toObject().as<SavedFrame>();
3513 } else {
3514 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3515 JSMSG_SC_BAD_SERIALIZED_DATA,
3516 "invalid SavedFrame parent");
3517 return false;
3520 frameObj->initParent(parentFrame);
3521 *state = true;
3522 return true;
3525 JSObject* JSStructuredCloneReader::readErrorHeader(uint32_t type) {
3526 JSContext* cx = context();
3528 switch (type) {
3529 case JSEXN_ERR:
3530 case JSEXN_EVALERR:
3531 case JSEXN_RANGEERR:
3532 case JSEXN_REFERENCEERR:
3533 case JSEXN_SYNTAXERR:
3534 case JSEXN_TYPEERR:
3535 case JSEXN_URIERR:
3536 case JSEXN_AGGREGATEERR:
3537 break;
3538 default:
3539 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3540 JSMSG_SC_BAD_SERIALIZED_DATA,
3541 "invalid error type");
3542 return nullptr;
3545 RootedString message(cx);
3547 RootedValue messageVal(cx);
3548 if (!startRead(&messageVal)) {
3549 return nullptr;
3551 if (messageVal.isString()) {
3552 message = messageVal.toString();
3553 } else if (!messageVal.isNull()) {
3554 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3555 JSMSG_SC_BAD_SERIALIZED_DATA,
3556 "invalid 'message' field for Error object");
3557 return nullptr;
3561 // We have to set |cause| to something if it exists, otherwise the shape
3562 // would be wrong. The actual value will be overwritten later.
3563 RootedValue val(cx);
3564 if (!startRead(&val)) {
3565 return nullptr;
3567 bool hasCause = ToBoolean(val);
3568 Rooted<Maybe<Value>> cause(cx, mozilla::Nothing());
3569 if (hasCause) {
3570 cause = mozilla::Some(BooleanValue(true));
3573 if (!startRead(&val)) {
3574 return nullptr;
3576 if (!val.isString()) {
3577 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3578 JSMSG_SC_BAD_SERIALIZED_DATA,
3579 "invalid 'fileName' field for Error object");
3580 return nullptr;
3582 RootedString fileName(cx, val.toString());
3584 uint32_t lineNumber;
3585 JS::ColumnNumberOneOrigin columnNumber;
3586 if (!readUint32(&lineNumber) ||
3587 !readUint32(columnNumber.addressOfValueForTranscode())) {
3588 return nullptr;
3591 // The |cause| and |stack| slots of the objects might be overwritten later.
3592 // For AggregateErrors the |errors| property will be added.
3593 RootedObject errorObj(
3594 cx, ErrorObject::create(cx, static_cast<JSExnType>(type), nullptr,
3595 fileName, 0, lineNumber, columnNumber, nullptr,
3596 message, cause));
3597 if (!errorObj) {
3598 return nullptr;
3601 return errorObj;
3604 // Error objects have 3 fields, some or all of them null: cause,
3605 // errors, and stack.
3606 bool JSStructuredCloneReader::readErrorFields(Handle<ErrorObject*> errorObj,
3607 HandleValue cause, bool* state) {
3608 JSContext* cx = context();
3609 if (*state) {
3610 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3611 JSMSG_SC_BAD_SERIALIZED_DATA,
3612 "unexpected child value seen for Error object");
3613 return false;
3616 RootedValue errors(cx);
3617 RootedValue stack(cx);
3618 if (!startRead(&errors) || !startRead(&stack)) {
3619 return false;
3622 bool hasCause = errorObj->getCause().isSome();
3623 if (hasCause) {
3624 errorObj->setCauseSlot(cause);
3625 } else if (!cause.isNull()) {
3626 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3627 JSMSG_SC_BAD_SERIALIZED_DATA,
3628 "invalid 'cause' field for Error object");
3629 return false;
3632 if (errorObj->type() == JSEXN_AGGREGATEERR) {
3633 if (!DefineDataProperty(context(), errorObj, cx->names().errors, errors,
3634 0)) {
3635 return false;
3637 } else if (!errors.isNull()) {
3638 JS_ReportErrorNumberASCII(
3639 cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
3640 "unexpected 'errors' field seen for non-AggregateError");
3641 return false;
3644 if (stack.isObject()) {
3645 RootedObject stackObj(cx, &stack.toObject());
3646 if (!stackObj->is<SavedFrame>()) {
3647 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3648 JSMSG_SC_BAD_SERIALIZED_DATA,
3649 "invalid 'stack' field for Error object");
3650 return false;
3652 errorObj->setStackSlot(stack);
3653 } else if (!stack.isNull()) {
3654 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3655 JSMSG_SC_BAD_SERIALIZED_DATA,
3656 "invalid 'stack' field for Error object");
3657 return false;
3660 *state = true;
3661 return true;
3664 // Read a value and treat as a key,value pair.
3665 bool JSStructuredCloneReader::readMapField(Handle<MapObject*> mapObj,
3666 HandleValue key) {
3667 RootedValue val(context());
3668 if (!startRead(&val)) {
3669 return false;
3671 return MapObject::set(context(), mapObj, key, val);
3674 // Read a value and treat as a key,value pair. Interpret as a plain property
3675 // value.
3676 bool JSStructuredCloneReader::readObjectField(HandleObject obj,
3677 HandleValue key) {
3678 if (!key.isString() && !key.isInt32()) {
3679 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3680 JSMSG_SC_BAD_SERIALIZED_DATA,
3681 "property key expected");
3682 return false;
3685 RootedValue val(context());
3686 if (!startRead(&val)) {
3687 return false;
3690 RootedId id(context());
3691 if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
3692 return false;
3695 // Fast path for adding a new property to a plain object. The property names
3696 // we see here should be unique, but we check for duplicates to guard against
3697 // corrupt or malicious data.
3698 if (id.isString() && obj->is<PlainObject>() &&
3699 MOZ_LIKELY(!obj->as<PlainObject>().contains(context(), id))) {
3700 return AddDataPropertyToPlainObject(context(), obj.as<PlainObject>(), id,
3701 val);
3704 // Fast path for adding an array element. The index shouldn't exceed the
3705 // array's length, but we check for this in `addDenseElementNoLengthChange` to
3706 // guard against corrupt or malicious data.
3707 if (id.isInt() && obj->is<ArrayObject>()) {
3708 ArrayObject* arr = &obj->as<ArrayObject>();
3709 switch (arr->addDenseElementNoLengthChange(context(), id.toInt(), val)) {
3710 case DenseElementResult::Failure:
3711 return false;
3712 case DenseElementResult::Success:
3713 return true;
3714 case DenseElementResult::Incomplete:
3715 // Fall-through to slow path.
3716 break;
3720 return DefineDataProperty(context(), obj, id, val);
3723 // Perform the whole recursive reading procedure.
3724 bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
3725 auto startTime = mozilla::TimeStamp::Now();
3727 if (!readHeader()) {
3728 return false;
3731 if (!readTransferMap()) {
3732 return false;
3735 MOZ_ASSERT(objs.length() == 0);
3736 MOZ_ASSERT(objState.length() == 1);
3738 // Start out by reading in the main object and pushing it onto the 'objs'
3739 // stack. The data related to this object and its descendants extends from
3740 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3741 if (!startRead(vp)) {
3742 return false;
3745 // Stop when the stack shows that all objects have been read.
3746 while (objs.length() != 0) {
3747 // What happens depends on the top obj on the objs stack.
3748 RootedObject obj(context(), &objs.back().toObject());
3750 uint32_t tag, data;
3751 if (!in.getPair(&tag, &data)) {
3752 return false;
3755 if (tag == SCTAG_END_OF_KEYS) {
3756 // Pop the current obj off the stack, since we are done with it and
3757 // its children.
3758 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3759 objs.popBack();
3760 if (objState.back().first == obj) {
3761 objState.popBack();
3763 continue;
3766 // Remember the index of the current top of the state stack, which will
3767 // correspond to the state for `obj` iff `obj` is a type that uses state.
3768 // startRead() may push additional entries before the state is accessed and
3769 // updated while filling in the object's data.
3770 size_t objStateIdx = objState.length() - 1;
3772 // The input stream contains a sequence of "child" values, whose
3773 // interpretation depends on the type of obj. These values can be
3774 // anything, and startRead() will push onto 'objs' for any non-leaf
3775 // value (i.e., anything that may contain children).
3777 // startRead() will allocate the (empty) object, but note that when
3778 // startRead() returns, 'key' is not yet initialized with any of its
3779 // properties. Those will be filled in by returning to the head of this
3780 // loop, processing the first child obj, and continuing until all
3781 // children have been fully created.
3783 // Note that this means the ordering in the stream is a little funky for
3784 // things like Map. See the comment above traverseMap() for an example.
3786 bool expectKeyValuePairs =
3787 !(obj->is<MapObject>() || obj->is<SetObject>() ||
3788 obj->is<SavedFrame>() || obj->is<ErrorObject>());
3790 RootedValue key(context());
3791 ShouldAtomizeStrings atomize =
3792 expectKeyValuePairs ? AtomizeStrings : DontAtomizeStrings;
3793 if (!startRead(&key, atomize)) {
3794 return false;
3797 if (key.isNull() && expectKeyValuePairs) {
3798 // Backwards compatibility: Null formerly indicated the end of
3799 // object properties.
3801 // No legacy objects used the state stack.
3802 MOZ_ASSERT(objState[objStateIdx].first() != obj);
3804 objs.popBack();
3805 continue;
3808 context()->check(key);
3810 if (obj->is<SetObject>()) {
3811 // Set object: the values between obj header (from startRead()) and
3812 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3813 if (!SetObject::add(context(), obj, key)) {
3814 return false;
3816 } else if (obj->is<MapObject>()) {
3817 Rooted<MapObject*> mapObj(context(), &obj->as<MapObject>());
3818 if (!readMapField(mapObj, key)) {
3819 return false;
3821 } else if (obj->is<SavedFrame>()) {
3822 Rooted<SavedFrame*> frameObj(context(), &obj->as<SavedFrame>());
3823 MOZ_ASSERT(objState[objStateIdx].first() == obj);
3824 bool state = objState[objStateIdx].second();
3825 if (!readSavedFrameFields(frameObj, key, &state)) {
3826 return false;
3828 objState[objStateIdx].second() = state;
3829 } else if (obj->is<ErrorObject>()) {
3830 Rooted<ErrorObject*> errorObj(context(), &obj->as<ErrorObject>());
3831 MOZ_ASSERT(objState[objStateIdx].first() == obj);
3832 bool state = objState[objStateIdx].second();
3833 if (!readErrorFields(errorObj, key, &state)) {
3834 return false;
3836 objState[objStateIdx].second() = state;
3837 } else {
3838 MOZ_ASSERT(expectKeyValuePairs);
3839 // Everything else uses a series of key,value,key,value,... Value
3840 // objects.
3841 if (!readObjectField(obj, key)) {
3842 return false;
3847 allObjs.clear();
3849 // For fuzzing, it is convenient to allow extra data at the end
3850 // of the input buffer so that more possible inputs are considered
3851 // valid.
3852 #ifndef FUZZING
3853 bool extraData;
3854 if (tailStartPos.isSome()) {
3855 // in.tell() is the end of the main data. If "tail" data was consumed,
3856 // then check whether there's any data between the main data and the
3857 // beginning of the tail, or after the last read point in the tail.
3858 extraData = (in.tell() != *tailStartPos || !tailEndPos->done());
3859 } else {
3860 extraData = !in.tell().done();
3862 if (extraData) {
3863 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3864 JSMSG_SC_BAD_SERIALIZED_DATA,
3865 "extra data after end");
3866 return false;
3868 #endif
3870 JSRuntime* rt = context()->runtime();
3871 rt->metrics().DESERIALIZE_BYTES(nbytes);
3872 rt->metrics().DESERIALIZE_ITEMS(numItemsRead);
3873 mozilla::TimeDuration elapsed = mozilla::TimeStamp::Now() - startTime;
3874 rt->metrics().DESERIALIZE_US(elapsed);
3876 return true;
3879 JS_PUBLIC_API bool JS_ReadStructuredClone(
3880 JSContext* cx, const JSStructuredCloneData& buf, uint32_t version,
3881 JS::StructuredCloneScope scope, MutableHandleValue vp,
3882 const JS::CloneDataPolicy& cloneDataPolicy,
3883 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3884 AssertHeapIsIdle();
3885 CHECK_THREAD(cx);
3887 if (version > JS_STRUCTURED_CLONE_VERSION) {
3888 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3889 JSMSG_SC_BAD_CLONE_VERSION);
3890 return false;
3892 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3893 return ReadStructuredClone(cx, buf, scope, vp, cloneDataPolicy, callbacks,
3894 closure);
3897 JS_PUBLIC_API bool JS_WriteStructuredClone(
3898 JSContext* cx, HandleValue value, JSStructuredCloneData* bufp,
3899 JS::StructuredCloneScope scope, const JS::CloneDataPolicy& cloneDataPolicy,
3900 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure,
3901 HandleValue transferable) {
3902 AssertHeapIsIdle();
3903 CHECK_THREAD(cx);
3904 cx->check(value);
3906 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3907 return WriteStructuredClone(cx, value, bufp, scope, cloneDataPolicy,
3908 callbacks, closure, transferable);
3911 JS_PUBLIC_API bool JS_StructuredCloneHasTransferables(
3912 JSStructuredCloneData& data, bool* hasTransferable) {
3913 *hasTransferable = StructuredCloneHasTransferObjects(data);
3914 return true;
3917 JS_PUBLIC_API bool JS_StructuredClone(
3918 JSContext* cx, HandleValue value, MutableHandleValue vp,
3919 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3920 AssertHeapIsIdle();
3921 CHECK_THREAD(cx);
3923 // Strings are associated with zones, not compartments,
3924 // so we copy the string by wrapping it.
3925 if (value.isString()) {
3926 RootedString strValue(cx, value.toString());
3927 if (!cx->compartment()->wrap(cx, &strValue)) {
3928 return false;
3930 vp.setString(strValue);
3931 return true;
3934 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3936 JSAutoStructuredCloneBuffer buf(JS::StructuredCloneScope::SameProcess,
3937 callbacks, closure);
3939 if (value.isObject()) {
3940 RootedObject obj(cx, &value.toObject());
3941 obj = CheckedUnwrapStatic(obj);
3942 if (!obj) {
3943 ReportAccessDenied(cx);
3944 return false;
3946 AutoRealm ar(cx, obj);
3947 RootedValue unwrappedVal(cx, ObjectValue(*obj));
3948 if (!buf.write(cx, unwrappedVal, callbacks, closure)) {
3949 return false;
3951 } else {
3952 if (!buf.write(cx, value, callbacks, closure)) {
3953 return false;
3958 return buf.read(cx, vp, JS::CloneDataPolicy(), callbacks, closure);
3961 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3962 JSAutoStructuredCloneBuffer&& other)
3963 : data_(other.scope()) {
3964 version_ = other.version_;
3965 other.giveTo(&data_);
3968 JSAutoStructuredCloneBuffer& JSAutoStructuredCloneBuffer::operator=(
3969 JSAutoStructuredCloneBuffer&& other) {
3970 MOZ_ASSERT(&other != this);
3971 MOZ_ASSERT(scope() == other.scope());
3972 clear();
3973 version_ = other.version_;
3974 other.giveTo(&data_);
3975 return *this;
3978 void JSAutoStructuredCloneBuffer::clear() {
3979 data_.discardTransferables();
3980 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
3981 data_.refsHeld_.releaseAll();
3982 data_.Clear();
3983 version_ = 0;
3986 void JSAutoStructuredCloneBuffer::adopt(
3987 JSStructuredCloneData&& data, uint32_t version,
3988 const JSStructuredCloneCallbacks* callbacks, void* closure) {
3989 clear();
3990 data_ = std::move(data);
3991 version_ = version;
3992 data_.setCallbacks(callbacks, closure,
3993 OwnTransferablePolicy::OwnsTransferablesIfAny);
3996 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData* data) {
3997 *data = std::move(data_);
3998 version_ = 0;
3999 data_.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables);
4000 data_.Clear();
4003 bool JSAutoStructuredCloneBuffer::read(
4004 JSContext* cx, MutableHandleValue vp,
4005 const JS::CloneDataPolicy& cloneDataPolicy,
4006 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4007 MOZ_ASSERT(cx);
4008 return !!JS_ReadStructuredClone(
4009 cx, data_, version_, data_.scope(), vp, cloneDataPolicy,
4010 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4011 optionalCallbacks ? closure : data_.closure_);
4014 bool JSAutoStructuredCloneBuffer::write(
4015 JSContext* cx, HandleValue value,
4016 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4017 HandleValue transferable = UndefinedHandleValue;
4018 return write(cx, value, transferable, JS::CloneDataPolicy(),
4019 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4020 optionalCallbacks ? closure : data_.closure_);
4023 bool JSAutoStructuredCloneBuffer::write(
4024 JSContext* cx, HandleValue value, HandleValue transferable,
4025 const JS::CloneDataPolicy& cloneDataPolicy,
4026 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4027 clear();
4028 bool ok = JS_WriteStructuredClone(
4029 cx, value, &data_, data_.scopeForInternalWriting(), cloneDataPolicy,
4030 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4031 optionalCallbacks ? closure : data_.closure_, transferable);
4032 if (!ok) {
4033 version_ = JS_STRUCTURED_CLONE_VERSION;
4035 return ok;
4038 JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1,
4039 uint32_t* p2) {
4040 return r->input().readPair((uint32_t*)p1, (uint32_t*)p2);
4043 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p,
4044 size_t len) {
4045 return r->input().readBytes(p, len);
4048 JS_PUBLIC_API bool JS_ReadString(JSStructuredCloneReader* r,
4049 MutableHandleString str) {
4050 uint32_t tag, data;
4051 if (!r->input().readPair(&tag, &data)) {
4052 return false;
4055 if (tag == SCTAG_STRING) {
4056 if (JSString* s =
4057 r->readString(data, JSStructuredCloneReader::DontAtomizeStrings)) {
4058 str.set(s);
4059 return true;
4061 return false;
4064 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
4065 JSMSG_SC_BAD_SERIALIZED_DATA, "expected string");
4066 return false;
4069 JS_PUBLIC_API bool JS_ReadDouble(JSStructuredCloneReader* r, double* v) {
4070 return r->input().readDouble(v);
4073 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r,
4074 MutableHandleValue vp) {
4075 uint32_t tag, data;
4076 if (!r->input().readPair(&tag, &data)) {
4077 return false;
4080 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
4081 return r->readTypedArray(TagToV1ArrayType(tag), data, vp, true);
4084 if (tag == SCTAG_TYPED_ARRAY_OBJECT_V2) {
4085 // V2 stores the length (nelems) in |data| and the arrayType separately.
4086 uint64_t arrayType;
4087 if (!r->input().read(&arrayType)) {
4088 return false;
4090 uint64_t nelems = data;
4091 return r->readTypedArray(arrayType, nelems, vp);
4094 if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
4095 // The current version stores the array type in |data| and the length
4096 // (nelems) separately to support large TypedArrays.
4097 uint32_t arrayType = data;
4098 uint64_t nelems;
4099 if (!r->input().read(&nelems)) {
4100 return false;
4102 return r->readTypedArray(arrayType, nelems, vp);
4105 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
4106 JSMSG_SC_BAD_SERIALIZED_DATA,
4107 "expected type array");
4108 return false;
4111 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag,
4112 uint32_t data) {
4113 return w->output().writePair(tag, data);
4116 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p,
4117 size_t len) {
4118 return w->output().writeBytes(p, len);
4121 JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w,
4122 HandleString str) {
4123 return w->writeString(SCTAG_STRING, str);
4126 JS_PUBLIC_API bool JS_WriteDouble(JSStructuredCloneWriter* w, double v) {
4127 return w->output().writeDouble(v);
4130 JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w,
4131 HandleValue v) {
4132 MOZ_ASSERT(v.isObject());
4133 w->context()->check(v);
4134 RootedObject obj(w->context(), &v.toObject());
4136 // startWrite can write everything, thus we should check here
4137 // and report error if the user passes a wrong type.
4138 if (!obj->canUnwrapAs<TypedArrayObject>()) {
4139 ReportAccessDenied(w->context());
4140 return false;
4143 // We should use startWrite instead of writeTypedArray, because
4144 // typed array is an object, we should add it to the |memory|
4145 // (allObjs) list. Directly calling writeTypedArray won't add it.
4146 return w->startWrite(v);
4149 JS_PUBLIC_API bool JS_ObjectNotWritten(JSStructuredCloneWriter* w,
4150 HandleObject obj) {
4151 w->memory.remove(w->memory.lookup(obj));
4153 return true;
4156 JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope(
4157 JSStructuredCloneWriter* w) {
4158 return w->output().scope();