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/. */
6
7 /*
8 * This file implements the structured data algorithms of
9 * https://html.spec.whatwg.org/multipage/structured-data.html
10 *
11 * The spec is in two parts:
12 *
13 * - StructuredSerialize examines a JS value and produces a graph of Records.
14 * - StructuredDeserialize walks the Records and produces a new JS value.
15 *
16 * The differences between our implementation and the spec are minor:
17 *
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.
27 */
28
29 #include "js/StructuredClone.h"
30
31 #include "mozilla/Casting.h"
32 #include "mozilla/CheckedInt.h"
33 #include "mozilla/EndianUtils.h"
34 #include "mozilla/FloatingPoint.h"
35 #include "mozilla/RangedPtr.h"
36 #include "mozilla/ScopeExit.h"
37
38 #include <algorithm>
39 #include <memory>
40 #include <utility>
41
42 #include "jsapi.h"
43 #include "jsdate.h"
44
45 #include "builtin/DataViewObject.h"
46 #include "builtin/MapObject.h"
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/Date.h"
50 #include "js/experimental/TypedData.h" // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer
51 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
52 #include "js/GCHashTable.h"
53 #include "js/Object.h" // JS::GetBuiltinClass
54 #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
55 #include "js/ScalarType.h" // js::Scalar::Type
56 #include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject
57 #include "js/Wrapper.h"
58 #include "vm/BigIntType.h"
59 #include "vm/JSContext.h"
60 #include "vm/PlainObject.h" // js::PlainObject
61 #include "vm/RegExpObject.h"
62 #include "vm/SavedFrame.h"
63 #include "vm/SharedArrayObject.h"
64 #include "vm/TypedArrayObject.h"
65 #include "vm/WrapperObject.h"
66 #include "wasm/WasmJS.h"
67
68 #include "vm/InlineCharBuffer-inl.h"
69 #include "vm/JSContext-inl.h"
70 #include "vm/JSObject-inl.h"
71
72 using namespace js;
73
74 using JS::CanonicalizeNaN;
75 using JS::GetBuiltinClass;
76 using JS::RegExpFlag;
77 using JS::RegExpFlags;
78 using JS::RootedValueVector;
79 using mozilla::AssertedCast;
80 using mozilla::BitwiseCast;
81 using mozilla::NativeEndian;
82 using mozilla::NumbersAreIdentical;
83 using mozilla::RangedPtr;
84
85 // When you make updates here, make sure you consider whether you need to bump
86 // the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You
87 // will likely need to increment the version if anything at all changes in the
88 // serialization format.
89 //
90 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should
91 // have a stable ID, it need not be at the end of the list and should not be
92 // used for sizing data structures.
93
94 enum StructuredDataType : uint32_t {
95 // Structured data types provided by the engine
96 SCTAG_FLOAT_MAX = 0xFFF00000,
97 SCTAG_HEADER = 0xFFF10000,
98 SCTAG_NULL = 0xFFFF0000,
99 SCTAG_UNDEFINED,
100 SCTAG_BOOLEAN,
101 SCTAG_INT32,
102 SCTAG_STRING,
103 SCTAG_DATE_OBJECT,
104 SCTAG_REGEXP_OBJECT,
105 SCTAG_ARRAY_OBJECT,
106 SCTAG_OBJECT_OBJECT,
107 SCTAG_ARRAY_BUFFER_OBJECT_V2, // Old version, for backwards compatibility.
108 SCTAG_BOOLEAN_OBJECT,
109 SCTAG_STRING_OBJECT,
110 SCTAG_NUMBER_OBJECT,
111 SCTAG_BACK_REFERENCE_OBJECT,
112 SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
113 SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
114 SCTAG_TYPED_ARRAY_OBJECT_V2, // Old version, for backwards compatibility.
115 SCTAG_MAP_OBJECT,
116 SCTAG_SET_OBJECT,
117 SCTAG_END_OF_KEYS,
118 SCTAG_DO_NOT_USE_3, // Required for backwards compatibility
119 SCTAG_DATA_VIEW_OBJECT_V2, // Old version, for backwards compatibility.
120 SCTAG_SAVED_FRAME_OBJECT,
121
122 // No new tags before principals.
123 SCTAG_JSPRINCIPALS,
124 SCTAG_NULL_JSPRINCIPALS,
125 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
126 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
127
128 SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
129 SCTAG_SHARED_WASM_MEMORY_OBJECT,
130
131 SCTAG_BIGINT,
132 SCTAG_BIGINT_OBJECT,
133
134 SCTAG_ARRAY_BUFFER_OBJECT,
135 SCTAG_TYPED_ARRAY_OBJECT,
136 SCTAG_DATA_VIEW_OBJECT,
137
138 SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
139 SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
140 SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
141 SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
142 SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
143 SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
144 SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
145 SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
146 SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
147 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED =
148 SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
149 // BigInt64 and BigUint64 are not supported in the v1 format.
150 SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED,
151
152 // Define a separate range of numbers for Transferable-only tags, since
153 // they are not used for persistent clone buffers and therefore do not
154 // require bumping JS_STRUCTURED_CLONE_VERSION.
155 SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
156 SCTAG_TRANSFER_MAP_PENDING_ENTRY,
157 SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
158 SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER,
159 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
160
161 SCTAG_END_OF_BUILTIN_TYPES
162 };
163
164 /*
165 * Format of transfer map:
166 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
167 * numTransferables (64 bits)
168 * array of:
169 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
170 * pointer (64 bits)
171 * extraData (64 bits), eg byte length for ArrayBuffers
172 */
173
174 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
175 // contents have been read out yet or not.
176 enum TransferableMapHeader { SCTAG_TM_UNREAD = 0, SCTAG_TM_TRANSFERRED };
177
PairToUInt64(uint32_t tag,uint32_t data)178 static inline uint64_t PairToUInt64(uint32_t tag, uint32_t data) {
179 return uint64_t(data) | (uint64_t(tag) << 32);
180 }
181
182 namespace js {
183
184 template <typename T, typename AllocPolicy>
185 struct BufferIterator {
186 using BufferList = mozilla::BufferList<AllocPolicy>;
187
BufferIteratorjs::BufferIterator188 explicit BufferIterator(const BufferList& buffer)
189 : mBuffer(buffer), mIter(buffer.Iter()) {
190 static_assert(8 % sizeof(T) == 0);
191 }
192
BufferIteratorjs::BufferIterator193 explicit BufferIterator(const JSStructuredCloneData& data)
194 : mBuffer(data.bufList_), mIter(data.Start()) {}
195
operator =js::BufferIterator196 BufferIterator& operator=(const BufferIterator& other) {
197 MOZ_ASSERT(&mBuffer == &other.mBuffer);
198 mIter = other.mIter;
199 return *this;
200 }
201
advancejs::BufferIterator202 [[nodiscard]] bool advance(size_t size = sizeof(T)) {
203 return mIter.AdvanceAcrossSegments(mBuffer, size);
204 }
205
operator ++js::BufferIterator206 BufferIterator operator++(int) {
207 BufferIterator ret = *this;
208 if (!advance(sizeof(T))) {
209 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
210 }
211 return ret;
212 }
213
operator +=js::BufferIterator214 BufferIterator& operator+=(size_t size) {
215 if (!advance(size)) {
216 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
217 }
218 return *this;
219 }
220
operator -js::BufferIterator221 size_t operator-(const BufferIterator& other) const {
222 MOZ_ASSERT(&mBuffer == &other.mBuffer);
223 return mBuffer.RangeLength(other.mIter, mIter);
224 }
225
donejs::BufferIterator226 bool done() const { return mIter.Done(); }
227
readBytesjs::BufferIterator228 [[nodiscard]] bool readBytes(char* outData, size_t size) {
229 return mBuffer.ReadBytes(mIter, outData, size);
230 }
231
writejs::BufferIterator232 void write(const T& data) {
233 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
234 *reinterpret_cast<T*>(mIter.Data()) = data;
235 }
236
peekjs::BufferIterator237 T peek() const {
238 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
239 return *reinterpret_cast<T*>(mIter.Data());
240 }
241
canPeekjs::BufferIterator242 bool canPeek() const { return mIter.HasRoomFor(sizeof(T)); }
243
244 const BufferList& mBuffer;
245 typename BufferList::IterImpl mIter;
246 };
247
operator =(SharedArrayRawBufferRefs && other)248 SharedArrayRawBufferRefs& SharedArrayRawBufferRefs::operator=(
249 SharedArrayRawBufferRefs&& other) {
250 takeOwnership(std::move(other));
251 return *this;
252 }
253
~SharedArrayRawBufferRefs()254 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }
255
acquire(JSContext * cx,SharedArrayRawBuffer * rawbuf)256 bool SharedArrayRawBufferRefs::acquire(JSContext* cx,
257 SharedArrayRawBuffer* rawbuf) {
258 if (!refs_.append(rawbuf)) {
259 ReportOutOfMemory(cx);
260 return false;
261 }
262
263 if (!rawbuf->addReference()) {
264 refs_.popBack();
265 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
266 JSMSG_SC_SAB_REFCNT_OFLO);
267 return false;
268 }
269
270 return true;
271 }
272
acquireAll(JSContext * cx,const SharedArrayRawBufferRefs & that)273 bool SharedArrayRawBufferRefs::acquireAll(
274 JSContext* cx, const SharedArrayRawBufferRefs& that) {
275 if (!refs_.reserve(refs_.length() + that.refs_.length())) {
276 ReportOutOfMemory(cx);
277 return false;
278 }
279
280 for (auto ref : that.refs_) {
281 if (!ref->addReference()) {
282 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
283 JSMSG_SC_SAB_REFCNT_OFLO);
284 return false;
285 }
286 MOZ_ALWAYS_TRUE(refs_.append(ref));
287 }
288
289 return true;
290 }
291
takeOwnership(SharedArrayRawBufferRefs && other)292 void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs&& other) {
293 MOZ_ASSERT(refs_.empty());
294 refs_ = std::move(other.refs_);
295 }
296
releaseAll()297 void SharedArrayRawBufferRefs::releaseAll() {
298 for (auto ref : refs_) {
299 ref->dropReference();
300 }
301 refs_.clear();
302 }
303
304 // SCOutput provides an interface to write raw data -- eg uint64_ts, doubles,
305 // arrays of bytes -- into a structured clone data output stream. It also knows
306 // how to free any transferable data within that stream.
307 //
308 // Note that it contains a full JSStructuredCloneData object, which holds the
309 // callbacks necessary to read/write/transfer/free the data. For the purpose of
310 // this class, only the freeTransfer callback is relevant; the rest of the
311 // callbacks are used by the higher-level JSStructuredCloneWriter interface.
312 struct SCOutput {
313 public:
314 using Iter = BufferIterator<uint64_t, SystemAllocPolicy>;
315
316 SCOutput(JSContext* cx, JS::StructuredCloneScope scope);
317
contextjs::SCOutput318 JSContext* context() const { return cx; }
scopejs::SCOutput319 JS::StructuredCloneScope scope() const { return buf.scope(); }
sameProcessScopeRequiredjs::SCOutput320 void sameProcessScopeRequired() { buf.sameProcessScopeRequired(); }
321
322 [[nodiscard]] bool write(uint64_t u);
323 [[nodiscard]] bool writePair(uint32_t tag, uint32_t data);
324 [[nodiscard]] bool writeDouble(double d);
325 [[nodiscard]] bool writeBytes(const void* p, size_t nbytes);
326 [[nodiscard]] bool writeChars(const Latin1Char* p, size_t nchars);
327 [[nodiscard]] bool writeChars(const char16_t* p, size_t nchars);
328
329 template <class T>
330 [[nodiscard]] bool writeArray(const T* p, size_t nelems);
331
setCallbacksjs::SCOutput332 void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure,
333 OwnTransferablePolicy policy) {
334 buf.setCallbacks(callbacks, closure, policy);
335 }
extractBufferjs::SCOutput336 void extractBuffer(JSStructuredCloneData* data) { *data = std::move(buf); }
337 void discardTransferables();
338
telljs::SCOutput339 uint64_t tell() const { return buf.Size(); }
countjs::SCOutput340 uint64_t count() const { return buf.Size() / sizeof(uint64_t); }
iterjs::SCOutput341 Iter iter() { return Iter(buf); }
342
offsetjs::SCOutput343 size_t offset(Iter dest) { return dest - iter(); }
344
345 JSContext* cx;
346 JSStructuredCloneData buf;
347 };
348
349 class SCInput {
350 typedef js::BufferIterator<uint64_t, SystemAllocPolicy> BufferIterator;
351
352 public:
353 SCInput(JSContext* cx, const JSStructuredCloneData& data);
354
context() const355 JSContext* context() const { return cx; }
356
357 static void getPtr(uint64_t data, void** ptr);
358 static void getPair(uint64_t data, uint32_t* tagp, uint32_t* datap);
359
360 [[nodiscard]] bool read(uint64_t* p);
361 [[nodiscard]] bool readPair(uint32_t* tagp, uint32_t* datap);
362 [[nodiscard]] bool readDouble(double* p);
363 [[nodiscard]] bool readBytes(void* p, size_t nbytes);
364 [[nodiscard]] bool readChars(Latin1Char* p, size_t nchars);
365 [[nodiscard]] bool readChars(char16_t* p, size_t nchars);
366 [[nodiscard]] bool readPtr(void**);
367
368 [[nodiscard]] bool get(uint64_t* p);
369 [[nodiscard]] bool getPair(uint32_t* tagp, uint32_t* datap);
370
tell() const371 const BufferIterator& tell() const { return point; }
seekTo(const BufferIterator & pos)372 void seekTo(const BufferIterator& pos) { point = pos; }
seekBy(size_t pos)373 [[nodiscard]] bool seekBy(size_t pos) {
374 if (!point.advance(pos)) {
375 reportTruncated();
376 return false;
377 }
378 return true;
379 }
380
381 template <class T>
382 [[nodiscard]] bool readArray(T* p, size_t nelems);
383
reportTruncated()384 bool reportTruncated() {
385 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
386 JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
387 return false;
388 }
389
390 private:
staticAssertions()391 void staticAssertions() {
392 static_assert(sizeof(char16_t) == 2);
393 static_assert(sizeof(uint32_t) == 4);
394 }
395
396 JSContext* cx;
397 BufferIterator point;
398 };
399
400 } // namespace js
401
402 struct JSStructuredCloneReader {
403 public:
JSStructuredCloneReaderJSStructuredCloneReader404 explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope,
405 const JS::CloneDataPolicy& cloneDataPolicy,
406 const JSStructuredCloneCallbacks* cb,
407 void* cbClosure)
408 : in(in),
409 allowedScope(scope),
410 cloneDataPolicy(cloneDataPolicy),
411 objs(in.context()),
412 allObjs(in.context()),
413 numItemsRead(0),
414 callbacks(cb),
415 closure(cbClosure) {}
416
inputJSStructuredCloneReader417 SCInput& input() { return in; }
418 bool read(MutableHandleValue vp, size_t nbytes);
419
420 private:
contextJSStructuredCloneReader421 JSContext* context() { return in.context(); }
422
423 bool readHeader();
424 bool readTransferMap();
425
426 template <typename CharT>
427 JSString* readStringImpl(uint32_t nchars, gc::InitialHeap heap);
428 JSString* readString(uint32_t data, gc::InitialHeap heap = gc::DefaultHeap);
429
430 BigInt* readBigInt(uint32_t data);
431
432 [[nodiscard]] bool readTypedArray(uint32_t arrayType, uint64_t nelems,
433 MutableHandleValue vp, bool v1Read = false);
434 [[nodiscard]] bool readDataView(uint64_t byteLength, MutableHandleValue vp);
435 [[nodiscard]] bool readArrayBuffer(StructuredDataType type, uint32_t data,
436 MutableHandleValue vp);
437 [[nodiscard]] bool readSharedArrayBuffer(MutableHandleValue vp);
438 [[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes,
439 MutableHandleValue vp);
440 [[nodiscard]] bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
441 MutableHandleValue vp);
442 JSObject* readSavedFrame(uint32_t principalsTag);
443 [[nodiscard]] bool startRead(MutableHandleValue vp,
444 gc::InitialHeap strHeap = gc::DefaultHeap);
445
446 SCInput& in;
447
448 // The widest scope that the caller will accept, where
449 // SameProcess is the widest (it can store anything it wants)
450 // and DifferentProcess is the narrowest (it cannot contain pointers and must
451 // be valid cross-process.)
452 JS::StructuredCloneScope allowedScope;
453
454 const JS::CloneDataPolicy cloneDataPolicy;
455
456 // Stack of objects with properties remaining to be read.
457 RootedValueVector objs;
458
459 // Array of all objects read during this deserialization, for resolving
460 // backreferences.
461 //
462 // For backreferences to work correctly, objects must be added to this
463 // array in exactly the order expected by the version of the Writer that
464 // created the serialized data, even across years and format versions. This
465 // is usually no problem, since both algorithms do a single linear pass
466 // over the serialized data. There is one hitch; see readTypedArray.
467 //
468 // The values in this vector are objects, except it can temporarily have
469 // one `undefined` placeholder value (the readTypedArray hack).
470 RootedValueVector allObjs;
471
472 size_t numItemsRead;
473
474 // The user defined callbacks that will be used for cloning.
475 const JSStructuredCloneCallbacks* callbacks;
476
477 // Any value passed to JS_ReadStructuredClone.
478 void* closure;
479
480 friend bool JS_ReadTypedArray(JSStructuredCloneReader* r,
481 MutableHandleValue vp);
482 };
483
484 struct JSStructuredCloneWriter {
485 public:
JSStructuredCloneWriterJSStructuredCloneWriter486 explicit JSStructuredCloneWriter(JSContext* cx,
487 JS::StructuredCloneScope scope,
488 const JS::CloneDataPolicy& cloneDataPolicy,
489 const JSStructuredCloneCallbacks* cb,
490 void* cbClosure, const Value& tVal)
491 : out(cx, scope),
492 callbacks(cb),
493 closure(cbClosure),
494 objs(out.context()),
495 counts(out.context()),
496 objectEntries(out.context()),
497 otherEntries(out.context()),
498 memory(out.context()),
499 transferable(out.context(), tVal),
500 transferableObjects(out.context(), TransferableObjectsSet(cx)),
501 cloneDataPolicy(cloneDataPolicy) {
502 out.setCallbacks(cb, cbClosure, OwnTransferablePolicy::NoTransferables);
503 }
504
505 ~JSStructuredCloneWriter();
506
initJSStructuredCloneWriter507 bool init() {
508 return parseTransferable() && writeHeader() && writeTransferMap();
509 }
510
511 bool write(HandleValue v);
512
outputJSStructuredCloneWriter513 SCOutput& output() { return out; }
514
extractBufferJSStructuredCloneWriter515 void extractBuffer(JSStructuredCloneData* newData) {
516 out.extractBuffer(newData);
517 }
518
519 private:
520 JSStructuredCloneWriter() = delete;
521 JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
522
contextJSStructuredCloneWriter523 JSContext* context() { return out.context(); }
524
525 bool writeHeader();
526 bool writeTransferMap();
527
528 bool writeString(uint32_t tag, JSString* str);
529 bool writeBigInt(uint32_t tag, BigInt* bi);
530 bool writeArrayBuffer(HandleObject obj);
531 bool writeTypedArray(HandleObject obj);
532 bool writeDataView(HandleObject obj);
533 bool writeSharedArrayBuffer(HandleObject obj);
534 bool writeSharedWasmMemory(HandleObject obj);
535 bool startObject(HandleObject obj, bool* backref);
536 bool startWrite(HandleValue v);
537 bool traverseObject(HandleObject obj, ESClass cls);
538 bool traverseMap(HandleObject obj);
539 bool traverseSet(HandleObject obj);
540 bool traverseSavedFrame(HandleObject obj);
541
542 template <typename... Args>
543 bool reportDataCloneError(uint32_t errorId, Args&&... aArgs);
544
545 bool parseTransferable();
546 bool transferOwnership();
547
548 inline void checkStack();
549
550 SCOutput out;
551
552 // The user defined callbacks that will be used to signal cloning, in some
553 // cases.
554 const JSStructuredCloneCallbacks* callbacks;
555
556 // Any value passed to the callbacks.
557 void* closure;
558
559 // Vector of objects with properties remaining to be written.
560 //
561 // NB: These can span multiple compartments, so the compartment must be
562 // entered before any manipulation is performed.
563 RootedValueVector objs;
564
565 // counts[i] is the number of entries of objs[i] remaining to be written.
566 // counts.length() == objs.length() and sum(counts) == entries.length().
567 Vector<size_t> counts;
568
569 // For JSObject: Property IDs as value
570 RootedIdVector objectEntries;
571
572 // For Map: Key followed by value
573 // For Set: Key
574 // For SavedFrame: parent SavedFrame
575 RootedValueVector otherEntries;
576
577 // The "memory" list described in the HTML5 internal structured cloning
578 // algorithm. memory is a superset of objs; items are never removed from
579 // Memory until a serialization operation is finished
580 using CloneMemory =
581 GCHashMap<JSObject*, uint32_t, MovableCellHasher<JSObject*>,
582 SystemAllocPolicy>;
583 Rooted<CloneMemory> memory;
584
585 struct TransferableObjectsHasher : public DefaultHasher<JSObject*> {
hashJSStructuredCloneWriter::TransferableObjectsHasher586 static inline HashNumber hash(const Lookup& l) {
587 return DefaultHasher<JSObject*>::hash(l);
588 }
589 };
590
591 // Set of transferable objects
592 RootedValue transferable;
593 typedef GCHashSet<JSObject*, TransferableObjectsHasher>
594 TransferableObjectsSet;
595 Rooted<TransferableObjectsSet> transferableObjects;
596
597 const JS::CloneDataPolicy cloneDataPolicy;
598
599 friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
600 friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
601 friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj);
602 };
603
GetSCOffset(JSStructuredCloneWriter * writer)604 JS_PUBLIC_API uint64_t js::GetSCOffset(JSStructuredCloneWriter* writer) {
605 MOZ_ASSERT(writer);
606 return writer->output().count() * sizeof(uint64_t);
607 }
608
609 static_assert(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
610 static_assert(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
611 static_assert(Scalar::Int8 == 0);
612
613 template <typename... Args>
ReportDataCloneError(JSContext * cx,const JSStructuredCloneCallbacks * callbacks,uint32_t errorId,void * closure,Args &&...aArgs)614 static void ReportDataCloneError(JSContext* cx,
615 const JSStructuredCloneCallbacks* callbacks,
616 uint32_t errorId, void* closure,
617 Args&&... aArgs) {
618 unsigned errorNumber;
619 switch (errorId) {
620 case JS_SCERR_DUP_TRANSFERABLE:
621 errorNumber = JSMSG_SC_DUP_TRANSFERABLE;
622 break;
623
624 case JS_SCERR_TRANSFERABLE:
625 errorNumber = JSMSG_SC_NOT_TRANSFERABLE;
626 break;
627
628 case JS_SCERR_UNSUPPORTED_TYPE:
629 errorNumber = JSMSG_SC_UNSUPPORTED_TYPE;
630 break;
631
632 case JS_SCERR_SHMEM_TRANSFERABLE:
633 errorNumber = JSMSG_SC_SHMEM_TRANSFERABLE;
634 break;
635
636 case JS_SCERR_TYPED_ARRAY_DETACHED:
637 errorNumber = JSMSG_TYPED_ARRAY_DETACHED;
638 break;
639
640 case JS_SCERR_WASM_NO_TRANSFER:
641 errorNumber = JSMSG_WASM_NO_TRANSFER;
642 break;
643
644 case JS_SCERR_NOT_CLONABLE:
645 errorNumber = JSMSG_SC_NOT_CLONABLE;
646 break;
647
648 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP:
649 errorNumber = JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP;
650 break;
651
652 default:
653 MOZ_CRASH("Unkown errorId");
654 break;
655 }
656
657 if (callbacks && callbacks->reportError) {
658 MOZ_RELEASE_ASSERT(!cx->isExceptionPending());
659
660 JSErrorReport report;
661 report.errorNumber = errorNumber;
662 // Get js error message if it's possible and propagate it through callback.
663 if (JS_ExpandErrorArgumentsASCII(cx, GetErrorMessage, errorNumber, &report,
664 std::forward<Args>(aArgs)...) &&
665 report.message()) {
666 callbacks->reportError(cx, errorId, closure, report.message().c_str());
667 } else {
668 ReportOutOfMemory(cx);
669
670 callbacks->reportError(cx, errorId, closure, "");
671 }
672
673 return;
674 }
675
676 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber,
677 std::forward<Args>(aArgs)...);
678 }
679
WriteStructuredClone(JSContext * cx,HandleValue v,JSStructuredCloneData * bufp,JS::StructuredCloneScope scope,const JS::CloneDataPolicy & cloneDataPolicy,const JSStructuredCloneCallbacks * cb,void * cbClosure,const Value & transferable)680 bool WriteStructuredClone(JSContext* cx, HandleValue v,
681 JSStructuredCloneData* bufp,
682 JS::StructuredCloneScope scope,
683 const JS::CloneDataPolicy& cloneDataPolicy,
684 const JSStructuredCloneCallbacks* cb, void* cbClosure,
685 const Value& transferable) {
686 JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure,
687 transferable);
688 if (!w.init()) {
689 return false;
690 }
691 if (!w.write(v)) {
692 return false;
693 }
694 w.extractBuffer(bufp);
695 return true;
696 }
697
ReadStructuredClone(JSContext * cx,const JSStructuredCloneData & data,JS::StructuredCloneScope scope,MutableHandleValue vp,const JS::CloneDataPolicy & cloneDataPolicy,const JSStructuredCloneCallbacks * cb,void * cbClosure)698 bool ReadStructuredClone(JSContext* cx, const JSStructuredCloneData& data,
699 JS::StructuredCloneScope scope, MutableHandleValue vp,
700 const JS::CloneDataPolicy& cloneDataPolicy,
701 const JSStructuredCloneCallbacks* cb,
702 void* cbClosure) {
703 if (data.Size() % 8) {
704 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
705 JSMSG_SC_BAD_SERIALIZED_DATA, "misaligned");
706 return false;
707 }
708 SCInput in(cx, data);
709 JSStructuredCloneReader r(in, scope, cloneDataPolicy, cb, cbClosure);
710 return r.read(vp, data.Size());
711 }
712
StructuredCloneHasTransferObjects(const JSStructuredCloneData & data)713 static bool StructuredCloneHasTransferObjects(
714 const JSStructuredCloneData& data) {
715 if (data.Size() < sizeof(uint64_t)) {
716 return false;
717 }
718
719 uint64_t u;
720 BufferIterator<uint64_t, SystemAllocPolicy> iter(data);
721 MOZ_ALWAYS_TRUE(iter.readBytes(reinterpret_cast<char*>(&u), sizeof(u)));
722 uint32_t tag = uint32_t(u >> 32);
723 return (tag == SCTAG_TRANSFER_MAP_HEADER);
724 }
725
726 namespace js {
727
SCInput(JSContext * cx,const JSStructuredCloneData & data)728 SCInput::SCInput(JSContext* cx, const JSStructuredCloneData& data)
729 : cx(cx), point(data) {
730 static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment % 8 == 0,
731 "structured clone buffer reads should be aligned");
732 MOZ_ASSERT(data.Size() % 8 == 0);
733 }
734
read(uint64_t * p)735 bool SCInput::read(uint64_t* p) {
736 if (!point.canPeek()) {
737 *p = 0; // initialize to shut GCC up
738 return reportTruncated();
739 }
740 *p = NativeEndian::swapFromLittleEndian(point.peek());
741 MOZ_ALWAYS_TRUE(point.advance());
742 return true;
743 }
744
readPair(uint32_t * tagp,uint32_t * datap)745 bool SCInput::readPair(uint32_t* tagp, uint32_t* datap) {
746 uint64_t u;
747 bool ok = read(&u);
748 if (ok) {
749 *tagp = uint32_t(u >> 32);
750 *datap = uint32_t(u);
751 }
752 return ok;
753 }
754
get(uint64_t * p)755 bool SCInput::get(uint64_t* p) {
756 if (!point.canPeek()) {
757 return reportTruncated();
758 }
759 *p = NativeEndian::swapFromLittleEndian(point.peek());
760 return true;
761 }
762
getPair(uint32_t * tagp,uint32_t * datap)763 bool SCInput::getPair(uint32_t* tagp, uint32_t* datap) {
764 uint64_t u = 0;
765 if (!get(&u)) {
766 return false;
767 }
768
769 *tagp = uint32_t(u >> 32);
770 *datap = uint32_t(u);
771 return true;
772 }
773
getPair(uint64_t data,uint32_t * tagp,uint32_t * datap)774 void SCInput::getPair(uint64_t data, uint32_t* tagp, uint32_t* datap) {
775 uint64_t u = NativeEndian::swapFromLittleEndian(data);
776 *tagp = uint32_t(u >> 32);
777 *datap = uint32_t(u);
778 }
779
readDouble(double * p)780 bool SCInput::readDouble(double* p) {
781 uint64_t u;
782 if (!read(&u)) {
783 return false;
784 }
785 *p = CanonicalizeNaN(mozilla::BitwiseCast<double>(u));
786 return true;
787 }
788
789 template <typename T>
swapFromLittleEndianInPlace(T * ptr,size_t nelems)790 static void swapFromLittleEndianInPlace(T* ptr, size_t nelems) {
791 if (nelems > 0) {
792 NativeEndian::swapFromLittleEndianInPlace(ptr, nelems);
793 }
794 }
795
796 template <>
swapFromLittleEndianInPlace(uint8_t * ptr,size_t nelems)797 void swapFromLittleEndianInPlace(uint8_t* ptr, size_t nelems) {}
798
799 // Data is packed into an integral number of uint64_t words. Compute the
800 // padding required to finish off the final word.
ComputePadding(size_t nelems,size_t elemSize)801 static size_t ComputePadding(size_t nelems, size_t elemSize) {
802 // We want total length mod 8, where total length is nelems * sizeof(T),
803 // but that might overflow. So reduce nelems to nelems mod 8, since we are
804 // going to be doing a mod 8 later anyway.
805 size_t leftoverLength = (nelems % sizeof(uint64_t)) * elemSize;
806 return (-leftoverLength) & (sizeof(uint64_t) - 1);
807 }
808
809 template <class T>
readArray(T * p,size_t nelems)810 bool SCInput::readArray(T* p, size_t nelems) {
811 if (!nelems) {
812 return true;
813 }
814
815 static_assert(sizeof(uint64_t) % sizeof(T) == 0);
816
817 // Fail if nelems is so huge that computing the full size will overflow.
818 mozilla::CheckedInt<size_t> size =
819 mozilla::CheckedInt<size_t>(nelems) * sizeof(T);
820 if (!size.isValid()) {
821 return reportTruncated();
822 }
823
824 if (!point.readBytes(reinterpret_cast<char*>(p), size.value())) {
825 // To avoid any way in which uninitialized data could escape, zero the array
826 // if filling it failed.
827 std::uninitialized_fill_n(p, nelems, 0);
828 return false;
829 }
830
831 swapFromLittleEndianInPlace(p, nelems);
832
833 point += ComputePadding(nelems, sizeof(T));
834
835 return true;
836 }
837
readBytes(void * p,size_t nbytes)838 bool SCInput::readBytes(void* p, size_t nbytes) {
839 return readArray((uint8_t*)p, nbytes);
840 }
841
readChars(Latin1Char * p,size_t nchars)842 bool SCInput::readChars(Latin1Char* p, size_t nchars) {
843 static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
844 "Latin1Char must fit in 1 byte");
845 return readBytes(p, nchars);
846 }
847
readChars(char16_t * p,size_t nchars)848 bool SCInput::readChars(char16_t* p, size_t nchars) {
849 MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
850 return readArray((uint16_t*)p, nchars);
851 }
852
getPtr(uint64_t data,void ** ptr)853 void SCInput::getPtr(uint64_t data, void** ptr) {
854 *ptr = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data));
855 }
856
readPtr(void ** p)857 bool SCInput::readPtr(void** p) {
858 uint64_t u;
859 if (!read(&u)) {
860 return false;
861 }
862 *p = reinterpret_cast<void*>(u);
863 return true;
864 }
865
SCOutput(JSContext * cx,JS::StructuredCloneScope scope)866 SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope)
867 : cx(cx), buf(scope) {}
868
write(uint64_t u)869 bool SCOutput::write(uint64_t u) {
870 uint64_t v = NativeEndian::swapToLittleEndian(u);
871 if (!buf.AppendBytes(reinterpret_cast<char*>(&v), sizeof(u))) {
872 ReportOutOfMemory(context());
873 return false;
874 }
875 return true;
876 }
877
writePair(uint32_t tag,uint32_t data)878 bool SCOutput::writePair(uint32_t tag, uint32_t data) {
879 // As it happens, the tag word appears after the data word in the output.
880 // This is because exponents occupy the last 2 bytes of doubles on the
881 // little-endian platforms we care most about.
882 //
883 // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
884 // PairToUInt64 produces the number 0xFFFF000200000001.
885 // That is written out as the bytes 01 00 00 00 02 00 FF FF.
886 return write(PairToUInt64(tag, data));
887 }
888
ReinterpretPairAsDouble(uint32_t tag,uint32_t data)889 static inline double ReinterpretPairAsDouble(uint32_t tag, uint32_t data) {
890 return BitwiseCast<double>(PairToUInt64(tag, data));
891 }
892
writeDouble(double d)893 bool SCOutput::writeDouble(double d) {
894 return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
895 }
896
897 template <class T>
writeArray(const T * p,size_t nelems)898 bool SCOutput::writeArray(const T* p, size_t nelems) {
899 static_assert(8 % sizeof(T) == 0);
900 static_assert(sizeof(uint64_t) % sizeof(T) == 0);
901
902 if (nelems == 0) {
903 return true;
904 }
905
906 for (size_t i = 0; i < nelems; i++) {
907 T value = NativeEndian::swapToLittleEndian(p[i]);
908 if (!buf.AppendBytes(reinterpret_cast<char*>(&value), sizeof(value))) {
909 return false;
910 }
911 }
912
913 // Zero-pad to 8 bytes boundary.
914 size_t padbytes = ComputePadding(nelems, sizeof(T));
915 char zeroes[sizeof(uint64_t)] = {0};
916 if (!buf.AppendBytes(zeroes, padbytes)) {
917 return false;
918 }
919
920 return true;
921 }
922
923 template <>
writeArray(const uint8_t * p,size_t nelems)924 bool SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems) {
925 if (nelems == 0) {
926 return true;
927 }
928
929 if (!buf.AppendBytes(reinterpret_cast<const char*>(p), nelems)) {
930 return false;
931 }
932
933 // zero-pad to 8 bytes boundary
934 size_t padbytes = ComputePadding(nelems, 1);
935 char zeroes[sizeof(uint64_t)] = {0};
936 if (!buf.AppendBytes(zeroes, padbytes)) {
937 return false;
938 }
939
940 return true;
941 }
942
writeBytes(const void * p,size_t nbytes)943 bool SCOutput::writeBytes(const void* p, size_t nbytes) {
944 return writeArray((const uint8_t*)p, nbytes);
945 }
946
writeChars(const char16_t * p,size_t nchars)947 bool SCOutput::writeChars(const char16_t* p, size_t nchars) {
948 static_assert(sizeof(char16_t) == sizeof(uint16_t),
949 "required so that treating char16_t[] memory as uint16_t[] "
950 "memory is permissible");
951 return writeArray((const uint16_t*)p, nchars);
952 }
953
writeChars(const Latin1Char * p,size_t nchars)954 bool SCOutput::writeChars(const Latin1Char* p, size_t nchars) {
955 static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
956 "Latin1Char must fit in 1 byte");
957 return writeBytes(p, nchars);
958 }
959
discardTransferables()960 void SCOutput::discardTransferables() { buf.discardTransferables(); }
961
962 } // namespace js
963
~JSStructuredCloneData()964 JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); }
965
966 // If the buffer contains Transferables, free them. Note that custom
967 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
968 // delete their transferables.
discardTransferables()969 void JSStructuredCloneData::discardTransferables() {
970 if (!Size()) {
971 return;
972 }
973
974 if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) {
975 return;
976 }
977
978 // DifferentProcess clones cannot contain pointers, so nothing needs to be
979 // released.
980 if (scope() == JS::StructuredCloneScope::DifferentProcess) {
981 return;
982 }
983
984 FreeTransferStructuredCloneOp freeTransfer = nullptr;
985 if (callbacks_) {
986 freeTransfer = callbacks_->freeTransfer;
987 }
988
989 auto point = BufferIterator<uint64_t, SystemAllocPolicy>(*this);
990 if (point.done()) {
991 return; // Empty buffer
992 }
993
994 uint32_t tag, data;
995 MOZ_RELEASE_ASSERT(point.canPeek());
996 SCInput::getPair(point.peek(), &tag, &data);
997 MOZ_ALWAYS_TRUE(point.advance());
998
999 if (tag == SCTAG_HEADER) {
1000 if (point.done()) {
1001 return;
1002 }
1003
1004 MOZ_RELEASE_ASSERT(point.canPeek());
1005 SCInput::getPair(point.peek(), &tag, &data);
1006 MOZ_ALWAYS_TRUE(point.advance());
1007 }
1008
1009 if (tag != SCTAG_TRANSFER_MAP_HEADER) {
1010 return;
1011 }
1012
1013 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
1014 return;
1015 }
1016
1017 // freeTransfer should not GC
1018 JS::AutoSuppressGCAnalysis nogc;
1019
1020 if (point.done()) {
1021 return;
1022 }
1023
1024 MOZ_RELEASE_ASSERT(point.canPeek());
1025 uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek());
1026 MOZ_ALWAYS_TRUE(point.advance());
1027 while (numTransferables--) {
1028 if (!point.canPeek()) {
1029 return;
1030 }
1031
1032 uint32_t ownership;
1033 SCInput::getPair(point.peek(), &tag, &ownership);
1034 MOZ_ALWAYS_TRUE(point.advance());
1035 MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1036 if (!point.canPeek()) {
1037 return;
1038 }
1039
1040 void* content;
1041 SCInput::getPtr(point.peek(), &content);
1042 MOZ_ALWAYS_TRUE(point.advance());
1043 if (!point.canPeek()) {
1044 return;
1045 }
1046
1047 uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek());
1048 MOZ_ALWAYS_TRUE(point.advance());
1049
1050 if (ownership < JS::SCTAG_TMO_FIRST_OWNED) {
1051 continue;
1052 }
1053
1054 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
1055 js_free(content);
1056 } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
1057 JS::ReleaseMappedArrayBufferContents(content, extraData);
1058 } else if (freeTransfer) {
1059 freeTransfer(tag, JS::TransferableOwnership(ownership), content,
1060 extraData, closure_);
1061 } else {
1062 MOZ_ASSERT(false, "unknown ownership");
1063 }
1064 }
1065 }
1066
1067 static_assert(JSString::MAX_LENGTH < UINT32_MAX);
1068
~JSStructuredCloneWriter()1069 JSStructuredCloneWriter::~JSStructuredCloneWriter() {
1070 // Free any transferable data left lying around in the buffer
1071 if (out.count()) {
1072 out.discardTransferables();
1073 }
1074 }
1075
parseTransferable()1076 bool JSStructuredCloneWriter::parseTransferable() {
1077 // NOTE: The transferables set is tested for non-emptiness at various
1078 // junctures in structured cloning, so this set must be initialized
1079 // by this method in all non-error cases.
1080 MOZ_ASSERT(transferableObjects.empty(),
1081 "parseTransferable called with stale data");
1082
1083 if (transferable.isNull() || transferable.isUndefined()) {
1084 return true;
1085 }
1086
1087 if (!transferable.isObject()) {
1088 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1089 }
1090
1091 JSContext* cx = context();
1092 RootedObject array(cx, &transferable.toObject());
1093 bool isArray;
1094 if (!JS::IsArrayObject(cx, array, &isArray)) {
1095 return false;
1096 }
1097 if (!isArray) {
1098 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1099 }
1100
1101 uint32_t length;
1102 if (!JS::GetArrayLength(cx, array, &length)) {
1103 return false;
1104 }
1105
1106 // Initialize the set for the provided array's length.
1107 if (!transferableObjects.reserve(length)) {
1108 return false;
1109 }
1110
1111 if (length == 0) {
1112 return true;
1113 }
1114
1115 RootedValue v(context());
1116 RootedObject tObj(context());
1117
1118 for (uint32_t i = 0; i < length; ++i) {
1119 if (!CheckForInterrupt(cx)) {
1120 return false;
1121 }
1122
1123 if (!JS_GetElement(cx, array, i, &v)) {
1124 return false;
1125 }
1126
1127 if (!v.isObject()) {
1128 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1129 }
1130 tObj = &v.toObject();
1131
1132 RootedObject unwrappedObj(cx, CheckedUnwrapStatic(tObj));
1133 if (!unwrappedObj) {
1134 ReportAccessDenied(cx);
1135 return false;
1136 }
1137
1138 // Shared memory cannot be transferred because it is not possible (nor
1139 // desirable) to detach the memory in agents that already hold a
1140 // reference to it.
1141
1142 if (unwrappedObj->is<SharedArrayBufferObject>()) {
1143 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
1144 }
1145
1146 else if (unwrappedObj->is<WasmMemoryObject>()) {
1147 if (unwrappedObj->as<WasmMemoryObject>().isShared()) {
1148 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
1149 }
1150 }
1151
1152 // External array buffers may be able to be transferred in the future,
1153 // but that is not currently implemented.
1154
1155 else if (unwrappedObj->is<ArrayBufferObject>()) {
1156 if (unwrappedObj->as<ArrayBufferObject>().isExternal()) {
1157 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1158 }
1159 }
1160
1161 else {
1162 if (!out.buf.callbacks_ || !out.buf.callbacks_->canTransfer) {
1163 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1164 }
1165
1166 JSAutoRealm ar(cx, unwrappedObj);
1167 bool sameProcessScopeRequired = false;
1168 if (!out.buf.callbacks_->canTransfer(
1169 cx, unwrappedObj, &sameProcessScopeRequired, out.buf.closure_)) {
1170 return false;
1171 }
1172
1173 if (sameProcessScopeRequired) {
1174 output().sameProcessScopeRequired();
1175 }
1176 }
1177
1178 // No duplicates allowed
1179 auto p = transferableObjects.lookupForAdd(tObj);
1180 if (p) {
1181 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
1182 }
1183
1184 if (!transferableObjects.add(p, tObj)) {
1185 return false;
1186 }
1187 }
1188
1189 return true;
1190 }
1191
1192 template <typename... Args>
reportDataCloneError(uint32_t errorId,Args &&...aArgs)1193 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId,
1194 Args&&... aArgs) {
1195 ReportDataCloneError(context(), out.buf.callbacks_, errorId, out.buf.closure_,
1196 std::forward<Args>(aArgs)...);
1197 return false;
1198 }
1199
writeString(uint32_t tag,JSString * str)1200 bool JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) {
1201 JSLinearString* linear = str->ensureLinear(context());
1202 if (!linear) {
1203 return false;
1204 }
1205
1206 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
1207 "String length must fit in 31 bits");
1208
1209 uint32_t length = linear->length();
1210 uint32_t lengthAndEncoding =
1211 length | (uint32_t(linear->hasLatin1Chars()) << 31);
1212 if (!out.writePair(tag, lengthAndEncoding)) {
1213 return false;
1214 }
1215
1216 JS::AutoCheckCannotGC nogc;
1217 return linear->hasLatin1Chars()
1218 ? out.writeChars(linear->latin1Chars(nogc), length)
1219 : out.writeChars(linear->twoByteChars(nogc), length);
1220 }
1221
writeBigInt(uint32_t tag,BigInt * bi)1222 bool JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) {
1223 bool signBit = bi->isNegative();
1224 size_t length = bi->digitLength();
1225 // The length must fit in 31 bits to leave room for a sign bit.
1226 if (length > size_t(INT32_MAX)) {
1227 return false;
1228 }
1229 uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31);
1230
1231 if (!out.writePair(tag, lengthAndSign)) {
1232 return false;
1233 }
1234 return out.writeArray(bi->digits().data(), length);
1235 }
1236
checkStack()1237 inline void JSStructuredCloneWriter::checkStack() {
1238 #ifdef DEBUG
1239 // To avoid making serialization O(n^2), limit stack-checking at 10.
1240 const size_t MAX = 10;
1241
1242 size_t limit = std::min(counts.length(), MAX);
1243 MOZ_ASSERT(objs.length() == counts.length());
1244 size_t total = 0;
1245 for (size_t i = 0; i < limit; i++) {
1246 MOZ_ASSERT(total + counts[i] >= total);
1247 total += counts[i];
1248 }
1249 if (counts.length() <= MAX) {
1250 MOZ_ASSERT(total == objectEntries.length() + otherEntries.length());
1251 } else {
1252 MOZ_ASSERT(total <= objectEntries.length() + otherEntries.length());
1253 }
1254
1255 size_t j = objs.length();
1256 for (size_t i = 0; i < limit; i++) {
1257 --j;
1258 MOZ_ASSERT(memory.has(&objs[j].toObject()));
1259 }
1260 #endif
1261 }
1262
1263 /*
1264 * Write out a typed array. Note that post-v1 structured clone buffers do not
1265 * perform endianness conversion on stored data, so multibyte typed arrays
1266 * cannot be deserialized into a different endianness machine. Endianness
1267 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
1268 * Int16Array views of the same ArrayBuffer, should the data bytes be
1269 * byte-swapped when writing or not? The Int8Array requires them to not be
1270 * swapped; the Int16Array requires that they are.
1271 */
writeTypedArray(HandleObject obj)1272 bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj) {
1273 Rooted<TypedArrayObject*> tarr(context(),
1274 obj->maybeUnwrapAs<TypedArrayObject>());
1275 JSAutoRealm ar(context(), tarr);
1276
1277 if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) {
1278 return false;
1279 }
1280
1281 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) {
1282 return false;
1283 }
1284
1285 uint64_t nelems = tarr->length();
1286 if (!out.write(nelems)) {
1287 return false;
1288 }
1289
1290 // Write out the ArrayBuffer tag and contents
1291 RootedValue val(context(), tarr->bufferValue());
1292 if (!startWrite(val)) {
1293 return false;
1294 }
1295
1296 uint64_t byteOffset = tarr->byteOffset();
1297 return out.write(byteOffset);
1298 }
1299
writeDataView(HandleObject obj)1300 bool JSStructuredCloneWriter::writeDataView(HandleObject obj) {
1301 Rooted<DataViewObject*> view(context(), obj->maybeUnwrapAs<DataViewObject>());
1302 JSAutoRealm ar(context(), view);
1303
1304 if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, 0)) {
1305 return false;
1306 }
1307
1308 uint64_t byteLength = view->byteLength();
1309 if (!out.write(byteLength)) {
1310 return false;
1311 }
1312
1313 // Write out the ArrayBuffer tag and contents
1314 RootedValue val(context(), view->bufferValue());
1315 if (!startWrite(val)) {
1316 return false;
1317 }
1318
1319 uint64_t byteOffset = view->byteOffset();
1320 return out.write(byteOffset);
1321 }
1322
writeArrayBuffer(HandleObject obj)1323 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) {
1324 Rooted<ArrayBufferObject*> buffer(context(),
1325 obj->maybeUnwrapAs<ArrayBufferObject>());
1326 JSAutoRealm ar(context(), buffer);
1327
1328 if (!out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, 0)) {
1329 return false;
1330 }
1331
1332 uint64_t byteLength = buffer->byteLength();
1333 if (!out.write(byteLength)) {
1334 return false;
1335 }
1336
1337 return out.writeBytes(buffer->dataPointer(), byteLength);
1338 }
1339
writeSharedArrayBuffer(HandleObject obj)1340 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj) {
1341 MOZ_ASSERT(obj->canUnwrapAs<SharedArrayBufferObject>());
1342
1343 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
1344 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
1345 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1346 : JS_SCERR_NOT_CLONABLE;
1347 reportDataCloneError(error, "SharedArrayBuffer");
1348 return false;
1349 }
1350
1351 output().sameProcessScopeRequired();
1352
1353 // We must not transmit SAB pointers (including for WebAssembly.Memory)
1354 // cross-process. The cloneDataPolicy should have guarded against this;
1355 // since it did not then throw, with a very explicit message.
1356
1357 if (output().scope() > JS::StructuredCloneScope::SameProcess) {
1358 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1359 JSMSG_SC_SHMEM_POLICY);
1360 return false;
1361 }
1362
1363 Rooted<SharedArrayBufferObject*> sharedArrayBuffer(
1364 context(), obj->maybeUnwrapAs<SharedArrayBufferObject>());
1365 SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
1366
1367 if (!out.buf.refsHeld_.acquire(context(), rawbuf)) {
1368 return false;
1369 }
1370
1371 // We must serialize the length so that the buffer object arrives in the
1372 // receiver with the same length, and not with the length read from the
1373 // rawbuf - that length can be different, and it can change at any time.
1374
1375 intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
1376 uint64_t byteLength = sharedArrayBuffer->byteLength();
1377 if (!(out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
1378 static_cast<uint32_t>(sizeof(p))) &&
1379 out.writeBytes(&byteLength, sizeof(byteLength)) &&
1380 out.writeBytes(&p, sizeof(p)))) {
1381 return false;
1382 }
1383
1384 if (callbacks && callbacks->sabCloned &&
1385 !callbacks->sabCloned(context(), /*receiving=*/false, closure)) {
1386 return false;
1387 }
1388
1389 return true;
1390 }
1391
writeSharedWasmMemory(HandleObject obj)1392 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj) {
1393 MOZ_ASSERT(obj->canUnwrapAs<WasmMemoryObject>());
1394
1395 // Check the policy here so that we can report a sane error.
1396 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
1397 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
1398 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
1399 : JS_SCERR_NOT_CLONABLE;
1400 reportDataCloneError(error, "WebAssembly.Memory");
1401 return false;
1402 }
1403
1404 // If this changes, might need to change what we write.
1405 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS == 2);
1406
1407 Rooted<WasmMemoryObject*> memoryObj(context(),
1408 &obj->unwrapAs<WasmMemoryObject>());
1409 Rooted<SharedArrayBufferObject*> sab(
1410 context(), &memoryObj->buffer().as<SharedArrayBufferObject>());
1411
1412 return out.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT, 0) &&
1413 writeSharedArrayBuffer(sab);
1414 }
1415
startObject(HandleObject obj,bool * backref)1416 bool JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref) {
1417 // Handle cycles in the object graph.
1418 CloneMemory::AddPtr p = memory.lookupForAdd(obj);
1419 if ((*backref = p.found())) {
1420 return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
1421 }
1422 if (!memory.add(p, obj, memory.count())) {
1423 ReportOutOfMemory(context());
1424 return false;
1425 }
1426
1427 if (memory.count() == UINT32_MAX) {
1428 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
1429 JSMSG_NEED_DIET, "object graph to serialize");
1430 return false;
1431 }
1432
1433 return true;
1434 }
1435
TryAppendNativeProperties(JSContext * cx,HandleObject obj,MutableHandleIdVector entries,size_t * properties,bool * optimized)1436 static bool TryAppendNativeProperties(JSContext* cx, HandleObject obj,
1437 MutableHandleIdVector entries,
1438 size_t* properties, bool* optimized) {
1439 *optimized = false;
1440
1441 if (!obj->is<NativeObject>()) {
1442 return true;
1443 }
1444
1445 HandleNativeObject nobj = obj.as<NativeObject>();
1446 if (nobj->isIndexed() || nobj->is<TypedArrayObject>() ||
1447 nobj->getClass()->getNewEnumerate() || nobj->getClass()->getEnumerate()) {
1448 return true;
1449 }
1450
1451 *optimized = true;
1452
1453 size_t count = 0;
1454 // We iterate from the last to the first property, so the property names
1455 // are already in reverse order.
1456 for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
1457 jsid id = iter->key();
1458
1459 // Ignore symbols and non-enumerable properties.
1460 if (!iter->enumerable() || id.isSymbol()) {
1461 continue;
1462 }
1463
1464 MOZ_ASSERT(JSID_IS_STRING(id));
1465 if (!entries.append(id)) {
1466 return false;
1467 }
1468
1469 count++;
1470 }
1471
1472 // Add dense element ids in reverse order.
1473 for (uint32_t i = nobj->getDenseInitializedLength(); i > 0; --i) {
1474 if (nobj->getDenseElement(i - 1).isMagic(JS_ELEMENTS_HOLE)) {
1475 continue;
1476 }
1477
1478 if (!entries.append(INT_TO_JSID(i - 1))) {
1479 return false;
1480 }
1481
1482 count++;
1483 }
1484
1485 *properties = count;
1486 return true;
1487 }
1488
1489 // Objects are written as a "preorder" traversal of the object graph: object
1490 // "headers" (the class tag and any data needed for initial construction) are
1491 // visited first, then the children are recursed through (where children are
1492 // properties, Set or Map entries, etc.). So for example
1493 //
1494 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
1495 //
1496 // would be stored as:
1497 //
1498 // <Object tag for obj1>
1499 // <key1 data>
1500 // <Object tag for key1's value>
1501 // <key1.1 data>
1502 // <val1.1 data>
1503 // <key1.2 data>
1504 // <val1.2 data>
1505 // <end-of-children marker for key1's value>
1506 // <key2 data>
1507 // <Object tag for key2's value>
1508 // <end-of-children marker for key2's value>
1509 // <end-of-children marker for obj1>
1510 //
1511 // This nests nicely (ie, an entire recursive value starts with its tag and
1512 // ends with its end-of-children marker) and so it can be presented indented.
1513 // But see traverseMap below for how this looks different for Maps.
traverseObject(HandleObject obj,ESClass cls)1514 bool JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls) {
1515 size_t count;
1516 bool optimized = false;
1517 if (!TryAppendNativeProperties(context(), obj, &objectEntries, &count,
1518 &optimized)) {
1519 return false;
1520 }
1521
1522 if (!optimized) {
1523 // Get enumerable property ids and put them in reverse order so that they
1524 // will come off the stack in forward order.
1525 RootedIdVector properties(context());
1526 if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties)) {
1527 return false;
1528 }
1529
1530 for (size_t i = properties.length(); i > 0; --i) {
1531 jsid id = properties[i - 1];
1532
1533 MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
1534 if (!objectEntries.append(id)) {
1535 return false;
1536 }
1537 }
1538
1539 count = properties.length();
1540 }
1541
1542 // Push obj and count to the stack.
1543 if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) {
1544 return false;
1545 }
1546
1547 checkStack();
1548
1549 #if DEBUG
1550 ESClass cls2;
1551 if (!GetBuiltinClass(context(), obj, &cls2)) {
1552 return false;
1553 }
1554 MOZ_ASSERT(cls2 == cls);
1555 #endif
1556
1557 // Write the header for obj.
1558 if (cls == ESClass::Array) {
1559 uint32_t length = 0;
1560 if (!JS::GetArrayLength(context(), obj, &length)) {
1561 return false;
1562 }
1563
1564 return out.writePair(SCTAG_ARRAY_OBJECT,
1565 NativeEndian::swapToLittleEndian(length));
1566 }
1567
1568 return out.writePair(SCTAG_OBJECT_OBJECT, 0);
1569 }
1570
1571 // Use the same basic setup as for traverseObject, but now keys can themselves
1572 // be complex objects. Keys and values are visited first via startWrite(), then
1573 // the key's children (if any) are handled, then the value's children.
1574 //
1575 // m = new Map();
1576 // m.set(key1 = ..., value1 = ...)
1577 //
1578 // where key1 and value2 are both objects would be stored as
1579 //
1580 // <Map tag>
1581 // <key1 class tag>
1582 // <value1 class tag>
1583 // ...key1 data...
1584 // <end-of-children marker for key1>
1585 // ...value1 data...
1586 // <end-of-children marker for value1>
1587 // <end-of-children marker for Map>
1588 //
1589 // Notice how the end-of-children marker for key1 is sandwiched between the
1590 // value1 beginning and end.
traverseMap(HandleObject obj)1591 bool JSStructuredCloneWriter::traverseMap(HandleObject obj) {
1592 Rooted<GCVector<Value>> newEntries(context(), GCVector<Value>(context()));
1593 {
1594 // If there is no wrapper, the compartment munging is a no-op.
1595 RootedObject unwrapped(context(), obj->maybeUnwrapAs<MapObject>());
1596 MOZ_ASSERT(unwrapped);
1597 JSAutoRealm ar(context(), unwrapped);
1598 if (!MapObject::getKeysAndValuesInterleaved(unwrapped, &newEntries)) {
1599 return false;
1600 }
1601 }
1602 if (!context()->compartment()->wrap(context(), &newEntries)) {
1603 return false;
1604 }
1605
1606 for (size_t i = newEntries.length(); i > 0; --i) {
1607 if (!otherEntries.append(newEntries[i - 1])) {
1608 return false;
1609 }
1610 }
1611
1612 // Push obj and count to the stack.
1613 if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length())) {
1614 return false;
1615 }
1616
1617 checkStack();
1618
1619 // Write the header for obj.
1620 return out.writePair(SCTAG_MAP_OBJECT, 0);
1621 }
1622
1623 // Similar to traverseMap, only there is a single value instead of a key and
1624 // value, and thus no interleaving is possible: a value will be fully emitted
1625 // before the next value is begun.
traverseSet(HandleObject obj)1626 bool JSStructuredCloneWriter::traverseSet(HandleObject obj) {
1627 Rooted<GCVector<Value>> keys(context(), GCVector<Value>(context()));
1628 {
1629 // If there is no wrapper, the compartment munging is a no-op.
1630 RootedObject unwrapped(context(), obj->maybeUnwrapAs<SetObject>());
1631 MOZ_ASSERT(unwrapped);
1632 JSAutoRealm ar(context(), unwrapped);
1633 if (!SetObject::keys(context(), unwrapped, &keys)) {
1634 return false;
1635 }
1636 }
1637 if (!context()->compartment()->wrap(context(), &keys)) {
1638 return false;
1639 }
1640
1641 for (size_t i = keys.length(); i > 0; --i) {
1642 if (!otherEntries.append(keys[i - 1])) {
1643 return false;
1644 }
1645 }
1646
1647 // Push obj and count to the stack.
1648 if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length())) {
1649 return false;
1650 }
1651
1652 checkStack();
1653
1654 // Write the header for obj.
1655 return out.writePair(SCTAG_SET_OBJECT, 0);
1656 }
1657
traverseSavedFrame(HandleObject obj)1658 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj) {
1659 RootedSavedFrame savedFrame(context(), obj->maybeUnwrapAs<SavedFrame>());
1660 MOZ_ASSERT(savedFrame);
1661
1662 RootedObject parent(context(), savedFrame->getParent());
1663 if (!context()->compartment()->wrap(context(), &parent)) {
1664 return false;
1665 }
1666
1667 if (!objs.append(ObjectValue(*obj)) ||
1668 !otherEntries.append(parent ? ObjectValue(*parent) : NullValue()) ||
1669 !counts.append(1)) {
1670 return false;
1671 }
1672
1673 checkStack();
1674
1675 // Write the SavedFrame tag and the SavedFrame's principals.
1676
1677 if (savedFrame->getPrincipals() ==
1678 &ReconstructedSavedFramePrincipals::IsSystem) {
1679 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1680 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM)) {
1681 return false;
1682 };
1683 } else if (savedFrame->getPrincipals() ==
1684 &ReconstructedSavedFramePrincipals::IsNotSystem) {
1685 if (!out.writePair(
1686 SCTAG_SAVED_FRAME_OBJECT,
1687 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM)) {
1688 return false;
1689 }
1690 } else {
1691 if (auto principals = savedFrame->getPrincipals()) {
1692 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
1693 !principals->write(context(), this)) {
1694 return false;
1695 }
1696 } else {
1697 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS)) {
1698 return false;
1699 }
1700 }
1701 }
1702
1703 // Write the SavedFrame's reserved slots, except for the parent, which is
1704 // queued on objs for further traversal.
1705
1706 RootedValue val(context());
1707
1708 val = BooleanValue(savedFrame->getMutedErrors());
1709 if (!startWrite(val)) {
1710 return false;
1711 }
1712
1713 context()->markAtom(savedFrame->getSource());
1714 val = StringValue(savedFrame->getSource());
1715 if (!startWrite(val)) {
1716 return false;
1717 }
1718
1719 val = NumberValue(savedFrame->getLine());
1720 if (!startWrite(val)) {
1721 return false;
1722 }
1723
1724 val = NumberValue(savedFrame->getColumn());
1725 if (!startWrite(val)) {
1726 return false;
1727 }
1728
1729 auto name = savedFrame->getFunctionDisplayName();
1730 if (name) {
1731 context()->markAtom(name);
1732 }
1733 val = name ? StringValue(name) : NullValue();
1734 if (!startWrite(val)) {
1735 return false;
1736 }
1737
1738 auto cause = savedFrame->getAsyncCause();
1739 if (cause) {
1740 context()->markAtom(cause);
1741 }
1742 val = cause ? StringValue(cause) : NullValue();
1743 if (!startWrite(val)) {
1744 return false;
1745 }
1746
1747 return true;
1748 }
1749
startWrite(HandleValue v)1750 bool JSStructuredCloneWriter::startWrite(HandleValue v) {
1751 context()->check(v);
1752
1753 if (v.isString()) {
1754 return writeString(SCTAG_STRING, v.toString());
1755 } else if (v.isInt32()) {
1756 return out.writePair(SCTAG_INT32, v.toInt32());
1757 } else if (v.isDouble()) {
1758 return out.writeDouble(v.toDouble());
1759 } else if (v.isBoolean()) {
1760 return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
1761 } else if (v.isNull()) {
1762 return out.writePair(SCTAG_NULL, 0);
1763 } else if (v.isUndefined()) {
1764 return out.writePair(SCTAG_UNDEFINED, 0);
1765 } else if (v.isBigInt()) {
1766 return writeBigInt(SCTAG_BIGINT, v.toBigInt());
1767 } else if (v.isObject()) {
1768 RootedObject obj(context(), &v.toObject());
1769
1770 bool backref;
1771 if (!startObject(obj, &backref)) {
1772 return false;
1773 }
1774 if (backref) {
1775 return true;
1776 }
1777
1778 ESClass cls;
1779 if (!GetBuiltinClass(context(), obj, &cls)) {
1780 return false;
1781 }
1782
1783 switch (cls) {
1784 case ESClass::Object:
1785 case ESClass::Array:
1786 return traverseObject(obj, cls);
1787 case ESClass::Number: {
1788 RootedValue unboxed(context());
1789 if (!Unbox(context(), obj, &unboxed)) {
1790 return false;
1791 }
1792 return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
1793 out.writeDouble(unboxed.toNumber());
1794 }
1795 case ESClass::String: {
1796 RootedValue unboxed(context());
1797 if (!Unbox(context(), obj, &unboxed)) {
1798 return false;
1799 }
1800 return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
1801 }
1802 case ESClass::Boolean: {
1803 RootedValue unboxed(context());
1804 if (!Unbox(context(), obj, &unboxed)) {
1805 return false;
1806 }
1807 return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
1808 }
1809 case ESClass::RegExp: {
1810 RegExpShared* re = RegExpToShared(context(), obj);
1811 if (!re) {
1812 return false;
1813 }
1814 return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags().value()) &&
1815 writeString(SCTAG_STRING, re->getSource());
1816 }
1817 case ESClass::ArrayBuffer: {
1818 if (JS::IsArrayBufferObject(obj) && JS::ArrayBufferHasData(obj)) {
1819 return writeArrayBuffer(obj);
1820 }
1821 break;
1822 }
1823 case ESClass::SharedArrayBuffer:
1824 if (JS::IsSharedArrayBufferObject(obj)) {
1825 return writeSharedArrayBuffer(obj);
1826 }
1827 break;
1828 case ESClass::Date: {
1829 RootedValue unboxed(context());
1830 if (!Unbox(context(), obj, &unboxed)) {
1831 return false;
1832 }
1833 return out.writePair(SCTAG_DATE_OBJECT, 0) &&
1834 out.writeDouble(unboxed.toNumber());
1835 }
1836 case ESClass::Set:
1837 return traverseSet(obj);
1838 case ESClass::Map:
1839 return traverseMap(obj);
1840 case ESClass::BigInt: {
1841 RootedValue unboxed(context());
1842 if (!Unbox(context(), obj, &unboxed)) {
1843 return false;
1844 }
1845 return writeBigInt(SCTAG_BIGINT_OBJECT, unboxed.toBigInt());
1846 }
1847 case ESClass::Promise:
1848 case ESClass::MapIterator:
1849 case ESClass::SetIterator:
1850 case ESClass::Arguments:
1851 case ESClass::Error:
1852 case ESClass::Function:
1853 break;
1854
1855 case ESClass::Other: {
1856 if (obj->canUnwrapAs<TypedArrayObject>()) {
1857 return writeTypedArray(obj);
1858 }
1859 if (obj->canUnwrapAs<DataViewObject>()) {
1860 return writeDataView(obj);
1861 }
1862 if (wasm::IsSharedWasmMemoryObject(obj)) {
1863 return writeSharedWasmMemory(obj);
1864 }
1865 if (obj->canUnwrapAs<SavedFrame>()) {
1866 return traverseSavedFrame(obj);
1867 }
1868 break;
1869 }
1870 }
1871
1872 if (out.buf.callbacks_ && out.buf.callbacks_->write) {
1873 bool sameProcessScopeRequired = false;
1874 if (!out.buf.callbacks_->write(context(), this, obj,
1875 &sameProcessScopeRequired,
1876 out.buf.closure_)) {
1877 return false;
1878 }
1879
1880 if (sameProcessScopeRequired) {
1881 output().sameProcessScopeRequired();
1882 }
1883
1884 return true;
1885 }
1886 // else fall through
1887 }
1888
1889 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
1890 }
1891
writeHeader()1892 bool JSStructuredCloneWriter::writeHeader() {
1893 return out.writePair(SCTAG_HEADER, (uint32_t)output().scope());
1894 }
1895
writeTransferMap()1896 bool JSStructuredCloneWriter::writeTransferMap() {
1897 if (transferableObjects.empty()) {
1898 return true;
1899 }
1900
1901 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) {
1902 return false;
1903 }
1904
1905 if (!out.write(transferableObjects.count())) {
1906 return false;
1907 }
1908
1909 RootedObject obj(context());
1910 for (auto tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1911 obj = tr.front();
1912 if (!memory.put(obj, memory.count())) {
1913 ReportOutOfMemory(context());
1914 return false;
1915 }
1916
1917 // Emit a placeholder pointer. We defer stealing the data until later
1918 // (and, if necessary, detaching this object if it's an ArrayBuffer).
1919 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY,
1920 JS::SCTAG_TMO_UNFILLED)) {
1921 return false;
1922 }
1923 if (!out.write(0)) { // Pointer to ArrayBuffer contents.
1924 return false;
1925 }
1926 if (!out.write(0)) { // extraData
1927 return false;
1928 }
1929 }
1930
1931 return true;
1932 }
1933
transferOwnership()1934 bool JSStructuredCloneWriter::transferOwnership() {
1935 if (transferableObjects.empty()) {
1936 return true;
1937 }
1938
1939 // Walk along the transferables and the transfer map at the same time,
1940 // grabbing out pointers from the transferables and stuffing them into the
1941 // transfer map.
1942 auto point = out.iter();
1943 MOZ_RELEASE_ASSERT(point.canPeek());
1944 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
1945 SCTAG_HEADER);
1946 point++;
1947 MOZ_RELEASE_ASSERT(point.canPeek());
1948 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) ==
1949 SCTAG_TRANSFER_MAP_HEADER);
1950 point++;
1951 MOZ_RELEASE_ASSERT(point.canPeek());
1952 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) ==
1953 transferableObjects.count());
1954 point++;
1955
1956 JSContext* cx = context();
1957 RootedObject obj(cx);
1958 JS::StructuredCloneScope scope = output().scope();
1959 for (auto tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1960 obj = tr.front();
1961
1962 uint32_t tag;
1963 JS::TransferableOwnership ownership;
1964 void* content;
1965 uint64_t extraData;
1966
1967 #if DEBUG
1968 SCInput::getPair(point.peek(), &tag, (uint32_t*)&ownership);
1969 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1970 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
1971 #endif
1972
1973 ESClass cls;
1974 if (!GetBuiltinClass(cx, obj, &cls)) {
1975 return false;
1976 }
1977
1978 if (cls == ESClass::ArrayBuffer) {
1979 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
1980
1981 // The current setup of the array buffer inheritance hierarchy doesn't
1982 // lend itself well to generic manipulation via proxies.
1983 Rooted<ArrayBufferObject*> arrayBuffer(
1984 cx, obj->maybeUnwrapAs<ArrayBufferObject>());
1985 JSAutoRealm ar(cx, arrayBuffer);
1986
1987 if (arrayBuffer->isDetached()) {
1988 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED);
1989 return false;
1990 }
1991
1992 if (arrayBuffer->isPreparedForAsmJS()) {
1993 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER);
1994 return false;
1995 }
1996
1997 if (scope == JS::StructuredCloneScope::DifferentProcess ||
1998 scope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
1999 // Write Transferred ArrayBuffers in DifferentProcess scope at
2000 // the end of the clone buffer, and store the offset within the
2001 // buffer to where the ArrayBuffer was written. Note that this
2002 // will invalidate the current position iterator.
2003
2004 size_t pointOffset = out.offset(point);
2005 tag = SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER;
2006 ownership = JS::SCTAG_TMO_UNOWNED;
2007 content = nullptr;
2008 extraData = out.tell() -
2009 pointOffset; // Offset from tag to current end of buffer
2010 if (!writeArrayBuffer(arrayBuffer)) {
2011 ReportOutOfMemory(cx);
2012 return false;
2013 }
2014
2015 // Must refresh the point iterator after its collection has
2016 // been modified.
2017 point = out.iter();
2018 point += pointOffset;
2019
2020 if (!JS::DetachArrayBuffer(cx, arrayBuffer)) {
2021 return false;
2022 }
2023 } else {
2024 size_t nbytes = arrayBuffer->byteLength();
2025
2026 using BufferContents = ArrayBufferObject::BufferContents;
2027
2028 BufferContents bufContents =
2029 ArrayBufferObject::extractStructuredCloneContents(cx, arrayBuffer);
2030 if (!bufContents) {
2031 return false; // out of memory
2032 }
2033
2034 content = bufContents.data();
2035 if (bufContents.kind() == ArrayBufferObject::MAPPED) {
2036 ownership = JS::SCTAG_TMO_MAPPED_DATA;
2037 } else {
2038 MOZ_ASSERT(bufContents.kind() == ArrayBufferObject::MALLOCED,
2039 "failing to handle new ArrayBuffer kind?");
2040 ownership = JS::SCTAG_TMO_ALLOC_DATA;
2041 }
2042 extraData = nbytes;
2043 }
2044 } else {
2045 if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) {
2046 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
2047 }
2048 if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag,
2049 &ownership, &content,
2050 &extraData)) {
2051 return false;
2052 }
2053 MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2054 }
2055
2056 point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
2057 MOZ_ALWAYS_TRUE(point.advance());
2058 point.write(
2059 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content)));
2060 MOZ_ALWAYS_TRUE(point.advance());
2061 point.write(NativeEndian::swapToLittleEndian(extraData));
2062 MOZ_ALWAYS_TRUE(point.advance());
2063 }
2064
2065 #if DEBUG
2066 // Make sure there aren't any more transfer map entries after the expected
2067 // number we read out.
2068 if (!point.done()) {
2069 uint32_t tag, data;
2070 SCInput::getPair(point.peek(), &tag, &data);
2071 MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER ||
2072 tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
2073 }
2074 #endif
2075 return true;
2076 }
2077
write(HandleValue v)2078 bool JSStructuredCloneWriter::write(HandleValue v) {
2079 if (!startWrite(v)) {
2080 return false;
2081 }
2082
2083 RootedObject obj(context());
2084 RootedValue key(context());
2085 RootedValue val(context());
2086 RootedId id(context());
2087
2088 while (!counts.empty()) {
2089 obj = &objs.back().toObject();
2090 context()->check(obj);
2091 if (counts.back()) {
2092 counts.back()--;
2093
2094 ESClass cls;
2095 if (!GetBuiltinClass(context(), obj, &cls)) {
2096 return false;
2097 }
2098
2099 if (cls == ESClass::Map) {
2100 key = otherEntries.popCopy();
2101 checkStack();
2102
2103 counts.back()--;
2104 val = otherEntries.popCopy();
2105 checkStack();
2106
2107 if (!startWrite(key) || !startWrite(val)) {
2108 return false;
2109 }
2110 } else if (cls == ESClass::Set || obj->canUnwrapAs<SavedFrame>()) {
2111 key = otherEntries.popCopy();
2112 checkStack();
2113
2114 if (!startWrite(key)) {
2115 return false;
2116 }
2117 } else {
2118 id = objectEntries.popCopy();
2119 key = IdToValue(id);
2120 checkStack();
2121
2122 // If obj still has an own property named id, write it out.
2123 bool found;
2124 if (GetOwnPropertyPure(context(), obj, id, val.address(), &found)) {
2125 if (found) {
2126 if (!startWrite(key) || !startWrite(val)) {
2127 return false;
2128 }
2129 }
2130 continue;
2131 }
2132
2133 if (!HasOwnProperty(context(), obj, id, &found)) {
2134 return false;
2135 }
2136
2137 if (found) {
2138 if (!startWrite(key) || !GetProperty(context(), obj, obj, id, &val) ||
2139 !startWrite(val)) {
2140 return false;
2141 }
2142 }
2143 }
2144 } else {
2145 if (!out.writePair(SCTAG_END_OF_KEYS, 0)) {
2146 return false;
2147 }
2148 objs.popBack();
2149 counts.popBack();
2150 }
2151 }
2152
2153 memory.clear();
2154 return transferOwnership();
2155 }
2156
2157 template <typename CharT>
readStringImpl(uint32_t nchars,gc::InitialHeap heap)2158 JSString* JSStructuredCloneReader::readStringImpl(uint32_t nchars,
2159 gc::InitialHeap heap) {
2160 if (nchars > JSString::MAX_LENGTH) {
2161 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2162 JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
2163 return nullptr;
2164 }
2165
2166 InlineCharBuffer<CharT> chars;
2167 if (!chars.maybeAlloc(context(), nchars) ||
2168 !in.readChars(chars.get(), nchars)) {
2169 return nullptr;
2170 }
2171 return chars.toStringDontDeflate(context(), nchars, heap);
2172 }
2173
readString(uint32_t data,gc::InitialHeap heap)2174 JSString* JSStructuredCloneReader::readString(uint32_t data,
2175 gc::InitialHeap heap) {
2176 uint32_t nchars = data & BitMask(31);
2177 bool latin1 = data & (1 << 31);
2178 return latin1 ? readStringImpl<Latin1Char>(nchars, heap)
2179 : readStringImpl<char16_t>(nchars, heap);
2180 }
2181
readBigInt(uint32_t data)2182 BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) {
2183 size_t length = data & BitMask(31);
2184 bool isNegative = data & (1 << 31);
2185 if (length == 0) {
2186 return BigInt::zero(context());
2187 }
2188 RootedBigInt result(
2189 context(), BigInt::createUninitialized(context(), length, isNegative));
2190 if (!result) {
2191 return nullptr;
2192 }
2193 if (!in.readArray(result->digits().data(), length)) {
2194 return nullptr;
2195 }
2196 return result;
2197 }
2198
TagToV1ArrayType(uint32_t tag)2199 static uint32_t TagToV1ArrayType(uint32_t tag) {
2200 MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN &&
2201 tag <= SCTAG_TYPED_ARRAY_V1_MAX);
2202 return tag - SCTAG_TYPED_ARRAY_V1_MIN;
2203 }
2204
readTypedArray(uint32_t arrayType,uint64_t nelems,MutableHandleValue vp,bool v1Read)2205 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType,
2206 uint64_t nelems,
2207 MutableHandleValue vp,
2208 bool v1Read) {
2209 if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) {
2210 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2211 JSMSG_SC_BAD_SERIALIZED_DATA,
2212 "unhandled typed array element type");
2213 return false;
2214 }
2215
2216 // Push a placeholder onto the allObjs list to stand in for the typed array.
2217 uint32_t placeholderIndex = allObjs.length();
2218 Value dummy = UndefinedValue();
2219 if (!allObjs.append(dummy)) {
2220 return false;
2221 }
2222
2223 // Read the ArrayBuffer object and its contents (but no properties)
2224 RootedValue v(context());
2225 uint64_t byteOffset;
2226 if (v1Read) {
2227 if (!readV1ArrayBuffer(arrayType, nelems, &v)) {
2228 return false;
2229 }
2230 byteOffset = 0;
2231 } else {
2232 if (!startRead(&v)) {
2233 return false;
2234 }
2235 if (!in.read(&byteOffset)) {
2236 return false;
2237 }
2238 }
2239
2240 // Ensure invalid 64-bit values won't be truncated below.
2241 if (nelems > ArrayBufferObject::maxBufferByteLength() ||
2242 byteOffset > ArrayBufferObject::maxBufferByteLength()) {
2243 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2244 JSMSG_SC_BAD_SERIALIZED_DATA,
2245 "invalid typed array length or offset");
2246 return false;
2247 }
2248
2249 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2250 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2251 JSMSG_SC_BAD_SERIALIZED_DATA,
2252 "typed array must be backed by an ArrayBuffer");
2253 return false;
2254 }
2255
2256 RootedObject buffer(context(), &v.toObject());
2257 RootedObject obj(context(), nullptr);
2258
2259 switch (arrayType) {
2260 case Scalar::Int8:
2261 obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2262 break;
2263 case Scalar::Uint8:
2264 obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2265 break;
2266 case Scalar::Int16:
2267 obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2268 break;
2269 case Scalar::Uint16:
2270 obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2271 break;
2272 case Scalar::Int32:
2273 obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2274 break;
2275 case Scalar::Uint32:
2276 obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2277 break;
2278 case Scalar::Float32:
2279 obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2280 break;
2281 case Scalar::Float64:
2282 obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2283 break;
2284 case Scalar::Uint8Clamped:
2285 obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset,
2286 nelems);
2287 break;
2288 case Scalar::BigInt64:
2289 obj =
2290 JS_NewBigInt64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2291 break;
2292 case Scalar::BigUint64:
2293 obj =
2294 JS_NewBigUint64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
2295 break;
2296 default:
2297 MOZ_CRASH("Can't happen: arrayType range checked above");
2298 }
2299
2300 if (!obj) {
2301 return false;
2302 }
2303 vp.setObject(*obj);
2304
2305 allObjs[placeholderIndex].set(vp);
2306
2307 return true;
2308 }
2309
readDataView(uint64_t byteLength,MutableHandleValue vp)2310 bool JSStructuredCloneReader::readDataView(uint64_t byteLength,
2311 MutableHandleValue vp) {
2312 // Push a placeholder onto the allObjs list to stand in for the DataView.
2313 uint32_t placeholderIndex = allObjs.length();
2314 Value dummy = UndefinedValue();
2315 if (!allObjs.append(dummy)) {
2316 return false;
2317 }
2318
2319 // Read the ArrayBuffer object and its contents (but no properties).
2320 RootedValue v(context());
2321 if (!startRead(&v)) {
2322 return false;
2323 }
2324 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
2325 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2326 JSMSG_SC_BAD_SERIALIZED_DATA,
2327 "DataView must be backed by an ArrayBuffer");
2328 return false;
2329 }
2330
2331 // Read byteOffset.
2332 uint64_t byteOffset;
2333 if (!in.read(&byteOffset)) {
2334 return false;
2335 }
2336
2337 // Ensure invalid 64-bit values won't be truncated below.
2338 if (byteLength > ArrayBufferObject::maxBufferByteLength() ||
2339 byteOffset > ArrayBufferObject::maxBufferByteLength()) {
2340 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2341 JSMSG_SC_BAD_SERIALIZED_DATA,
2342 "invalid DataView length or offset");
2343 return false;
2344 }
2345
2346 RootedObject buffer(context(), &v.toObject());
2347 RootedObject obj(context(),
2348 JS_NewDataView(context(), buffer, byteOffset, byteLength));
2349 if (!obj) {
2350 return false;
2351 }
2352 vp.setObject(*obj);
2353
2354 allObjs[placeholderIndex].set(vp);
2355
2356 return true;
2357 }
2358
readArrayBuffer(StructuredDataType type,uint32_t data,MutableHandleValue vp)2359 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type,
2360 uint32_t data,
2361 MutableHandleValue vp) {
2362 // V2 stores the length in |data|. The current version stores the
2363 // length separately to allow larger length values.
2364 uint64_t nbytes = 0;
2365 if (type == SCTAG_ARRAY_BUFFER_OBJECT) {
2366 if (!in.read(&nbytes)) {
2367 return false;
2368 }
2369 } else {
2370 MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2);
2371 nbytes = data;
2372 }
2373
2374 // The maximum ArrayBuffer size depends on the platform and prefs, and we cast
2375 // to size_t below, so we have to check this here.
2376 if (nbytes > ArrayBufferObject::maxBufferByteLength()) {
2377 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2378 JSMSG_BAD_ARRAY_LENGTH);
2379 return false;
2380 }
2381
2382 JSObject* obj = ArrayBufferObject::createZeroed(context(), size_t(nbytes));
2383 if (!obj) {
2384 return false;
2385 }
2386 vp.setObject(*obj);
2387 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2388 MOZ_ASSERT(buffer.byteLength() == nbytes);
2389 return in.readArray(buffer.dataPointer(), nbytes);
2390 }
2391
readSharedArrayBuffer(MutableHandleValue vp)2392 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp) {
2393 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2394 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2395 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2396 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2397 : JS_SCERR_NOT_CLONABLE;
2398 ReportDataCloneError(context(), callbacks, error, closure,
2399 "SharedArrayBuffer");
2400 return false;
2401 }
2402
2403 uint64_t byteLength;
2404 if (!in.readBytes(&byteLength, sizeof(byteLength))) {
2405 return in.reportTruncated();
2406 }
2407
2408 // The maximum ArrayBuffer size depends on the platform and prefs, and we cast
2409 // to size_t below, so we have to check this here.
2410 if (byteLength > ArrayBufferObject::maxBufferByteLength()) {
2411 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2412 JSMSG_BAD_ARRAY_LENGTH);
2413 return false;
2414 }
2415
2416 intptr_t p;
2417 if (!in.readBytes(&p, sizeof(p))) {
2418 return in.reportTruncated();
2419 }
2420
2421 SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p);
2422
2423 // There's no guarantee that the receiving agent has enabled shared memory
2424 // even if the transmitting agent has done so. Ideally we'd check at the
2425 // transmission point, but that's tricky, and it will be a very rare problem
2426 // in any case. Just fail at the receiving end if we can't handle it.
2427
2428 if (!context()
2429 ->realm()
2430 ->creationOptions()
2431 .getSharedMemoryAndAtomicsEnabled()) {
2432 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2433 JSMSG_SC_SAB_DISABLED);
2434 return false;
2435 }
2436
2437 // The new object will have a new reference to the rawbuf.
2438
2439 if (!rawbuf->addReference()) {
2440 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2441 JSMSG_SC_SAB_REFCNT_OFLO);
2442 return false;
2443 }
2444
2445 RootedObject obj(context(),
2446 SharedArrayBufferObject::New(context(), rawbuf, byteLength));
2447 if (!obj) {
2448 rawbuf->dropReference();
2449 return false;
2450 }
2451
2452 // `rawbuf` is now owned by `obj`.
2453
2454 if (callbacks && callbacks->sabCloned &&
2455 !callbacks->sabCloned(context(), /*receiving=*/true, closure)) {
2456 return false;
2457 }
2458
2459 vp.setObject(*obj);
2460 return true;
2461 }
2462
readSharedWasmMemory(uint32_t nbytes,MutableHandleValue vp)2463 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes,
2464 MutableHandleValue vp) {
2465 JSContext* cx = context();
2466 if (nbytes != 0) {
2467 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2468 JSMSG_SC_BAD_SERIALIZED_DATA,
2469 "invalid shared wasm memory tag");
2470 return false;
2471 }
2472
2473 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
2474 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
2475 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
2476 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
2477 : JS_SCERR_NOT_CLONABLE;
2478 ReportDataCloneError(cx, callbacks, error, closure, "WebAssembly.Memory");
2479 return false;
2480 }
2481
2482 // Read the SharedArrayBuffer object.
2483 RootedValue payload(cx);
2484 if (!startRead(&payload)) {
2485 return false;
2486 }
2487 if (!payload.isObject() ||
2488 !payload.toObject().is<SharedArrayBufferObject>()) {
2489 JS_ReportErrorNumberASCII(
2490 context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
2491 "shared wasm memory must be backed by a SharedArrayBuffer");
2492 return false;
2493 }
2494
2495 Rooted<ArrayBufferObjectMaybeShared*> sab(
2496 cx, &payload.toObject().as<SharedArrayBufferObject>());
2497
2498 // Construct the memory.
2499 RootedObject proto(
2500 cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
2501 RootedObject memory(cx, WasmMemoryObject::create(cx, sab, proto));
2502 if (!memory) {
2503 return false;
2504 }
2505
2506 vp.setObject(*memory);
2507 return true;
2508 }
2509
2510 /*
2511 * Read in the data for a structured clone version 1 ArrayBuffer, performing
2512 * endianness-conversion while reading.
2513 */
readV1ArrayBuffer(uint32_t arrayType,uint32_t nelems,MutableHandleValue vp)2514 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType,
2515 uint32_t nelems,
2516 MutableHandleValue vp) {
2517 if (arrayType > Scalar::Uint8Clamped) {
2518 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2519 JSMSG_SC_BAD_SERIALIZED_DATA,
2520 "invalid TypedArray type");
2521 return false;
2522 }
2523
2524 mozilla::CheckedInt<size_t> nbytes =
2525 mozilla::CheckedInt<size_t>(nelems) *
2526 TypedArrayElemSize(static_cast<Scalar::Type>(arrayType));
2527 if (!nbytes.isValid() || nbytes.value() > UINT32_MAX) {
2528 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2529 JSMSG_SC_BAD_SERIALIZED_DATA,
2530 "invalid typed array size");
2531 return false;
2532 }
2533
2534 JSObject* obj = ArrayBufferObject::createZeroed(context(), nbytes.value());
2535 if (!obj) {
2536 return false;
2537 }
2538 vp.setObject(*obj);
2539 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2540 MOZ_ASSERT(buffer.byteLength() == nbytes);
2541
2542 switch (arrayType) {
2543 case Scalar::Int8:
2544 case Scalar::Uint8:
2545 case Scalar::Uint8Clamped:
2546 return in.readArray((uint8_t*)buffer.dataPointer(), nelems);
2547 case Scalar::Int16:
2548 case Scalar::Uint16:
2549 return in.readArray((uint16_t*)buffer.dataPointer(), nelems);
2550 case Scalar::Int32:
2551 case Scalar::Uint32:
2552 case Scalar::Float32:
2553 return in.readArray((uint32_t*)buffer.dataPointer(), nelems);
2554 case Scalar::Float64:
2555 case Scalar::BigInt64:
2556 case Scalar::BigUint64:
2557 return in.readArray((uint64_t*)buffer.dataPointer(), nelems);
2558 default:
2559 MOZ_CRASH("Can't happen: arrayType range checked by caller");
2560 }
2561 }
2562
PrimitiveToObject(JSContext * cx,MutableHandleValue vp)2563 static bool PrimitiveToObject(JSContext* cx, MutableHandleValue vp) {
2564 JSObject* obj = js::PrimitiveToObject(cx, vp);
2565 if (!obj) {
2566 return false;
2567 }
2568
2569 vp.setObject(*obj);
2570 return true;
2571 }
2572
startRead(MutableHandleValue vp,gc::InitialHeap strHeap)2573 bool JSStructuredCloneReader::startRead(MutableHandleValue vp,
2574 gc::InitialHeap strHeap) {
2575 uint32_t tag, data;
2576 bool alreadAppended = false;
2577
2578 if (!in.readPair(&tag, &data)) {
2579 return false;
2580 }
2581
2582 numItemsRead++;
2583
2584 switch (tag) {
2585 case SCTAG_NULL:
2586 vp.setNull();
2587 break;
2588
2589 case SCTAG_UNDEFINED:
2590 vp.setUndefined();
2591 break;
2592
2593 case SCTAG_INT32:
2594 vp.setInt32(data);
2595 break;
2596
2597 case SCTAG_BOOLEAN:
2598 case SCTAG_BOOLEAN_OBJECT:
2599 vp.setBoolean(!!data);
2600 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) {
2601 return false;
2602 }
2603 break;
2604
2605 case SCTAG_STRING:
2606 case SCTAG_STRING_OBJECT: {
2607 JSString* str = readString(data, strHeap);
2608 if (!str) {
2609 return false;
2610 }
2611 vp.setString(str);
2612 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) {
2613 return false;
2614 }
2615 break;
2616 }
2617
2618 case SCTAG_NUMBER_OBJECT: {
2619 double d;
2620 if (!in.readDouble(&d)) {
2621 return false;
2622 }
2623 vp.setDouble(CanonicalizeNaN(d));
2624 if (!PrimitiveToObject(context(), vp)) {
2625 return false;
2626 }
2627 break;
2628 }
2629
2630 case SCTAG_BIGINT:
2631 case SCTAG_BIGINT_OBJECT: {
2632 RootedBigInt bi(context(), readBigInt(data));
2633 if (!bi) {
2634 return false;
2635 }
2636 vp.setBigInt(bi);
2637 if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) {
2638 return false;
2639 }
2640 break;
2641 }
2642
2643 case SCTAG_DATE_OBJECT: {
2644 double d;
2645 if (!in.readDouble(&d)) {
2646 return false;
2647 }
2648 JS::ClippedTime t = JS::TimeClip(d);
2649 if (!NumbersAreIdentical(d, t.toDouble())) {
2650 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2651 JSMSG_SC_BAD_SERIALIZED_DATA, "date");
2652 return false;
2653 }
2654 JSObject* obj = NewDateObjectMsec(context(), t);
2655 if (!obj) {
2656 return false;
2657 }
2658 vp.setObject(*obj);
2659 break;
2660 }
2661
2662 case SCTAG_REGEXP_OBJECT: {
2663 if ((data & RegExpFlag::AllFlags) != data) {
2664 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2665 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2666 return false;
2667 }
2668
2669 RegExpFlags flags(AssertedCast<uint8_t>(data));
2670
2671 uint32_t tag2, stringData;
2672 if (!in.readPair(&tag2, &stringData)) {
2673 return false;
2674 }
2675 if (tag2 != SCTAG_STRING) {
2676 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2677 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
2678 return false;
2679 }
2680
2681 JSString* str = readString(stringData, gc::TenuredHeap);
2682 if (!str) {
2683 return false;
2684 }
2685
2686 RootedAtom atom(context(), AtomizeString(context(), str));
2687 if (!atom) {
2688 return false;
2689 }
2690
2691 RegExpObject* reobj =
2692 RegExpObject::create(context(), atom, flags, GenericObject);
2693 if (!reobj) {
2694 return false;
2695 }
2696 vp.setObject(*reobj);
2697 break;
2698 }
2699
2700 case SCTAG_ARRAY_OBJECT:
2701 case SCTAG_OBJECT_OBJECT: {
2702 JSObject* obj =
2703 (tag == SCTAG_ARRAY_OBJECT)
2704 ? (JSObject*)NewDenseUnallocatedArray(
2705 context(), NativeEndian::swapFromLittleEndian(data))
2706 : (JSObject*)NewBuiltinClassInstance<PlainObject>(context());
2707 if (!obj || !objs.append(ObjectValue(*obj))) {
2708 return false;
2709 }
2710 vp.setObject(*obj);
2711 break;
2712 }
2713
2714 case SCTAG_BACK_REFERENCE_OBJECT: {
2715 if (data >= allObjs.length() || !allObjs[data].isObject()) {
2716 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2717 JSMSG_SC_BAD_SERIALIZED_DATA,
2718 "invalid back reference in input");
2719 return false;
2720 }
2721 vp.set(allObjs[data]);
2722 return true;
2723 }
2724
2725 case SCTAG_TRANSFER_MAP_HEADER:
2726 case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
2727 // We should be past all the transfer map tags.
2728 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2729 JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input");
2730 return false;
2731
2732 case SCTAG_ARRAY_BUFFER_OBJECT_V2:
2733 case SCTAG_ARRAY_BUFFER_OBJECT:
2734 if (!readArrayBuffer(StructuredDataType(tag), data, vp)) {
2735 return false;
2736 }
2737 break;
2738
2739 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
2740 if (!readSharedArrayBuffer(vp)) {
2741 return false;
2742 }
2743 break;
2744
2745 case SCTAG_SHARED_WASM_MEMORY_OBJECT:
2746 if (!readSharedWasmMemory(data, vp)) {
2747 return false;
2748 }
2749 break;
2750
2751 case SCTAG_TYPED_ARRAY_OBJECT_V2: {
2752 // readTypedArray adds the array to allObjs.
2753 // V2 stores the length (nelems) in |data| and the arrayType separately.
2754 uint64_t arrayType;
2755 if (!in.read(&arrayType)) {
2756 return false;
2757 }
2758 uint64_t nelems = data;
2759 return readTypedArray(arrayType, nelems, vp);
2760 }
2761
2762 case SCTAG_TYPED_ARRAY_OBJECT: {
2763 // readTypedArray adds the array to allObjs.
2764 // The current version stores the array type in |data| and the length
2765 // (nelems) separately to support large TypedArrays.
2766 uint32_t arrayType = data;
2767 uint64_t nelems;
2768 if (!in.read(&nelems)) {
2769 return false;
2770 }
2771 return readTypedArray(arrayType, nelems, vp);
2772 }
2773
2774 case SCTAG_DATA_VIEW_OBJECT_V2: {
2775 // readDataView adds the array to allObjs.
2776 uint64_t byteLength = data;
2777 return readDataView(byteLength, vp);
2778 }
2779
2780 case SCTAG_DATA_VIEW_OBJECT: {
2781 // readDataView adds the array to allObjs.
2782 uint64_t byteLength;
2783 if (!in.read(&byteLength)) {
2784 return false;
2785 }
2786 return readDataView(byteLength, vp);
2787 }
2788
2789 case SCTAG_MAP_OBJECT: {
2790 JSObject* obj = MapObject::create(context());
2791 if (!obj || !objs.append(ObjectValue(*obj))) {
2792 return false;
2793 }
2794 vp.setObject(*obj);
2795 break;
2796 }
2797
2798 case SCTAG_SET_OBJECT: {
2799 JSObject* obj = SetObject::create(context());
2800 if (!obj || !objs.append(ObjectValue(*obj))) {
2801 return false;
2802 }
2803 vp.setObject(*obj);
2804 break;
2805 }
2806
2807 case SCTAG_SAVED_FRAME_OBJECT: {
2808 auto obj = readSavedFrame(data);
2809 if (!obj || !objs.append(ObjectValue(*obj))) {
2810 return false;
2811 }
2812 vp.setObject(*obj);
2813 break;
2814 }
2815
2816 default: {
2817 if (tag <= SCTAG_FLOAT_MAX) {
2818 double d = ReinterpretPairAsDouble(tag, data);
2819 vp.setNumber(CanonicalizeNaN(d));
2820 break;
2821 }
2822
2823 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
2824 // A v1-format typed array
2825 // readTypedArray adds the array to allObjs
2826 return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
2827 }
2828
2829 if (!callbacks || !callbacks->read) {
2830 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2831 JSMSG_SC_BAD_SERIALIZED_DATA,
2832 "unsupported type");
2833 return false;
2834 }
2835
2836 // callbacks->read() might read other objects from the buffer.
2837 // In startWrite we always write the object itself before calling
2838 // the custom function. We should do the same here to keep
2839 // indexing consistent.
2840 uint32_t placeholderIndex = allObjs.length();
2841 Value dummy = UndefinedValue();
2842 if (!allObjs.append(dummy)) {
2843 return false;
2844 }
2845 JSObject* obj =
2846 callbacks->read(context(), this, cloneDataPolicy, tag, data, closure);
2847 if (!obj) {
2848 return false;
2849 }
2850 vp.setObject(*obj);
2851 allObjs[placeholderIndex].set(vp);
2852 alreadAppended = true;
2853 }
2854 }
2855
2856 if (!alreadAppended && vp.isObject() && !allObjs.append(vp)) {
2857 return false;
2858 }
2859
2860 return true;
2861 }
2862
readHeader()2863 bool JSStructuredCloneReader::readHeader() {
2864 uint32_t tag, data;
2865 if (!in.getPair(&tag, &data)) {
2866 return in.reportTruncated();
2867 }
2868
2869 JS::StructuredCloneScope storedScope;
2870 if (tag == SCTAG_HEADER) {
2871 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2872 storedScope = JS::StructuredCloneScope(data);
2873 } else {
2874 // Old structured clone buffer. We must have read it from disk.
2875 storedScope = JS::StructuredCloneScope::DifferentProcessForIndexedDB;
2876 }
2877
2878 // Backward compatibility with old structured clone buffers. Value '0' was
2879 // used for SameProcessSameThread scope.
2880 if ((int)storedScope == 0) {
2881 storedScope = JS::StructuredCloneScope::SameProcess;
2882 }
2883
2884 if (storedScope < JS::StructuredCloneScope::SameProcess ||
2885 storedScope > JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
2886 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2887 JSMSG_SC_BAD_SERIALIZED_DATA,
2888 "invalid structured clone scope");
2889 return false;
2890 }
2891
2892 if (allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
2893 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB
2894 // clones are incorrect. Treat them as if they were DifferentProcess.
2895 allowedScope = JS::StructuredCloneScope::DifferentProcess;
2896 return true;
2897 }
2898
2899 if (storedScope < allowedScope) {
2900 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2901 JSMSG_SC_BAD_SERIALIZED_DATA,
2902 "incompatible structured clone scope");
2903 return false;
2904 }
2905
2906 return true;
2907 }
2908
readTransferMap()2909 bool JSStructuredCloneReader::readTransferMap() {
2910 JSContext* cx = context();
2911 auto headerPos = in.tell();
2912
2913 uint32_t tag, data;
2914 if (!in.getPair(&tag, &data)) {
2915 return in.reportTruncated();
2916 }
2917
2918 if (tag != SCTAG_TRANSFER_MAP_HEADER ||
2919 TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
2920 return true;
2921 }
2922
2923 uint64_t numTransferables;
2924 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2925 if (!in.read(&numTransferables)) {
2926 return false;
2927 }
2928
2929 for (uint64_t i = 0; i < numTransferables; i++) {
2930 auto pos = in.tell();
2931
2932 if (!in.readPair(&tag, &data)) {
2933 return false;
2934 }
2935
2936 if (tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY) {
2937 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2938 return false;
2939 }
2940
2941 RootedObject obj(cx);
2942
2943 void* content;
2944 if (!in.readPtr(&content)) {
2945 return false;
2946 }
2947
2948 uint64_t extraData;
2949 if (!in.read(&extraData)) {
2950 return false;
2951 }
2952
2953 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
2954 if (allowedScope == JS::StructuredCloneScope::DifferentProcess ||
2955 allowedScope ==
2956 JS::StructuredCloneScope::DifferentProcessForIndexedDB) {
2957 // Transferred ArrayBuffers in a DifferentProcess clone buffer
2958 // are treated as if they weren't Transferred at all. We should
2959 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER.
2960 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2961 return false;
2962 }
2963
2964 MOZ_RELEASE_ASSERT(extraData <= ArrayBufferObject::maxBufferByteLength());
2965 size_t nbytes = extraData;
2966
2967 MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
2968 data == JS::SCTAG_TMO_MAPPED_DATA);
2969 if (data == JS::SCTAG_TMO_ALLOC_DATA) {
2970 obj = JS::NewArrayBufferWithContents(cx, nbytes, content);
2971 } else if (data == JS::SCTAG_TMO_MAPPED_DATA) {
2972 obj = JS::NewMappedArrayBufferWithContents(cx, nbytes, content);
2973 }
2974 } else if (tag == SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER) {
2975 auto savedPos = in.tell();
2976 auto guard = mozilla::MakeScopeExit([&] { in.seekTo(savedPos); });
2977 in.seekTo(pos);
2978 if (!in.seekBy(static_cast<size_t>(extraData))) {
2979 return false;
2980 }
2981
2982 uint32_t tag, data;
2983 if (!in.readPair(&tag, &data)) {
2984 return false;
2985 }
2986 if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 &&
2987 tag != SCTAG_ARRAY_BUFFER_OBJECT) {
2988 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2989 return false;
2990 }
2991 RootedValue val(cx);
2992 if (!readArrayBuffer(StructuredDataType(tag), data, &val)) {
2993 return false;
2994 }
2995 obj = &val.toObject();
2996 } else {
2997 if (!callbacks || !callbacks->readTransfer) {
2998 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
2999 return false;
3000 }
3001 if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure,
3002 &obj)) {
3003 return false;
3004 }
3005 MOZ_ASSERT(obj);
3006 MOZ_ASSERT(!cx->isExceptionPending());
3007 }
3008
3009 // On failure, the buffer will still own the data (since its ownership
3010 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
3011 // DiscardTransferables.
3012 if (!obj) {
3013 return false;
3014 }
3015
3016 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
3017 // buffer.
3018 pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED));
3019 MOZ_ASSERT(!pos.done());
3020
3021 if (!allObjs.append(ObjectValue(*obj))) {
3022 return false;
3023 }
3024 }
3025
3026 // Mark the whole transfer map as consumed.
3027 #ifdef DEBUG
3028 SCInput::getPair(headerPos.peek(), &tag, &data);
3029 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
3030 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
3031 #endif
3032 headerPos.write(
3033 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
3034
3035 return true;
3036 }
3037
readSavedFrame(uint32_t principalsTag)3038 JSObject* JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag) {
3039 RootedSavedFrame savedFrame(context(), SavedFrame::create(context()));
3040 if (!savedFrame) {
3041 return nullptr;
3042 }
3043
3044 JSPrincipals* principals;
3045 if (principalsTag == SCTAG_JSPRINCIPALS) {
3046 if (!context()->runtime()->readPrincipals) {
3047 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3048 JSMSG_SC_UNSUPPORTED_TYPE);
3049 return nullptr;
3050 }
3051
3052 if (!context()->runtime()->readPrincipals(context(), this, &principals)) {
3053 return nullptr;
3054 }
3055 } else if (principalsTag ==
3056 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
3057 principals = &ReconstructedSavedFramePrincipals::IsSystem;
3058 principals->refcount++;
3059 } else if (principalsTag ==
3060 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
3061 principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
3062 principals->refcount++;
3063 } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
3064 principals = nullptr;
3065 } else {
3066 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3067 JSMSG_SC_BAD_SERIALIZED_DATA,
3068 "bad SavedFrame principals");
3069 return nullptr;
3070 }
3071
3072 RootedValue mutedErrors(context());
3073 RootedValue source(context());
3074 {
3075 // Read a |mutedErrors| boolean followed by a |source| string.
3076 // The |mutedErrors| boolean is present in all new structured-clone data,
3077 // but in older data it will be absent and only the |source| string will be
3078 // found.
3079 if (!startRead(&mutedErrors)) {
3080 return nullptr;
3081 }
3082
3083 if (mutedErrors.isBoolean()) {
3084 if (!startRead(&source, gc::TenuredHeap) || !source.isString()) {
3085 return nullptr;
3086 }
3087 } else if (mutedErrors.isString()) {
3088 // Backwards compatibility: Handle missing |mutedErrors| boolean,
3089 // this is actually just a |source| string.
3090 source = mutedErrors;
3091 mutedErrors.setBoolean(true); // Safe default value.
3092 } else {
3093 // Invalid type.
3094 return nullptr;
3095 }
3096 }
3097
3098 savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals,
3099 mutedErrors.toBoolean());
3100
3101 auto atomSource = AtomizeString(context(), source.toString());
3102 if (!atomSource) {
3103 return nullptr;
3104 }
3105 savedFrame->initSource(atomSource);
3106
3107 RootedValue lineVal(context());
3108 uint32_t line;
3109 if (!startRead(&lineVal) || !lineVal.isNumber() ||
3110 !ToUint32(context(), lineVal, &line)) {
3111 return nullptr;
3112 }
3113 savedFrame->initLine(line);
3114
3115 RootedValue columnVal(context());
3116 uint32_t column;
3117 if (!startRead(&columnVal) || !columnVal.isNumber() ||
3118 !ToUint32(context(), columnVal, &column)) {
3119 return nullptr;
3120 }
3121 savedFrame->initColumn(column);
3122
3123 // Don't specify a source ID when reading a cloned saved frame, as these IDs
3124 // are only valid within a specific process.
3125 savedFrame->initSourceId(0);
3126
3127 RootedValue name(context());
3128 if (!startRead(&name, gc::TenuredHeap)) {
3129 return nullptr;
3130 }
3131 if (!(name.isString() || name.isNull())) {
3132 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3133 JSMSG_SC_BAD_SERIALIZED_DATA,
3134 "invalid saved frame cause");
3135 return nullptr;
3136 }
3137 JSAtom* atomName = nullptr;
3138 if (name.isString()) {
3139 atomName = AtomizeString(context(), name.toString());
3140 if (!atomName) {
3141 return nullptr;
3142 }
3143 }
3144
3145 savedFrame->initFunctionDisplayName(atomName);
3146
3147 RootedValue cause(context());
3148 if (!startRead(&cause, gc::TenuredHeap)) {
3149 return nullptr;
3150 }
3151 if (!(cause.isString() || cause.isNull())) {
3152 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3153 JSMSG_SC_BAD_SERIALIZED_DATA,
3154 "invalid saved frame cause");
3155 return nullptr;
3156 }
3157 JSAtom* atomCause = nullptr;
3158 if (cause.isString()) {
3159 atomCause = AtomizeString(context(), cause.toString());
3160 if (!atomCause) {
3161 return nullptr;
3162 }
3163 }
3164 savedFrame->initAsyncCause(atomCause);
3165
3166 return savedFrame;
3167 }
3168
3169 // Class for counting "children" (actually parent frames) of the SavedFrames on
3170 // the `objs` stack. When a SavedFrame is complete, it should have exactly 1
3171 // parent frame.
3172 //
3173 // This class must be notified after every startRead() call.
3174 //
3175 // If we add other types with restrictions on the number of children, this
3176 // should be expanded to handle those types as well.
3177 //
3178 class ChildCounter {
3179 JSContext* cx;
3180 Vector<size_t> counts;
3181 size_t objsLength;
3182 size_t objCountsIndex;
3183
3184 public:
ChildCounter(JSContext * cx)3185 explicit ChildCounter(JSContext* cx) : cx(cx), counts(cx), objsLength(0) {}
3186
noteObjIsOnTopOfStack()3187 void noteObjIsOnTopOfStack() { objCountsIndex = counts.length() - 1; }
3188
3189 // startRead() will have pushed any newly seen object onto the `objs` stack.
3190 // If it did not read an object, or if the object it read was a backreference
3191 // to an earlier object, the stack will be unchanged.
postStartRead(HandleValueVector objs)3192 bool postStartRead(HandleValueVector objs) {
3193 if (objs.length() == objsLength) {
3194 // No new object pushed.
3195 return true;
3196 }
3197
3198 // Push a new child counter (initialized to zero) for the new object.
3199 MOZ_ASSERT(objs.length() == objsLength + 1);
3200 objsLength = objs.length();
3201 if (objs.back().toObject().is<SavedFrame>()) {
3202 return counts.append(0);
3203 }
3204
3205 // Not a SavedFrame; do nothing.
3206 return true;
3207 }
3208
3209 // Reading has reached the end of the children for an object. Check whether
3210 // we saw the right number of children.
handleEndOfChildren(HandleValueVector objs)3211 bool handleEndOfChildren(HandleValueVector objs) {
3212 MOZ_ASSERT(objsLength > 0);
3213 objsLength--;
3214
3215 if (objs.back().toObject().is<SavedFrame>()) {
3216 if (counts.back() != 1) {
3217 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3218 JSMSG_SC_BAD_SERIALIZED_DATA,
3219 "must have single SavedFrame parent");
3220 return false;
3221 }
3222
3223 counts.popBack();
3224 }
3225 return true;
3226 }
3227
3228 // While we are reading children, we need to know whether this is the first
3229 // child seen or not, in order to avoid double-initializing in the error
3230 // case.
checkSingleParentFrame()3231 bool checkSingleParentFrame() {
3232 // We are checking at a point where we have read 0 or more parent frames,
3233 // in which case `obj` may not be on top of the `objs` stack anymore and
3234 // the count on top of the `counts` stack will correspond to the most
3235 // recently read frame, not `obj`. Use the remembered `counts` index from
3236 // when `obj` *was* on top of the stack.
3237 return ++counts[objCountsIndex] == 1;
3238 }
3239 };
3240
3241 // Perform the whole recursive reading procedure.
read(MutableHandleValue vp,size_t nbytes)3242 bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) {
3243 auto startTime = mozilla::TimeStamp::NowUnfuzzed();
3244
3245 if (!readHeader()) {
3246 return false;
3247 }
3248
3249 if (!readTransferMap()) {
3250 return false;
3251 }
3252
3253 ChildCounter childCounter(context());
3254
3255 // Start out by reading in the main object and pushing it onto the 'objs'
3256 // stack. The data related to this object and its descendants extends from
3257 // here to the SCTAG_END_OF_KEYS at the end of the stream.
3258 if (!startRead(vp) || !childCounter.postStartRead(objs)) {
3259 return false;
3260 }
3261
3262 // Stop when the stack shows that all objects have been read.
3263 while (objs.length() != 0) {
3264 // What happens depends on the top obj on the objs stack.
3265 RootedObject obj(context(), &objs.back().toObject());
3266 childCounter.noteObjIsOnTopOfStack();
3267
3268 uint32_t tag, data;
3269 if (!in.getPair(&tag, &data)) {
3270 return false;
3271 }
3272
3273 if (tag == SCTAG_END_OF_KEYS) {
3274 if (!childCounter.handleEndOfChildren(objs)) {
3275 return false;
3276 }
3277
3278 // Pop the current obj off the stack, since we are done with it and
3279 // its children.
3280 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
3281 objs.popBack();
3282 continue;
3283 }
3284
3285 // The input stream contains a sequence of "child" values, whose
3286 // interpretation depends on the type of obj. These values can be
3287 // anything, and startRead() will push onto 'objs' for any non-leaf
3288 // value (i.e., anything that may contain children).
3289 //
3290 // startRead() will allocate the (empty) object, but note that when
3291 // startRead() returns, 'key' is not yet initialized with any of its
3292 // properties. Those will be filled in by returning to the head of this
3293 // loop, processing the first child obj, and continuing until all
3294 // children have been fully created.
3295 //
3296 // Note that this means the ordering in the stream is a little funky for
3297 // things like Map. See the comment above traverseMap() for an example.
3298 RootedValue key(context());
3299 if (!startRead(&key) || !childCounter.postStartRead(objs)) {
3300 return false;
3301 }
3302
3303 if (key.isNull() && !(obj->is<MapObject>() || obj->is<SetObject>() ||
3304 obj->is<SavedFrame>())) {
3305 // Backwards compatibility: Null formerly indicated the end of
3306 // object properties.
3307 if (!childCounter.handleEndOfChildren(objs)) {
3308 return false;
3309 }
3310 objs.popBack();
3311 continue;
3312 }
3313
3314 // Set object: the values between obj header (from startRead()) and
3315 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
3316 if (obj->is<SetObject>()) {
3317 if (!SetObject::add(context(), obj, key)) {
3318 return false;
3319 }
3320 continue;
3321 }
3322
3323 // SavedFrame object: there is one following value, the parent SavedFrame,
3324 // which is either null or another SavedFrame object.
3325 if (obj->is<SavedFrame>()) {
3326 SavedFrame* parentFrame;
3327 if (key.isNull()) {
3328 parentFrame = nullptr;
3329 } else if (key.isObject() && key.toObject().is<SavedFrame>()) {
3330 parentFrame = &key.toObject().as<SavedFrame>();
3331 } else {
3332 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3333 JSMSG_SC_BAD_SERIALIZED_DATA,
3334 "invalid SavedFrame parent");
3335 return false;
3336 }
3337
3338 if (!childCounter.checkSingleParentFrame()) {
3339 // This is an error (more than one parent given), but it will be
3340 // reported when the SavedFrame is complete so it can be handled along
3341 // with the "no parent given" case.
3342 } else {
3343 obj->as<SavedFrame>().initParent(parentFrame);
3344 }
3345
3346 continue;
3347 }
3348
3349 // Everything else uses a series of key,value,key,value,... Value
3350 // objects.
3351 RootedValue val(context());
3352 if (!startRead(&val) || !childCounter.postStartRead(objs)) {
3353 return false;
3354 }
3355
3356 if (obj->is<MapObject>()) {
3357 // For a Map, store those <key,value> pairs in the contained map
3358 // data structure.
3359 if (!MapObject::set(context(), obj, key, val)) {
3360 return false;
3361 }
3362 } else {
3363 // For any other Object, interpret them as plain properties.
3364 RootedId id(context());
3365
3366 if (!key.isString() && !key.isInt32()) {
3367 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
3368 JSMSG_SC_BAD_SERIALIZED_DATA,
3369 "property key expected");
3370 return false;
3371 }
3372
3373 if (!PrimitiveValueToId<CanGC>(context(), key, &id)) {
3374 return false;
3375 }
3376
3377 if (!DefineDataProperty(context(), obj, id, val)) {
3378 return false;
3379 }
3380 }
3381 }
3382
3383 allObjs.clear();
3384
3385 JSRuntime* rt = context()->runtime();
3386 rt->addTelemetry(JS_TELEMETRY_DESERIALIZE_BYTES,
3387 static_cast<uint32_t>(std::min(nbytes, size_t(MAX_UINT32))));
3388 rt->addTelemetry(
3389 JS_TELEMETRY_DESERIALIZE_ITEMS,
3390 static_cast<uint32_t>(std::min(numItemsRead, size_t(MAX_UINT32))));
3391 mozilla::TimeDuration elapsed = mozilla::TimeStamp::Now() - startTime;
3392 rt->addTelemetry(JS_TELEMETRY_DESERIALIZE_US,
3393 static_cast<uint32_t>(elapsed.ToMicroseconds()));
3394
3395 return true;
3396 }
3397
3398 using namespace js;
3399
JS_ReadStructuredClone(JSContext * cx,const JSStructuredCloneData & buf,uint32_t version,JS::StructuredCloneScope scope,MutableHandleValue vp,const JS::CloneDataPolicy & cloneDataPolicy,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)3400 JS_PUBLIC_API bool JS_ReadStructuredClone(
3401 JSContext* cx, const JSStructuredCloneData& buf, uint32_t version,
3402 JS::StructuredCloneScope scope, MutableHandleValue vp,
3403 const JS::CloneDataPolicy& cloneDataPolicy,
3404 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3405 AssertHeapIsIdle();
3406 CHECK_THREAD(cx);
3407
3408 if (version > JS_STRUCTURED_CLONE_VERSION) {
3409 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3410 JSMSG_SC_BAD_CLONE_VERSION);
3411 return false;
3412 }
3413 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3414 return ReadStructuredClone(cx, buf, scope, vp, cloneDataPolicy, callbacks,
3415 closure);
3416 }
3417
JS_WriteStructuredClone(JSContext * cx,HandleValue value,JSStructuredCloneData * bufp,JS::StructuredCloneScope scope,const JS::CloneDataPolicy & cloneDataPolicy,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure,HandleValue transferable)3418 JS_PUBLIC_API bool JS_WriteStructuredClone(
3419 JSContext* cx, HandleValue value, JSStructuredCloneData* bufp,
3420 JS::StructuredCloneScope scope, const JS::CloneDataPolicy& cloneDataPolicy,
3421 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure,
3422 HandleValue transferable) {
3423 AssertHeapIsIdle();
3424 CHECK_THREAD(cx);
3425 cx->check(value);
3426
3427 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3428 return WriteStructuredClone(cx, value, bufp, scope, cloneDataPolicy,
3429 callbacks, closure, transferable);
3430 }
3431
JS_StructuredCloneHasTransferables(JSStructuredCloneData & data,bool * hasTransferable)3432 JS_PUBLIC_API bool JS_StructuredCloneHasTransferables(
3433 JSStructuredCloneData& data, bool* hasTransferable) {
3434 *hasTransferable = StructuredCloneHasTransferObjects(data);
3435 return true;
3436 }
3437
JS_StructuredClone(JSContext * cx,HandleValue value,MutableHandleValue vp,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)3438 JS_PUBLIC_API bool JS_StructuredClone(
3439 JSContext* cx, HandleValue value, MutableHandleValue vp,
3440 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3441 AssertHeapIsIdle();
3442 CHECK_THREAD(cx);
3443
3444 // Strings are associated with zones, not compartments,
3445 // so we copy the string by wrapping it.
3446 if (value.isString()) {
3447 RootedString strValue(cx, value.toString());
3448 if (!cx->compartment()->wrap(cx, &strValue)) {
3449 return false;
3450 }
3451 vp.setString(strValue);
3452 return true;
3453 }
3454
3455 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
3456
3457 JSAutoStructuredCloneBuffer buf(JS::StructuredCloneScope::SameProcess,
3458 callbacks, closure);
3459 {
3460 if (value.isObject()) {
3461 RootedObject obj(cx, &value.toObject());
3462 obj = CheckedUnwrapStatic(obj);
3463 if (!obj) {
3464 ReportAccessDenied(cx);
3465 return false;
3466 }
3467 AutoRealm ar(cx, obj);
3468 RootedValue unwrappedVal(cx, ObjectValue(*obj));
3469 if (!buf.write(cx, unwrappedVal, callbacks, closure)) {
3470 return false;
3471 }
3472 } else {
3473 if (!buf.write(cx, value, callbacks, closure)) {
3474 return false;
3475 }
3476 }
3477 }
3478
3479 return buf.read(cx, vp, JS::CloneDataPolicy(), callbacks, closure);
3480 }
3481
JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer && other)3482 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(
3483 JSAutoStructuredCloneBuffer&& other)
3484 : data_(other.scope()) {
3485 data_.ownTransferables_ = other.data_.ownTransferables_;
3486 other.steal(&data_, &version_, &data_.callbacks_, &data_.closure_);
3487 }
3488
operator =(JSAutoStructuredCloneBuffer && other)3489 JSAutoStructuredCloneBuffer& JSAutoStructuredCloneBuffer::operator=(
3490 JSAutoStructuredCloneBuffer&& other) {
3491 MOZ_ASSERT(&other != this);
3492 MOZ_ASSERT(scope() == other.scope());
3493 clear();
3494 data_.ownTransferables_ = other.data_.ownTransferables_;
3495 other.steal(&data_, &version_, &data_.callbacks_, &data_.closure_);
3496 return *this;
3497 }
3498
clear()3499 void JSAutoStructuredCloneBuffer::clear() {
3500 data_.discardTransferables();
3501 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
3502 data_.refsHeld_.releaseAll();
3503 data_.Clear();
3504 version_ = 0;
3505 }
3506
adopt(JSStructuredCloneData && data,uint32_t version,const JSStructuredCloneCallbacks * callbacks,void * closure)3507 void JSAutoStructuredCloneBuffer::adopt(
3508 JSStructuredCloneData&& data, uint32_t version,
3509 const JSStructuredCloneCallbacks* callbacks, void* closure) {
3510 clear();
3511 data_ = std::move(data);
3512 version_ = version;
3513 data_.setCallbacks(callbacks, closure,
3514 OwnTransferablePolicy::OwnsTransferablesIfAny);
3515 }
3516
steal(JSStructuredCloneData * data,uint32_t * versionp,const JSStructuredCloneCallbacks ** callbacks,void ** closure)3517 void JSAutoStructuredCloneBuffer::steal(
3518 JSStructuredCloneData* data, uint32_t* versionp,
3519 const JSStructuredCloneCallbacks** callbacks, void** closure) {
3520 if (versionp) {
3521 *versionp = version_;
3522 }
3523 if (callbacks) {
3524 *callbacks = data_.callbacks_;
3525 }
3526 if (closure) {
3527 *closure = data_.closure_;
3528 }
3529 *data = std::move(data_);
3530
3531 version_ = 0;
3532 data_.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables);
3533 }
3534
read(JSContext * cx,MutableHandleValue vp,const JS::CloneDataPolicy & cloneDataPolicy,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)3535 bool JSAutoStructuredCloneBuffer::read(
3536 JSContext* cx, MutableHandleValue vp,
3537 const JS::CloneDataPolicy& cloneDataPolicy,
3538 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3539 MOZ_ASSERT(cx);
3540 return !!JS_ReadStructuredClone(
3541 cx, data_, version_, data_.scope(), vp, cloneDataPolicy,
3542 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
3543 optionalCallbacks ? closure : data_.closure_);
3544 }
3545
write(JSContext * cx,HandleValue value,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)3546 bool JSAutoStructuredCloneBuffer::write(
3547 JSContext* cx, HandleValue value,
3548 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3549 HandleValue transferable = UndefinedHandleValue;
3550 return write(cx, value, transferable, JS::CloneDataPolicy(),
3551 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
3552 optionalCallbacks ? closure : data_.closure_);
3553 }
3554
write(JSContext * cx,HandleValue value,HandleValue transferable,const JS::CloneDataPolicy & cloneDataPolicy,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)3555 bool JSAutoStructuredCloneBuffer::write(
3556 JSContext* cx, HandleValue value, HandleValue transferable,
3557 const JS::CloneDataPolicy& cloneDataPolicy,
3558 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) {
3559 clear();
3560 bool ok = JS_WriteStructuredClone(
3561 cx, value, &data_, data_.scopeForInternalWriting(), cloneDataPolicy,
3562 optionalCallbacks ? optionalCallbacks : data_.callbacks_,
3563 optionalCallbacks ? closure : data_.closure_, transferable);
3564
3565 if (ok) {
3566 data_.ownTransferables_ = OwnTransferablePolicy::OwnsTransferablesIfAny;
3567 } else {
3568 version_ = JS_STRUCTURED_CLONE_VERSION;
3569 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
3570 }
3571 return ok;
3572 }
3573
JS_ReadUint32Pair(JSStructuredCloneReader * r,uint32_t * p1,uint32_t * p2)3574 JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1,
3575 uint32_t* p2) {
3576 return r->input().readPair((uint32_t*)p1, (uint32_t*)p2);
3577 }
3578
JS_ReadBytes(JSStructuredCloneReader * r,void * p,size_t len)3579 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p,
3580 size_t len) {
3581 return r->input().readBytes(p, len);
3582 }
3583
JS_ReadTypedArray(JSStructuredCloneReader * r,MutableHandleValue vp)3584 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r,
3585 MutableHandleValue vp) {
3586 uint32_t tag, data;
3587 if (!r->input().readPair(&tag, &data)) {
3588 return false;
3589 }
3590
3591 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
3592 return r->readTypedArray(TagToV1ArrayType(tag), data, vp, true);
3593 }
3594
3595 if (tag == SCTAG_TYPED_ARRAY_OBJECT_V2) {
3596 // V2 stores the length (nelems) in |data| and the arrayType separately.
3597 uint64_t arrayType;
3598 if (!r->input().read(&arrayType)) {
3599 return false;
3600 }
3601 uint64_t nelems = data;
3602 return r->readTypedArray(arrayType, nelems, vp);
3603 }
3604
3605 if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
3606 // The current version stores the array type in |data| and the length
3607 // (nelems) separately to support large TypedArrays.
3608 uint32_t arrayType = data;
3609 uint64_t nelems;
3610 if (!r->input().read(&nelems)) {
3611 return false;
3612 }
3613 return r->readTypedArray(arrayType, nelems, vp);
3614 }
3615
3616 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
3617 JSMSG_SC_BAD_SERIALIZED_DATA,
3618 "expected type array");
3619 return false;
3620 }
3621
JS_WriteUint32Pair(JSStructuredCloneWriter * w,uint32_t tag,uint32_t data)3622 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag,
3623 uint32_t data) {
3624 return w->output().writePair(tag, data);
3625 }
3626
JS_WriteBytes(JSStructuredCloneWriter * w,const void * p,size_t len)3627 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p,
3628 size_t len) {
3629 return w->output().writeBytes(p, len);
3630 }
3631
JS_WriteString(JSStructuredCloneWriter * w,HandleString str)3632 JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w,
3633 HandleString str) {
3634 return w->writeString(SCTAG_STRING, str);
3635 }
3636
JS_WriteTypedArray(JSStructuredCloneWriter * w,HandleValue v)3637 JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w,
3638 HandleValue v) {
3639 MOZ_ASSERT(v.isObject());
3640 w->context()->check(v);
3641 RootedObject obj(w->context(), &v.toObject());
3642
3643 // startWrite can write everything, thus we should check here
3644 // and report error if the user passes a wrong type.
3645 if (!obj->canUnwrapAs<TypedArrayObject>()) {
3646 ReportAccessDenied(w->context());
3647 return false;
3648 }
3649
3650 // We should use startWrite instead of writeTypedArray, because
3651 // typed array is an object, we should add it to the |memory|
3652 // (allObjs) list. Directly calling writeTypedArray won't add it.
3653 return w->startWrite(v);
3654 }
3655
JS_ObjectNotWritten(JSStructuredCloneWriter * w,HandleObject obj)3656 JS_PUBLIC_API bool JS_ObjectNotWritten(JSStructuredCloneWriter* w,
3657 HandleObject obj) {
3658 w->memory.remove(w->memory.lookup(obj));
3659
3660 return true;
3661 }
3662
JS_GetStructuredCloneScope(JSStructuredCloneWriter * w)3663 JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope(
3664 JSStructuredCloneWriter* w) {
3665 return w->output().scope();
3666 }
3667