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