Bug 1842773 - Part 11: Make DataView byteOffset and byteLength accessors aware of...
[gecko.git] / js / src / vm / StructuredClone.cpp
blobcfd8eab394e0ab4cffa430d345dc2bb3669c50e0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * This file implements the structured data algorithms of
9 * https://html.spec.whatwg.org/multipage/structured-data.html
11 * The spec is in two parts:
13 * - StructuredSerialize examines a JS value and produces a graph of Records.
14 * - StructuredDeserialize walks the Records and produces a new JS value.
16 * The differences between our implementation and the spec are minor:
18 * - We call the two phases "write" and "read".
19 * - Our algorithms use an explicit work stack, rather than recursion.
20 * - Serialized data is a flat array of bytes, not a (possibly cyclic) graph
21 * of "Records".
22 * - As a consequence, we handle non-treelike object graphs differently.
23 * We serialize objects that appear in multiple places in the input as
24 * backreferences, using sequential integer indexes.
25 * See `JSStructuredCloneReader::allObjs`, our take on the "memory" map
26 * in the spec's StructuredDeserialize.
29 #include "js/StructuredClone.h"
31 #include "mozilla/Casting.h"
32 #include "mozilla/CheckedInt.h"
33 #include "mozilla/EndianUtils.h"
34 #include "mozilla/FloatingPoint.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/ScopeExit.h"
38 #include <algorithm>
39 #include <memory>
40 #include <utility>
42 #include "jsdate.h"
44 #include "builtin/DataViewObject.h"
45 #include "builtin/MapObject.h"
46 #include "gc/GC.h" // AutoSelectGCHeap
47 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
48 #include "js/ArrayBuffer.h" // JS::{ArrayBufferHasData,DetachArrayBuffer,IsArrayBufferObject,New{,Mapped}ArrayBufferWithContents,ReleaseMappedArrayBufferContents}
49 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
50 #include "js/Date.h"
51 #include "js/experimental/TypedData.h" // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer
52 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
53 #include "js/GCAPI.h"
54 #include "js/GCHashTable.h"
55 #include "js/Object.h" // JS::GetBuiltinClass
56 #include "js/PropertyAndElement.h" // JS_GetElement
57 #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
58 #include "js/ScalarType.h" // js::Scalar::Type
59 #include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject
60 #include "js/Wrapper.h"
61 #include "util/DifferentialTesting.h"
62 #include "vm/BigIntType.h"
63 #include "vm/ErrorObject.h"
64 #include "vm/JSContext.h"
65 #include "vm/PlainObject.h" // js::PlainObject
66 #include "vm/RegExpObject.h"
67 #include "vm/SavedFrame.h"
68 #include "vm/SharedArrayObject.h"
69 #include "vm/TypedArrayObject.h"
70 #include "wasm/WasmJS.h"
72 #include "vm/ArrayObject-inl.h"
73 #include "vm/Compartment-inl.h"
74 #include "vm/ErrorObject-inl.h"
75 #include "vm/InlineCharBuffer-inl.h"
76 #include "vm/JSContext-inl.h"
77 #include "vm/JSObject-inl.h"
78 #include "vm/NativeObject-inl.h"
79 #include "vm/ObjectOperations-inl.h"
80 #include "vm/Realm-inl.h"
82 using namespace js;
84 using JS::CanonicalizeNaN;
85 using JS::GetBuiltinClass;
86 using JS::RegExpFlag;
87 using JS::RegExpFlags;
88 using JS::RootedValueVector;
89 using mozilla::AssertedCast;
90 using mozilla::BitwiseCast;
91 using mozilla::Maybe;
92 using mozilla::NativeEndian;
93 using mozilla::NumbersAreIdentical;
95 // When you make updates here, make sure you consider whether you need to bump
96 // the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You
97 // will likely need to increment the version if anything at all changes in the
98 // serialization format.
100 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should
101 // have a stable ID, it need not be at the end of the list and should not be
102 // used for sizing data structures.
104 enum StructuredDataType : uint32_t {
105 // Structured data types provided by the engine
106 SCTAG_FLOAT_MAX = 0xFFF00000,
107 SCTAG_HEADER = 0xFFF10000,
108 SCTAG_NULL = 0xFFFF0000,
109 SCTAG_UNDEFINED,
110 SCTAG_BOOLEAN,
111 SCTAG_INT32,
112 SCTAG_STRING,
113 SCTAG_DATE_OBJECT,
114 SCTAG_REGEXP_OBJECT,
115 SCTAG_ARRAY_OBJECT,
116 SCTAG_OBJECT_OBJECT,
117 SCTAG_ARRAY_BUFFER_OBJECT_V2, // Old version, for backwards compatibility.
118 SCTAG_BOOLEAN_OBJECT,
119 SCTAG_STRING_OBJECT,
120 SCTAG_NUMBER_OBJECT,
121 SCTAG_BACK_REFERENCE_OBJECT,
122 SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
123 SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
124 SCTAG_TYPED_ARRAY_OBJECT_V2, // Old version, for backwards compatibility.
125 SCTAG_MAP_OBJECT,
126 SCTAG_SET_OBJECT,
127 SCTAG_END_OF_KEYS,
128 SCTAG_DO_NOT_USE_3, // Required for backwards compatibility
129 SCTAG_DATA_VIEW_OBJECT_V2, // Old version, for backwards compatibility.
130 SCTAG_SAVED_FRAME_OBJECT,
132 // No new tags before principals.
133 SCTAG_JSPRINCIPALS,
134 SCTAG_NULL_JSPRINCIPALS,
135 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
136 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
138 SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
139 SCTAG_SHARED_WASM_MEMORY_OBJECT,
141 SCTAG_BIGINT,
142 SCTAG_BIGINT_OBJECT,
144 SCTAG_ARRAY_BUFFER_OBJECT,
145 SCTAG_TYPED_ARRAY_OBJECT,
146 SCTAG_DATA_VIEW_OBJECT,
148 SCTAG_ERROR_OBJECT,
150 SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
151 SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
152 SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
153 SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
154 SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
155 SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
156 SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
157 SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
158 SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
159 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED =
160 SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
161 // BigInt64 and BigUint64 are not supported in the v1 format.
162 SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED,
164 // Define a separate range of numbers for Transferable-only tags, since
165 // they are not used for persistent clone buffers and therefore do not
166 // require bumping JS_STRUCTURED_CLONE_VERSION.
167 SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
168 SCTAG_TRANSFER_MAP_PENDING_ENTRY,
169 SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
170 SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER,
171 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
173 SCTAG_END_OF_BUILTIN_TYPES
177 * Format of transfer map:
178 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
179 * numTransferables (64 bits)
180 * array of:
181 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
182 * pointer (64 bits)
183 * extraData (64 bits), eg byte length for ArrayBuffers
186 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
187 // contents have been read out yet or not.
188 enum TransferableMapHeader { SCTAG_TM_UNREAD = 0, SCTAG_TM_TRANSFERRED };
190 static inline uint64_t PairToUInt64(uint32_t tag, uint32_t data) {
191 return uint64_t(data) | (uint64_t(tag) << 32);
194 namespace js {
196 template <typename T, typename AllocPolicy>
197 struct BufferIterator {
198 using BufferList = mozilla::BufferList<AllocPolicy>;
200 explicit BufferIterator(const BufferList& buffer)
201 : mBuffer(buffer), mIter(buffer.Iter()) {
202 static_assert(8 % sizeof(T) == 0);
205 explicit BufferIterator(const JSStructuredCloneData& data)
206 : mBuffer(data.bufList_), mIter(data.Start()) {}
208 BufferIterator& operator=(const BufferIterator& other) {
209 MOZ_ASSERT(&mBuffer == &other.mBuffer);
210 mIter = other.mIter;
211 return *this;
214 [[nodiscard]] bool advance(size_t size = sizeof(T)) {
215 return mIter.AdvanceAcrossSegments(mBuffer, size);
218 BufferIterator operator++(int) {
219 BufferIterator ret = *this;
220 if (!advance(sizeof(T))) {
221 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
223 return ret;
226 BufferIterator& operator+=(size_t size) {
227 if (!advance(size)) {
228 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
230 return *this;
233 size_t operator-(const BufferIterator& other) const {
234 MOZ_ASSERT(&mBuffer == &other.mBuffer);
235 return mBuffer.RangeLength(other.mIter, mIter);
238 bool operator==(const BufferIterator& other) const {
239 return mBuffer.Start() == other.mBuffer.Start() && mIter == other.mIter;
241 bool operator!=(const BufferIterator& other) const {
242 return !(*this == other);
245 bool done() const { return mIter.Done(); }
247 [[nodiscard]] bool readBytes(char* outData, size_t size) {
248 return mBuffer.ReadBytes(mIter, outData, size);
251 void write(const T& data) {
252 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
253 *reinterpret_cast<T*>(mIter.Data()) = data;
256 T peek() const {
257 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
258 return *reinterpret_cast<T*>(mIter.Data());
261 bool canPeek() const { return mIter.HasRoomFor(sizeof(T)); }
263 const BufferList& mBuffer;
264 typename BufferList::IterImpl mIter;
267 SharedArrayRawBufferRefs& SharedArrayRawBufferRefs::operator=(
268 SharedArrayRawBufferRefs&& other) {
269 takeOwnership(std::move(other));
270 return *this;
273 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }
275 bool SharedArrayRawBufferRefs::acquire(JSContext* cx,
276 SharedArrayRawBuffer* rawbuf) {
277 if (!refs_.append(rawbuf)) {
278 ReportOutOfMemory(cx);
279 return false;
282 if (!rawbuf->addReference()) {
283 refs_.popBack();
284 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
285 JSMSG_SC_SAB_REFCNT_OFLO);
286 return false;
289 return true;
292 bool SharedArrayRawBufferRefs::acquireAll(
293 JSContext* cx, const SharedArrayRawBufferRefs& that) {
294 if (!refs_.reserve(refs_.length() + that.refs_.length())) {
295 ReportOutOfMemory(cx);
296 return false;
299 for (auto ref : that.refs_) {
300 if (!ref->addReference()) {
301 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
302 JSMSG_SC_SAB_REFCNT_OFLO);
303 return false;
305 MOZ_ALWAYS_TRUE(refs_.append(ref));
308 return true;
311 void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs&& other) {
312 MOZ_ASSERT(refs_.empty());
313 refs_ = std::move(other.refs_);
316 void SharedArrayRawBufferRefs::releaseAll() {
317 for (auto ref : refs_) {
318 ref->dropReference();
320 refs_.clear();
323 // SCOutput provides an interface to write raw data -- eg uint64_ts, doubles,
324 // arrays of bytes -- into a structured clone data output stream. It also knows
325 // how to free any transferable data within that stream.
327 // Note that it contains a full JSStructuredCloneData object, which holds the
328 // callbacks necessary to read/write/transfer/free the data. For the purpose of
329 // this class, only the freeTransfer callback is relevant; the rest of the
330 // callbacks are used by the higher-level JSStructuredCloneWriter interface.
331 struct SCOutput {
332 public:
333 using Iter = BufferIterator<uint64_t, SystemAllocPolicy>;
335 SCOutput(JSContext* cx, JS::StructuredCloneScope scope);
337 JSContext* context() const { return cx; }
338 JS::StructuredCloneScope scope() const { return buf.scope(); }
339 void sameProcessScopeRequired() { buf.sameProcessScopeRequired(); }
341 [[nodiscard]] bool write(uint64_t u);
342 [[nodiscard]] bool writePair(uint32_t tag, uint32_t data);
343 [[nodiscard]] bool writeDouble(double d);
344 [[nodiscard]] bool writeBytes(const void* p, size_t nbytes);
345 [[nodiscard]] bool writeChars(const Latin1Char* p, size_t nchars);
346 [[nodiscard]] bool writeChars(const char16_t* p, size_t nchars);
348 template <class T>
349 [[nodiscard]] bool writeArray(const T* p, size_t nelems);
351 void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure,
352 OwnTransferablePolicy policy) {
353 buf.setCallbacks(callbacks, closure, policy);
355 void extractBuffer(JSStructuredCloneData* data) { *data = std::move(buf); }
357 uint64_t tell() const { return buf.Size(); }
358 uint64_t count() const { return buf.Size() / sizeof(uint64_t); }
359 Iter iter() { return Iter(buf); }
361 size_t offset(Iter dest) { return dest - iter(); }
363 JSContext* cx;
364 JSStructuredCloneData buf;
367 class SCInput {
368 public:
369 using BufferIterator = js::BufferIterator<uint64_t, SystemAllocPolicy>;
371 SCInput(JSContext* cx, const JSStructuredCloneData& data);
373 JSContext* context() const { return cx; }
375 static void getPtr(uint64_t data, void** ptr);
376 static void getPair(uint64_t data, uint32_t* tagp, uint32_t* datap);
378 [[nodiscard]] bool read(uint64_t* p);
379 [[nodiscard]] bool readPair(uint32_t* tagp, uint32_t* datap);
380 [[nodiscard]] bool readDouble(double* p);
381 [[nodiscard]] bool readBytes(void* p, size_t nbytes);
382 [[nodiscard]] bool readChars(Latin1Char* p, size_t nchars);
383 [[nodiscard]] bool readChars(char16_t* p, size_t nchars);
384 [[nodiscard]] bool readPtr(void**);
386 [[nodiscard]] bool get(uint64_t* p);
387 [[nodiscard]] bool getPair(uint32_t* tagp, uint32_t* datap);
389 const BufferIterator& tell() const { return point; }
390 void seekTo(const BufferIterator& pos) { point = pos; }
391 [[nodiscard]] bool seekBy(size_t pos) {
392 if (!point.advance(pos)) {
393 reportTruncated();
394 return false;
396 return true;
399 template <class T>
400 [[nodiscard]] bool readArray(T* p, size_t nelems);
402 bool reportTruncated() {
403 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
404 JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
405 return false;
408 private:
409 void staticAssertions() {
410 static_assert(sizeof(char16_t) == 2);
411 static_assert(sizeof(uint32_t) == 4);
414 JSContext* cx;
415 BufferIterator point;
418 } // namespace js
420 struct JSStructuredCloneReader {
421 public:
422 explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope,
423 const JS::CloneDataPolicy& cloneDataPolicy,
424 const JSStructuredCloneCallbacks* cb,
425 void* cbClosure);
427 SCInput& input() { return in; }
428 bool read(MutableHandleValue vp, size_t nbytes);
430 private:
431 JSContext* context() { return in.context(); }
433 bool readHeader();
434 bool readTransferMap();
436 [[nodiscard]] bool readUint32(uint32_t* num);
438 enum ShouldAtomizeStrings : bool {
439 DontAtomizeStrings = false,
440 AtomizeStrings = true
443 template <typename CharT>
444 JSString* readStringImpl(uint32_t nchars, ShouldAtomizeStrings atomize);
445 JSString* readString(uint32_t data, ShouldAtomizeStrings atomize);
447 BigInt* readBigInt(uint32_t data);
449 [[nodiscard]] bool readTypedArray(uint32_t arrayType, uint64_t nelems,
450 MutableHandleValue vp, bool v1Read = false);
452 [[nodiscard]] bool readDataView(uint64_t byteLength, MutableHandleValue vp);
454 [[nodiscard]] bool readArrayBuffer(StructuredDataType type, uint32_t data,
455 MutableHandleValue vp);
456 [[nodiscard]] bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
457 MutableHandleValue vp);
459 [[nodiscard]] bool readSharedArrayBuffer(MutableHandleValue vp);
461 [[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes,
462 MutableHandleValue vp);
464 // A serialized SavedFrame contains primitive values in a header followed by
465 // an optional parent frame that is read recursively.
466 [[nodiscard]] JSObject* readSavedFrameHeader(uint32_t principalsTag);
467 [[nodiscard]] bool readSavedFrameFields(Handle<SavedFrame*> frameObj,
468 HandleValue parent, bool* state);
470 // A serialized Error contains primitive values in a header followed by
471 // 'cause', 'errors', and 'stack' fields that are read recursively.
472 [[nodiscard]] JSObject* readErrorHeader(uint32_t type);
473 [[nodiscard]] bool readErrorFields(Handle<ErrorObject*> errorObj,
474 HandleValue cause, bool* state);
476 [[nodiscard]] bool readMapField(Handle<MapObject*> mapObj, HandleValue key);
478 [[nodiscard]] bool readObjectField(HandleObject obj, HandleValue key);
480 [[nodiscard]] bool startRead(
481 MutableHandleValue vp,
482 ShouldAtomizeStrings atomizeStrings = DontAtomizeStrings);
484 SCInput& in;
486 // The widest scope that the caller will accept, where
487 // SameProcess is the widest (it can store anything it wants)
488 // and DifferentProcess is the narrowest (it cannot contain pointers and must
489 // be valid cross-process.)
490 JS::StructuredCloneScope allowedScope;
492 const JS::CloneDataPolicy cloneDataPolicy;
494 // Stack of objects with properties remaining to be read.
495 RootedValueVector objs;
497 // Maintain a stack of state values for the `objs` stack. Since this is only
498 // needed for a very small subset of objects (those with a known set of
499 // object children), the state information is stored as a stack of
500 // <object, state> pairs where the object determines which element of the
501 // `objs` stack that it corresponds to. So when reading from the `objs` stack,
502 // the state will be retrieved only if the top object on `objState` matches
503 // the top object of `objs`.
505 // Currently, the only state needed is a boolean indicating whether the fields
506 // have been read yet.
507 Rooted<GCVector<std::pair<HeapPtr<JSObject*>, bool>, 8>> objState;
509 // Array of all objects read during this deserialization, for resolving
510 // backreferences.
512 // For backreferences to work correctly, objects must be added to this
513 // array in exactly the order expected by the version of the Writer that
514 // created the serialized data, even across years and format versions. This
515 // is usually no problem, since both algorithms do a single linear pass
516 // over the serialized data. There is one hitch; see readTypedArray.
518 // The values in this vector are objects, except it can temporarily have
519 // one `undefined` placeholder value (the readTypedArray hack).
520 RootedValueVector allObjs;
522 size_t numItemsRead;
524 // The user defined callbacks that will be used for cloning.
525 const JSStructuredCloneCallbacks* callbacks;
527 // Any value passed to JS_ReadStructuredClone.
528 void* closure;
530 // The heap to use for allocating common GC things. This starts out as the
531 // nursery (the default) but may switch to the tenured heap if nursery
532 // collection occurs, as nursery allocation is pointless after the
533 // deserialized root object is tenured.
535 // This is only used for the most common kind, e.g. plain objects, strings
536 // and a couple of others.
537 AutoSelectGCHeap gcHeap;
539 friend bool JS_ReadString(JSStructuredCloneReader* r,
540 JS::MutableHandleString str);
541 friend bool JS_ReadTypedArray(JSStructuredCloneReader* r,
542 MutableHandleValue vp);
544 // Provide a way to detect whether any of the clone data is never used. When
545 // "tail" data (currently, this is only stored data for Transferred
546 // ArrayBuffers in the DifferentProcess scope) is read, record the first and
547 // last positions. At the end of deserialization, make sure there's nothing
548 // between the end of the main data and the beginning of the tail, nor after
549 // the end of the tail.
550 mozilla::Maybe<SCInput::BufferIterator> tailStartPos;
551 mozilla::Maybe<SCInput::BufferIterator> tailEndPos;
554 struct JSStructuredCloneWriter {
555 public:
556 explicit JSStructuredCloneWriter(JSContext* cx,
557 JS::StructuredCloneScope scope,
558 const JS::CloneDataPolicy& cloneDataPolicy,
559 const JSStructuredCloneCallbacks* cb,
560 void* cbClosure, const Value& tVal)
561 : out(cx, scope),
562 callbacks(cb),
563 closure(cbClosure),
564 objs(cx),
565 counts(cx),
566 objectEntries(cx),
567 otherEntries(cx),
568 memory(cx),
569 transferable(cx, tVal),
570 transferableObjects(cx, TransferableObjectsList(cx)),
571 cloneDataPolicy(cloneDataPolicy) {
572 out.setCallbacks(cb, cbClosure,
573 OwnTransferablePolicy::OwnsTransferablesIfAny);
576 bool init() {
577 return parseTransferable() && writeHeader() && writeTransferMap();
580 bool write(HandleValue v);
582 SCOutput& output() { return out; }
584 void extractBuffer(JSStructuredCloneData* newData) {
585 out.extractBuffer(newData);
588 private:
589 JSStructuredCloneWriter() = delete;
590 JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
592 JSContext* context() { return out.context(); }
594 bool writeHeader();
595 bool writeTransferMap();
597 bool writeString(uint32_t tag, JSString* str);
598 bool writeBigInt(uint32_t tag, BigInt* bi);
599 bool writeArrayBuffer(HandleObject obj);
600 bool writeTypedArray(HandleObject obj);
601 bool writeDataView(HandleObject obj);
602 bool writeSharedArrayBuffer(HandleObject obj);
603 bool writeSharedWasmMemory(HandleObject obj);
604 bool startObject(HandleObject obj, bool* backref);
605 bool writePrimitive(HandleValue v);
606 bool startWrite(HandleValue v);
607 bool traverseObject(HandleObject obj, ESClass cls);
608 bool traverseMap(HandleObject obj);
609 bool traverseSet(HandleObject obj);
610 bool traverseSavedFrame(HandleObject obj);
611 bool traverseError(HandleObject obj);
613 template <typename... Args>
614 bool reportDataCloneError(uint32_t errorId, Args&&... aArgs);
616 bool parseTransferable();
617 bool transferOwnership();
619 inline void checkStack();
621 SCOutput out;
623 // The user defined callbacks that will be used to signal cloning, in some
624 // cases.
625 const JSStructuredCloneCallbacks* callbacks;
627 // Any value passed to the callbacks.
628 void* closure;
630 // Vector of objects with properties remaining to be written.
632 // NB: These can span multiple compartments, so the compartment must be
633 // entered before any manipulation is performed.
634 RootedValueVector objs;
636 // counts[i] is the number of entries of objs[i] remaining to be written.
637 // counts.length() == objs.length() and sum(counts) == entries.length().
638 Vector<size_t> counts;
640 // For JSObject: Property IDs as value
641 RootedIdVector objectEntries;
643 // For Map: Key followed by value
644 // For Set: Key
645 // For SavedFrame: parent SavedFrame
646 // For Error: cause, errors, stack
647 RootedValueVector otherEntries;
649 // The "memory" list described in the HTML5 internal structured cloning
650 // algorithm. memory is a superset of objs; items are never removed from
651 // Memory until a serialization operation is finished
652 using CloneMemory = GCHashMap<JSObject*, uint32_t,
653 StableCellHasher<JSObject*>, SystemAllocPolicy>;
654 Rooted<CloneMemory> memory;
656 // Set of transferable objects
657 RootedValue transferable;
658 using TransferableObjectsList = GCVector<JSObject*>;
659 Rooted<TransferableObjectsList> transferableObjects;
661 const JS::CloneDataPolicy cloneDataPolicy;
663 friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
664 friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
665 friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj);
668 JS_PUBLIC_API uint64_t js::GetSCOffset(JSStructuredCloneWriter* writer) {
669 MOZ_ASSERT(writer);
670 return writer->output().count() * sizeof(uint64_t);
673 static_assert(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
674 static_assert(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
675 static_assert(Scalar::Int8 == 0);
677 template <typename... Args>
678 static void ReportDataCloneError(JSContext* cx,
679 const JSStructuredCloneCallbacks* callbacks,
680 uint32_t errorId, void* closure,
681 Args&&... aArgs) {
682 unsigned errorNumber;
683 switch (errorId) {
684 case JS_SCERR_DUP_TRANSFERABLE:
685 errorNumber = JSMSG_SC_DUP_TRANSFERABLE;
686 break;
688 case JS_SCERR_TRANSFERABLE:
689 errorNumber = JSMSG_SC_NOT_TRANSFERABLE;
690 break;
692 case JS_SCERR_UNSUPPORTED_TYPE:
693 errorNumber = JSMSG_SC_UNSUPPORTED_TYPE;
694 break;
696 case JS_SCERR_SHMEM_TRANSFERABLE:
697 errorNumber = JSMSG_SC_SHMEM_TRANSFERABLE;
698 break;
700 case JS_SCERR_TYPED_ARRAY_DETACHED:
701 errorNumber = JSMSG_TYPED_ARRAY_DETACHED;
702 break;
704 case JS_SCERR_WASM_NO_TRANSFER:
705 errorNumber = JSMSG_WASM_NO_TRANSFER;
706 break;
708 case JS_SCERR_NOT_CLONABLE:
709 errorNumber = JSMSG_SC_NOT_CLONABLE;
710 break;
712 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP:
713 errorNumber = JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP;
714 break;
716 default:
717 MOZ_CRASH("Unkown errorId");
718 break;
721 if (callbacks && callbacks->reportError) {
722 MOZ_RELEASE_ASSERT(!cx->isExceptionPending());
724 JSErrorReport report;
725 report.errorNumber = errorNumber;
726 // Get js error message if it's possible and propagate it through callback.
727 if (JS_ExpandErrorArgumentsASCII(cx, GetErrorMessage, errorNumber, &report,
728 std::forward<Args>(aArgs)...) &&
729 report.message()) {
730 callbacks->reportError(cx, errorId, closure, report.message().c_str());
731 } else {
732 ReportOutOfMemory(cx);
734 callbacks->reportError(cx, errorId, closure, "");
737 return;
740 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber,
741 std::forward<Args>(aArgs)...);
744 bool WriteStructuredClone(JSContext* cx, HandleValue v,
745 JSStructuredCloneData* bufp,
746 JS::StructuredCloneScope scope,
747 const JS::CloneDataPolicy& cloneDataPolicy,
748 const JSStructuredCloneCallbacks* cb, void* cbClosure,
749 const Value& transferable) {
750 JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure,
751 transferable);
752 if (!w.init()) {
753 return false;
755 if (!w.write(v)) {
756 return false;
758 w.extractBuffer(bufp);
759 return true;
762 bool ReadStructuredClone(JSContext* cx, const JSStructuredCloneData& data,
763 JS::StructuredCloneScope scope, MutableHandleValue vp,
764 const JS::CloneDataPolicy& cloneDataPolicy,
765 const JSStructuredCloneCallbacks* cb,
766 void* cbClosure) {
767 if (data.Size() % 8) {
768 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
769 JSMSG_SC_BAD_SERIALIZED_DATA, "misaligned");
770 return false;
772 SCInput in(cx, data);
773 JSStructuredCloneReader r(in, scope, cloneDataPolicy, cb, cbClosure);
774 return r.read(vp, data.Size());
777 static bool StructuredCloneHasTransferObjects(
778 const JSStructuredCloneData& data) {
779 if (data.Size() < sizeof(uint64_t)) {
780 return false;
783 uint64_t u;
784 BufferIterator<uint64_t, SystemAllocPolicy> iter(data);
785 MOZ_ALWAYS_TRUE(iter.readBytes(reinterpret_cast<char*>(&u), sizeof(u)));
786 uint32_t tag = uint32_t(u >> 32);
787 return (tag == SCTAG_TRANSFER_MAP_HEADER);
790 namespace js {
792 SCInput::SCInput(JSContext* cx, const JSStructuredCloneData& data)
793 : cx(cx), point(data) {
794 static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment % 8 == 0,
795 "structured clone buffer reads should be aligned");
796 MOZ_ASSERT(data.Size() % 8 == 0);
799 bool SCInput::read(uint64_t* p) {
800 if (!point.canPeek()) {
801 *p = 0; // initialize to shut GCC up
802 return reportTruncated();
804 *p = NativeEndian::swapFromLittleEndian(point.peek());
805 MOZ_ALWAYS_TRUE(point.advance());
806 return true;
809 bool SCInput::readPair(uint32_t* tagp, uint32_t* datap) {
810 uint64_t u;
811 bool ok = read(&u);
812 if (ok) {
813 *tagp = uint32_t(u >> 32);
814 *datap = uint32_t(u);
816 return ok;
819 bool SCInput::get(uint64_t* p) {
820 if (!point.canPeek()) {
821 return reportTruncated();
823 *p = NativeEndian::swapFromLittleEndian(point.peek());
824 return true;
827 bool SCInput::getPair(uint32_t* tagp, uint32_t* datap) {
828 uint64_t u = 0;
829 if (!get(&u)) {
830 return false;
833 *tagp = uint32_t(u >> 32);
834 *datap = uint32_t(u);
835 return true;
838 void SCInput::getPair(uint64_t data, uint32_t* tagp, uint32_t* datap) {
839 uint64_t u = NativeEndian::swapFromLittleEndian(data);
840 *tagp = uint32_t(u >> 32);
841 *datap = uint32_t(u);
844 bool SCInput::readDouble(double* p) {
845 uint64_t u;
846 if (!read(&u)) {
847 return false;
849 *p = CanonicalizeNaN(mozilla::BitwiseCast<double>(u));
850 return true;
853 template <typename T>
854 static void swapFromLittleEndianInPlace(T* ptr, size_t nelems) {
855 if (nelems > 0) {
856 NativeEndian::swapFromLittleEndianInPlace(ptr, nelems);
860 template <>
861 void swapFromLittleEndianInPlace(uint8_t* ptr, size_t nelems) {}
863 // Data is packed into an integral number of uint64_t words. Compute the
864 // padding required to finish off the final word.
865 static size_t ComputePadding(size_t nelems, size_t elemSize) {
866 // We want total length mod 8, where total length is nelems * sizeof(T),
867 // but that might overflow. So reduce nelems to nelems mod 8, since we are
868 // going to be doing a mod 8 later anyway.
869 size_t leftoverLength = (nelems % sizeof(uint64_t)) * elemSize;
870 return (-leftoverLength) & (sizeof(uint64_t) - 1);
873 template <class T>
874 bool SCInput::readArray(T* p, size_t nelems) {
875 if (!nelems) {
876 return true;
879 static_assert(sizeof(uint64_t) % sizeof(T) == 0);
881 // Fail if nelems is so huge that computing the full size will overflow.
882 mozilla::CheckedInt<size_t> size =
883 mozilla::CheckedInt<size_t>(nelems) * sizeof(T);
884 if (!size.isValid()) {
885 return reportTruncated();
888 if (!point.readBytes(reinterpret_cast<char*>(p), size.value())) {
889 // To avoid any way in which uninitialized data could escape, zero the array
890 // if filling it failed.
891 std::uninitialized_fill_n(p, nelems, 0);
892 return false;
895 swapFromLittleEndianInPlace(p, nelems);
897 point += ComputePadding(nelems, sizeof(T));
899 return true;
902 bool SCInput::readBytes(void* p, size_t nbytes) {
903 return readArray((uint8_t*)p, nbytes);
906 bool SCInput::readChars(Latin1Char* p, size_t nchars) {
907 static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
908 "Latin1Char must fit in 1 byte");
909 return readBytes(p, nchars);
912 bool SCInput::readChars(char16_t* p, size_t nchars) {
913 MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
914 return readArray((uint16_t*)p, nchars);
917 void SCInput::getPtr(uint64_t data, void** ptr) {
918 *ptr = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data));
921 bool SCInput::readPtr(void** p) {
922 uint64_t u;
923 if (!read(&u)) {
924 return false;
926 *p = reinterpret_cast<void*>(u);
927 return true;
930 SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope)
931 : cx(cx), buf(scope) {}
933 bool SCOutput::write(uint64_t u) {
934 uint64_t v = NativeEndian::swapToLittleEndian(u);
935 if (!buf.AppendBytes(reinterpret_cast<char*>(&v), sizeof(u))) {
936 ReportOutOfMemory(context());
937 return false;
939 return true;
942 bool SCOutput::writePair(uint32_t tag, uint32_t data) {
943 // As it happens, the tag word appears after the data word in the output.
944 // This is because exponents occupy the last 2 bytes of doubles on the
945 // little-endian platforms we care most about.
947 // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
948 // PairToUInt64 produces the number 0xFFFF000200000001.
949 // That is written out as the bytes 01 00 00 00 02 00 FF FF.
950 return write(PairToUInt64(tag, data));
953 static inline double ReinterpretPairAsDouble(uint32_t tag, uint32_t data) {
954 return BitwiseCast<double>(PairToUInt64(tag, data));
957 bool SCOutput::writeDouble(double d) {
958 return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
961 template <class T>
962 bool SCOutput::writeArray(const T* p, size_t nelems) {
963 static_assert(8 % sizeof(T) == 0);
964 static_assert(sizeof(uint64_t) % sizeof(T) == 0);
966 if (nelems == 0) {
967 return true;
970 for (size_t i = 0; i < nelems; i++) {
971 T value = NativeEndian::swapToLittleEndian(p[i]);
972 if (!buf.AppendBytes(reinterpret_cast<char*>(&value), sizeof(value))) {
973 return false;
977 // Zero-pad to 8 bytes boundary.
978 size_t padbytes = ComputePadding(nelems, sizeof(T));
979 char zeroes[sizeof(uint64_t)] = {0};
980 if (!buf.AppendBytes(zeroes, padbytes)) {
981 return false;
984 return true;
987 template <>
988 bool SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems) {
989 if (nelems == 0) {
990 return true;
993 if (!buf.AppendBytes(reinterpret_cast<const char*>(p), nelems)) {
994 return false;
997 // zero-pad to 8 bytes boundary
998 size_t padbytes = ComputePadding(nelems, 1);
999 char zeroes[sizeof(uint64_t)] = {0};
1000 if (!buf.AppendBytes(zeroes, padbytes)) {
1001 return false;
1004 return true;
1007 bool SCOutput::writeBytes(const void* p, size_t nbytes) {
1008 return writeArray((const uint8_t*)p, nbytes);
1011 bool SCOutput::writeChars(const char16_t* p, size_t nchars) {
1012 static_assert(sizeof(char16_t) == sizeof(uint16_t),
1013 "required so that treating char16_t[] memory as uint16_t[] "
1014 "memory is permissible");
1015 return writeArray((const uint16_t*)p, nchars);
1018 bool SCOutput::writeChars(const Latin1Char* p, size_t nchars) {
1019 static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
1020 "Latin1Char must fit in 1 byte");
1021 return writeBytes(p, nchars);
1024 } // namespace js
1026 JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); }
1028 // If the buffer contains Transferables, free them. Note that custom
1029 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
1030 // delete their transferables.
1031 void JSStructuredCloneData::discardTransferables() {
1032 if (!Size()) {
1033 return;
1036 if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) {
1037 return;
1040 // DifferentProcess clones cannot contain pointers, so nothing needs to be
1041 // released.
1042 if (scope() == JS::StructuredCloneScope::DifferentProcess) {
1043 return;
1046 FreeTransferStructuredCloneOp freeTransfer = nullptr;
1047 if (callbacks_) {
1048 freeTransfer = callbacks_->freeTransfer;
1051 auto point = BufferIterator<uint64_t, SystemAllocPolicy>(*this);
1052 if (point.done()) {
1053 return; // Empty buffer
1056 uint32_t tag, data;
1057 MOZ_RELEASE_ASSERT(point.canPeek());
1058 SCInput::getPair(point.peek(), &tag, &data);
1059 MOZ_ALWAYS_TRUE(point.advance());
1061 if (tag == SCTAG_HEADER) {
1062 if (point.done()) {
1063 return;
1066 MOZ_RELEASE_ASSERT(point.canPeek());
1067 SCInput::getPair(point.peek(), &tag, &data);
1068 MOZ_ALWAYS_TRUE(point.advance());
1071 if (tag != SCTAG_TRANSFER_MAP_HEADER) {
1072 return;
1075 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
1076 return;
1079 // freeTransfer should not GC
1080 JS::AutoSuppressGCAnalysis nogc;
1082 if (point.done()) {
1083 return;
1086 MOZ_RELEASE_ASSERT(point.canPeek());
1087 uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek());
1088 MOZ_ALWAYS_TRUE(point.advance());
1089 while (numTransferables--) {
1090 if (!point.canPeek()) {
1091 return;
1094 uint32_t ownership;
1095 SCInput::getPair(point.peek(), &tag, &ownership);
1096 MOZ_ALWAYS_TRUE(point.advance());
1097 MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1098 if (!point.canPeek()) {
1099 return;
1102 void* content;
1103 SCInput::getPtr(point.peek(), &content);
1104 MOZ_ALWAYS_TRUE(point.advance());
1105 if (!point.canPeek()) {
1106 return;
1109 uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek());
1110 MOZ_ALWAYS_TRUE(point.advance());
1112 if (ownership < JS::SCTAG_TMO_FIRST_OWNED) {
1113 continue;
1116 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
1117 js_free(content);
1118 } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
1119 JS::ReleaseMappedArrayBufferContents(content, extraData);
1120 } else if (freeTransfer) {
1121 freeTransfer(tag, JS::TransferableOwnership(ownership), content,
1122 extraData, closure_);
1123 } else {
1124 MOZ_ASSERT(false, "unknown ownership");
1129 static_assert(JSString::MAX_LENGTH < UINT32_MAX);
1131 bool JSStructuredCloneWriter::parseTransferable() {
1132 // NOTE: The transferables set is tested for non-emptiness at various
1133 // junctures in structured cloning, so this set must be initialized
1134 // by this method in all non-error cases.
1135 MOZ_ASSERT(transferableObjects.empty(),
1136 "parseTransferable called with stale data");
1138 if (transferable.isNull() || transferable.isUndefined()) {
1139 return true;
1142 if (!transferable.isObject()) {
1143 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1146 JSContext* cx = context();
1147 RootedObject array(cx, &transferable.toObject());
1148 bool isArray;
1149 if (!JS::IsArrayObject(cx, array, &isArray)) {
1150 return false;
1152 if (!isArray) {
1153 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1156 uint32_t length;
1157 if (!JS::GetArrayLength(cx, array, &length)) {
1158 return false;
1161 // Initialize the set for the provided array's length.
1162 if (!transferableObjects.reserve(length)) {
1163 return false;
1166 if (length == 0) {
1167 return true;
1170 RootedValue v(context());
1171 RootedObject tObj(context());
1173 for (uint32_t i = 0; i < length; ++i) {
1174 if (!CheckForInterrupt(cx)) {
1175 return false;
1178 if (!JS_GetElement(cx, array, i, &v)) {
1179 return false;
1182 if (!v.isObject()) {
1183 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1185 tObj = &v.toObject();
1187 RootedObject unwrappedObj(cx, CheckedUnwrapStatic(tObj));
1188 if (!unwrappedObj) {
1189 ReportAccessDenied(cx);
1190 return false;
1193 // Shared memory cannot be transferred because it is not possible (nor
1194 // desirable) to detach the memory in agents that already hold a
1195 // reference to it.
1197 if (unwrappedObj->is<SharedArrayBufferObject>()) {
1198 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
1201 else if (unwrappedObj->is<WasmMemoryObject>()) {
1202 if (unwrappedObj->as<WasmMemoryObject>().isShared()) {
1203 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
1207 // External array buffers may be able to be transferred in the future,
1208 // but that is not currently implemented.
1210 else if (unwrappedObj->is<ArrayBufferObject>()) {
1211 if (unwrappedObj->as<ArrayBufferObject>().isExternal()) {
1212 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1216 else {
1217 if (!out.buf.callbacks_ || !out.buf.callbacks_->canTransfer) {
1218 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1221 JSAutoRealm ar(cx, unwrappedObj);
1222 bool sameProcessScopeRequired = false;
1223 if (!out.buf.callbacks_->canTransfer(
1224 cx, unwrappedObj, &sameProcessScopeRequired, out.buf.closure_)) {
1225 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1228 if (sameProcessScopeRequired) {
1229 output().sameProcessScopeRequired();
1233 // No duplicates allowed
1234 if (std::find(transferableObjects.begin(), transferableObjects.end(),
1235 tObj) != transferableObjects.end()) {
1236 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
1239 if (!transferableObjects.append(tObj)) {
1240 return false;
1244 return true;
1247 template <typename... Args>
1248 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId,
1249 Args&&... aArgs) {
1250 ReportDataCloneError(context(), out.buf.callbacks_, errorId, out.buf.closure_,
1251 std::forward<Args>(aArgs)...);
1252 return false;
1255 bool JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) {
1256 JSLinearString* linear = str->ensureLinear(context());
1257 if (!linear) {
1258 return false;
1261 #if FUZZING_JS_FUZZILLI
1262 if (js::SupportDifferentialTesting()) {
1263 // TODO we could always output a twoByteChar string
1264 return true;
1266 #endif
1268 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
1269 "String length must fit in 31 bits");
1271 uint32_t length = linear->length();
1272 uint32_t lengthAndEncoding =
1273 length | (uint32_t(linear->hasLatin1Chars()) << 31);
1274 if (!out.writePair(tag, lengthAndEncoding)) {
1275 return false;
1278 JS::AutoCheckCannotGC nogc;
1279 return linear->hasLatin1Chars()
1280 ? out.writeChars(linear->latin1Chars(nogc), length)
1281 : out.writeChars(linear->twoByteChars(nogc), length);
1284 bool JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) {
1285 bool signBit = bi->isNegative();
1286 size_t length = bi->digitLength();
1287 // The length must fit in 31 bits to leave room for a sign bit.
1288 if (length > size_t(INT32_MAX)) {
1289 return false;
1291 uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31);
1293 if (!out.writePair(tag, lengthAndSign)) {
1294 return false;
1296 return out.writeArray(bi->digits().data(), length);
1299 inline void JSStructuredCloneWriter::checkStack() {
1300 #ifdef DEBUG
1301 // To avoid making serialization O(n^2), limit stack-checking at 10.
1302 const size_t MAX = 10;
1304 size_t limit = std::min(counts.length(), MAX);
1305 MOZ_ASSERT(objs.length() == counts.length());
1306 size_t total = 0;
1307 for (size_t i = 0; i < limit; i++) {
1308 MOZ_ASSERT(total + counts[i] >= total);
1309 total += counts[i];
1311 if (counts.length() <= MAX) {
1312 MOZ_ASSERT(total == objectEntries.length() + otherEntries.length());
1313 } else {
1314 MOZ_ASSERT(total <= objectEntries.length() + otherEntries.length());
1317 size_t j = objs.length();
1318 for (size_t i = 0; i < limit; i++) {
1319 --j;
1320 MOZ_ASSERT(memory.has(&objs[j].toObject()));
1322 #endif
1326 * Write out a typed array. Note that post-v1 structured clone buffers do not
1327 * perform endianness conversion on stored data, so multibyte typed arrays
1328 * cannot be deserialized into a different endianness machine. Endianness
1329 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
1330 * Int16Array views of the same ArrayBuffer, should the data bytes be
1331 * byte-swapped when writing or not? The Int8Array requires them to not be
1332 * swapped; the Int16Array requires that they are.
1334 bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj) {
1335 Rooted<TypedArrayObject*> tarr(context(),
1336 obj->maybeUnwrapAs<TypedArrayObject>());
1337 JSAutoRealm ar(context(), tarr);
1339 #ifdef FUZZING_JS_FUZZILLI
1340 if (js::SupportDifferentialTesting() && !tarr->hasBuffer()) {
1341 // fake oom because differential testing will fail
1342 fprintf(stderr, "[unhandlable oom]");
1343 _exit(-1);
1344 return false;
1346 #endif
1348 if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) {
1349 return false;
1352 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) {
1353 return false;
1356 uint64_t nelems = tarr->length();
1357 if (!out.write(nelems)) {
1358 return false;
1361 // Write out the ArrayBuffer tag and contents
1362 RootedValue val(context(), tarr->bufferValue());
1363 if (!startWrite(val)) {
1364 return false;
1367 uint64_t byteOffset = tarr->byteOffset();
1368 return out.write(byteOffset);
1371 bool JSStructuredCloneWriter::writeDataView(HandleObject obj) {
1372 Rooted<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<TypedArrayObject>()) {
2126 return writeTypedArray(obj);
2128 if (obj->canUnwrapAs<FixedLengthDataViewObject>()) {
2129 return writeDataView(obj);
2131 if (obj->canUnwrapAs<ResizableDataViewObject>()) {
2132 // TODO(anba): support resizable.
2133 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2135 if (wasm::IsSharedWasmMemoryObject(obj)) {
2136 return writeSharedWasmMemory(obj);
2138 if (obj->canUnwrapAs<SavedFrame>()) {
2139 return traverseSavedFrame(obj);
2141 break;
2145 if (out.buf.callbacks_ && out.buf.callbacks_->write) {
2146 bool sameProcessScopeRequired = false;
2147 if (!out.buf.callbacks_->write(context(), this, obj,
2148 &sameProcessScopeRequired,
2149 out.buf.closure_)) {
2150 return false;
2153 if (sameProcessScopeRequired) {
2154 output().sameProcessScopeRequired();
2157 return true;
2160 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2163 bool JSStructuredCloneWriter::writeHeader() {
2164 return out.writePair(SCTAG_HEADER, (uint32_t)output().scope());
2167 bool JSStructuredCloneWriter::writeTransferMap() {
2168 if (transferableObjects.empty()) {
2169 return true;
2172 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) {
2173 return false;
2176 if (!out.write(transferableObjects.length())) {
2177 return false;
2180 RootedObject obj(context());
2181 for (auto* o : transferableObjects) {
2182 obj = o;
2183 if (!memory.put(obj, memory.count())) {
2184 ReportOutOfMemory(context());
2185 return false;
2188 // Emit a placeholder pointer. We defer stealing the data until later
2189 // (and, if necessary, detaching this object if it's an ArrayBuffer).
2190 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY,
2191 JS::SCTAG_TMO_UNFILLED)) {
2192 return false;
2194 if (!out.write(0)) { // Pointer to ArrayBuffer contents.
2195 return false;
2197 if (!out.write(0)) { // extraData
2198 return false;
2202 return true;
2205 bool JSStructuredCloneWriter::transferOwnership() {
2206 if (transferableObjects.empty()) {
2207 return true;
2210 // Walk along the transferables and the transfer map at the same time,
2211 // grabbing out pointers from the transferables and stuffing them into the
2212 // transfer map.
2213 auto point = out.iter();
2214 MOZ_RELEASE_ASSERT(point.canPeek());
2215 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
2216 SCTAG_HEADER);
2217 point++;
2218 MOZ_RELEASE_ASSERT(point.canPeek());
2219 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
2220 SCTAG_TRANSFER_MAP_HEADER);
2221 point++;
2222 MOZ_RELEASE_ASSERT(point.canPeek());
2223 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) ==
2224 transferableObjects.length());
2225 point++;
2227 JSContext* cx = context();
2228 RootedObject obj(cx);
2229 JS::StructuredCloneScope scope = output().scope();
2230 for (auto* o : transferableObjects) {
2231 obj = o;
2233 uint32_t tag;
2234 JS::TransferableOwnership ownership;
2235 void* content;
2236 uint64_t extraData;
2238 #if DEBUG
2239 SCInput::getPair(point.peek(), &tag, (uint32_t*)&ownership);
2240 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2241 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
2242 #endif
2244 ESClass cls;
2245 if (!GetBuiltinClass(cx, obj, &cls)) {
2246 return false;
2249 if (cls == ESClass::ArrayBuffer) {
2250 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
2252 // The current setup of the array buffer inheritance hierarchy doesn't
2253 // lend itself well to generic manipulation via proxies.
2254 Rooted<ArrayBufferObject*> arrayBuffer(
2255 cx, obj->maybeUnwrapAs<ArrayBufferObject>());
2256 JSAutoRealm ar(cx, arrayBuffer);
2258 if (arrayBuffer->isDetached()) {
2259 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED);
2260 return false;
2263 if (arrayBuffer->isPreparedForAsmJS()) {
2264 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER);
2265 return false;
2268 // FIXME: Support structured cloning for resizable ArrayBuffers.
2269 if (arrayBuffer->isResizable()) {
2270 reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
2271 return false;
2274 if (scope == JS::StructuredCloneScope::DifferentProcess ||
2275 scope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
2276 // Write Transferred ArrayBuffers in DifferentProcess scope at
2277 // the end of the clone buffer, and store the offset within the
2278 // buffer to where the ArrayBuffer was written. Note that this
2279 // will invalidate the current position iterator.
2281 size_t pointOffset = out.offset(point);
2282 tag = SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER;
2283 ownership = JS::SCTAG_TMO_UNOWNED;
2284 content = nullptr;
2285 extraData = out.tell() -
2286 pointOffset; // Offset from tag to current end of buffer
2287 if (!writeArrayBuffer(arrayBuffer)) {
2288 ReportOutOfMemory(cx);
2289 return false;
2292 // Must refresh the point iterator after its collection has
2293 // been modified.
2294 point = out.iter();
2295 point += pointOffset;
2297 if (!JS::DetachArrayBuffer(cx, arrayBuffer)) {
2298 return false;
2300 } else {
2301 size_t nbytes = arrayBuffer->byteLength();
2303 using BufferContents = ArrayBufferObject::BufferContents;
2305 BufferContents bufContents =
2306 ArrayBufferObject::extractStructuredCloneContents(cx, arrayBuffer);
2307 if (!bufContents) {
2308 return false; // out of memory
2311 content = bufContents.data();
2312 if (bufContents.kind() == ArrayBufferObject::MAPPED) {
2313 ownership = JS::SCTAG_TMO_MAPPED_DATA;
2314 } else {
2315 MOZ_ASSERT(
2316 bufContents.kind() ==
2317 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
2318 bufContents.kind() ==
2319 ArrayBufferObject::MALLOCED_UNKNOWN_ARENA,
2320 "failing to handle new ArrayBuffer kind?");
2321 ownership = JS::SCTAG_TMO_ALLOC_DATA;
2323 extraData = nbytes;
2325 } else {
2326 if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) {
2327 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
2329 if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag,
2330 &ownership, &content,
2331 &extraData)) {
2332 return false;
2334 MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2337 point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
2338 MOZ_ALWAYS_TRUE(point.advance());
2339 point.write(
2340 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content)));
2341 MOZ_ALWAYS_TRUE(point.advance());
2342 point.write(NativeEndian::swapToLittleEndian(extraData));
2343 MOZ_ALWAYS_TRUE(point.advance());
2346 #if DEBUG
2347 // Make sure there aren't any more transfer map entries after the expected
2348 // number we read out.
2349 if (!point.done()) {
2350 uint32_t tag, data;
2351 SCInput::getPair(point.peek(), &tag, &data);
2352 MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER ||
2353 tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
2355 #endif
2356 return true;
2359 bool JSStructuredCloneWriter::write(HandleValue v) {
2360 if (!startWrite(v)) {
2361 return false;
2364 RootedObject obj(context());
2365 RootedValue key(context());
2366 RootedValue val(context());
2367 RootedId id(context());
2369 RootedValue cause(context());
2370 RootedValue errors(context());
2371 RootedValue stack(context());
2373 while (!counts.empty()) {
2374 obj = &objs.back().toObject();
2375 context()->check(obj);
2376 if (counts.back()) {
2377 counts.back()--;
2379 ESClass cls;
2380 if (!GetBuiltinClass(context(), obj, &cls)) {
2381 return false;
2384 if (cls == ESClass::Map) {
2385 key = otherEntries.popCopy();
2386 checkStack();
2388 counts.back()--;
2389 val = otherEntries.popCopy();
2390 checkStack();
2392 if (!startWrite(key) || !startWrite(val)) {
2393 return false;
2395 } else if (cls == ESClass::Set || obj->canUnwrapAs<SavedFrame>()) {
2396 key = otherEntries.popCopy();
2397 checkStack();
2399 if (!startWrite(key)) {
2400 return false;
2402 } else if (cls == ESClass::Error) {
2403 cause = otherEntries.popCopy();
2404 checkStack();
2406 counts.back()--;
2407 errors = otherEntries.popCopy();
2408 checkStack();
2410 counts.back()--;
2411 stack = otherEntries.popCopy();
2412 checkStack();
2414 if (!startWrite(cause) || !startWrite(errors) || !startWrite(stack)) {
2415 return false;
2417 } else {
2418 id = objectEntries.popCopy();
2419 key = IdToValue(id);
2420 checkStack();
2422 // If obj still has an own property named id, write it out.
2423 bool found;
2424 if (GetOwnPropertyPure(context(), obj, id, val.address(), &found)) {
2425 if (found) {
2426 if (!writePrimitive(key) || !startWrite(val)) {
2427 return false;
2430 continue;
2433 if (!HasOwnProperty(context(), obj, id, &found)) {
2434 return false;
2437 if (found) {
2438 #if FUZZING_JS_FUZZILLI
2439 // supress calls into user code
2440 if (js::SupportDifferentialTesting()) {
2441 fprintf(stderr, "Differential testing: cannot call GetProperty\n");
2442 return false;
2444 #endif
2446 if (!writePrimitive(key) ||
2447 !GetProperty(context(), obj, obj, id, &val) || !startWrite(val)) {
2448 return false;
2452 } else {
2453 if (!out.writePair(SCTAG_END_OF_KEYS, 0)) {
2454 return false;
2456 objs.popBack();
2457 counts.popBack();
2461 memory.clear();
2462 return transferOwnership();
2465 JSStructuredCloneReader::JSStructuredCloneReader(
2466 SCInput& in, JS::StructuredCloneScope scope,
2467 const JS::CloneDataPolicy& cloneDataPolicy,
2468 const JSStructuredCloneCallbacks* cb, void* cbClosure)
2469 : in(in),
2470 allowedScope(scope),
2471 cloneDataPolicy(cloneDataPolicy),
2472 objs(in.context()),
2473 objState(in.context(), in.context()),
2474 allObjs(in.context()),
2475 numItemsRead(0),
2476 callbacks(cb),
2477 closure(cbClosure),
2478 gcHeap(in.context()) {
2479 // Avoid the need to bounds check by keeping a never-matching element at the
2480 // base of the `objState` stack. This append() will always succeed because
2481 // the objState vector has a nonzero MinInlineCapacity.
2482 MOZ_ALWAYS_TRUE(objState.append(std::make_pair(nullptr, true)));
2485 template <typename CharT>
2486 JSString* JSStructuredCloneReader::readStringImpl(
2487 uint32_t nchars, ShouldAtomizeStrings atomize) {
2488 InlineCharBuffer<CharT> chars;
2489 if (!chars.maybeAlloc(context(), nchars) ||
2490 !in.readChars(chars.get(), nchars)) {
2491 return nullptr;
2494 if (atomize) {
2495 return chars.toAtom(context(), nchars);
2498 return chars.toStringDontDeflate(context(), nchars, gcHeap);
2501 JSString* JSStructuredCloneReader::readString(uint32_t data,
2502 ShouldAtomizeStrings atomize) {
2503 uint32_t nchars = data & BitMask(31);
2504 bool latin1 = data & (1 << 31);
2506 if (nchars > JSString::MAX_LENGTH) {
2507 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2508 JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
2509 return nullptr;
2512 return latin1 ? readStringImpl<Latin1Char>(nchars, atomize)
2513 : readStringImpl<char16_t>(nchars, atomize);
2516 [[nodiscard]] bool JSStructuredCloneReader::readUint32(uint32_t* num) {
2517 Rooted<Value> lineVal(context());
2518 if (!startRead(&lineVal)) {
2519 return false;
2521 if (!lineVal.isInt32()) {
2522 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2523 JSMSG_SC_BAD_SERIALIZED_DATA, "integer required");
2524 return false;
2526 *num = uint32_t(lineVal.toInt32());
2527 return true;
2530 BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) {
2531 size_t length = data & BitMask(31);
2532 bool isNegative = data & (1 << 31);
2533 if (length == 0) {
2534 return BigInt::zero(context());
2536 RootedBigInt result(context(), BigInt::createUninitialized(
2537 context(), length, isNegative, gcHeap));
2538 if (!result) {
2539 return nullptr;
2541 if (!in.readArray(result->digits().data(), length)) {
2542 return nullptr;
2544 return result;
2547 static uint32_t TagToV1ArrayType(uint32_t tag) {
2548 MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN &&
2549 tag <= SCTAG_TYPED_ARRAY_V1_MAX);
2550 return tag - SCTAG_TYPED_ARRAY_V1_MIN;
2553 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType,
2554 uint64_t nelems,
2555 MutableHandleValue vp,
2556 bool v1Read) {
2557 if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) {
2558 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2559 JSMSG_SC_BAD_SERIALIZED_DATA,
2560 "unhandled typed array element type");
2561 return false;
2564 // Push a placeholder onto the allObjs list to stand in for the typed array.
2565 uint32_t placeholderIndex = allObjs.length();
2566 Value dummy = UndefinedValue();
2567 if (!allObjs.append(dummy)) {
2568 return false;
2571 // Read the ArrayBuffer object and its contents (but no properties)
2572 RootedValue v(context());
2573 uint64_t byteOffset;
2574 if (v1Read) {
2575 if (!readV1ArrayBuffer(arrayType, nelems, &v)) {
2576 return false;
2578 byteOffset = 0;
2579 } else {
2580 if (!startRead(&v)) {
2581 return false;
2583 if (!in.read(&byteOffset)) {
2584 return false;
2588 // Ensure invalid 64-bit values won't be truncated below.
2589 if (nelems > ArrayBufferObject::MaxByteLength ||
2590 byteOffset > ArrayBufferObject::MaxByteLength) {
2591 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2592 JSMSG_SC_BAD_SERIALIZED_DATA,
2593 "invalid typed array length or offset");
2594 return false;
2597 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2598 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2599 JSMSG_SC_BAD_SERIALIZED_DATA,
2600 "typed array must be backed by an ArrayBuffer");
2601 return false;
2604 RootedObject buffer(context(), &v.toObject());
2605 RootedObject obj(context(), nullptr);
2607 switch (arrayType) {
2608 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \
2609 case Scalar::Name: \
2610 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \
2611 byteOffset, nelems) \
2612 .asObject(); \
2613 break;
2615 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER)
2616 #undef CREATE_FROM_BUFFER
2618 default:
2619 MOZ_CRASH("Can't happen: arrayType range checked above");
2622 if (!obj) {
2623 return false;
2625 vp.setObject(*obj);
2627 allObjs[placeholderIndex].set(vp);
2629 return true;
2632 bool JSStructuredCloneReader::readDataView(uint64_t byteLength,
2633 MutableHandleValue vp) {
2634 // Push a placeholder onto the allObjs list to stand in for the DataView.
2635 uint32_t placeholderIndex = allObjs.length();
2636 Value dummy = UndefinedValue();
2637 if (!allObjs.append(dummy)) {
2638 return false;
2641 // Read the ArrayBuffer object and its contents (but no properties).
2642 RootedValue v(context());
2643 if (!startRead(&v)) {
2644 return false;
2646 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2647 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2648 JSMSG_SC_BAD_SERIALIZED_DATA,
2649 "DataView must be backed by an ArrayBuffer");
2650 return false;
2653 // Read byteOffset.
2654 uint64_t byteOffset;
2655 if (!in.read(&byteOffset)) {
2656 return false;
2659 // Ensure invalid 64-bit values won't be truncated below.
2660 if (byteLength > ArrayBufferObject::MaxByteLength ||
2661 byteOffset > ArrayBufferObject::MaxByteLength) {
2662 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2663 JSMSG_SC_BAD_SERIALIZED_DATA,
2664 "invalid DataView length or offset");
2665 return false;
2668 RootedObject buffer(context(), &v.toObject());
2669 RootedObject obj(context(),
2670 JS_NewDataView(context(), buffer, byteOffset, byteLength));
2671 if (!obj) {
2672 return false;
2674 vp.setObject(*obj);
2676 allObjs[placeholderIndex].set(vp);
2678 return true;
2681 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type,
2682 uint32_t data,
2683 MutableHandleValue vp) {
2684 // V2 stores the length in |data|. The current version stores the
2685 // length separately to allow larger length values.
2686 uint64_t nbytes = 0;
2687 if (type == SCTAG_ARRAY_BUFFER_OBJECT) {
2688 if (!in.read(&nbytes)) {
2689 return false;
2691 } else {
2692 MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2);
2693 nbytes = data;
2696 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2697 // below, so we have to check this here.
2698 if (nbytes > ArrayBufferObject::MaxByteLength) {
2699 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2700 JSMSG_BAD_ARRAY_LENGTH);
2701 return false;
2704 JSObject* obj = ArrayBufferObject::createZeroed(context(), size_t(nbytes));
2705 if (!obj) {
2706 return false;
2708 vp.setObject(*obj);
2709 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2710 MOZ_ASSERT(buffer.byteLength() == nbytes);
2711 return in.readArray(buffer.dataPointer(), nbytes);
2714 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp) {
2715 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2716 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2717 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2718 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2719 : JS_SCERR_NOT_CLONABLE;
2720 ReportDataCloneError(context(), callbacks, error, closure,
2721 "SharedArrayBuffer");
2722 return false;
2725 uint64_t byteLength;
2726 if (!in.readBytes(&byteLength, sizeof(byteLength))) {
2727 return in.reportTruncated();
2730 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t
2731 // below, so we have to check this here.
2732 if (byteLength > ArrayBufferObject::MaxByteLength) {
2733 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2734 JSMSG_BAD_ARRAY_LENGTH);
2735 return false;
2738 intptr_t p;
2739 if (!in.readBytes(&p, sizeof(p))) {
2740 return in.reportTruncated();
2743 SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p);
2745 // There's no guarantee that the receiving agent has enabled shared memory
2746 // even if the transmitting agent has done so. Ideally we'd check at the
2747 // transmission point, but that's tricky, and it will be a very rare problem
2748 // in any case. Just fail at the receiving end if we can't handle it.
2750 if (!context()
2751 ->realm()
2752 ->creationOptions()
2753 .getSharedMemoryAndAtomicsEnabled()) {
2754 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2755 JSMSG_SC_SAB_DISABLED);
2756 return false;
2759 // The new object will have a new reference to the rawbuf.
2761 if (!rawbuf->addReference()) {
2762 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2763 JSMSG_SC_SAB_REFCNT_OFLO);
2764 return false;
2767 RootedObject obj(context(),
2768 SharedArrayBufferObject::New(context(), rawbuf, byteLength));
2769 if (!obj) {
2770 rawbuf->dropReference();
2771 return false;
2774 // `rawbuf` is now owned by `obj`.
2776 if (callbacks && callbacks->sabCloned &&
2777 !callbacks->sabCloned(context(), /*receiving=*/true, closure)) {
2778 return false;
2781 vp.setObject(*obj);
2782 return true;
2785 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes,
2786 MutableHandleValue vp) {
2787 JSContext* cx = context();
2788 if (nbytes != 0) {
2789 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2790 JSMSG_SC_BAD_SERIALIZED_DATA,
2791 "invalid shared wasm memory tag");
2792 return false;
2795 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2796 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2797 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2798 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2799 : JS_SCERR_NOT_CLONABLE;
2800 ReportDataCloneError(cx, callbacks, error, closure, "WebAssembly.Memory");
2801 return false;
2804 // Read the isHuge flag
2805 RootedValue isHuge(cx);
2806 if (!startRead(&isHuge)) {
2807 return false;
2810 // Read the SharedArrayBuffer object.
2811 RootedValue payload(cx);
2812 if (!startRead(&payload)) {
2813 return false;
2815 if (!payload.isObject() ||
2816 !payload.toObject().is<SharedArrayBufferObject>()) {
2817 JS_ReportErrorNumberASCII(
2818 context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
2819 "shared wasm memory must be backed by a SharedArrayBuffer");
2820 return false;
2823 Rooted<ArrayBufferObjectMaybeShared*> sab(
2824 cx, &payload.toObject().as<SharedArrayBufferObject>());
2826 // Construct the memory.
2827 RootedObject proto(
2828 cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmMemory));
2829 if (!proto) {
2830 return false;
2832 RootedObject memory(
2833 cx, WasmMemoryObject::create(cx, sab, isHuge.toBoolean(), proto));
2834 if (!memory) {
2835 return false;
2838 vp.setObject(*memory);
2839 return true;
2843 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2844 * endianness-conversion while reading.
2846 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType,
2847 uint32_t nelems,
2848 MutableHandleValue vp) {
2849 if (arrayType > Scalar::Uint8Clamped) {
2850 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2851 JSMSG_SC_BAD_SERIALIZED_DATA,
2852 "invalid TypedArray type");
2853 return false;
2856 mozilla::CheckedInt<size_t> nbytes =
2857 mozilla::CheckedInt<size_t>(nelems) *
2858 TypedArrayElemSize(static_cast<Scalar::Type>(arrayType));
2859 if (!nbytes.isValid() || nbytes.value() > UINT32_MAX) {
2860 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2861 JSMSG_SC_BAD_SERIALIZED_DATA,
2862 "invalid typed array size");
2863 return false;
2866 JSObject* obj = ArrayBufferObject::createZeroed(context(), nbytes.value());
2867 if (!obj) {
2868 return false;
2870 vp.setObject(*obj);
2871 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2872 MOZ_ASSERT(buffer.byteLength() == nbytes);
2874 switch (arrayType) {
2875 case Scalar::Int8:
2876 case Scalar::Uint8:
2877 case Scalar::Uint8Clamped:
2878 return in.readArray((uint8_t*)buffer.dataPointer(), nelems);
2879 case Scalar::Int16:
2880 case Scalar::Uint16:
2881 return in.readArray((uint16_t*)buffer.dataPointer(), nelems);
2882 case Scalar::Int32:
2883 case Scalar::Uint32:
2884 case Scalar::Float32:
2885 return in.readArray((uint32_t*)buffer.dataPointer(), nelems);
2886 case Scalar::Float64:
2887 case Scalar::BigInt64:
2888 case Scalar::BigUint64:
2889 return in.readArray((uint64_t*)buffer.dataPointer(), nelems);
2890 default:
2891 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2895 static bool PrimitiveToObject(JSContext* cx, MutableHandleValue vp) {
2896 JSObject* obj = js::PrimitiveToObject(cx, vp);
2897 if (!obj) {
2898 return false;
2901 vp.setObject(*obj);
2902 return true;
2905 bool JSStructuredCloneReader::startRead(MutableHandleValue vp,
2906 ShouldAtomizeStrings atomizeStrings) {
2907 uint32_t tag, data;
2908 bool alreadAppended = false;
2910 if (!in.readPair(&tag, &data)) {
2911 return false;
2914 numItemsRead++;
2916 switch (tag) {
2917 case SCTAG_NULL:
2918 vp.setNull();
2919 break;
2921 case SCTAG_UNDEFINED:
2922 vp.setUndefined();
2923 break;
2925 case SCTAG_INT32:
2926 vp.setInt32(data);
2927 break;
2929 case SCTAG_BOOLEAN:
2930 case SCTAG_BOOLEAN_OBJECT:
2931 vp.setBoolean(!!data);
2932 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) {
2933 return false;
2935 break;
2937 case SCTAG_STRING:
2938 case SCTAG_STRING_OBJECT: {
2939 JSString* str = readString(data, atomizeStrings);
2940 if (!str) {
2941 return false;
2943 vp.setString(str);
2944 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) {
2945 return false;
2947 break;
2950 case SCTAG_NUMBER_OBJECT: {
2951 double d;
2952 if (!in.readDouble(&d)) {
2953 return false;
2955 vp.setDouble(CanonicalizeNaN(d));
2956 if (!PrimitiveToObject(context(), vp)) {
2957 return false;
2959 break;
2962 case SCTAG_BIGINT:
2963 case SCTAG_BIGINT_OBJECT: {
2964 RootedBigInt bi(context(), readBigInt(data));
2965 if (!bi) {
2966 return false;
2968 vp.setBigInt(bi);
2969 if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) {
2970 return false;
2972 break;
2975 case SCTAG_DATE_OBJECT: {
2976 double d;
2977 if (!in.readDouble(&d)) {
2978 return false;
2980 JS::ClippedTime t = JS::TimeClip(d);
2981 if (!NumbersAreIdentical(d, t.toDouble())) {
2982 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2983 JSMSG_SC_BAD_SERIALIZED_DATA, "date");
2984 return false;
2986 JSObject* obj = NewDateObjectMsec(context(), t);
2987 if (!obj) {
2988 return false;
2990 vp.setObject(*obj);
2991 break;
2994 case SCTAG_REGEXP_OBJECT: {
2995 if ((data & RegExpFlag::AllFlags) != data) {
2996 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2997 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2998 return false;
3001 RegExpFlags flags(AssertedCast<uint8_t>(data));
3003 uint32_t tag2, stringData;
3004 if (!in.readPair(&tag2, &stringData)) {
3005 return false;
3007 if (tag2 != SCTAG_STRING) {
3008 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3009 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
3010 return false;
3013 JSString* str = readString(stringData, AtomizeStrings);
3014 if (!str) {
3015 return false;
3018 Rooted<JSAtom*> atom(context(), &str->asAtom());
3020 NewObjectKind kind =
3021 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject;
3022 RegExpObject* reobj = RegExpObject::create(context(), atom, flags, kind);
3023 if (!reobj) {
3024 return false;
3026 vp.setObject(*reobj);
3027 break;
3030 case SCTAG_ARRAY_OBJECT:
3031 case SCTAG_OBJECT_OBJECT: {
3032 NewObjectKind kind =
3033 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject;
3034 JSObject* obj;
3035 if (tag == SCTAG_ARRAY_OBJECT) {
3036 obj = NewDenseUnallocatedArray(
3037 context(), NativeEndian::swapFromLittleEndian(data), kind);
3038 } else {
3039 obj = NewPlainObject(context(), kind);
3041 if (!obj || !objs.append(ObjectValue(*obj))) {
3042 return false;
3045 vp.setObject(*obj);
3046 break;
3049 case SCTAG_BACK_REFERENCE_OBJECT: {
3050 if (data >= allObjs.length() || !allObjs[data].isObject()) {
3051 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3052 JSMSG_SC_BAD_SERIALIZED_DATA,
3053 "invalid back reference in input");
3054 return false;
3056 vp.set(allObjs[data]);
3057 return true;
3060 case SCTAG_TRANSFER_MAP_HEADER:
3061 case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
3062 // We should be past all the transfer map tags.
3063 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3064 JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input");
3065 return false;
3067 case SCTAG_ARRAY_BUFFER_OBJECT_V2:
3068 case SCTAG_ARRAY_BUFFER_OBJECT:
3069 if (!readArrayBuffer(StructuredDataType(tag), data, vp)) {
3070 return false;
3072 break;
3074 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
3075 if (!readSharedArrayBuffer(vp)) {
3076 return false;
3078 break;
3080 case SCTAG_SHARED_WASM_MEMORY_OBJECT:
3081 if (!readSharedWasmMemory(data, vp)) {
3082 return false;
3084 break;
3086 case SCTAG_TYPED_ARRAY_OBJECT_V2: {
3087 // readTypedArray adds the array to allObjs.
3088 // V2 stores the length (nelems) in |data| and the arrayType separately.
3089 uint64_t arrayType;
3090 if (!in.read(&arrayType)) {
3091 return false;
3093 uint64_t nelems = data;
3094 return readTypedArray(arrayType, nelems, vp);
3097 case SCTAG_TYPED_ARRAY_OBJECT: {
3098 // readTypedArray adds the array to allObjs.
3099 // The current version stores the array type in |data| and the length
3100 // (nelems) separately to support large TypedArrays.
3101 uint32_t arrayType = data;
3102 uint64_t nelems;
3103 if (!in.read(&nelems)) {
3104 return false;
3106 return readTypedArray(arrayType, nelems, vp);
3109 case SCTAG_DATA_VIEW_OBJECT_V2: {
3110 // readDataView adds the array to allObjs.
3111 uint64_t byteLength = data;
3112 return readDataView(byteLength, vp);
3115 case SCTAG_DATA_VIEW_OBJECT: {
3116 // readDataView adds the array to allObjs.
3117 uint64_t byteLength;
3118 if (!in.read(&byteLength)) {
3119 return false;
3121 return readDataView(byteLength, vp);
3124 case SCTAG_MAP_OBJECT: {
3125 JSObject* obj = MapObject::create(context());
3126 if (!obj || !objs.append(ObjectValue(*obj))) {
3127 return false;
3129 vp.setObject(*obj);
3130 break;
3133 case SCTAG_SET_OBJECT: {
3134 JSObject* obj = SetObject::create(context());
3135 if (!obj || !objs.append(ObjectValue(*obj))) {
3136 return false;
3138 vp.setObject(*obj);
3139 break;
3142 case SCTAG_SAVED_FRAME_OBJECT: {
3143 auto* obj = readSavedFrameHeader(data);
3144 if (!obj || !objs.append(ObjectValue(*obj)) ||
3145 !objState.append(std::make_pair(obj, false))) {
3146 return false;
3148 vp.setObject(*obj);
3149 break;
3152 case SCTAG_ERROR_OBJECT: {
3153 auto* obj = readErrorHeader(data);
3154 if (!obj || !objs.append(ObjectValue(*obj)) ||
3155 !objState.append(std::make_pair(obj, false))) {
3156 return false;
3158 vp.setObject(*obj);
3159 break;
3162 case SCTAG_END_OF_KEYS:
3163 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3164 JSMSG_SC_BAD_SERIALIZED_DATA,
3165 "truncated input");
3166 return false;
3167 break;
3169 default: {
3170 if (tag <= SCTAG_FLOAT_MAX) {
3171 double d = ReinterpretPairAsDouble(tag, data);
3172 vp.setNumber(CanonicalizeNaN(d));
3173 break;
3176 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
3177 // A v1-format typed array
3178 // readTypedArray adds the array to allObjs
3179 return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
3182 if (!callbacks || !callbacks->read) {
3183 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3184 JSMSG_SC_BAD_SERIALIZED_DATA,
3185 "unsupported type");
3186 return false;
3189 // callbacks->read() might read other objects from the buffer.
3190 // In startWrite we always write the object itself before calling
3191 // the custom function. We should do the same here to keep
3192 // indexing consistent.
3193 uint32_t placeholderIndex = allObjs.length();
3194 Value dummy = UndefinedValue();
3195 if (!allObjs.append(dummy)) {
3196 return false;
3198 JSObject* obj =
3199 callbacks->read(context(), this, cloneDataPolicy, tag, data, closure);
3200 if (!obj) {
3201 return false;
3203 vp.setObject(*obj);
3204 allObjs[placeholderIndex].set(vp);
3205 alreadAppended = true;
3209 if (!alreadAppended && vp.isObject() && !allObjs.append(vp)) {
3210 return false;
3213 return true;
3216 bool JSStructuredCloneReader::readHeader() {
3217 uint32_t tag, data;
3218 if (!in.getPair(&tag, &data)) {
3219 return in.reportTruncated();
3222 JS::StructuredCloneScope storedScope;
3223 if (tag == SCTAG_HEADER) {
3224 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3225 storedScope = JS::StructuredCloneScope(data);
3226 } else {
3227 // Old structured clone buffer. We must have read it from disk.
3228 storedScope = JS::StructuredCloneScope::DifferentProcessForIndexedDB;
3231 // Backward compatibility with old structured clone buffers. Value '0' was
3232 // used for SameProcessSameThread scope.
3233 if ((int)storedScope == 0) {
3234 storedScope = JS::StructuredCloneScope::SameProcess;
3237 if (storedScope < JS::StructuredCloneScope::SameProcess ||
3238 storedScope > JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3239 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3240 JSMSG_SC_BAD_SERIALIZED_DATA,
3241 "invalid structured clone scope");
3242 return false;
3245 if (allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3246 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
3247 // clones are incorrect. Treat them as if they were DifferentProcess.
3248 allowedScope = JS::StructuredCloneScope::DifferentProcess;
3249 return true;
3252 if (storedScope < allowedScope) {
3253 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3254 JSMSG_SC_BAD_SERIALIZED_DATA,
3255 "incompatible structured clone scope");
3256 return false;
3259 return true;
3262 bool JSStructuredCloneReader::readTransferMap() {
3263 JSContext* cx = context();
3264 auto headerPos = in.tell();
3266 uint32_t tag, data;
3267 if (!in.getPair(&tag, &data)) {
3268 return in.reportTruncated();
3271 if (tag != SCTAG_TRANSFER_MAP_HEADER ||
3272 TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
3273 return true;
3276 uint64_t numTransferables;
3277 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3278 if (!in.read(&numTransferables)) {
3279 return false;
3282 for (uint64_t i = 0; i < numTransferables; i++) {
3283 auto pos = in.tell();
3285 if (!in.readPair(&tag, &data)) {
3286 return false;
3289 if (tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY) {
3290 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3291 return false;
3294 RootedObject obj(cx);
3296 void* content;
3297 if (!in.readPtr(&content)) {
3298 return false;
3301 uint64_t extraData;
3302 if (!in.read(&extraData)) {
3303 return false;
3306 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
3307 if (allowedScope == JS::StructuredCloneScope::DifferentProcess ||
3308 allowedScope ==
3309 JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
3310 // Transferred ArrayBuffers in a DifferentProcess clone buffer
3311 // are treated as if they weren't Transferred at all. We should
3312 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
3313 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3314 return false;
3317 MOZ_RELEASE_ASSERT(extraData <= ArrayBufferObject::MaxByteLength);
3318 size_t nbytes = extraData;
3320 MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
3321 data == JS::SCTAG_TMO_MAPPED_DATA);
3322 if (data == JS::SCTAG_TMO_ALLOC_DATA) {
3323 // When the ArrayBuffer can't be allocated, |content| will be free'ed
3324 // in `JSStructuredCloneData::discardTransferables()`.
3325 obj = JS::NewArrayBufferWithContents(
3326 cx, nbytes, content,
3327 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
3328 } else if (data == JS::SCTAG_TMO_MAPPED_DATA) {
3329 obj = JS::NewMappedArrayBufferWithContents(cx, nbytes, content);
3331 } else if (tag == SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER) {
3332 auto savedPos = in.tell();
3333 auto guard = mozilla::MakeScopeExit([&] { in.seekTo(savedPos); });
3334 in.seekTo(pos);
3335 if (!in.seekBy(static_cast<size_t>(extraData))) {
3336 return false;
3339 if (tailStartPos.isNothing()) {
3340 tailStartPos = mozilla::Some(in.tell());
3343 uint32_t tag, data;
3344 if (!in.readPair(&tag, &data)) {
3345 return false;
3347 if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 &&
3348 tag != SCTAG_ARRAY_BUFFER_OBJECT) {
3349 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3350 return false;
3352 RootedValue val(cx);
3353 if (!readArrayBuffer(StructuredDataType(tag), data, &val)) {
3354 return false;
3356 obj = &val.toObject();
3357 tailEndPos = mozilla::Some(in.tell());
3358 } else {
3359 if (!callbacks || !callbacks->readTransfer) {
3360 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3361 return false;
3363 if (!callbacks->readTransfer(cx, this, cloneDataPolicy, tag, content,
3364 extraData, closure, &obj)) {
3365 if (!cx->isExceptionPending()) {
3366 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
3368 return false;
3370 MOZ_ASSERT(obj);
3371 MOZ_ASSERT(!cx->isExceptionPending());
3374 // On failure, the buffer will still own the data (since its ownership
3375 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3376 // DiscardTransferables.
3377 if (!obj) {
3378 return false;
3381 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3382 // buffer.
3383 pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED));
3384 MOZ_ASSERT(!pos.done());
3386 if (!allObjs.append(ObjectValue(*obj))) {
3387 return false;
3391 // Mark the whole transfer map as consumed.
3392 #ifdef DEBUG
3393 SCInput::getPair(headerPos.peek(), &tag, &data);
3394 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
3395 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
3396 #endif
3397 headerPos.write(
3398 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
3400 return true;
3403 JSObject* JSStructuredCloneReader::readSavedFrameHeader(
3404 uint32_t principalsTag) {
3405 Rooted<SavedFrame*> savedFrame(context(), SavedFrame::create(context()));
3406 if (!savedFrame) {
3407 return nullptr;
3410 JSPrincipals* principals;
3411 if (principalsTag == SCTAG_JSPRINCIPALS) {
3412 if (!context()->runtime()->readPrincipals) {
3413 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3414 JSMSG_SC_UNSUPPORTED_TYPE);
3415 return nullptr;
3418 if (!context()->runtime()->readPrincipals(context(), this, &principals)) {
3419 return nullptr;
3421 } else if (principalsTag ==
3422 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
3423 principals = &ReconstructedSavedFramePrincipals::IsSystem;
3424 principals->refcount++;
3425 } else if (principalsTag ==
3426 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
3427 principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
3428 principals->refcount++;
3429 } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
3430 principals = nullptr;
3431 } else {
3432 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3433 JSMSG_SC_BAD_SERIALIZED_DATA,
3434 "bad SavedFrame principals");
3435 return nullptr;
3438 RootedValue mutedErrors(context());
3439 RootedValue source(context());
3441 // Read a |mutedErrors| boolean followed by a |source| string.
3442 // The |mutedErrors| boolean is present in all new structured-clone data,
3443 // but in older data it will be absent and only the |source| string will be
3444 // found.
3445 if (!startRead(&mutedErrors, AtomizeStrings)) {
3446 return nullptr;
3449 if (mutedErrors.isBoolean()) {
3450 if (!startRead(&source, AtomizeStrings) || !source.isString()) {
3451 return nullptr;
3453 } else if (mutedErrors.isString()) {
3454 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3455 // this is actually just a |source| string.
3456 source = mutedErrors;
3457 mutedErrors.setBoolean(true); // Safe default value.
3458 } else {
3459 // Invalid type.
3460 return nullptr;
3464 savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals,
3465 mutedErrors.toBoolean());
3467 savedFrame->initSource(&source.toString()->asAtom());
3469 uint32_t line;
3470 if (!readUint32(&line)) {
3471 return nullptr;
3473 savedFrame->initLine(line);
3475 JS::TaggedColumnNumberOneOrigin column;
3476 if (!readUint32(column.addressOfValueForTranscode())) {
3477 return nullptr;
3479 savedFrame->initColumn(column);
3481 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3482 // are only valid within a specific process.
3483 savedFrame->initSourceId(0);
3485 RootedValue name(context());
3486 if (!startRead(&name, AtomizeStrings)) {
3487 return nullptr;
3489 if (!(name.isString() || name.isNull())) {
3490 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3491 JSMSG_SC_BAD_SERIALIZED_DATA,
3492 "invalid saved frame cause");
3493 return nullptr;
3495 JSAtom* atomName = nullptr;
3496 if (name.isString()) {
3497 atomName = &name.toString()->asAtom();
3500 savedFrame->initFunctionDisplayName(atomName);
3502 RootedValue cause(context());
3503 if (!startRead(&cause, AtomizeStrings)) {
3504 return nullptr;
3506 if (!(cause.isString() || cause.isNull())) {
3507 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3508 JSMSG_SC_BAD_SERIALIZED_DATA,
3509 "invalid saved frame cause");
3510 return nullptr;
3512 JSAtom* atomCause = nullptr;
3513 if (cause.isString()) {
3514 atomCause = &cause.toString()->asAtom();
3516 savedFrame->initAsyncCause(atomCause);
3518 return savedFrame;
3521 // SavedFrame object: there is one child value, the parent SavedFrame,
3522 // which is either null or another SavedFrame object.
3523 bool JSStructuredCloneReader::readSavedFrameFields(Handle<SavedFrame*> frameObj,
3524 HandleValue parent,
3525 bool* state) {
3526 if (*state) {
3527 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3528 JSMSG_SC_BAD_SERIALIZED_DATA,
3529 "multiple SavedFrame parents");
3530 return false;
3533 SavedFrame* parentFrame;
3534 if (parent.isNull()) {
3535 parentFrame = nullptr;
3536 } else if (parent.isObject() && parent.toObject().is<SavedFrame>()) {
3537 parentFrame = &parent.toObject().as<SavedFrame>();
3538 } else {
3539 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3540 JSMSG_SC_BAD_SERIALIZED_DATA,
3541 "invalid SavedFrame parent");
3542 return false;
3545 frameObj->initParent(parentFrame);
3546 *state = true;
3547 return true;
3550 JSObject* JSStructuredCloneReader::readErrorHeader(uint32_t type) {
3551 JSContext* cx = context();
3553 switch (type) {
3554 case JSEXN_ERR:
3555 case JSEXN_EVALERR:
3556 case JSEXN_RANGEERR:
3557 case JSEXN_REFERENCEERR:
3558 case JSEXN_SYNTAXERR:
3559 case JSEXN_TYPEERR:
3560 case JSEXN_URIERR:
3561 case JSEXN_AGGREGATEERR:
3562 break;
3563 default:
3564 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3565 JSMSG_SC_BAD_SERIALIZED_DATA,
3566 "invalid error type");
3567 return nullptr;
3570 RootedString message(cx);
3572 RootedValue messageVal(cx);
3573 if (!startRead(&messageVal)) {
3574 return nullptr;
3576 if (messageVal.isString()) {
3577 message = messageVal.toString();
3578 } else if (!messageVal.isNull()) {
3579 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3580 JSMSG_SC_BAD_SERIALIZED_DATA,
3581 "invalid 'message' field for Error object");
3582 return nullptr;
3586 // We have to set |cause| to something if it exists, otherwise the shape
3587 // would be wrong. The actual value will be overwritten later.
3588 RootedValue val(cx);
3589 if (!startRead(&val)) {
3590 return nullptr;
3592 bool hasCause = ToBoolean(val);
3593 Rooted<Maybe<Value>> cause(cx, mozilla::Nothing());
3594 if (hasCause) {
3595 cause = mozilla::Some(BooleanValue(true));
3598 if (!startRead(&val)) {
3599 return nullptr;
3601 if (!val.isString()) {
3602 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3603 JSMSG_SC_BAD_SERIALIZED_DATA,
3604 "invalid 'fileName' field for Error object");
3605 return nullptr;
3607 RootedString fileName(cx, val.toString());
3609 uint32_t lineNumber;
3610 JS::ColumnNumberOneOrigin columnNumber;
3611 if (!readUint32(&lineNumber) ||
3612 !readUint32(columnNumber.addressOfValueForTranscode())) {
3613 return nullptr;
3616 // The |cause| and |stack| slots of the objects might be overwritten later.
3617 // For AggregateErrors the |errors| property will be added.
3618 RootedObject errorObj(
3619 cx, ErrorObject::create(cx, static_cast<JSExnType>(type), nullptr,
3620 fileName, 0, lineNumber, columnNumber, nullptr,
3621 message, cause));
3622 if (!errorObj) {
3623 return nullptr;
3626 return errorObj;
3629 // Error objects have 3 fields, some or all of them null: cause,
3630 // errors, and stack.
3631 bool JSStructuredCloneReader::readErrorFields(Handle<ErrorObject*> errorObj,
3632 HandleValue cause, bool* state) {
3633 JSContext* cx = context();
3634 if (*state) {
3635 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3636 JSMSG_SC_BAD_SERIALIZED_DATA,
3637 "unexpected child value seen for Error object");
3638 return false;
3641 RootedValue errors(cx);
3642 RootedValue stack(cx);
3643 if (!startRead(&errors) || !startRead(&stack)) {
3644 return false;
3647 bool hasCause = errorObj->getCause().isSome();
3648 if (hasCause) {
3649 errorObj->setCauseSlot(cause);
3650 } else if (!cause.isNull()) {
3651 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3652 JSMSG_SC_BAD_SERIALIZED_DATA,
3653 "invalid 'cause' field for Error object");
3654 return false;
3657 if (errorObj->type() == JSEXN_AGGREGATEERR) {
3658 if (!DefineDataProperty(context(), errorObj, cx->names().errors, errors,
3659 0)) {
3660 return false;
3662 } else if (!errors.isNull()) {
3663 JS_ReportErrorNumberASCII(
3664 cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
3665 "unexpected 'errors' field seen for non-AggregateError");
3666 return false;
3669 if (stack.isObject()) {
3670 RootedObject stackObj(cx, &stack.toObject());
3671 if (!stackObj->is<SavedFrame>()) {
3672 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3673 JSMSG_SC_BAD_SERIALIZED_DATA,
3674 "invalid 'stack' field for Error object");
3675 return false;
3677 errorObj->setStackSlot(stack);
3678 } else if (!stack.isNull()) {
3679 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3680 JSMSG_SC_BAD_SERIALIZED_DATA,
3681 "invalid 'stack' field for Error object");
3682 return false;
3685 *state = true;
3686 return true;
3689 // Read a value and treat as a key,value pair.
3690 bool JSStructuredCloneReader::readMapField(Handle<MapObject*> mapObj,
3691 HandleValue key) {
3692 RootedValue val(context());
3693 if (!startRead(&val)) {
3694 return false;
3696 return MapObject::set(context(), mapObj, key, val);
3699 // Read a value and treat as a key,value pair. Interpret as a plain property
3700 // value.
3701 bool JSStructuredCloneReader::readObjectField(HandleObject obj,
3702 HandleValue key) {
3703 if (!key.isString() && !key.isInt32()) {
3704 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3705 JSMSG_SC_BAD_SERIALIZED_DATA,
3706 "property key expected");
3707 return false;
3710 RootedValue val(context());
3711 if (!startRead(&val)) {
3712 return false;
3715 RootedId id(context());
3716 if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
3717 return false;
3720 // Fast path for adding a new property to a plain object. The property names
3721 // we see here should be unique, but we check for duplicates to guard against
3722 // corrupt or malicious data.
3723 if (id.isString() && obj->is<PlainObject>() &&
3724 MOZ_LIKELY(!obj->as<PlainObject>().contains(context(), id))) {
3725 return AddDataPropertyToPlainObject(context(), obj.as<PlainObject>(), id,
3726 val);
3729 // Fast path for adding an array element. The index shouldn't exceed the
3730 // array's length, but we check for this in `addDenseElementNoLengthChange` to
3731 // guard against corrupt or malicious data.
3732 if (id.isInt() && obj->is<ArrayObject>()) {
3733 ArrayObject* arr = &obj->as<ArrayObject>();
3734 switch (arr->addDenseElementNoLengthChange(context(), id.toInt(), val)) {
3735 case DenseElementResult::Failure:
3736 return false;
3737 case DenseElementResult::Success:
3738 return true;
3739 case DenseElementResult::Incomplete:
3740 // Fall-through to slow path.
3741 break;
3745 return DefineDataProperty(context(), obj, id, val);
3748 // Perform the whole recursive reading procedure.
3749 bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
3750 auto startTime = mozilla::TimeStamp::Now();
3752 if (!readHeader()) {
3753 return false;
3756 if (!readTransferMap()) {
3757 return false;
3760 MOZ_ASSERT(objs.length() == 0);
3761 MOZ_ASSERT(objState.length() == 1);
3763 // Start out by reading in the main object and pushing it onto the 'objs'
3764 // stack. The data related to this object and its descendants extends from
3765 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3766 if (!startRead(vp)) {
3767 return false;
3770 // Stop when the stack shows that all objects have been read.
3771 while (objs.length() != 0) {
3772 // What happens depends on the top obj on the objs stack.
3773 RootedObject obj(context(), &objs.back().toObject());
3775 uint32_t tag, data;
3776 if (!in.getPair(&tag, &data)) {
3777 return false;
3780 if (tag == SCTAG_END_OF_KEYS) {
3781 // Pop the current obj off the stack, since we are done with it and
3782 // its children.
3783 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3784 objs.popBack();
3785 if (objState.back().first == obj) {
3786 objState.popBack();
3788 continue;
3791 // Remember the index of the current top of the state stack, which will
3792 // correspond to the state for `obj` iff `obj` is a type that uses state.
3793 // startRead() may push additional entries before the state is accessed and
3794 // updated while filling in the object's data.
3795 size_t objStateIdx = objState.length() - 1;
3797 // The input stream contains a sequence of "child" values, whose
3798 // interpretation depends on the type of obj. These values can be
3799 // anything, and startRead() will push onto 'objs' for any non-leaf
3800 // value (i.e., anything that may contain children).
3802 // startRead() will allocate the (empty) object, but note that when
3803 // startRead() returns, 'key' is not yet initialized with any of its
3804 // properties. Those will be filled in by returning to the head of this
3805 // loop, processing the first child obj, and continuing until all
3806 // children have been fully created.
3808 // Note that this means the ordering in the stream is a little funky for
3809 // things like Map. See the comment above traverseMap() for an example.
3811 bool expectKeyValuePairs =
3812 !(obj->is<MapObject>() || obj->is<SetObject>() ||
3813 obj->is<SavedFrame>() || obj->is<ErrorObject>());
3815 RootedValue key(context());
3816 ShouldAtomizeStrings atomize =
3817 expectKeyValuePairs ? AtomizeStrings : DontAtomizeStrings;
3818 if (!startRead(&key, atomize)) {
3819 return false;
3822 if (key.isNull() && expectKeyValuePairs) {
3823 // Backwards compatibility: Null formerly indicated the end of
3824 // object properties.
3826 // No legacy objects used the state stack.
3827 MOZ_ASSERT(objState[objStateIdx].first() != obj);
3829 objs.popBack();
3830 continue;
3833 context()->check(key);
3835 if (obj->is<SetObject>()) {
3836 // Set object: the values between obj header (from startRead()) and
3837 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3838 if (!SetObject::add(context(), obj, key)) {
3839 return false;
3841 } else if (obj->is<MapObject>()) {
3842 Rooted<MapObject*> mapObj(context(), &obj->as<MapObject>());
3843 if (!readMapField(mapObj, key)) {
3844 return false;
3846 } else if (obj->is<SavedFrame>()) {
3847 Rooted<SavedFrame*> frameObj(context(), &obj->as<SavedFrame>());
3848 MOZ_ASSERT(objState[objStateIdx].first() == obj);
3849 bool state = objState[objStateIdx].second();
3850 if (!readSavedFrameFields(frameObj, key, &state)) {
3851 return false;
3853 objState[objStateIdx].second() = state;
3854 } else if (obj->is<ErrorObject>()) {
3855 Rooted<ErrorObject*> errorObj(context(), &obj->as<ErrorObject>());
3856 MOZ_ASSERT(objState[objStateIdx].first() == obj);
3857 bool state = objState[objStateIdx].second();
3858 if (!readErrorFields(errorObj, key, &state)) {
3859 return false;
3861 objState[objStateIdx].second() = state;
3862 } else {
3863 MOZ_ASSERT(expectKeyValuePairs);
3864 // Everything else uses a series of key,value,key,value,... Value
3865 // objects.
3866 if (!readObjectField(obj, key)) {
3867 return false;
3872 allObjs.clear();
3874 // For fuzzing, it is convenient to allow extra data at the end
3875 // of the input buffer so that more possible inputs are considered
3876 // valid.
3877 #ifndef FUZZING
3878 bool extraData;
3879 if (tailStartPos.isSome()) {
3880 // in.tell() is the end of the main data. If "tail" data was consumed,
3881 // then check whether there's any data between the main data and the
3882 // beginning of the tail, or after the last read point in the tail.
3883 extraData = (in.tell() != *tailStartPos || !tailEndPos->done());
3884 } else {
3885 extraData = !in.tell().done();
3887 if (extraData) {
3888 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3889 JSMSG_SC_BAD_SERIALIZED_DATA,
3890 "extra data after end");
3891 return false;
3893 #endif
3895 JSRuntime* rt = context()->runtime();
3896 rt->metrics().DESERIALIZE_BYTES(nbytes);
3897 rt->metrics().DESERIALIZE_ITEMS(numItemsRead);
3898 mozilla::TimeDuration elapsed = mozilla::TimeStamp::Now() - startTime;
3899 rt->metrics().DESERIALIZE_US(elapsed);
3901 return true;
3904 JS_PUBLIC_API bool JS_ReadStructuredClone(
3905 JSContext* cx, const JSStructuredCloneData& buf, uint32_t version,
3906 JS::StructuredCloneScope scope, MutableHandleValue vp,
3907 const JS::CloneDataPolicy& cloneDataPolicy,
3908 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3909 AssertHeapIsIdle();
3910 CHECK_THREAD(cx);
3912 if (version > JS_STRUCTURED_CLONE_VERSION) {
3913 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3914 JSMSG_SC_BAD_CLONE_VERSION);
3915 return false;
3917 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3918 return ReadStructuredClone(cx, buf, scope, vp, cloneDataPolicy, callbacks,
3919 closure);
3922 JS_PUBLIC_API bool JS_WriteStructuredClone(
3923 JSContext* cx, HandleValue value, JSStructuredCloneData* bufp,
3924 JS::StructuredCloneScope scope, const JS::CloneDataPolicy& cloneDataPolicy,
3925 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure,
3926 HandleValue transferable) {
3927 AssertHeapIsIdle();
3928 CHECK_THREAD(cx);
3929 cx->check(value);
3931 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3932 return WriteStructuredClone(cx, value, bufp, scope, cloneDataPolicy,
3933 callbacks, closure, transferable);
3936 JS_PUBLIC_API bool JS_StructuredCloneHasTransferables(
3937 JSStructuredCloneData& data, bool* hasTransferable) {
3938 *hasTransferable = StructuredCloneHasTransferObjects(data);
3939 return true;
3942 JS_PUBLIC_API bool JS_StructuredClone(
3943 JSContext* cx, HandleValue value, MutableHandleValue vp,
3944 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3945 AssertHeapIsIdle();
3946 CHECK_THREAD(cx);
3948 // Strings are associated with zones, not compartments,
3949 // so we copy the string by wrapping it.
3950 if (value.isString()) {
3951 RootedString strValue(cx, value.toString());
3952 if (!cx->compartment()->wrap(cx, &strValue)) {
3953 return false;
3955 vp.setString(strValue);
3956 return true;
3959 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3961 JSAutoStructuredCloneBuffer buf(JS::StructuredCloneScope::SameProcess,
3962 callbacks, closure);
3964 if (value.isObject()) {
3965 RootedObject obj(cx, &value.toObject());
3966 obj = CheckedUnwrapStatic(obj);
3967 if (!obj) {
3968 ReportAccessDenied(cx);
3969 return false;
3971 AutoRealm ar(cx, obj);
3972 RootedValue unwrappedVal(cx, ObjectValue(*obj));
3973 if (!buf.write(cx, unwrappedVal, callbacks, closure)) {
3974 return false;
3976 } else {
3977 if (!buf.write(cx, value, callbacks, closure)) {
3978 return false;
3983 return buf.read(cx, vp, JS::CloneDataPolicy(), callbacks, closure);
3986 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3987 JSAutoStructuredCloneBuffer&& other)
3988 : data_(other.scope()) {
3989 version_ = other.version_;
3990 other.giveTo(&data_);
3993 JSAutoStructuredCloneBuffer& JSAutoStructuredCloneBuffer::operator=(
3994 JSAutoStructuredCloneBuffer&& other) {
3995 MOZ_ASSERT(&other != this);
3996 MOZ_ASSERT(scope() == other.scope());
3997 clear();
3998 version_ = other.version_;
3999 other.giveTo(&data_);
4000 return *this;
4003 void JSAutoStructuredCloneBuffer::clear() {
4004 data_.discardTransferables();
4005 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
4006 data_.refsHeld_.releaseAll();
4007 data_.Clear();
4008 version_ = 0;
4011 void JSAutoStructuredCloneBuffer::adopt(
4012 JSStructuredCloneData&& data, uint32_t version,
4013 const JSStructuredCloneCallbacks* callbacks, void* closure) {
4014 clear();
4015 data_ = std::move(data);
4016 version_ = version;
4017 data_.setCallbacks(callbacks, closure,
4018 OwnTransferablePolicy::OwnsTransferablesIfAny);
4021 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData* data) {
4022 *data = std::move(data_);
4023 version_ = 0;
4024 data_.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables);
4025 data_.Clear();
4028 bool JSAutoStructuredCloneBuffer::read(
4029 JSContext* cx, MutableHandleValue vp,
4030 const JS::CloneDataPolicy& cloneDataPolicy,
4031 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4032 MOZ_ASSERT(cx);
4033 return !!JS_ReadStructuredClone(
4034 cx, data_, version_, data_.scope(), vp, cloneDataPolicy,
4035 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4036 optionalCallbacks ? closure : data_.closure_);
4039 bool JSAutoStructuredCloneBuffer::write(
4040 JSContext* cx, HandleValue value,
4041 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4042 HandleValue transferable = UndefinedHandleValue;
4043 return write(cx, value, transferable, JS::CloneDataPolicy(),
4044 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4045 optionalCallbacks ? closure : data_.closure_);
4048 bool JSAutoStructuredCloneBuffer::write(
4049 JSContext* cx, HandleValue value, HandleValue transferable,
4050 const JS::CloneDataPolicy& cloneDataPolicy,
4051 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
4052 clear();
4053 bool ok = JS_WriteStructuredClone(
4054 cx, value, &data_, data_.scopeForInternalWriting(), cloneDataPolicy,
4055 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
4056 optionalCallbacks ? closure : data_.closure_, transferable);
4057 if (!ok) {
4058 version_ = JS_STRUCTURED_CLONE_VERSION;
4060 return ok;
4063 JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1,
4064 uint32_t* p2) {
4065 return r->input().readPair((uint32_t*)p1, (uint32_t*)p2);
4068 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p,
4069 size_t len) {
4070 return r->input().readBytes(p, len);
4073 JS_PUBLIC_API bool JS_ReadString(JSStructuredCloneReader* r,
4074 MutableHandleString str) {
4075 uint32_t tag, data;
4076 if (!r->input().readPair(&tag, &data)) {
4077 return false;
4080 if (tag == SCTAG_STRING) {
4081 if (JSString* s =
4082 r->readString(data, JSStructuredCloneReader::DontAtomizeStrings)) {
4083 str.set(s);
4084 return true;
4086 return false;
4089 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
4090 JSMSG_SC_BAD_SERIALIZED_DATA, "expected string");
4091 return false;
4094 JS_PUBLIC_API bool JS_ReadDouble(JSStructuredCloneReader* r, double* v) {
4095 return r->input().readDouble(v);
4098 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r,
4099 MutableHandleValue vp) {
4100 uint32_t tag, data;
4101 if (!r->input().readPair(&tag, &data)) {
4102 return false;
4105 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
4106 return r->readTypedArray(TagToV1ArrayType(tag), data, vp, true);
4109 if (tag == SCTAG_TYPED_ARRAY_OBJECT_V2) {
4110 // V2 stores the length (nelems) in |data| and the arrayType separately.
4111 uint64_t arrayType;
4112 if (!r->input().read(&arrayType)) {
4113 return false;
4115 uint64_t nelems = data;
4116 return r->readTypedArray(arrayType, nelems, vp);
4119 if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
4120 // The current version stores the array type in |data| and the length
4121 // (nelems) separately to support large TypedArrays.
4122 uint32_t arrayType = data;
4123 uint64_t nelems;
4124 if (!r->input().read(&nelems)) {
4125 return false;
4127 return r->readTypedArray(arrayType, nelems, vp);
4130 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
4131 JSMSG_SC_BAD_SERIALIZED_DATA,
4132 "expected type array");
4133 return false;
4136 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag,
4137 uint32_t data) {
4138 return w->output().writePair(tag, data);
4141 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p,
4142 size_t len) {
4143 return w->output().writeBytes(p, len);
4146 JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w,
4147 HandleString str) {
4148 return w->writeString(SCTAG_STRING, str);
4151 JS_PUBLIC_API bool JS_WriteDouble(JSStructuredCloneWriter* w, double v) {
4152 return w->output().writeDouble(v);
4155 JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w,
4156 HandleValue v) {
4157 MOZ_ASSERT(v.isObject());
4158 w->context()->check(v);
4159 RootedObject obj(w->context(), &v.toObject());
4161 // startWrite can write everything, thus we should check here
4162 // and report error if the user passes a wrong type.
4163 if (!obj->canUnwrapAs<TypedArrayObject>()) {
4164 ReportAccessDenied(w->context());
4165 return false;
4168 // We should use startWrite instead of writeTypedArray, because
4169 // typed array is an object, we should add it to the |memory|
4170 // (allObjs) list. Directly calling writeTypedArray won't add it.
4171 return w->startWrite(v);
4174 JS_PUBLIC_API bool JS_ObjectNotWritten(JSStructuredCloneWriter* w,
4175 HandleObject obj) {
4176 w->memory.remove(w->memory.lookup(obj));
4178 return true;
4181 JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope(
4182 JSStructuredCloneWriter* w) {
4183 return w->output().scope();