Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / StructuredClone.cpp
blob85ec4654ebe9371b1196abbf4d34bb4dffc4371b
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<FixedLengthTypedArrayObject*> tarr(
1336 context(), obj->maybeUnwrapAs<FixedLengthTypedArrayObject>());
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<FixedLengthDataViewObject*> view(
1373 context(), obj->maybeUnwrapAs<FixedLengthDataViewObject>());
1374 JSAutoRealm ar(context(), view);
1376 if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, 0)) {
1377 return false;
1380 uint64_t byteLength = view->byteLength();
1381 if (!out.write(byteLength)) {
1382 return false;
1385 // Write out the ArrayBuffer tag and contents
1386 RootedValue val(context(), view->bufferValue());
1387 if (!startWrite(val)) {
1388 return false;
1391 uint64_t byteOffset = view->byteOffset();
1392 return out.write(byteOffset);
1395 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) {
1396 Rooted<ArrayBufferObject*> buffer(context(),
1397 obj->maybeUnwrapAs<ArrayBufferObject>());
1398 JSAutoRealm ar(context(), buffer);
1400 // FIXME: Support structured cloning for resizable ArrayBuffers.
1401 if (buffer->isResizable()) {
1402 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
1403 return false;
1406 if (!out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, 0)) {
1407 return false;
1410 uint64_t byteLength = buffer->byteLength();
1411 if (!out.write(byteLength)) {
1412 return false;
1415 return out.writeBytes(buffer->dataPointer(), byteLength);
1418 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj) {
1419 MOZ_ASSERT(obj->canUnwrapAs<SharedArrayBufferObject>());
1421 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
1422 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
1423 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1424 : JS_SCERR_NOT_CLONABLE;
1425 reportDataCloneError(error, "SharedArrayBuffer");
1426 return false;
1429 output().sameProcessScopeRequired();
1431 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1432 // cross-process. The cloneDataPolicy should have guarded against this;
1433 // since it did not then throw, with a very explicit message.
1435 if (output().scope() > JS::StructuredCloneScope::SameProcess) {
1436 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1437 JSMSG_SC_SHMEM_POLICY);
1438 return false;
1441 Rooted<SharedArrayBufferObject*> sharedArrayBuffer(
1442 context(), obj->maybeUnwrapAs<SharedArrayBufferObject>());
1443 SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
1445 if (!out.buf.refsHeld_.acquire(context(), rawbuf)) {
1446 return false;
1449 // We must serialize the length so that the buffer object arrives in the
1450 // receiver with the same length, and not with the length read from the
1451 // rawbuf - that length can be different, and it can change at any time.
1453 intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
1454 uint64_t byteLength = sharedArrayBuffer->byteLength();
1455 if (!(out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
1456 static_cast<uint32_t>(sizeof(p))) &&
1457 out.writeBytes(&byteLength, sizeof(byteLength)) &&
1458 out.writeBytes(&p, sizeof(p)))) {
1459 return false;
1462 if (callbacks && callbacks->sabCloned &&
1463 !callbacks->sabCloned(context(), /*receiving=*/false, closure)) {
1464 return false;
1467 return true;
1470 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj) {
1471 MOZ_ASSERT(obj->canUnwrapAs<WasmMemoryObject>());
1473 // Check the policy here so that we can report a sane error.
1474 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
1475 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
1476 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1477 : JS_SCERR_NOT_CLONABLE;
1478 reportDataCloneError(error, "WebAssembly.Memory");
1479 return false;
1482 // If this changes, might need to change what we write.
1483 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS == 3);
1485 Rooted<WasmMemoryObject*> memoryObj(context(),
1486 &obj->unwrapAs<WasmMemoryObject>());
1487 Rooted<SharedArrayBufferObject*> sab(
1488 context(), &memoryObj->buffer().as<SharedArrayBufferObject>());
1490 return out.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT, 0) &&
1491 out.writePair(SCTAG_BOOLEAN, memoryObj->isHuge()) &&
1492 writeSharedArrayBuffer(sab);
1495 bool JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref) {
1496 // Handle cycles in the object graph.
1497 CloneMemory::AddPtr p = memory.lookupForAdd(obj);
1498 if ((*backref = p.found())) {
1499 return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
1501 if (!memory.add(p, obj, memory.count())) {
1502 ReportOutOfMemory(context());
1503 return false;
1506 if (memory.count() == UINT32_MAX) {
1507 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1508 JSMSG_NEED_DIET, "object graph to serialize");
1509 return false;
1512 return true;
1515 static bool TryAppendNativeProperties(JSContext* cx, HandleObject obj,
1516 MutableHandleIdVector entries,
1517 size_t* properties, bool* optimized) {
1518 *optimized = false;
1520 if (!obj->is<NativeObject>()) {
1521 return true;
1524 Handle<NativeObject*> nobj = obj.as<NativeObject>();
1525 if (nobj->isIndexed() || nobj->is<TypedArrayObject>() ||
1526 nobj->getClass()->getNewEnumerate() || nobj->getClass()->getEnumerate()) {
1527 return true;
1530 *optimized = true;
1532 size_t count = 0;
1533 // We iterate from the last to the first property, so the property names
1534 // are already in reverse order.
1535 for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
1536 jsid id = iter->key();
1538 // Ignore symbols and non-enumerable properties.
1539 if (!iter->enumerable() || id.isSymbol()) {
1540 continue;
1543 MOZ_ASSERT(id.isString());
1544 if (!entries.append(id)) {
1545 return false;
1548 count++;
1551 // Add dense element ids in reverse order.
1552 for (uint32_t i = nobj->getDenseInitializedLength(); i > 0; --i) {
1553 if (nobj->getDenseElement(i - 1).isMagic(JS_ELEMENTS_HOLE)) {
1554 continue;
1557 if (!entries.append(PropertyKey::Int(i - 1))) {
1558 return false;
1561 count++;
1564 *properties = count;
1565 return true;
1568 // Objects are written as a "preorder" traversal of the object graph: object
1569 // "headers" (the class tag and any data needed for initial construction) are
1570 // visited first, then the children are recursed through (where children are
1571 // properties, Set or Map entries, etc.). So for example
1573 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1575 // would be stored as:
1577 // <Object tag for obj1>
1578 // <key1 data>
1579 // <Object tag for key1's value>
1580 // <key1.1 data>
1581 // <val1.1 data>
1582 // <key1.2 data>
1583 // <val1.2 data>
1584 // <end-of-children marker for key1's value>
1585 // <key2 data>
1586 // <Object tag for key2's value>
1587 // <end-of-children marker for key2's value>
1588 // <end-of-children marker for obj1>
1590 // This nests nicely (ie, an entire recursive value starts with its tag and
1591 // ends with its end-of-children marker) and so it can be presented indented.
1592 // But see traverseMap below for how this looks different for Maps.
1593 bool JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls) {
1594 size_t count;
1595 bool optimized = false;
1596 if (!js::SupportDifferentialTesting()) {
1597 if (!TryAppendNativeProperties(context(), obj, &objectEntries, &count,
1598 &optimized)) {
1599 return false;
1603 if (!optimized) {
1604 // Get enumerable property ids and put them in reverse order so that they
1605 // will come off the stack in forward order.
1606 RootedIdVector properties(context());
1607 if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties)) {
1608 return false;
1611 for (size_t i = properties.length(); i > 0; --i) {
1612 jsid id = properties[i - 1];
1614 MOZ_ASSERT(id.isString() || id.isInt());
1615 if (!objectEntries.append(id)) {
1616 return false;
1620 count = properties.length();
1623 // Push obj and count to the stack.
1624 if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) {
1625 return false;
1628 checkStack();
1630 #if DEBUG
1631 ESClass cls2;
1632 if (!GetBuiltinClass(context(), obj, &cls2)) {
1633 return false;
1635 MOZ_ASSERT(cls2 == cls);
1636 #endif
1638 // Write the header for obj.
1639 if (cls == ESClass::Array) {
1640 uint32_t length = 0;
1641 if (!JS::GetArrayLength(context(), obj, &length)) {
1642 return false;
1645 return out.writePair(SCTAG_ARRAY_OBJECT,
1646 NativeEndian::swapToLittleEndian(length));
1649 return out.writePair(SCTAG_OBJECT_OBJECT, 0);
1652 // Use the same basic setup as for traverseObject, but now keys can themselves
1653 // be complex objects. Keys and values are visited first via startWrite(), then
1654 // the key's children (if any) are handled, then the value's children.
1656 // m = new Map();
1657 // m.set(key1 = ..., value1 = ...)
1659 // where key1 and value2 are both objects would be stored as
1661 // <Map tag>
1662 // <key1 class tag>
1663 // <value1 class tag>
1664 // ...key1 fields...
1665 // <end-of-children marker for key1>
1666 // ...value1 fields...
1667 // <end-of-children marker for value1>
1668 // <end-of-children marker for Map>
1670 // Notice how the end-of-children marker for key1 is sandwiched between the
1671 // value1 beginning and end.
1672 bool JSStructuredCloneWriter::traverseMap(HandleObject obj) {
1673 Rooted<GCVector<Value>> newEntries(context(), GCVector<Value>(context()));
1675 // If there is no wrapper, the compartment munging is a no-op.
1676 RootedObject unwrapped(context(), obj->maybeUnwrapAs<MapObject>());
1677 MOZ_ASSERT(unwrapped);
1678 JSAutoRealm ar(context(), unwrapped);
1679 if (!MapObject::getKeysAndValuesInterleaved(unwrapped, &newEntries)) {
1680 return false;
1683 if (!context()->compartment()->wrap(context(), &newEntries)) {
1684 return false;
1687 for (size_t i = newEntries.length(); i > 0; --i) {
1688 if (!otherEntries.append(newEntries[i - 1])) {
1689 return false;
1693 // Push obj and count to the stack.
1694 if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length())) {
1695 return false;
1698 checkStack();
1700 // Write the header for obj.
1701 return out.writePair(SCTAG_MAP_OBJECT, 0);
1704 // Similar to traverseMap, only there is a single value instead of a key and
1705 // value, and thus no interleaving is possible: a value will be fully emitted
1706 // before the next value is begun.
1707 bool JSStructuredCloneWriter::traverseSet(HandleObject obj) {
1708 Rooted<GCVector<Value>> keys(context(), GCVector<Value>(context()));
1710 // If there is no wrapper, the compartment munging is a no-op.
1711 RootedObject unwrapped(context(), obj->maybeUnwrapAs<SetObject>());
1712 MOZ_ASSERT(unwrapped);
1713 JSAutoRealm ar(context(), unwrapped);
1714 if (!SetObject::keys(context(), unwrapped, &keys)) {
1715 return false;
1718 if (!context()->compartment()->wrap(context(), &keys)) {
1719 return false;
1722 for (size_t i = keys.length(); i > 0; --i) {
1723 if (!otherEntries.append(keys[i - 1])) {
1724 return false;
1728 // Push obj and count to the stack.
1729 if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length())) {
1730 return false;
1733 checkStack();
1735 // Write the header for obj.
1736 return out.writePair(SCTAG_SET_OBJECT, 0);
1739 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj) {
1740 Rooted<SavedFrame*> savedFrame(context(), obj->maybeUnwrapAs<SavedFrame>());
1741 MOZ_ASSERT(savedFrame);
1743 RootedObject parent(context(), savedFrame->getParent());
1744 if (!context()->compartment()->wrap(context(), &parent)) {
1745 return false;
1748 if (!objs.append(ObjectValue(*obj)) ||
1749 !otherEntries.append(parent ? ObjectValue(*parent) : NullValue()) ||
1750 !counts.append(1)) {
1751 return false;
1754 checkStack();
1756 // Write the SavedFrame tag and the SavedFrame's principals.
1758 if (savedFrame->getPrincipals() ==
1759 &ReconstructedSavedFramePrincipals::IsSystem) {
1760 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1761 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM)) {
1762 return false;
1764 } else if (savedFrame->getPrincipals() ==
1765 &ReconstructedSavedFramePrincipals::IsNotSystem) {
1766 if (!out.writePair(
1767 SCTAG_SAVED_FRAME_OBJECT,
1768 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM)) {
1769 return false;
1771 } else {
1772 if (auto principals = savedFrame->getPrincipals()) {
1773 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
1774 !principals->write(context(), this)) {
1775 return false;
1777 } else {
1778 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS)) {
1779 return false;
1784 // Write the SavedFrame's reserved slots, except for the parent, which is
1785 // queued on objs for further traversal.
1787 RootedValue val(context());
1789 val = BooleanValue(savedFrame->getMutedErrors());
1790 if (!writePrimitive(val)) {
1791 return false;
1794 context()->markAtom(savedFrame->getSource());
1795 val = StringValue(savedFrame->getSource());
1796 if (!writePrimitive(val)) {
1797 return false;
1800 val = NumberValue(savedFrame->getLine());
1801 if (!writePrimitive(val)) {
1802 return false;
1805 val = NumberValue(*savedFrame->getColumn().addressOfValueForTranscode());
1806 if (!writePrimitive(val)) {
1807 return false;
1810 auto name = savedFrame->getFunctionDisplayName();
1811 if (name) {
1812 context()->markAtom(name);
1814 val = name ? StringValue(name) : NullValue();
1815 if (!writePrimitive(val)) {
1816 return false;
1819 auto cause = savedFrame->getAsyncCause();
1820 if (cause) {
1821 context()->markAtom(cause);
1823 val = cause ? StringValue(cause) : NullValue();
1824 if (!writePrimitive(val)) {
1825 return false;
1828 return true;
1831 // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
1832 // 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
1834 // Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
1835 // value is not a platform object, then:
1837 // Note: This contains custom extensions for handling non-standard properties.
1838 bool JSStructuredCloneWriter::traverseError(HandleObject obj) {
1839 JSContext* cx = context();
1841 // 1. Let name be ? Get(value, "name").
1842 RootedValue name(cx);
1843 if (!GetProperty(cx, obj, obj, cx->names().name, &name)) {
1844 return false;
1847 // 2. If name is not one of "Error", "EvalError", "RangeError",
1848 // "ReferenceError", "SyntaxError", "TypeError", or "URIError",
1849 // (not yet specified: or "AggregateError")
1850 // then set name to "Error".
1851 JSExnType type = JSEXN_ERR;
1852 if (name.isString()) {
1853 JSLinearString* linear = name.toString()->ensureLinear(cx);
1854 if (!linear) {
1855 return false;
1858 if (EqualStrings(linear, cx->names().Error)) {
1859 type = JSEXN_ERR;
1860 } else if (EqualStrings(linear, cx->names().EvalError)) {
1861 type = JSEXN_EVALERR;
1862 } else if (EqualStrings(linear, cx->names().RangeError)) {
1863 type = JSEXN_RANGEERR;
1864 } else if (EqualStrings(linear, cx->names().ReferenceError)) {
1865 type = JSEXN_REFERENCEERR;
1866 } else if (EqualStrings(linear, cx->names().SyntaxError)) {
1867 type = JSEXN_SYNTAXERR;
1868 } else if (EqualStrings(linear, cx->names().TypeError)) {
1869 type = JSEXN_TYPEERR;
1870 } else if (EqualStrings(linear, cx->names().URIError)) {
1871 type = JSEXN_URIERR;
1872 } else if (EqualStrings(linear, cx->names().AggregateError)) {
1873 type = JSEXN_AGGREGATEERR;
1877 // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
1878 RootedId messageId(cx, NameToId(cx->names().message));
1879 Rooted<Maybe<PropertyDescriptor>> messageDesc(cx);
1880 if (!GetOwnPropertyDescriptor(cx, obj, messageId, &messageDesc)) {
1881 return false;
1884 // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
1885 // and ? ToString(valueMessageDesc.[[Value]]) otherwise.
1886 RootedString message(cx);
1887 if (messageDesc.isSome() && messageDesc->isDataDescriptor()) {
1888 RootedValue messageVal(cx, messageDesc->value());
1889 message = ToString<CanGC>(cx, messageVal);
1890 if (!message) {
1891 return false;
1895 // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
1896 // message }.
1898 if (!objs.append(ObjectValue(*obj))) {
1899 return false;
1902 Rooted<ErrorObject*> unwrapped(cx, obj->maybeUnwrapAs<ErrorObject>());
1903 MOZ_ASSERT(unwrapped);
1905 // Non-standard: Serialize |stack|.
1906 // The Error stack property is saved as SavedFrames.
1907 RootedValue stack(cx, NullValue());
1908 RootedObject stackObj(cx, unwrapped->stack());
1909 if (stackObj && stackObj->canUnwrapAs<SavedFrame>()) {
1910 stack.setObject(*stackObj);
1911 if (!cx->compartment()->wrap(cx, &stack)) {
1912 return false;
1915 if (!otherEntries.append(stack)) {
1916 return false;
1919 // Serialize |errors|
1920 if (type == JSEXN_AGGREGATEERR) {
1921 RootedValue errors(cx);
1922 if (!GetProperty(cx, obj, obj, cx->names().errors, &errors)) {
1923 return false;
1925 if (!otherEntries.append(errors)) {
1926 return false;
1928 } else {
1929 if (!otherEntries.append(NullValue())) {
1930 return false;
1934 // Non-standard: Serialize |cause|. Because this property
1935 // might be missing we also write "hasCause" later.
1936 RootedId causeId(cx, NameToId(cx->names().cause));
1937 Rooted<Maybe<PropertyDescriptor>> causeDesc(cx);
1938 if (!GetOwnPropertyDescriptor(cx, obj, causeId, &causeDesc)) {
1939 return false;
1942 Rooted<Maybe<Value>> cause(cx);
1943 if (causeDesc.isSome() && causeDesc->isDataDescriptor()) {
1944 cause = mozilla::Some(causeDesc->value());
1946 if (!cx->compartment()->wrap(cx, &cause)) {
1947 return false;
1949 if (!otherEntries.append(cause.get().valueOr(NullValue()))) {
1950 return false;
1953 // |cause| + |errors| + |stack|, pushed in reverse order
1954 if (!counts.append(3)) {
1955 return false;
1958 checkStack();
1960 if (!out.writePair(SCTAG_ERROR_OBJECT, type)) {
1961 return false;
1964 RootedValue val(cx, message ? StringValue(message) : NullValue());
1965 if (!writePrimitive(val)) {
1966 return false;
1969 // hasCause
1970 val = BooleanValue(cause.isSome());
1971 if (!writePrimitive(val)) {
1972 return false;
1975 // Non-standard: Also serialize fileName, lineNumber and columnNumber.
1977 JSAutoRealm ar(cx, unwrapped);
1978 val = StringValue(unwrapped->fileName(cx));
1980 if (!cx->compartment()->wrap(cx, &val) || !writePrimitive(val)) {
1981 return false;
1984 val = Int32Value(unwrapped->lineNumber());
1985 if (!writePrimitive(val)) {
1986 return false;
1989 val = Int32Value(*unwrapped->columnNumber().addressOfValueForTranscode());
1990 return writePrimitive(val);
1993 bool JSStructuredCloneWriter::writePrimitive(HandleValue v) {
1994 MOZ_ASSERT(v.isPrimitive());
1995 context()->check(v);
1997 if (v.isString()) {
1998 return writeString(SCTAG_STRING, v.toString());
1999 } else if (v.isInt32()) {
2000 if (js::SupportDifferentialTesting()) {
2001 return out.writeDouble(v.toInt32());
2003 return out.writePair(SCTAG_INT32, v.toInt32());
2004 } else if (v.isDouble()) {
2005 return out.writeDouble(v.toDouble());
2006 } else if (v.isBoolean()) {
2007 return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
2008 } else if (v.isNull()) {
2009 return out.writePair(SCTAG_NULL, 0);
2010 } else if (v.isUndefined()) {
2011 return out.writePair(SCTAG_UNDEFINED, 0);
2012 } else if (v.isBigInt()) {
2013 return writeBigInt(SCTAG_BIGINT, v.toBigInt());
2016 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2019 bool JSStructuredCloneWriter::startWrite(HandleValue v) {
2020 context()->check(v);
2022 if (v.isPrimitive()) {
2023 return writePrimitive(v);
2026 if (!v.isObject()) {
2027 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2030 RootedObject obj(context(), &v.toObject());
2032 bool backref;
2033 if (!startObject(obj, &backref)) {
2034 return false;
2036 if (backref) {
2037 return true;
2040 ESClass cls;
2041 if (!GetBuiltinClass(context(), obj, &cls)) {
2042 return false;
2045 switch (cls) {
2046 case ESClass::Object:
2047 case ESClass::Array:
2048 return traverseObject(obj, cls);
2049 case ESClass::Number: {
2050 RootedValue unboxed(context());
2051 if (!Unbox(context(), obj, &unboxed)) {
2052 return false;
2054 return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
2055 out.writeDouble(unboxed.toNumber());
2057 case ESClass::String: {
2058 RootedValue unboxed(context());
2059 if (!Unbox(context(), obj, &unboxed)) {
2060 return false;
2062 return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
2064 case ESClass::Boolean: {
2065 RootedValue unboxed(context());
2066 if (!Unbox(context(), obj, &unboxed)) {
2067 return false;
2069 return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
2071 case ESClass::RegExp: {
2072 RegExpShared* re = RegExpToShared(context(), obj);
2073 if (!re) {
2074 return false;
2076 return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags().value()) &&
2077 writeString(SCTAG_STRING, re->getSource());
2079 case ESClass::ArrayBuffer: {
2080 if (JS::IsArrayBufferObject(obj) && JS::ArrayBufferHasData(obj)) {
2081 return writeArrayBuffer(obj);
2083 break;
2085 case ESClass::SharedArrayBuffer:
2086 if (JS::IsSharedArrayBufferObject(obj)) {
2087 return writeSharedArrayBuffer(obj);
2089 break;
2090 case ESClass::Date: {
2091 RootedValue unboxed(context());
2092 if (!Unbox(context(), obj, &unboxed)) {
2093 return false;
2095 return out.writePair(SCTAG_DATE_OBJECT, 0) &&
2096 out.writeDouble(unboxed.toNumber());
2098 case ESClass::Set:
2099 return traverseSet(obj);
2100 case ESClass::Map:
2101 return traverseMap(obj);
2102 case ESClass::Error:
2103 return traverseError(obj);
2104 case ESClass::BigInt: {
2105 RootedValue unboxed(context());
2106 if (!Unbox(context(), obj, &unboxed)) {
2107 return false;
2109 return writeBigInt(SCTAG_BIGINT_OBJECT, unboxed.toBigInt());
2111 case ESClass::Promise:
2112 case ESClass::MapIterator:
2113 case ESClass::SetIterator:
2114 case ESClass::Arguments:
2115 case ESClass::Function:
2116 break;
2118 #ifdef ENABLE_RECORD_TUPLE
2119 case ESClass::Record:
2120 case ESClass::Tuple:
2121 MOZ_CRASH("Record and Tuple are not supported");
2122 #endif
2124 case ESClass::Other: {
2125 if (obj->canUnwrapAs<FixedLengthTypedArrayObject>()) {
2126 return writeTypedArray(obj);
2128 if (obj->canUnwrapAs<ResizableTypedArrayObject>()) {
2129 // TODO(anba): support resizable.
2130 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2132 if (obj->canUnwrapAs<FixedLengthDataViewObject>()) {
2133 return writeDataView(obj);
2135 if (obj->canUnwrapAs<ResizableDataViewObject>()) {
2136 // TODO(anba): support resizable.
2137 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2139 if (wasm::IsSharedWasmMemoryObject(obj)) {
2140 return writeSharedWasmMemory(obj);
2142 if (obj->canUnwrapAs<SavedFrame>()) {
2143 return traverseSavedFrame(obj);
2145 break;
2149 if (out.buf.callbacks_ && out.buf.callbacks_->write) {
2150 bool sameProcessScopeRequired = false;
2151 if (!out.buf.callbacks_->write(context(), this, obj,
2152 &sameProcessScopeRequired,
2153 out.buf.closure_)) {
2154 return false;
2157 if (sameProcessScopeRequired) {
2158 output().sameProcessScopeRequired();
2161 return true;
2164 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2167 bool JSStructuredCloneWriter::writeHeader() {
2168 return out.writePair(SCTAG_HEADER, (uint32_t)output().scope());
2171 bool JSStructuredCloneWriter::writeTransferMap() {
2172 if (transferableObjects.empty()) {
2173 return true;
2176 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) {
2177 return false;
2180 if (!out.write(transferableObjects.length())) {
2181 return false;
2184 RootedObject obj(context());
2185 for (auto* o : transferableObjects) {
2186 obj = o;
2187 if (!memory.put(obj, memory.count())) {
2188 ReportOutOfMemory(context());
2189 return false;
2192 // Emit a placeholder pointer. We defer stealing the data until later
2193 // (and, if necessary, detaching this object if it's an ArrayBuffer).
2194 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY,
2195 JS::SCTAG_TMO_UNFILLED)) {
2196 return false;
2198 if (!out.write(0)) { // Pointer to ArrayBuffer contents.
2199 return false;
2201 if (!out.write(0)) { // extraData
2202 return false;
2206 return true;
2209 bool JSStructuredCloneWriter::transferOwnership() {
2210 if (transferableObjects.empty()) {
2211 return true;
2214 // Walk along the transferables and the transfer map at the same time,
2215 // grabbing out pointers from the transferables and stuffing them into the
2216 // transfer map.
2217 auto point = out.iter();
2218 MOZ_RELEASE_ASSERT(point.canPeek());
2219 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
2220 SCTAG_HEADER);
2221 point++;
2222 MOZ_RELEASE_ASSERT(point.canPeek());
2223 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
2224 SCTAG_TRANSFER_MAP_HEADER);
2225 point++;
2226 MOZ_RELEASE_ASSERT(point.canPeek());
2227 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) ==
2228 transferableObjects.length());
2229 point++;
2231 JSContext* cx = context();
2232 RootedObject obj(cx);
2233 JS::StructuredCloneScope scope = output().scope();
2234 for (auto* o : transferableObjects) {
2235 obj = o;
2237 uint32_t tag;
2238 JS::TransferableOwnership ownership;
2239 void* content;
2240 uint64_t extraData;
2242 #if DEBUG
2243 SCInput::getPair(point.peek(), &tag, (uint32_t*)&ownership);
2244 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2245 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
2246 #endif
2248 ESClass cls;
2249 if (!GetBuiltinClass(cx, obj, &cls)) {
2250 return false;
2253 if (cls == ESClass::ArrayBuffer) {
2254 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
2256 // The current setup of the array buffer inheritance hierarchy doesn't
2257 // lend itself well to generic manipulation via proxies.
2258 Rooted<ArrayBufferObject*> arrayBuffer(
2259 cx, obj->maybeUnwrapAs<ArrayBufferObject>());
2260 JSAutoRealm ar(cx, arrayBuffer);
2262 if (arrayBuffer->isDetached()) {
2263 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED);
2264 return false;
2267 if (arrayBuffer->isPreparedForAsmJS()) {
2268 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER);
2269 return false;
2272 // FIXME: Support structured cloning for resizable ArrayBuffers.
2273 if (arrayBuffer->isResizable()) {
2274 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2275 return false;
2278 if (scope == JS::StructuredCloneScope::DifferentProcess ||
2279 scope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
2280 // Write Transferred ArrayBuffers in DifferentProcess scope at
2281 // the end of the clone buffer, and store the offset within the
2282 // buffer to where the ArrayBuffer was written. Note that this
2283 // will invalidate the current position iterator.
2285 size_t pointOffset = out.offset(point);
2286 tag = SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER;
2287 ownership = JS::SCTAG_TMO_UNOWNED;
2288 content = nullptr;
2289 extraData = out.tell() -
2290 pointOffset; // Offset from tag to current end of buffer
2291 if (!writeArrayBuffer(arrayBuffer)) {
2292 ReportOutOfMemory(cx);
2293 return false;
2296 // Must refresh the point iterator after its collection has
2297 // been modified.
2298 point = out.iter();
2299 point += pointOffset;
2301 if (!JS::DetachArrayBuffer(cx, arrayBuffer)) {
2302 return false;
2304 } else {
2305 size_t nbytes = arrayBuffer->byteLength();
2307 using BufferContents = ArrayBufferObject::BufferContents;
2309 BufferContents bufContents =
2310 ArrayBufferObject::extractStructuredCloneContents(cx, arrayBuffer);
2311 if (!bufContents) {
2312 return false; // out of memory
2315 content = bufContents.data();
2316 if (bufContents.kind() == ArrayBufferObject::MAPPED) {
2317 ownership = JS::SCTAG_TMO_MAPPED_DATA;
2318 } else {
2319 MOZ_ASSERT(
2320 bufContents.kind() ==
2321 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
2322 bufContents.kind() ==
2323 ArrayBufferObject::MALLOCED_UNKNOWN_ARENA,
2324 "failing to handle new ArrayBuffer kind?");
2325 ownership = JS::SCTAG_TMO_ALLOC_DATA;
2327 extraData = nbytes;
2329 } else {
2330 if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) {
2331 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
2333 if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag,
2334 &ownership, &content,
2335 &extraData)) {
2336 return false;
2338 MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2341 point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
2342 MOZ_ALWAYS_TRUE(point.advance());
2343 point.write(
2344 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content)));
2345 MOZ_ALWAYS_TRUE(point.advance());
2346 point.write(NativeEndian::swapToLittleEndian(extraData));
2347 MOZ_ALWAYS_TRUE(point.advance());
2350 #if DEBUG
2351 // Make sure there aren't any more transfer map entries after the expected
2352 // number we read out.
2353 if (!point.done()) {
2354 uint32_t tag, data;
2355 SCInput::getPair(point.peek(), &tag, &data);
2356 MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER ||
2357 tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
2359 #endif
2360 return true;
2363 bool JSStructuredCloneWriter::write(HandleValue v) {
2364 if (!startWrite(v)) {
2365 return false;
2368 RootedObject obj(context());
2369 RootedValue key(context());
2370 RootedValue val(context());
2371 RootedId id(context());
2373 RootedValue cause(context());
2374 RootedValue errors(context());
2375 RootedValue stack(context());
2377 while (!counts.empty()) {
2378 obj = &objs.back().toObject();
2379 context()->check(obj);
2380 if (counts.back()) {
2381 counts.back()--;
2383 ESClass cls;
2384 if (!GetBuiltinClass(context(), obj, &cls)) {
2385 return false;
2388 if (cls == ESClass::Map) {
2389 key = otherEntries.popCopy();
2390 checkStack();
2392 counts.back()--;
2393 val = otherEntries.popCopy();
2394 checkStack();
2396 if (!startWrite(key) || !startWrite(val)) {
2397 return false;
2399 } else if (cls == ESClass::Set || obj->canUnwrapAs<SavedFrame>()) {
2400 key = otherEntries.popCopy();
2401 checkStack();
2403 if (!startWrite(key)) {
2404 return false;
2406 } else if (cls == ESClass::Error) {
2407 cause = otherEntries.popCopy();
2408 checkStack();
2410 counts.back()--;
2411 errors = otherEntries.popCopy();
2412 checkStack();
2414 counts.back()--;
2415 stack = otherEntries.popCopy();
2416 checkStack();
2418 if (!startWrite(cause) || !startWrite(errors) || !startWrite(stack)) {
2419 return false;
2421 } else {
2422 id = objectEntries.popCopy();
2423 key = IdToValue(id);
2424 checkStack();
2426 // If obj still has an own property named id, write it out.
2427 bool found;
2428 if (GetOwnPropertyPure(context(), obj, id, val.address(), &found)) {
2429 if (found) {
2430 if (!writePrimitive(key) || !startWrite(val)) {
2431 return false;
2434 continue;
2437 if (!HasOwnProperty(context(), obj, id, &found)) {
2438 return false;
2441 if (found) {
2442 #if FUZZING_JS_FUZZILLI
2443 // supress calls into user code
2444 if (js::SupportDifferentialTesting()) {
2445 fprintf(stderr, "Differential testing: cannot call GetProperty\n");
2446 return false;
2448 #endif
2450 if (!writePrimitive(key) ||
2451 !GetProperty(context(), obj, obj, id, &val) || !startWrite(val)) {
2452 return false;
2456 } else {
2457 if (!out.writePair(SCTAG_END_OF_KEYS, 0)) {
2458 return false;
2460 objs.popBack();
2461 counts.popBack();
2465 memory.clear();
2466 return transferOwnership();
2469 JSStructuredCloneReader::JSStructuredCloneReader(
2470 SCInput& in, JS::StructuredCloneScope scope,
2471 const JS::CloneDataPolicy& cloneDataPolicy,
2472 const JSStructuredCloneCallbacks* cb, void* cbClosure)
2473 : in(in),
2474 allowedScope(scope),
2475 cloneDataPolicy(cloneDataPolicy),
2476 objs(in.context()),
2477 objState(in.context(), in.context()),
2478 allObjs(in.context()),
2479 numItemsRead(0),
2480 callbacks(cb),
2481 closure(cbClosure),
2482 gcHeap(in.context()) {
2483 // Avoid the need to bounds check by keeping a never-matching element at the
2484 // base of the `objState` stack. This append() will always succeed because
2485 // the objState vector has a nonzero MinInlineCapacity.
2486 MOZ_ALWAYS_TRUE(objState.append(std::make_pair(nullptr, true)));
2489 template <typename CharT>
2490 JSString* JSStructuredCloneReader::readStringImpl(
2491 uint32_t nchars, ShouldAtomizeStrings atomize) {
2492 InlineCharBuffer<CharT> chars;
2493 if (!chars.maybeAlloc(context(), nchars) ||
2494 !in.readChars(chars.get(), nchars)) {
2495 return nullptr;
2498 if (atomize) {
2499 return chars.toAtom(context(), nchars);
2502 return chars.toStringDontDeflate(context(), nchars, gcHeap);
2505 JSString* JSStructuredCloneReader::readString(uint32_t data,
2506 ShouldAtomizeStrings atomize) {
2507 uint32_t nchars = data & BitMask(31);
2508 bool latin1 = data & (1 << 31);
2510 if (nchars > JSString::MAX_LENGTH) {
2511 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2512 JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
2513 return nullptr;
2516 return latin1 ? readStringImpl<Latin1Char>(nchars, atomize)
2517 : readStringImpl<char16_t>(nchars, atomize);
2520 [[nodiscard]] bool JSStructuredCloneReader::readUint32(uint32_t* num) {
2521 Rooted<Value> lineVal(context());
2522 if (!startRead(&lineVal)) {
2523 return false;
2525 if (!lineVal.isInt32()) {
2526 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2527 JSMSG_SC_BAD_SERIALIZED_DATA, "integer required");
2528 return false;
2530 *num = uint32_t(lineVal.toInt32());
2531 return true;
2534 BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) {
2535 size_t length = data & BitMask(31);
2536 bool isNegative = data & (1 << 31);
2537 if (length == 0) {
2538 return BigInt::zero(context());
2540 RootedBigInt result(context(), BigInt::createUninitialized(
2541 context(), length, isNegative, gcHeap));
2542 if (!result) {
2543 return nullptr;
2545 if (!in.readArray(result->digits().data(), length)) {
2546 return nullptr;
2548 return result;
2551 static uint32_t TagToV1ArrayType(uint32_t tag) {
2552 MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN &&
2553 tag <= SCTAG_TYPED_ARRAY_V1_MAX);
2554 return tag - SCTAG_TYPED_ARRAY_V1_MIN;
2557 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType,
2558 uint64_t nelems,
2559 MutableHandleValue vp,
2560 bool v1Read) {
2561 if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) {
2562 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2563 JSMSG_SC_BAD_SERIALIZED_DATA,
2564 "unhandled typed array element type");
2565 return false;
2568 // Push a placeholder onto the allObjs list to stand in for the typed array.
2569 uint32_t placeholderIndex = allObjs.length();
2570 Value dummy = UndefinedValue();
2571 if (!allObjs.append(dummy)) {
2572 return false;
2575 // Read the ArrayBuffer object and its contents (but no properties)
2576 RootedValue v(context());
2577 uint64_t byteOffset;
2578 if (v1Read) {
2579 if (!readV1ArrayBuffer(arrayType, nelems, &v)) {
2580 return false;
2582 byteOffset = 0;
2583 } else {
2584 if (!startRead(&v)) {
2585 return false;
2587 if (!in.read(&byteOffset)) {
2588 return false;
2592 // Ensure invalid 64-bit values won't be truncated below.
2593 if (nelems > ArrayBufferObject::MaxByteLength ||
2594 byteOffset > ArrayBufferObject::MaxByteLength) {
2595 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2596 JSMSG_SC_BAD_SERIALIZED_DATA,
2597 "invalid typed array length or offset");
2598 return false;
2601 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2602 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2603 JSMSG_SC_BAD_SERIALIZED_DATA,
2604 "typed array must be backed by an ArrayBuffer");
2605 return false;
2608 RootedObject buffer(context(), &v.toObject());
2609 RootedObject obj(context(), nullptr);
2611 switch (arrayType) {
2612 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2613 case Scalar::Name: \
2614 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2615 byteOffset, nelems) \
2616 .asObject(); \
2617 break;
2619 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER)
2620 #undef CREATE_FROM_BUFFER
2622 default:
2623 MOZ_CRASH("Can't happen: arrayType range checked above");
2626 if (!obj) {
2627 return false;
2629 vp.setObject(*obj);
2631 allObjs[placeholderIndex].set(vp);
2633 return true;
2636 bool JSStructuredCloneReader::readDataView(uint64_t byteLength,
2637 MutableHandleValue vp) {
2638 // Push a placeholder onto the allObjs list to stand in for the DataView.
2639 uint32_t placeholderIndex = allObjs.length();
2640 Value dummy = UndefinedValue();
2641 if (!allObjs.append(dummy)) {
2642 return false;
2645 // Read the ArrayBuffer object and its contents (but no properties).
2646 RootedValue v(context());
2647 if (!startRead(&v)) {
2648 return false;
2650 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2651 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2652 JSMSG_SC_BAD_SERIALIZED_DATA,
2653 "DataView must be backed by an ArrayBuffer");
2654 return false;
2657 // Read byteOffset.
2658 uint64_t byteOffset;
2659 if (!in.read(&byteOffset)) {
2660 return false;
2663 // Ensure invalid 64-bit values won't be truncated below.
2664 if (byteLength > ArrayBufferObject::MaxByteLength ||
2665 byteOffset > ArrayBufferObject::MaxByteLength) {
2666 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2667 JSMSG_SC_BAD_SERIALIZED_DATA,
2668 "invalid DataView length or offset");
2669 return false;
2672 RootedObject buffer(context(), &v.toObject());
2673 RootedObject obj(context(),
2674 JS_NewDataView(context(), buffer, byteOffset, byteLength));
2675 if (!obj) {
2676 return false;
2678 vp.setObject(*obj);
2680 allObjs[placeholderIndex].set(vp);
2682 return true;
2685 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type,
2686 uint32_t data,
2687 MutableHandleValue vp) {
2688 // V2 stores the length in |data|. The current version stores the
2689 // length separately to allow larger length values.
2690 uint64_t nbytes = 0;
2691 if (type == SCTAG_ARRAY_BUFFER_OBJECT) {
2692 if (!in.read(&nbytes)) {
2693 return false;
2695 } else {
2696 MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2);
2697 nbytes = data;
2700 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2701 // below, so we have to check this here.
2702 if (nbytes > ArrayBufferObject::MaxByteLength) {
2703 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2704 JSMSG_BAD_ARRAY_LENGTH);
2705 return false;
2708 JSObject* obj = ArrayBufferObject::createZeroed(context(), size_t(nbytes));
2709 if (!obj) {
2710 return false;
2712 vp.setObject(*obj);
2713 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2714 MOZ_ASSERT(buffer.byteLength() == nbytes);
2715 return in.readArray(buffer.dataPointer(), nbytes);
2718 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp) {
2719 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2720 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2721 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2722 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2723 : JS_SCERR_NOT_CLONABLE;
2724 ReportDataCloneError(context(), callbacks, error, closure,
2725 "SharedArrayBuffer");
2726 return false;
2729 uint64_t byteLength;
2730 if (!in.readBytes(&byteLength, sizeof(byteLength))) {
2731 return in.reportTruncated();
2734 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2735 // below, so we have to check this here.
2736 if (byteLength > ArrayBufferObject::MaxByteLength) {
2737 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2738 JSMSG_BAD_ARRAY_LENGTH);
2739 return false;
2742 intptr_t p;
2743 if (!in.readBytes(&p, sizeof(p))) {
2744 return in.reportTruncated();
2747 SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p);
2749 // There's no guarantee that the receiving agent has enabled shared memory
2750 // even if the transmitting agent has done so. Ideally we'd check at the
2751 // transmission point, but that's tricky, and it will be a very rare problem
2752 // in any case. Just fail at the receiving end if we can't handle it.
2754 if (!context()
2755 ->realm()
2756 ->creationOptions()
2757 .getSharedMemoryAndAtomicsEnabled()) {
2758 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2759 JSMSG_SC_SAB_DISABLED);
2760 return false;
2763 // The new object will have a new reference to the rawbuf.
2765 if (!rawbuf->addReference()) {
2766 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2767 JSMSG_SC_SAB_REFCNT_OFLO);
2768 return false;
2771 RootedObject obj(context(),
2772 SharedArrayBufferObject::New(context(), rawbuf, byteLength));
2773 if (!obj) {
2774 rawbuf->dropReference();
2775 return false;
2778 // `rawbuf` is now owned by `obj`.
2780 if (callbacks && callbacks->sabCloned &&
2781 !callbacks->sabCloned(context(), /*receiving=*/true, closure)) {
2782 return false;
2785 vp.setObject(*obj);
2786 return true;
2789 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes,
2790 MutableHandleValue vp) {
2791 JSContext* cx = context();
2792 if (nbytes != 0) {
2793 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2794 JSMSG_SC_BAD_SERIALIZED_DATA,
2795 "invalid shared wasm memory tag");
2796 return false;
2799 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2800 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2801 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2802 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2803 : JS_SCERR_NOT_CLONABLE;
2804 ReportDataCloneError(cx, callbacks, error, closure, "WebAssembly.Memory");
2805 return false;
2808 // Read the isHuge flag
2809 RootedValue isHuge(cx);
2810 if (!startRead(&isHuge)) {
2811 return false;
2814 // Read the SharedArrayBuffer object.
2815 RootedValue payload(cx);
2816 if (!startRead(&payload)) {
2817 return false;
2819 if (!payload.isObject() ||
2820 !payload.toObject().is<SharedArrayBufferObject>()) {
2821 JS_ReportErrorNumberASCII(
2822 context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
2823 "shared wasm memory must be backed by a SharedArrayBuffer");
2824 return false;
2827 Rooted<ArrayBufferObjectMaybeShared*> sab(
2828 cx, &payload.toObject().as<SharedArrayBufferObject>());
2830 // Construct the memory.
2831 RootedObject proto(
2832 cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmMemory));
2833 if (!proto) {
2834 return false;
2836 RootedObject memory(
2837 cx, WasmMemoryObject::create(cx, sab, isHuge.toBoolean(), proto));
2838 if (!memory) {
2839 return false;
2842 vp.setObject(*memory);
2843 return true;
2847 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2848 * endianness-conversion while reading.
2850 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType,
2851 uint32_t nelems,
2852 MutableHandleValue vp) {
2853 if (arrayType > Scalar::Uint8Clamped) {
2854 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2855 JSMSG_SC_BAD_SERIALIZED_DATA,
2856 "invalid TypedArray type");
2857 return false;
2860 mozilla::CheckedInt<size_t> nbytes =
2861 mozilla::CheckedInt<size_t>(nelems) *
2862 TypedArrayElemSize(static_cast<Scalar::Type>(arrayType));
2863 if (!nbytes.isValid() || nbytes.value() > UINT32_MAX) {
2864 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2865 JSMSG_SC_BAD_SERIALIZED_DATA,
2866 "invalid typed array size");
2867 return false;
2870 JSObject* obj = ArrayBufferObject::createZeroed(context(), nbytes.value());
2871 if (!obj) {
2872 return false;
2874 vp.setObject(*obj);
2875 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2876 MOZ_ASSERT(buffer.byteLength() == nbytes);
2878 switch (arrayType) {
2879 case Scalar::Int8:
2880 case Scalar::Uint8:
2881 case Scalar::Uint8Clamped:
2882 return in.readArray((uint8_t*)buffer.dataPointer(), nelems);
2883 case Scalar::Int16:
2884 case Scalar::Uint16:
2885 return in.readArray((uint16_t*)buffer.dataPointer(), nelems);
2886 case Scalar::Int32:
2887 case Scalar::Uint32:
2888 case Scalar::Float32:
2889 return in.readArray((uint32_t*)buffer.dataPointer(), nelems);
2890 case Scalar::Float64:
2891 case Scalar::BigInt64:
2892 case Scalar::BigUint64:
2893 return in.readArray((uint64_t*)buffer.dataPointer(), nelems);
2894 default:
2895 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2899 static bool PrimitiveToObject(JSContext* cx, MutableHandleValue vp) {
2900 JSObject* obj = js::PrimitiveToObject(cx, vp);
2901 if (!obj) {
2902 return false;
2905 vp.setObject(*obj);
2906 return true;
2909 bool JSStructuredCloneReader::startRead(MutableHandleValue vp,
2910 ShouldAtomizeStrings atomizeStrings) {
2911 uint32_t tag, data;
2912 bool alreadAppended = false;
2914 if (!in.readPair(&tag, &data)) {
2915 return false;
2918 numItemsRead++;
2920 switch (tag) {
2921 case SCTAG_NULL:
2922 vp.setNull();
2923 break;
2925 case SCTAG_UNDEFINED:
2926 vp.setUndefined();
2927 break;
2929 case SCTAG_INT32:
2930 vp.setInt32(data);
2931 break;
2933 case SCTAG_BOOLEAN:
2934 case SCTAG_BOOLEAN_OBJECT:
2935 vp.setBoolean(!!data);
2936 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) {
2937 return false;
2939 break;
2941 case SCTAG_STRING:
2942 case SCTAG_STRING_OBJECT: {
2943 JSString* str = readString(data, atomizeStrings);
2944 if (!str) {
2945 return false;
2947 vp.setString(str);
2948 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) {
2949 return false;
2951 break;
2954 case SCTAG_NUMBER_OBJECT: {
2955 double d;
2956 if (!in.readDouble(&d)) {
2957 return false;
2959 vp.setDouble(CanonicalizeNaN(d));
2960 if (!PrimitiveToObject(context(), vp)) {
2961 return false;
2963 break;
2966 case SCTAG_BIGINT:
2967 case SCTAG_BIGINT_OBJECT: {
2968 RootedBigInt bi(context(), readBigInt(data));
2969 if (!bi) {
2970 return false;
2972 vp.setBigInt(bi);
2973 if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) {
2974 return false;
2976 break;
2979 case SCTAG_DATE_OBJECT: {
2980 double d;
2981 if (!in.readDouble(&d)) {
2982 return false;
2984 JS::ClippedTime t = JS::TimeClip(d);
2985 if (!NumbersAreIdentical(d, t.toDouble())) {
2986 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2987 JSMSG_SC_BAD_SERIALIZED_DATA, "date");
2988 return false;
2990 JSObject* obj = NewDateObjectMsec(context(), t);
2991 if (!obj) {
2992 return false;
2994 vp.setObject(*obj);
2995 break;
2998 case SCTAG_REGEXP_OBJECT: {
2999 if ((data & RegExpFlag::AllFlags) != data) {
3000 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3001 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
3002 return false;
3005 RegExpFlags flags(AssertedCast<uint8_t>(data));
3007 uint32_t tag2, stringData;
3008 if (!in.readPair(&tag2, &stringData)) {
3009 return false;
3011 if (tag2 != SCTAG_STRING) {
3012 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3013 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
3014 return false;
3017 JSString* str = readString(stringData, AtomizeStrings);
3018 if (!str) {
3019 return false;
3022 Rooted<JSAtom*> atom(context(), &str->asAtom());
3024 NewObjectKind kind =
3025 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject;
3026 RegExpObject* reobj = RegExpObject::create(context(), atom, flags, kind);
3027 if (!reobj) {
3028 return false;
3030 vp.setObject(*reobj);
3031 break;
3034 case SCTAG_ARRAY_OBJECT:
3035 case SCTAG_OBJECT_OBJECT: {
3036 NewObjectKind kind =
3037 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject;
3038 JSObject* obj;
3039 if (tag == SCTAG_ARRAY_OBJECT) {
3040 obj = NewDenseUnallocatedArray(
3041 context(), NativeEndian::swapFromLittleEndian(data), kind);
3042 } else {
3043 obj = NewPlainObject(context(), kind);
3045 if (!obj || !objs.append(ObjectValue(*obj))) {
3046 return false;
3049 vp.setObject(*obj);
3050 break;
3053 case SCTAG_BACK_REFERENCE_OBJECT: {
3054 if (data >= allObjs.length() || !allObjs[data].isObject()) {
3055 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3056 JSMSG_SC_BAD_SERIALIZED_DATA,
3057 "invalid back reference in input");
3058 return false;
3060 vp.set(allObjs[data]);
3061 return true;
3064 case SCTAG_TRANSFER_MAP_HEADER:
3065 case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
3066 // We should be past all the transfer map tags.
3067 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3068 JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input");
3069 return false;
3071 case SCTAG_ARRAY_BUFFER_OBJECT_V2:
3072 case SCTAG_ARRAY_BUFFER_OBJECT:
3073 if (!readArrayBuffer(StructuredDataType(tag), data, vp)) {
3074 return false;
3076 break;
3078 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
3079 if (!readSharedArrayBuffer(vp)) {
3080 return false;
3082 break;
3084 case SCTAG_SHARED_WASM_MEMORY_OBJECT:
3085 if (!readSharedWasmMemory(data, vp)) {
3086 return false;
3088 break;
3090 case SCTAG_TYPED_ARRAY_OBJECT_V2: {
3091 // readTypedArray adds the array to allObjs.
3092 // V2 stores the length (nelems) in |data| and the arrayType separately.
3093 uint64_t arrayType;
3094 if (!in.read(&arrayType)) {
3095 return false;
3097 uint64_t nelems = data;
3098 return readTypedArray(arrayType, nelems, vp);
3101 case SCTAG_TYPED_ARRAY_OBJECT: {
3102 // readTypedArray adds the array to allObjs.
3103 // The current version stores the array type in |data| and the length
3104 // (nelems) separately to support large TypedArrays.
3105 uint32_t arrayType = data;
3106 uint64_t nelems;
3107 if (!in.read(&nelems)) {
3108 return false;
3110 return readTypedArray(arrayType, nelems, vp);
3113 case SCTAG_DATA_VIEW_OBJECT_V2: {
3114 // readDataView adds the array to allObjs.
3115 uint64_t byteLength = data;
3116 return readDataView(byteLength, vp);
3119 case SCTAG_DATA_VIEW_OBJECT: {
3120 // readDataView adds the array to allObjs.
3121 uint64_t byteLength;
3122 if (!in.read(&byteLength)) {
3123 return false;
3125 return readDataView(byteLength, vp);
3128 case SCTAG_MAP_OBJECT: {
3129 JSObject* obj = MapObject::create(context());
3130 if (!obj || !objs.append(ObjectValue(*obj))) {
3131 return false;
3133 vp.setObject(*obj);
3134 break;
3137 case SCTAG_SET_OBJECT: {
3138 JSObject* obj = SetObject::create(context());
3139 if (!obj || !objs.append(ObjectValue(*obj))) {
3140 return false;
3142 vp.setObject(*obj);
3143 break;
3146 case SCTAG_SAVED_FRAME_OBJECT: {
3147 auto* obj = readSavedFrameHeader(data);
3148 if (!obj || !objs.append(ObjectValue(*obj)) ||
3149 !objState.append(std::make_pair(obj, false))) {
3150 return false;
3152 vp.setObject(*obj);
3153 break;
3156 case SCTAG_ERROR_OBJECT: {
3157 auto* obj = readErrorHeader(data);
3158 if (!obj || !objs.append(ObjectValue(*obj)) ||
3159 !objState.append(std::make_pair(obj, false))) {
3160 return false;
3162 vp.setObject(*obj);
3163 break;
3166 case SCTAG_END_OF_KEYS:
3167 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3168 JSMSG_SC_BAD_SERIALIZED_DATA,
3169 "truncated input");
3170 return false;
3171 break;
3173 default: {
3174 if (tag <= SCTAG_FLOAT_MAX) {
3175 double d = ReinterpretPairAsDouble(tag, data);
3176 vp.setNumber(CanonicalizeNaN(d));
3177 break;
3180 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
3181 // A v1-format typed array
3182 // readTypedArray adds the array to allObjs
3183 return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
3186 if (!callbacks || !callbacks->read) {
3187 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3188 JSMSG_SC_BAD_SERIALIZED_DATA,
3189 "unsupported type");
3190 return false;
3193 // callbacks->read() might read other objects from the buffer.
3194 // In startWrite we always write the object itself before calling
3195 // the custom function. We should do the same here to keep
3196 // indexing consistent.
3197 uint32_t placeholderIndex = allObjs.length();
3198 Value dummy = UndefinedValue();
3199 if (!allObjs.append(dummy)) {
3200 return false;
3202 JSObject* obj =
3203 callbacks->read(context(), this, cloneDataPolicy, tag, data, closure);
3204 if (!obj) {
3205 return false;
3207 vp.setObject(*obj);
3208 allObjs[placeholderIndex].set(vp);
3209 alreadAppended = true;
3213 if (!alreadAppended && vp.isObject() && !allObjs.append(vp)) {
3214 return false;
3217 return true;
3220 bool JSStructuredCloneReader::readHeader() {
3221 uint32_t tag, data;
3222 if (!in.getPair(&tag, &data)) {
3223 return in.reportTruncated();
3226 JS::StructuredCloneScope storedScope;
3227 if (tag == SCTAG_HEADER) {
3228 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3229 storedScope = JS::StructuredCloneScope(data);
3230 } else {
3231 // Old structured clone buffer. We must have read it from disk.
3232 storedScope = JS::StructuredCloneScope::DifferentProcessForIndexedDB;
3235 // Backward compatibility with old structured clone buffers. Value '0' was
3236 // used for SameProcessSameThread scope.
3237 if ((int)storedScope == 0) {
3238 storedScope = JS::StructuredCloneScope::SameProcess;
3241 if (storedScope < JS::StructuredCloneScope::SameProcess ||
3242 storedScope > JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3243 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3244 JSMSG_SC_BAD_SERIALIZED_DATA,
3245 "invalid structured clone scope");
3246 return false;
3249 if (allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3250 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
3251 // clones are incorrect. Treat them as if they were DifferentProcess.
3252 allowedScope = JS::StructuredCloneScope::DifferentProcess;
3253 return true;
3256 if (storedScope < allowedScope) {
3257 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3258 JSMSG_SC_BAD_SERIALIZED_DATA,
3259 "incompatible structured clone scope");
3260 return false;
3263 return true;
3266 bool JSStructuredCloneReader::readTransferMap() {
3267 JSContext* cx = context();
3268 auto headerPos = in.tell();
3270 uint32_t tag, data;
3271 if (!in.getPair(&tag, &data)) {
3272 return in.reportTruncated();
3275 if (tag != SCTAG_TRANSFER_MAP_HEADER ||
3276 TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
3277 return true;
3280 uint64_t numTransferables;
3281 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3282 if (!in.read(&numTransferables)) {
3283 return false;
3286 for (uint64_t i = 0; i < numTransferables; i++) {
3287 auto pos = in.tell();
3289 if (!in.readPair(&tag, &data)) {
3290 return false;
3293 if (tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY) {
3294 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3295 return false;
3298 RootedObject obj(cx);
3300 void* content;
3301 if (!in.readPtr(&content)) {
3302 return false;
3305 uint64_t extraData;
3306 if (!in.read(&extraData)) {
3307 return false;
3310 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
3311 if (allowedScope == JS::StructuredCloneScope::DifferentProcess ||
3312 allowedScope ==
3313 JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3314 // Transferred ArrayBuffers in a DifferentProcess clone buffer
3315 // are treated as if they weren't Transferred at all. We should
3316 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
3317 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3318 return false;
3321 MOZ_RELEASE_ASSERT(extraData <= ArrayBufferObject::MaxByteLength);
3322 size_t nbytes = extraData;
3324 MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
3325 data == JS::SCTAG_TMO_MAPPED_DATA);
3326 if (data == JS::SCTAG_TMO_ALLOC_DATA) {
3327 // When the ArrayBuffer can't be allocated, |content| will be free'ed
3328 // in `JSStructuredCloneData::discardTransferables()`.
3329 obj = JS::NewArrayBufferWithContents(
3330 cx, nbytes, content,
3331 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
3332 } else if (data == JS::SCTAG_TMO_MAPPED_DATA) {
3333 obj = JS::NewMappedArrayBufferWithContents(cx, nbytes, content);
3335 } else if (tag == SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER) {
3336 auto savedPos = in.tell();
3337 auto guard = mozilla::MakeScopeExit([&] { in.seekTo(savedPos); });
3338 in.seekTo(pos);
3339 if (!in.seekBy(static_cast<size_t>(extraData))) {
3340 return false;
3343 if (tailStartPos.isNothing()) {
3344 tailStartPos = mozilla::Some(in.tell());
3347 uint32_t tag, data;
3348 if (!in.readPair(&tag, &data)) {
3349 return false;
3351 if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 &&
3352 tag != SCTAG_ARRAY_BUFFER_OBJECT) {
3353 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3354 return false;
3356 RootedValue val(cx);
3357 if (!readArrayBuffer(StructuredDataType(tag), data, &val)) {
3358 return false;
3360 obj = &val.toObject();
3361 tailEndPos = mozilla::Some(in.tell());
3362 } else {
3363 if (!callbacks || !callbacks->readTransfer) {
3364 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3365 return false;
3367 if (!callbacks->readTransfer(cx, this, cloneDataPolicy, tag, content,
3368 extraData, closure, &obj)) {
3369 if (!cx->isExceptionPending()) {
3370 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3372 return false;
3374 MOZ_ASSERT(obj);
3375 MOZ_ASSERT(!cx->isExceptionPending());
3378 // On failure, the buffer will still own the data (since its ownership
3379 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3380 // DiscardTransferables.
3381 if (!obj) {
3382 return false;
3385 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3386 // buffer.
3387 pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED));
3388 MOZ_ASSERT(!pos.done());
3390 if (!allObjs.append(ObjectValue(*obj))) {
3391 return false;
3395 // Mark the whole transfer map as consumed.
3396 #ifdef DEBUG
3397 SCInput::getPair(headerPos.peek(), &tag, &data);
3398 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
3399 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
3400 #endif
3401 headerPos.write(
3402 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
3404 return true;
3407 JSObject* JSStructuredCloneReader::readSavedFrameHeader(
3408 uint32_t principalsTag) {
3409 Rooted<SavedFrame*> savedFrame(context(), SavedFrame::create(context()));
3410 if (!savedFrame) {
3411 return nullptr;
3414 JSPrincipals* principals;
3415 if (principalsTag == SCTAG_JSPRINCIPALS) {
3416 if (!context()->runtime()->readPrincipals) {
3417 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3418 JSMSG_SC_UNSUPPORTED_TYPE);
3419 return nullptr;
3422 if (!context()->runtime()->readPrincipals(context(), this, &principals)) {
3423 return nullptr;
3425 } else if (principalsTag ==
3426 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
3427 principals = &ReconstructedSavedFramePrincipals::IsSystem;
3428 principals->refcount++;
3429 } else if (principalsTag ==
3430 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
3431 principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
3432 principals->refcount++;
3433 } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
3434 principals = nullptr;
3435 } else {
3436 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3437 JSMSG_SC_BAD_SERIALIZED_DATA,
3438 "bad SavedFrame principals");
3439 return nullptr;
3442 RootedValue mutedErrors(context());
3443 RootedValue source(context());
3445 // Read a |mutedErrors| boolean followed by a |source| string.
3446 // The |mutedErrors| boolean is present in all new structured-clone data,
3447 // but in older data it will be absent and only the |source| string will be
3448 // found.
3449 if (!startRead(&mutedErrors, AtomizeStrings)) {
3450 return nullptr;
3453 if (mutedErrors.isBoolean()) {
3454 if (!startRead(&source, AtomizeStrings) || !source.isString()) {
3455 return nullptr;
3457 } else if (mutedErrors.isString()) {
3458 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3459 // this is actually just a |source| string.
3460 source = mutedErrors;
3461 mutedErrors.setBoolean(true); // Safe default value.
3462 } else {
3463 // Invalid type.
3464 return nullptr;
3468 savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals,
3469 mutedErrors.toBoolean());
3471 savedFrame->initSource(&source.toString()->asAtom());
3473 uint32_t line;
3474 if (!readUint32(&line)) {
3475 return nullptr;
3477 savedFrame->initLine(line);
3479 JS::TaggedColumnNumberOneOrigin column;
3480 if (!readUint32(column.addressOfValueForTranscode())) {
3481 return nullptr;
3483 savedFrame->initColumn(column);
3485 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3486 // are only valid within a specific process.
3487 savedFrame->initSourceId(0);
3489 RootedValue name(context());
3490 if (!startRead(&name, AtomizeStrings)) {
3491 return nullptr;
3493 if (!(name.isString() || name.isNull())) {
3494 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3495 JSMSG_SC_BAD_SERIALIZED_DATA,
3496 "invalid saved frame cause");
3497 return nullptr;
3499 JSAtom* atomName = nullptr;
3500 if (name.isString()) {
3501 atomName = &name.toString()->asAtom();
3504 savedFrame->initFunctionDisplayName(atomName);
3506 RootedValue cause(context());
3507 if (!startRead(&cause, AtomizeStrings)) {
3508 return nullptr;
3510 if (!(cause.isString() || cause.isNull())) {
3511 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3512 JSMSG_SC_BAD_SERIALIZED_DATA,
3513 "invalid saved frame cause");
3514 return nullptr;
3516 JSAtom* atomCause = nullptr;
3517 if (cause.isString()) {
3518 atomCause = &cause.toString()->asAtom();
3520 savedFrame->initAsyncCause(atomCause);
3522 return savedFrame;
3525 // SavedFrame object: there is one child value, the parent SavedFrame,
3526 // which is either null or another SavedFrame object.
3527 bool JSStructuredCloneReader::readSavedFrameFields(Handle<SavedFrame*> frameObj,
3528 HandleValue parent,
3529 bool* state) {
3530 if (*state) {
3531 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3532 JSMSG_SC_BAD_SERIALIZED_DATA,
3533 "multiple SavedFrame parents");
3534 return false;
3537 SavedFrame* parentFrame;
3538 if (parent.isNull()) {
3539 parentFrame = nullptr;
3540 } else if (parent.isObject() && parent.toObject().is<SavedFrame>()) {
3541 parentFrame = &parent.toObject().as<SavedFrame>();
3542 } else {
3543 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3544 JSMSG_SC_BAD_SERIALIZED_DATA,
3545 "invalid SavedFrame parent");
3546 return false;
3549 frameObj->initParent(parentFrame);
3550 *state = true;
3551 return true;
3554 JSObject* JSStructuredCloneReader::readErrorHeader(uint32_t type) {
3555 JSContext* cx = context();
3557 switch (type) {
3558 case JSEXN_ERR:
3559 case JSEXN_EVALERR:
3560 case JSEXN_RANGEERR:
3561 case JSEXN_REFERENCEERR:
3562 case JSEXN_SYNTAXERR:
3563 case JSEXN_TYPEERR:
3564 case JSEXN_URIERR:
3565 case JSEXN_AGGREGATEERR:
3566 break;
3567 default:
3568 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3569 JSMSG_SC_BAD_SERIALIZED_DATA,
3570 "invalid error type");
3571 return nullptr;
3574 RootedString message(cx);
3576 RootedValue messageVal(cx);
3577 if (!startRead(&messageVal)) {
3578 return nullptr;
3580 if (messageVal.isString()) {
3581 message = messageVal.toString();
3582 } else if (!messageVal.isNull()) {
3583 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3584 JSMSG_SC_BAD_SERIALIZED_DATA,
3585 "invalid 'message' field for Error object");
3586 return nullptr;
3590 // We have to set |cause| to something if it exists, otherwise the shape
3591 // would be wrong. The actual value will be overwritten later.
3592 RootedValue val(cx);
3593 if (!startRead(&val)) {
3594 return nullptr;
3596 bool hasCause = ToBoolean(val);
3597 Rooted<Maybe<Value>> cause(cx, mozilla::Nothing());
3598 if (hasCause) {
3599 cause = mozilla::Some(BooleanValue(true));
3602 if (!startRead(&val)) {
3603 return nullptr;
3605 if (!val.isString()) {
3606 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3607 JSMSG_SC_BAD_SERIALIZED_DATA,
3608 "invalid 'fileName' field for Error object");
3609 return nullptr;
3611 RootedString fileName(cx, val.toString());
3613 uint32_t lineNumber;
3614 JS::ColumnNumberOneOrigin columnNumber;
3615 if (!readUint32(&lineNumber) ||
3616 !readUint32(columnNumber.addressOfValueForTranscode())) {
3617 return nullptr;
3620 // The |cause| and |stack| slots of the objects might be overwritten later.
3621 // For AggregateErrors the |errors| property will be added.
3622 RootedObject errorObj(
3623 cx, ErrorObject::create(cx, static_cast<JSExnType>(type), nullptr,
3624 fileName, 0, lineNumber, columnNumber, nullptr,
3625 message, cause));
3626 if (!errorObj) {
3627 return nullptr;
3630 return errorObj;
3633 // Error objects have 3 fields, some or all of them null: cause,
3634 // errors, and stack.
3635 bool JSStructuredCloneReader::readErrorFields(Handle<ErrorObject*> errorObj,
3636 HandleValue cause, bool* state) {
3637 JSContext* cx = context();
3638 if (*state) {
3639 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3640 JSMSG_SC_BAD_SERIALIZED_DATA,
3641 "unexpected child value seen for Error object");
3642 return false;
3645 RootedValue errors(cx);
3646 RootedValue stack(cx);
3647 if (!startRead(&errors) || !startRead(&stack)) {
3648 return false;
3651 bool hasCause = errorObj->getCause().isSome();
3652 if (hasCause) {
3653 errorObj->setCauseSlot(cause);
3654 } else if (!cause.isNull()) {
3655 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3656 JSMSG_SC_BAD_SERIALIZED_DATA,
3657 "invalid 'cause' field for Error object");
3658 return false;
3661 if (errorObj->type() == JSEXN_AGGREGATEERR) {
3662 if (!DefineDataProperty(context(), errorObj, cx->names().errors, errors,
3663 0)) {
3664 return false;
3666 } else if (!errors.isNull()) {
3667 JS_ReportErrorNumberASCII(
3668 cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
3669 "unexpected 'errors' field seen for non-AggregateError");
3670 return false;
3673 if (stack.isObject()) {
3674 RootedObject stackObj(cx, &stack.toObject());
3675 if (!stackObj->is<SavedFrame>()) {
3676 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3677 JSMSG_SC_BAD_SERIALIZED_DATA,
3678 "invalid 'stack' field for Error object");
3679 return false;
3681 errorObj->setStackSlot(stack);
3682 } else if (!stack.isNull()) {
3683 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3684 JSMSG_SC_BAD_SERIALIZED_DATA,
3685 "invalid 'stack' field for Error object");
3686 return false;
3689 *state = true;
3690 return true;
3693 // Read a value and treat as a key,value pair.
3694 bool JSStructuredCloneReader::readMapField(Handle<MapObject*> mapObj,
3695 HandleValue key) {
3696 RootedValue val(context());
3697 if (!startRead(&val)) {
3698 return false;
3700 return MapObject::set(context(), mapObj, key, val);
3703 // Read a value and treat as a key,value pair. Interpret as a plain property
3704 // value.
3705 bool JSStructuredCloneReader::readObjectField(HandleObject obj,
3706 HandleValue key) {
3707 if (!key.isString() && !key.isInt32()) {
3708 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3709 JSMSG_SC_BAD_SERIALIZED_DATA,
3710 "property key expected");
3711 return false;
3714 RootedValue val(context());
3715 if (!startRead(&val)) {
3716 return false;
3719 RootedId id(context());
3720 if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
3721 return false;
3724 // Fast path for adding a new property to a plain object. The property names
3725 // we see here should be unique, but we check for duplicates to guard against
3726 // corrupt or malicious data.
3727 if (id.isString() && obj->is<PlainObject>() &&
3728 MOZ_LIKELY(!obj->as<PlainObject>().contains(context(), id))) {
3729 return AddDataPropertyToPlainObject(context(), obj.as<PlainObject>(), id,
3730 val);
3733 // Fast path for adding an array element. The index shouldn't exceed the
3734 // array's length, but we check for this in `addDenseElementNoLengthChange` to
3735 // guard against corrupt or malicious data.
3736 if (id.isInt() && obj->is<ArrayObject>()) {
3737 ArrayObject* arr = &obj->as<ArrayObject>();
3738 switch (arr->addDenseElementNoLengthChange(context(), id.toInt(), val)) {
3739 case DenseElementResult::Failure:
3740 return false;
3741 case DenseElementResult::Success:
3742 return true;
3743 case DenseElementResult::Incomplete:
3744 // Fall-through to slow path.
3745 break;
3749 return DefineDataProperty(context(), obj, id, val);
3752 // Perform the whole recursive reading procedure.
3753 bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
3754 auto startTime = mozilla::TimeStamp::Now();
3756 if (!readHeader()) {
3757 return false;
3760 if (!readTransferMap()) {
3761 return false;
3764 MOZ_ASSERT(objs.length() == 0);
3765 MOZ_ASSERT(objState.length() == 1);
3767 // Start out by reading in the main object and pushing it onto the 'objs'
3768 // stack. The data related to this object and its descendants extends from
3769 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3770 if (!startRead(vp)) {
3771 return false;
3774 // Stop when the stack shows that all objects have been read.
3775 while (objs.length() != 0) {
3776 // What happens depends on the top obj on the objs stack.
3777 RootedObject obj(context(), &objs.back().toObject());
3779 uint32_t tag, data;
3780 if (!in.getPair(&tag, &data)) {
3781 return false;
3784 if (tag == SCTAG_END_OF_KEYS) {
3785 // Pop the current obj off the stack, since we are done with it and
3786 // its children.
3787 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3788 objs.popBack();
3789 if (objState.back().first == obj) {
3790 objState.popBack();
3792 continue;
3795 // Remember the index of the current top of the state stack, which will
3796 // correspond to the state for `obj` iff `obj` is a type that uses state.
3797 // startRead() may push additional entries before the state is accessed and
3798 // updated while filling in the object's data.
3799 size_t objStateIdx = objState.length() - 1;
3801 // The input stream contains a sequence of "child" values, whose
3802 // interpretation depends on the type of obj. These values can be
3803 // anything, and startRead() will push onto 'objs' for any non-leaf
3804 // value (i.e., anything that may contain children).
3806 // startRead() will allocate the (empty) object, but note that when
3807 // startRead() returns, 'key' is not yet initialized with any of its
3808 // properties. Those will be filled in by returning to the head of this
3809 // loop, processing the first child obj, and continuing until all
3810 // children have been fully created.
3812 // Note that this means the ordering in the stream is a little funky for
3813 // things like Map. See the comment above traverseMap() for an example.
3815 bool expectKeyValuePairs =
3816 !(obj->is<MapObject>() || obj->is<SetObject>() ||
3817 obj->is<SavedFrame>() || obj->is<ErrorObject>());
3819 RootedValue key(context());
3820 ShouldAtomizeStrings atomize =
3821 expectKeyValuePairs ? AtomizeStrings : DontAtomizeStrings;
3822 if (!startRead(&key, atomize)) {
3823 return false;
3826 if (key.isNull() && expectKeyValuePairs) {
3827 // Backwards compatibility: Null formerly indicated the end of
3828 // object properties.
3830 // No legacy objects used the state stack.
3831 MOZ_ASSERT(objState[objStateIdx].first() != obj);
3833 objs.popBack();
3834 continue;
3837 context()->check(key);
3839 if (obj->is<SetObject>()) {
3840 // Set object: the values between obj header (from startRead()) and
3841 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3842 if (!SetObject::add(context(), obj, key)) {
3843 return false;
3845 } else if (obj->is<MapObject>()) {
3846 Rooted<MapObject*> mapObj(context(), &obj->as<MapObject>());
3847 if (!readMapField(mapObj, key)) {
3848 return false;
3850 } else if (obj->is<SavedFrame>()) {
3851 Rooted<SavedFrame*> frameObj(context(), &obj->as<SavedFrame>());
3852 MOZ_ASSERT(objState[objStateIdx].first() == obj);
3853 bool state = objState[objStateIdx].second();
3854 if (!readSavedFrameFields(frameObj, key, &state)) {
3855 return false;
3857 objState[objStateIdx].second() = state;
3858 } else if (obj->is<ErrorObject>()) {
3859 Rooted<ErrorObject*> errorObj(context(), &obj->as<ErrorObject>());
3860 MOZ_ASSERT(objState[objStateIdx].first() == obj);
3861 bool state = objState[objStateIdx].second();
3862 if (!readErrorFields(errorObj, key, &state)) {
3863 return false;
3865 objState[objStateIdx].second() = state;
3866 } else {
3867 MOZ_ASSERT(expectKeyValuePairs);
3868 // Everything else uses a series of key,value,key,value,... Value
3869 // objects.
3870 if (!readObjectField(obj, key)) {
3871 return false;
3876 allObjs.clear();
3878 // For fuzzing, it is convenient to allow extra data at the end
3879 // of the input buffer so that more possible inputs are considered
3880 // valid.
3881 #ifndef FUZZING
3882 bool extraData;
3883 if (tailStartPos.isSome()) {
3884 // in.tell() is the end of the main data. If "tail" data was consumed,
3885 // then check whether there's any data between the main data and the
3886 // beginning of the tail, or after the last read point in the tail.
3887 extraData = (in.tell() != *tailStartPos || !tailEndPos->done());
3888 } else {
3889 extraData = !in.tell().done();
3891 if (extraData) {
3892 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3893 JSMSG_SC_BAD_SERIALIZED_DATA,
3894 "extra data after end");
3895 return false;
3897 #endif
3899 JSRuntime* rt = context()->runtime();
3900 rt->metrics().DESERIALIZE_BYTES(nbytes);
3901 rt->metrics().DESERIALIZE_ITEMS(numItemsRead);
3902 mozilla::TimeDuration elapsed = mozilla::TimeStamp::Now() - startTime;
3903 rt->metrics().DESERIALIZE_US(elapsed);
3905 return true;
3908 JS_PUBLIC_API bool JS_ReadStructuredClone(
3909 JSContext* cx, const JSStructuredCloneData& buf, uint32_t version,
3910 JS::StructuredCloneScope scope, MutableHandleValue vp,
3911 const JS::CloneDataPolicy& cloneDataPolicy,
3912 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3913 AssertHeapIsIdle();
3914 CHECK_THREAD(cx);
3916 if (version > JS_STRUCTURED_CLONE_VERSION) {
3917 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3918 JSMSG_SC_BAD_CLONE_VERSION);
3919 return false;
3921 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3922 return ReadStructuredClone(cx, buf, scope, vp, cloneDataPolicy, callbacks,
3923 closure);
3926 JS_PUBLIC_API bool JS_WriteStructuredClone(
3927 JSContext* cx, HandleValue value, JSStructuredCloneData* bufp,
3928 JS::StructuredCloneScope scope, const JS::CloneDataPolicy& cloneDataPolicy,
3929 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure,
3930 HandleValue transferable) {
3931 AssertHeapIsIdle();
3932 CHECK_THREAD(cx);
3933 cx->check(value);
3935 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3936 return WriteStructuredClone(cx, value, bufp, scope, cloneDataPolicy,
3937 callbacks, closure, transferable);
3940 JS_PUBLIC_API bool JS_StructuredCloneHasTransferables(
3941 JSStructuredCloneData& data, bool* hasTransferable) {
3942 *hasTransferable = StructuredCloneHasTransferObjects(data);
3943 return true;
3946 JS_PUBLIC_API bool JS_StructuredClone(
3947 JSContext* cx, HandleValue value, MutableHandleValue vp,
3948 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3949 AssertHeapIsIdle();
3950 CHECK_THREAD(cx);
3952 // Strings are associated with zones, not compartments,
3953 // so we copy the string by wrapping it.
3954 if (value.isString()) {
3955 RootedString strValue(cx, value.toString());
3956 if (!cx->compartment()->wrap(cx, &strValue)) {
3957 return false;
3959 vp.setString(strValue);
3960 return true;
3963 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3965 JSAutoStructuredCloneBuffer buf(JS::StructuredCloneScope::SameProcess,
3966 callbacks, closure);
3968 if (value.isObject()) {
3969 RootedObject obj(cx, &value.toObject());
3970 obj = CheckedUnwrapStatic(obj);
3971 if (!obj) {
3972 ReportAccessDenied(cx);
3973 return false;
3975 AutoRealm ar(cx, obj);
3976 RootedValue unwrappedVal(cx, ObjectValue(*obj));
3977 if (!buf.write(cx, unwrappedVal, callbacks, closure)) {
3978 return false;
3980 } else {
3981 if (!buf.write(cx, value, callbacks, closure)) {
3982 return false;
3987 return buf.read(cx, vp, JS::CloneDataPolicy(), callbacks, closure);
3990 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3991 JSAutoStructuredCloneBuffer&& other)
3992 : data_(other.scope()) {
3993 version_ = other.version_;
3994 other.giveTo(&data_);
3997 JSAutoStructuredCloneBuffer& JSAutoStructuredCloneBuffer::operator=(
3998 JSAutoStructuredCloneBuffer&& other) {
3999 MOZ_ASSERT(&other != this);
4000 MOZ_ASSERT(scope() == other.scope());
4001 clear();
4002 version_ = other.version_;
4003 other.giveTo(&data_);
4004 return *this;
4007 void JSAutoStructuredCloneBuffer::clear() {
4008 data_.discardTransferables();
4009 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
4010 data_.refsHeld_.releaseAll();
4011 data_.Clear();
4012 version_ = 0;
4015 void JSAutoStructuredCloneBuffer::adopt(
4016 JSStructuredCloneData&& data, uint32_t version,
4017 const JSStructuredCloneCallbacks* callbacks, void* closure) {
4018 clear();
4019 data_ = std::move(data);
4020 version_ = version;
4021 data_.setCallbacks(callbacks, closure,
4022 OwnTransferablePolicy::OwnsTransferablesIfAny);
4025 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData* data) {
4026 *data = std::move(data_);
4027 version_ = 0;
4028 data_.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables);
4029 data_.Clear();
4032 bool JSAutoStructuredCloneBuffer::read(
4033 JSContext* cx, MutableHandleValue vp,
4034 const JS::CloneDataPolicy& cloneDataPolicy,
4035 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4036 MOZ_ASSERT(cx);
4037 return !!JS_ReadStructuredClone(
4038 cx, data_, version_, data_.scope(), vp, cloneDataPolicy,
4039 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4040 optionalCallbacks ? closure : data_.closure_);
4043 bool JSAutoStructuredCloneBuffer::write(
4044 JSContext* cx, HandleValue value,
4045 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4046 HandleValue transferable = UndefinedHandleValue;
4047 return write(cx, value, transferable, JS::CloneDataPolicy(),
4048 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4049 optionalCallbacks ? closure : data_.closure_);
4052 bool JSAutoStructuredCloneBuffer::write(
4053 JSContext* cx, HandleValue value, HandleValue transferable,
4054 const JS::CloneDataPolicy& cloneDataPolicy,
4055 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4056 clear();
4057 bool ok = JS_WriteStructuredClone(
4058 cx, value, &data_, data_.scopeForInternalWriting(), cloneDataPolicy,
4059 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4060 optionalCallbacks ? closure : data_.closure_, transferable);
4061 if (!ok) {
4062 version_ = JS_STRUCTURED_CLONE_VERSION;
4064 return ok;
4067 JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1,
4068 uint32_t* p2) {
4069 return r->input().readPair((uint32_t*)p1, (uint32_t*)p2);
4072 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p,
4073 size_t len) {
4074 return r->input().readBytes(p, len);
4077 JS_PUBLIC_API bool JS_ReadString(JSStructuredCloneReader* r,
4078 MutableHandleString str) {
4079 uint32_t tag, data;
4080 if (!r->input().readPair(&tag, &data)) {
4081 return false;
4084 if (tag == SCTAG_STRING) {
4085 if (JSString* s =
4086 r->readString(data, JSStructuredCloneReader::DontAtomizeStrings)) {
4087 str.set(s);
4088 return true;
4090 return false;
4093 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
4094 JSMSG_SC_BAD_SERIALIZED_DATA, "expected string");
4095 return false;
4098 JS_PUBLIC_API bool JS_ReadDouble(JSStructuredCloneReader* r, double* v) {
4099 return r->input().readDouble(v);
4102 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r,
4103 MutableHandleValue vp) {
4104 uint32_t tag, data;
4105 if (!r->input().readPair(&tag, &data)) {
4106 return false;
4109 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
4110 return r->readTypedArray(TagToV1ArrayType(tag), data, vp, true);
4113 if (tag == SCTAG_TYPED_ARRAY_OBJECT_V2) {
4114 // V2 stores the length (nelems) in |data| and the arrayType separately.
4115 uint64_t arrayType;
4116 if (!r->input().read(&arrayType)) {
4117 return false;
4119 uint64_t nelems = data;
4120 return r->readTypedArray(arrayType, nelems, vp);
4123 if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
4124 // The current version stores the array type in |data| and the length
4125 // (nelems) separately to support large TypedArrays.
4126 uint32_t arrayType = data;
4127 uint64_t nelems;
4128 if (!r->input().read(&nelems)) {
4129 return false;
4131 return r->readTypedArray(arrayType, nelems, vp);
4134 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
4135 JSMSG_SC_BAD_SERIALIZED_DATA,
4136 "expected type array");
4137 return false;
4140 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag,
4141 uint32_t data) {
4142 return w->output().writePair(tag, data);
4145 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p,
4146 size_t len) {
4147 return w->output().writeBytes(p, len);
4150 JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w,
4151 HandleString str) {
4152 return w->writeString(SCTAG_STRING, str);
4155 JS_PUBLIC_API bool JS_WriteDouble(JSStructuredCloneWriter* w, double v) {
4156 return w->output().writeDouble(v);
4159 JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w,
4160 HandleValue v) {
4161 MOZ_ASSERT(v.isObject());
4162 w->context()->check(v);
4163 RootedObject obj(w->context(), &v.toObject());
4165 // startWrite can write everything, thus we should check here
4166 // and report error if the user passes a wrong type.
4167 if (!obj->canUnwrapAs<TypedArrayObject>()) {
4168 ReportAccessDenied(w->context());
4169 return false;
4172 // We should use startWrite instead of writeTypedArray, because
4173 // typed array is an object, we should add it to the |memory|
4174 // (allObjs) list. Directly calling writeTypedArray won't add it.
4175 return w->startWrite(v);
4178 JS_PUBLIC_API bool JS_ObjectNotWritten(JSStructuredCloneWriter* w,
4179 HandleObject obj) {
4180 w->memory.remove(w->memory.lookup(obj));
4182 return true;
4185 JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope(
4186 JSStructuredCloneWriter* w) {
4187 return w->output().scope();