1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
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 clone algorithm of
9  * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#safe-passing-of-structured-data
10  *
11  * The implementation differs slightly in that it uses an explicit stack, and
12  * the "memory" maps source objects to sequential integer indexes rather than
13  * directly pointing to destination objects. As a result, the order in which
14  * things are added to the memory must exactly match the order in which they
15  * are placed into 'allObjs', an analogous array of back-referenceable
16  * destination objects constructed while reading.
17  *
18  * For the most part, this is easy: simply add objects to the memory when first
19  * encountering them. But reading in a typed array requires an ArrayBuffer for
20  * construction, so objects cannot just be added to 'allObjs' in the order they
21  * are created. If they were, ArrayBuffers would come before typed arrays when
22  * in fact the typed array was added to 'memory' first.
23  *
24  * So during writing, we add objects to the memory when first encountering
25  * them. When reading a typed array, a placeholder is pushed onto allObjs until
26  * the ArrayBuffer has been read, then it is updated with the actual typed
27  * array object.
28  */
29 
30 #include "js/StructuredClone.h"
31 
32 #include "mozilla/Endian.h"
33 #include "mozilla/FloatingPoint.h"
34 
35 #include <algorithm>
36 
37 #include "jsapi.h"
38 #include "jscntxt.h"
39 #include "jsdate.h"
40 #include "jswrapper.h"
41 
42 #include "builtin/MapObject.h"
43 #include "js/Date.h"
44 #include "js/GCHashTable.h"
45 #include "vm/SavedFrame.h"
46 #include "vm/SharedArrayObject.h"
47 #include "vm/TypedArrayObject.h"
48 #include "vm/WrapperObject.h"
49 
50 #include "jscntxtinlines.h"
51 #include "jsobjinlines.h"
52 
53 using namespace js;
54 
55 using mozilla::BitwiseCast;
56 using mozilla::IsNaN;
57 using mozilla::LittleEndian;
58 using mozilla::NativeEndian;
59 using mozilla::NumbersAreIdentical;
60 using JS::CanonicalizeNaN;
61 
62 // When you make updates here, make sure you consider whether you need to bump the
63 // value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h.  You will
64 // likely need to increment the version if anything at all changes in the serialization
65 // format.
66 //
67 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should have
68 // a stable ID, it need not be at the end of the list and should not be used for
69 // sizing data structures.
70 
71 enum StructuredDataType : uint32_t {
72     /* Structured data types provided by the engine */
73     SCTAG_FLOAT_MAX = 0xFFF00000,
74     SCTAG_NULL = 0xFFFF0000,
75     SCTAG_UNDEFINED,
76     SCTAG_BOOLEAN,
77     SCTAG_INT32,
78     SCTAG_STRING,
79     SCTAG_DATE_OBJECT,
80     SCTAG_REGEXP_OBJECT,
81     SCTAG_ARRAY_OBJECT,
82     SCTAG_OBJECT_OBJECT,
83     SCTAG_ARRAY_BUFFER_OBJECT,
84     SCTAG_BOOLEAN_OBJECT,
85     SCTAG_STRING_OBJECT,
86     SCTAG_NUMBER_OBJECT,
87     SCTAG_BACK_REFERENCE_OBJECT,
88     SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
89     SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
90     SCTAG_TYPED_ARRAY_OBJECT,
91     SCTAG_MAP_OBJECT,
92     SCTAG_SET_OBJECT,
93     SCTAG_END_OF_KEYS,
94     SCTAG_SHARED_TYPED_ARRAY_OBJECT,
95     SCTAG_DATA_VIEW_OBJECT,
96     SCTAG_SAVED_FRAME_OBJECT,
97 
98     SCTAG_JSPRINCIPALS,
99     SCTAG_NULL_JSPRINCIPALS,
100     SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
101     SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
102 
103     SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
104     SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
105     SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
106     SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
107     SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
108     SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
109     SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
110     SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
111     SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
112     SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
113     SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::MaxTypedArrayViewType - 1,
114 
115     /*
116      * Define a separate range of numbers for Transferable-only tags, since
117      * they are not used for persistent clone buffers and therefore do not
118      * require bumping JS_STRUCTURED_CLONE_VERSION.
119      */
120     SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
121     SCTAG_TRANSFER_MAP_PENDING_ENTRY,
122     SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
123     SCTAG_TRANSFER_MAP_SHARED_BUFFER,
124     SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
125 
126     SCTAG_END_OF_BUILTIN_TYPES
127 };
128 
129 /*
130  * Format of transfer map:
131  *   <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
132  *   numTransferables (64 bits)
133  *   array of:
134  *     <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
135  *     pointer (64 bits)
136  *     extraData (64 bits), eg byte length for ArrayBuffers
137  */
138 
139 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
140 // contents have been read out yet or not.
141 enum TransferableMapHeader {
142     SCTAG_TM_UNREAD = 0,
143     SCTAG_TM_TRANSFERRED
144 };
145 
146 static inline uint64_t
PairToUInt64(uint32_t tag,uint32_t data)147 PairToUInt64(uint32_t tag, uint32_t data)
148 {
149     return uint64_t(data) | (uint64_t(tag) << 32);
150 }
151 
152 namespace js {
153 
154 struct SCOutput {
155   public:
156     explicit SCOutput(JSContext* cx);
157 
contextjs::SCOutput158     JSContext* context() const { return cx; }
159 
160     bool write(uint64_t u);
161     bool writePair(uint32_t tag, uint32_t data);
162     bool writeDouble(double d);
163     bool writeBytes(const void* p, size_t nbytes);
164     bool writeChars(const Latin1Char* p, size_t nchars);
165     bool writeChars(const char16_t* p, size_t nchars);
166     bool writePtr(const void*);
167 
168     template <class T>
169     bool writeArray(const T* p, size_t nbytes);
170 
171     bool extractBuffer(uint64_t** datap, size_t* sizep);
172 
countjs::SCOutput173     uint64_t count() const { return buf.length(); }
rawBufferjs::SCOutput174     uint64_t* rawBuffer() { return buf.begin(); }
175 
176   private:
177     JSContext* cx;
178     Vector<uint64_t> buf;
179 };
180 
181 class SCInput {
182   public:
183     SCInput(JSContext* cx, uint64_t* data, size_t nbytes);
184 
context() const185     JSContext* context() const { return cx; }
186 
187     static void getPtr(const uint64_t* buffer, void** ptr);
188     static void getPair(const uint64_t* buffer, uint32_t* tagp, uint32_t* datap);
189 
190     bool read(uint64_t* p);
191     bool readNativeEndian(uint64_t* p);
192     bool readPair(uint32_t* tagp, uint32_t* datap);
193     bool readDouble(double* p);
194     bool readBytes(void* p, size_t nbytes);
195     bool readChars(Latin1Char* p, size_t nchars);
196     bool readChars(char16_t* p, size_t nchars);
197     bool readPtr(void**);
198 
199     bool get(uint64_t* p);
200     bool getPair(uint32_t* tagp, uint32_t* datap);
201 
tell() const202     uint64_t* tell() const { return point; }
end() const203     uint64_t* end() const { return bufEnd; }
204 
205     template <class T>
206     bool readArray(T* p, size_t nelems);
207 
reportTruncated()208     bool reportTruncated() {
209          JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
210                               JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
211          return false;
212      }
213 
214   private:
staticAssertions()215     void staticAssertions() {
216         JS_STATIC_ASSERT(sizeof(char16_t) == 2);
217         JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
218     }
219 
220     JSContext* cx;
221     uint64_t* point;
222     uint64_t* bufEnd;
223 };
224 
225 } /* namespace js */
226 
227 struct JSStructuredCloneReader {
228   public:
JSStructuredCloneReaderJSStructuredCloneReader229     explicit JSStructuredCloneReader(SCInput& in, const JSStructuredCloneCallbacks* cb,
230                                      void* cbClosure)
231         : in(in), objs(in.context()), allObjs(in.context()),
232           callbacks(cb), closure(cbClosure) { }
233 
inputJSStructuredCloneReader234     SCInput& input() { return in; }
235     bool read(MutableHandleValue vp);
236 
237   private:
contextJSStructuredCloneReader238     JSContext* context() { return in.context(); }
239 
240     bool readTransferMap();
241 
242     template <typename CharT>
243     JSString* readStringImpl(uint32_t nchars);
244     JSString* readString(uint32_t data);
245 
246     bool checkDouble(double d);
247     bool readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp,
248                         bool v1Read = false);
249     bool readDataView(uint32_t byteLength, MutableHandleValue vp);
250     bool readArrayBuffer(uint32_t nbytes, MutableHandleValue vp);
251     bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp);
252     JSObject* readSavedFrame(uint32_t principalsTag);
253     bool startRead(MutableHandleValue vp);
254 
255     SCInput& in;
256 
257     // Stack of objects with properties remaining to be read.
258     AutoValueVector objs;
259 
260     // Stack of all objects read during this deserialization
261     AutoValueVector allObjs;
262 
263     // The user defined callbacks that will be used for cloning.
264     const JSStructuredCloneCallbacks* callbacks;
265 
266     // Any value passed to JS_ReadStructuredClone.
267     void* closure;
268 
269     friend bool JS_ReadTypedArray(JSStructuredCloneReader* r, MutableHandleValue vp);
270 };
271 
272 struct JSStructuredCloneWriter {
273   public:
JSStructuredCloneWriterJSStructuredCloneWriter274     explicit JSStructuredCloneWriter(JSContext* cx,
275                                      const JSStructuredCloneCallbacks* cb,
276                                      void* cbClosure,
277                                      Value tVal)
278         : out(cx), objs(out.context()),
279           counts(out.context()), entries(out.context()),
280           memory(out.context(), CloneMemory(out.context())), callbacks(cb),
281           closure(cbClosure), transferable(out.context(), tVal), transferableObjects(out.context())
282     {}
283 
284     ~JSStructuredCloneWriter();
285 
initJSStructuredCloneWriter286     bool init() { return memory.init() && parseTransferable() && writeTransferMap(); }
287 
288     bool write(HandleValue v);
289 
outputJSStructuredCloneWriter290     SCOutput& output() { return out; }
291 
extractBufferJSStructuredCloneWriter292     bool extractBuffer(uint64_t** datap, size_t* sizep) {
293         return out.extractBuffer(datap, sizep);
294     }
295 
296   private:
297     JSStructuredCloneWriter() = delete;
298     JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
299 
contextJSStructuredCloneWriter300     JSContext* context() { return out.context(); }
301 
302     bool writeTransferMap();
303 
304     bool writeString(uint32_t tag, JSString* str);
305     bool writeArrayBuffer(HandleObject obj);
306     bool writeTypedArray(HandleObject obj);
307     bool writeDataView(HandleObject obj);
308     bool writeSharedArrayBuffer(HandleObject obj);
309     bool startObject(HandleObject obj, bool* backref);
310     bool startWrite(HandleValue v);
311     bool traverseObject(HandleObject obj);
312     bool traverseMap(HandleObject obj);
313     bool traverseSet(HandleObject obj);
314     bool traverseSavedFrame(HandleObject obj);
315 
316     bool parseTransferable();
317     bool reportErrorTransferable(uint32_t errorId);
318     bool transferOwnership();
319 
320     inline void checkStack();
321 
322     SCOutput out;
323 
324     // Vector of objects with properties remaining to be written.
325     //
326     // NB: These can span multiple compartments, so the compartment must be
327     // entered before any manipulation is performed.
328     AutoValueVector objs;
329 
330     // counts[i] is the number of entries of objs[i] remaining to be written.
331     // counts.length() == objs.length() and sum(counts) == entries.length().
332     Vector<size_t> counts;
333 
334     // For JSObject: Property IDs as value
335     // For Map: Key followed by value
336     // For Set: Key
337     // For SavedFrame: parent SavedFrame
338     AutoValueVector entries;
339 
340     // The "memory" list described in the HTML5 internal structured cloning algorithm.
341     // memory is a superset of objs; items are never removed from Memory
342     // until a serialization operation is finished
343     using CloneMemory = GCHashMap<JSObject*, uint32_t, MovableCellHasher<JSObject*>>;
344     Rooted<CloneMemory> memory;
345 
346     // The user defined callbacks that will be used for cloning.
347     const JSStructuredCloneCallbacks* callbacks;
348 
349     // Any value passed to JS_WriteStructuredClone.
350     void* closure;
351 
352     // List of transferable objects
353     RootedValue transferable;
354     AutoObjectVector transferableObjects;
355 
356     friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
357     friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
358 };
359 
JS_FRIEND_API(uint64_t)360 JS_FRIEND_API(uint64_t)
361 js::GetSCOffset(JSStructuredCloneWriter* writer)
362 {
363     MOZ_ASSERT(writer);
364     return writer->output().count() * sizeof(uint64_t);
365 }
366 
367 JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
368 JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
369 JS_STATIC_ASSERT(Scalar::Int8 == 0);
370 
371 static void
ReportErrorTransferable(JSContext * cx,const JSStructuredCloneCallbacks * callbacks,uint32_t errorId)372 ReportErrorTransferable(JSContext* cx,
373                         const JSStructuredCloneCallbacks* callbacks,
374                         uint32_t errorId)
375 {
376     if (callbacks && callbacks->reportError)
377         callbacks->reportError(cx, errorId);
378     else if (errorId == JS_SCERR_DUP_TRANSFERABLE)
379         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SC_DUP_TRANSFERABLE);
380     else
381         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE);
382 }
383 
384 bool
WriteStructuredClone(JSContext * cx,HandleValue v,uint64_t ** bufp,size_t * nbytesp,const JSStructuredCloneCallbacks * cb,void * cbClosure,Value transferable)385 WriteStructuredClone(JSContext* cx, HandleValue v, uint64_t** bufp, size_t* nbytesp,
386                      const JSStructuredCloneCallbacks* cb, void* cbClosure,
387                      Value transferable)
388 {
389     JSStructuredCloneWriter w(cx, cb, cbClosure, transferable);
390     return w.init() && w.write(v) && w.extractBuffer(bufp, nbytesp);
391 }
392 
393 bool
ReadStructuredClone(JSContext * cx,uint64_t * data,size_t nbytes,MutableHandleValue vp,const JSStructuredCloneCallbacks * cb,void * cbClosure)394 ReadStructuredClone(JSContext* cx, uint64_t* data, size_t nbytes, MutableHandleValue vp,
395                     const JSStructuredCloneCallbacks* cb, void* cbClosure)
396 {
397     SCInput in(cx, data, nbytes);
398     JSStructuredCloneReader r(in, cb, cbClosure);
399     return r.read(vp);
400 }
401 
402 // If the given buffer contains Transferables, free them. Note that custom
403 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
404 // delete their transferables.
405 static void
DiscardTransferables(uint64_t * buffer,size_t nbytes,const JSStructuredCloneCallbacks * cb,void * cbClosure)406 DiscardTransferables(uint64_t* buffer, size_t nbytes,
407                      const JSStructuredCloneCallbacks* cb, void* cbClosure)
408 {
409     MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0);
410     uint64_t* end = buffer + nbytes / sizeof(uint64_t);
411     uint64_t* point = buffer;
412     if (point == end)
413         return; // Empty buffer
414 
415     uint32_t tag, data;
416     SCInput::getPair(point++, &tag, &data);
417     if (tag != SCTAG_TRANSFER_MAP_HEADER)
418         return;
419 
420     if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
421         return;
422 
423     // freeTransfer should not GC
424     JS::AutoSuppressGCAnalysis nogc;
425 
426     if (point == end)
427         return;
428 
429     uint64_t numTransferables = LittleEndian::readUint64(point++);
430     while (numTransferables--) {
431         if (point == end)
432             return;
433 
434         uint32_t ownership;
435         SCInput::getPair(point++, &tag, &ownership);
436         MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
437         if (point == end)
438             return;
439 
440         void* content;
441         SCInput::getPtr(point++, &content);
442         if (point == end)
443             return;
444 
445         uint64_t extraData = LittleEndian::readUint64(point++);
446 
447         if (ownership < JS::SCTAG_TMO_FIRST_OWNED)
448             continue;
449 
450         if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
451             js_free(content);
452         } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
453             JS_ReleaseMappedArrayBufferContents(content, extraData);
454         } else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) {
455             SharedArrayRawBuffer* raw = static_cast<SharedArrayRawBuffer*>(content);
456             if (raw)
457                 raw->dropReference();
458         } else if (cb && cb->freeTransfer) {
459             cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure);
460         } else {
461             MOZ_ASSERT(false, "unknown ownership");
462         }
463     }
464 }
465 
466 static bool
StructuredCloneHasTransferObjects(const uint64_t * data,size_t nbytes)467 StructuredCloneHasTransferObjects(const uint64_t* data, size_t nbytes)
468 {
469     if (!data)
470         return false;
471 
472     uint64_t u = LittleEndian::readUint64(data);
473     uint32_t tag = uint32_t(u >> 32);
474     return (tag == SCTAG_TRANSFER_MAP_HEADER);
475 }
476 
477 namespace js {
478 
SCInput(JSContext * cx,uint64_t * data,size_t nbytes)479 SCInput::SCInput(JSContext* cx, uint64_t* data, size_t nbytes)
480     : cx(cx), point(data), bufEnd(data + nbytes / 8)
481 {
482     // On 32-bit, we sometimes construct an SCInput from an SCOutput buffer,
483     // which is not guaranteed to be 8-byte aligned
484     MOZ_ASSERT((uintptr_t(data) & (sizeof(int) - 1)) == 0);
485     MOZ_ASSERT((nbytes & 7) == 0);
486 }
487 
488 bool
read(uint64_t * p)489 SCInput::read(uint64_t* p)
490 {
491     if (point == bufEnd) {
492         *p = 0;  /* initialize to shut GCC up */
493         return reportTruncated();
494     }
495     *p = LittleEndian::readUint64(point++);
496     return true;
497 }
498 
499 bool
readNativeEndian(uint64_t * p)500 SCInput::readNativeEndian(uint64_t* p)
501 {
502     if (point == bufEnd) {
503         *p = 0;  /* initialize to shut GCC up */
504         return reportTruncated();
505     }
506     *p = *(point++);
507     return true;
508 }
509 
510 bool
readPair(uint32_t * tagp,uint32_t * datap)511 SCInput::readPair(uint32_t* tagp, uint32_t* datap)
512 {
513     uint64_t u;
514     bool ok = read(&u);
515     if (ok) {
516         *tagp = uint32_t(u >> 32);
517         *datap = uint32_t(u);
518     }
519     return ok;
520 }
521 
522 bool
get(uint64_t * p)523 SCInput::get(uint64_t* p)
524 {
525     if (point == bufEnd)
526         return reportTruncated();
527     *p = LittleEndian::readUint64(point);
528     return true;
529 }
530 
531 bool
getPair(uint32_t * tagp,uint32_t * datap)532 SCInput::getPair(uint32_t* tagp, uint32_t* datap)
533 {
534     uint64_t u = 0;
535     if (!get(&u))
536         return false;
537 
538     *tagp = uint32_t(u >> 32);
539     *datap = uint32_t(u);
540     return true;
541 }
542 
543 void
getPair(const uint64_t * p,uint32_t * tagp,uint32_t * datap)544 SCInput::getPair(const uint64_t* p, uint32_t* tagp, uint32_t* datap)
545 {
546     uint64_t u = LittleEndian::readUint64(p);
547     *tagp = uint32_t(u >> 32);
548     *datap = uint32_t(u);
549 }
550 
551 bool
readDouble(double * p)552 SCInput::readDouble(double* p)
553 {
554     union {
555         uint64_t u;
556         double d;
557     } pun;
558     if (!read(&pun.u))
559         return false;
560     *p = CanonicalizeNaN(pun.d);
561     return true;
562 }
563 
564 template <typename T>
565 static void
copyAndSwapFromLittleEndian(T * dest,const void * src,size_t nelems)566 copyAndSwapFromLittleEndian(T* dest, const void* src, size_t nelems)
567 {
568     if (nelems > 0)
569         NativeEndian::copyAndSwapFromLittleEndian(dest, src, nelems);
570 }
571 
572 template <>
573 void
copyAndSwapFromLittleEndian(uint8_t * dest,const void * src,size_t nelems)574 copyAndSwapFromLittleEndian(uint8_t* dest, const void* src, size_t nelems)
575 {
576     memcpy(dest, src, nelems);
577 }
578 
579 template <class T>
580 bool
readArray(T * p,size_t nelems)581 SCInput::readArray(T* p, size_t nelems)
582 {
583     JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
584 
585     /*
586      * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
587      * larger than the remaining data.
588      */
589     size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
590     if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point))
591         return reportTruncated();
592 
593     copyAndSwapFromLittleEndian(p, point, nelems);
594     point += nwords;
595     return true;
596 }
597 
598 bool
readBytes(void * p,size_t nbytes)599 SCInput::readBytes(void* p, size_t nbytes)
600 {
601     return readArray((uint8_t*) p, nbytes);
602 }
603 
604 bool
readChars(Latin1Char * p,size_t nchars)605 SCInput::readChars(Latin1Char* p, size_t nchars)
606 {
607     static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
608     return readBytes(p, nchars);
609 }
610 
611 bool
readChars(char16_t * p,size_t nchars)612 SCInput::readChars(char16_t* p, size_t nchars)
613 {
614     MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
615     return readArray((uint16_t*) p, nchars);
616 }
617 
618 void
getPtr(const uint64_t * p,void ** ptr)619 SCInput::getPtr(const uint64_t* p, void** ptr)
620 {
621     // No endianness conversion is used for pointers, since they are not sent
622     // across address spaces anyway.
623     *ptr = reinterpret_cast<void*>(*p);
624 }
625 
626 bool
readPtr(void ** p)627 SCInput::readPtr(void** p)
628 {
629     uint64_t u;
630     if (!readNativeEndian(&u))
631         return false;
632     *p = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(u));
633     return true;
634 }
635 
SCOutput(JSContext * cx)636 SCOutput::SCOutput(JSContext* cx) : cx(cx), buf(cx) {}
637 
638 bool
write(uint64_t u)639 SCOutput::write(uint64_t u)
640 {
641     return buf.append(NativeEndian::swapToLittleEndian(u));
642 }
643 
644 bool
writePair(uint32_t tag,uint32_t data)645 SCOutput::writePair(uint32_t tag, uint32_t data)
646 {
647     /*
648      * As it happens, the tag word appears after the data word in the output.
649      * This is because exponents occupy the last 2 bytes of doubles on the
650      * little-endian platforms we care most about.
651      *
652      * For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
653      * PairToUInt64 produces the number 0xFFFF000200000001.
654      * That is written out as the bytes 01 00 00 00 02 00 FF FF.
655      */
656     return write(PairToUInt64(tag, data));
657 }
658 
659 static inline double
ReinterpretPairAsDouble(uint32_t tag,uint32_t data)660 ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
661 {
662     return BitwiseCast<double>(PairToUInt64(tag, data));
663 }
664 
665 bool
writeDouble(double d)666 SCOutput::writeDouble(double d)
667 {
668     return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
669 }
670 
671 template <typename T>
672 static void
copyAndSwapToLittleEndian(void * dest,const T * src,size_t nelems)673 copyAndSwapToLittleEndian(void* dest, const T* src, size_t nelems)
674 {
675     if (nelems > 0)
676         NativeEndian::copyAndSwapToLittleEndian(dest, src, nelems);
677 }
678 
679 template <>
680 void
copyAndSwapToLittleEndian(void * dest,const uint8_t * src,size_t nelems)681 copyAndSwapToLittleEndian(void* dest, const uint8_t* src, size_t nelems)
682 {
683     memcpy(dest, src, nelems);
684 }
685 
686 template <class T>
687 bool
writeArray(const T * p,size_t nelems)688 SCOutput::writeArray(const T* p, size_t nelems)
689 {
690     MOZ_ASSERT(8 % sizeof(T) == 0);
691     MOZ_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
692 
693     if (nelems == 0)
694         return true;
695 
696     if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
697         ReportAllocationOverflow(context());
698         return false;
699     }
700     size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
701     size_t start = buf.length();
702     if (!buf.growByUninitialized(nwords))
703         return false;
704 
705     buf.back() = 0;  /* zero-pad to an 8-byte boundary */
706 
707     T* q = (T*) &buf[start];
708     copyAndSwapToLittleEndian(q, p, nelems);
709     return true;
710 }
711 
712 bool
writeBytes(const void * p,size_t nbytes)713 SCOutput::writeBytes(const void* p, size_t nbytes)
714 {
715     return writeArray((const uint8_t*) p, nbytes);
716 }
717 
718 bool
writeChars(const char16_t * p,size_t nchars)719 SCOutput::writeChars(const char16_t* p, size_t nchars)
720 {
721     static_assert(sizeof(char16_t) == sizeof(uint16_t),
722                   "required so that treating char16_t[] memory as uint16_t[] "
723                   "memory is permissible");
724     return writeArray((const uint16_t*) p, nchars);
725 }
726 
727 bool
writeChars(const Latin1Char * p,size_t nchars)728 SCOutput::writeChars(const Latin1Char* p, size_t nchars)
729 {
730     static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
731     return writeBytes(p, nchars);
732 }
733 
734 bool
writePtr(const void * p)735 SCOutput::writePtr(const void* p)
736 {
737     return write(reinterpret_cast<uint64_t>(p));
738 }
739 
740 bool
extractBuffer(uint64_t ** datap,size_t * sizep)741 SCOutput::extractBuffer(uint64_t** datap, size_t* sizep)
742 {
743     *sizep = buf.length() * sizeof(uint64_t);
744     return (*datap = buf.extractRawBuffer()) != nullptr;
745 }
746 
747 } /* namespace js */
748 
749 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
750 
~JSStructuredCloneWriter()751 JSStructuredCloneWriter::~JSStructuredCloneWriter()
752 {
753     // Free any transferable data left lying around in the buffer
754     uint64_t* data;
755     size_t size;
756     {
757         AutoEnterOOMUnsafeRegion oomUnsafe;
758         if (!extractBuffer(&data, &size))
759             oomUnsafe.crash("Unable to extract clone buffer");
760         DiscardTransferables(data, size, callbacks, closure);
761         js_free(data);
762     }
763 }
764 
765 bool
parseTransferable()766 JSStructuredCloneWriter::parseTransferable()
767 {
768     MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data");
769 
770     if (transferable.isNull() || transferable.isUndefined())
771         return true;
772 
773     if (!transferable.isObject())
774         return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
775 
776     JSContext* cx = context();
777     RootedObject array(cx, &transferable.toObject());
778     bool isArray;
779     if (!JS_IsArrayObject(cx, array, &isArray))
780         return false;
781     if (!isArray)
782         return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
783 
784     uint32_t length;
785     if (!JS_GetArrayLength(cx, array, &length)) {
786         return false;
787     }
788 
789     RootedValue v(context());
790 
791     for (uint32_t i = 0; i < length; ++i) {
792         if (!JS_GetElement(cx, array, i, &v))
793             return false;
794 
795         if (!v.isObject())
796             return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
797         RootedObject tObj(context(), &v.toObject());
798 
799         // No duplicates allowed
800         if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) {
801             return reportErrorTransferable(JS_SCERR_DUP_TRANSFERABLE);
802         }
803 
804         if (!transferableObjects.append(tObj))
805             return false;
806     }
807 
808     return true;
809 }
810 
811 bool
reportErrorTransferable(uint32_t errorId)812 JSStructuredCloneWriter::reportErrorTransferable(uint32_t errorId)
813 {
814     ReportErrorTransferable(context(), callbacks, errorId);
815     return false;
816 }
817 
818 bool
writeString(uint32_t tag,JSString * str)819 JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str)
820 {
821     JSLinearString* linear = str->ensureLinear(context());
822     if (!linear)
823         return false;
824 
825     static_assert(JSString::MAX_LENGTH <= INT32_MAX, "String length must fit in 31 bits");
826 
827     uint32_t length = linear->length();
828     uint32_t lengthAndEncoding = length | (uint32_t(linear->hasLatin1Chars()) << 31);
829     if (!out.writePair(tag, lengthAndEncoding))
830         return false;
831 
832     JS::AutoCheckCannotGC nogc;
833     return linear->hasLatin1Chars()
834            ? out.writeChars(linear->latin1Chars(nogc), length)
835            : out.writeChars(linear->twoByteChars(nogc), length);
836 }
837 
838 inline void
checkStack()839 JSStructuredCloneWriter::checkStack()
840 {
841 #ifdef DEBUG
842     /* To avoid making serialization O(n^2), limit stack-checking at 10. */
843     const size_t MAX = 10;
844 
845     size_t limit = Min(counts.length(), MAX);
846     MOZ_ASSERT(objs.length() == counts.length());
847     size_t total = 0;
848     for (size_t i = 0; i < limit; i++) {
849         MOZ_ASSERT(total + counts[i] >= total);
850         total += counts[i];
851     }
852     if (counts.length() <= MAX)
853         MOZ_ASSERT(total == entries.length());
854     else
855         MOZ_ASSERT(total <= entries.length());
856 
857     size_t j = objs.length();
858     for (size_t i = 0; i < limit; i++)
859         MOZ_ASSERT(memory.has(&objs[--j].toObject()));
860 #endif
861 }
862 
863 /*
864  * Write out a typed array. Note that post-v1 structured clone buffers do not
865  * perform endianness conversion on stored data, so multibyte typed arrays
866  * cannot be deserialized into a different endianness machine. Endianness
867  * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
868  * Int16Array views of the same ArrayBuffer, should the data bytes be
869  * byte-swapped when writing or not? The Int8Array requires them to not be
870  * swapped; the Int16Array requires that they are.
871  */
872 bool
writeTypedArray(HandleObject obj)873 JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
874 {
875     Rooted<TypedArrayObject*> tarr(context(), &CheckedUnwrap(obj)->as<TypedArrayObject>());
876     JSAutoCompartment ac(context(), tarr);
877 
878     if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
879         return false;
880 
881     if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
882         return false;
883     uint64_t type = tarr->type();
884     if (!out.write(type))
885         return false;
886 
887     // Write out the ArrayBuffer tag and contents
888     RootedValue val(context(), TypedArrayObject::bufferValue(tarr));
889     if (!startWrite(val))
890         return false;
891 
892     return out.write(tarr->byteOffset());
893 }
894 
895 bool
writeDataView(HandleObject obj)896 JSStructuredCloneWriter::writeDataView(HandleObject obj)
897 {
898     Rooted<DataViewObject*> view(context(), &CheckedUnwrap(obj)->as<DataViewObject>());
899     JSAutoCompartment ac(context(), view);
900 
901     if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, view->byteLength()))
902         return false;
903 
904     // Write out the ArrayBuffer tag and contents
905     RootedValue val(context(), DataViewObject::bufferValue(view));
906     if (!startWrite(val))
907         return false;
908 
909     return out.write(view->byteOffset());
910 }
911 
912 bool
writeArrayBuffer(HandleObject obj)913 JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj)
914 {
915     ArrayBufferObject& buffer = CheckedUnwrap(obj)->as<ArrayBufferObject>();
916     JSAutoCompartment ac(context(), &buffer);
917 
918     return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
919            out.writeBytes(buffer.dataPointer(), buffer.byteLength());
920 }
921 
922 bool
writeSharedArrayBuffer(HandleObject obj)923 JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj)
924 {
925     JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_SHMEM_MUST_TRANSFER);
926     return false;
927 }
928 
929 bool
startObject(HandleObject obj,bool * backref)930 JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref)
931 {
932     /* Handle cycles in the object graph. */
933     CloneMemory::AddPtr p = memory.lookupForAdd(obj);
934     if ((*backref = p.found()))
935         return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
936     if (!memory.add(p, obj, memory.count()))
937         return false;
938 
939     if (memory.count() == UINT32_MAX) {
940         JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
941                              JSMSG_NEED_DIET, "object graph to serialize");
942         return false;
943     }
944 
945     return true;
946 }
947 
948 bool
traverseObject(HandleObject obj)949 JSStructuredCloneWriter::traverseObject(HandleObject obj)
950 {
951     /*
952      * Get enumerable property ids and put them in reverse order so that they
953      * will come off the stack in forward order.
954      */
955     AutoIdVector properties(context());
956     if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties))
957         return false;
958 
959     for (size_t i = properties.length(); i > 0; --i) {
960         MOZ_ASSERT(JSID_IS_STRING(properties[i - 1]) || JSID_IS_INT(properties[i - 1]));
961         RootedValue val(context(), IdToValue(properties[i - 1]));
962         if (!entries.append(val))
963             return false;
964     }
965 
966     /* Push obj and count to the stack. */
967     if (!objs.append(ObjectValue(*obj)) || !counts.append(properties.length()))
968         return false;
969 
970     checkStack();
971 
972     /* Write the header for obj. */
973     ESClassValue cls;
974     if (!GetBuiltinClass(context(), obj, &cls))
975         return false;
976     return out.writePair(cls == ESClass_Array ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
977 }
978 
979 bool
traverseMap(HandleObject obj)980 JSStructuredCloneWriter::traverseMap(HandleObject obj)
981 {
982     AutoValueVector newEntries(context());
983     {
984         // If there is no wrapper, the compartment munging is a no-op.
985         RootedObject unwrapped(context(), CheckedUnwrap(obj));
986         MOZ_ASSERT(unwrapped);
987         JSAutoCompartment ac(context(), unwrapped);
988         if (!MapObject::getKeysAndValuesInterleaved(context(), unwrapped, &newEntries))
989             return false;
990     }
991     if (!context()->compartment()->wrap(context(), newEntries))
992         return false;
993 
994     for (size_t i = newEntries.length(); i > 0; --i) {
995         if (!entries.append(newEntries[i - 1]))
996             return false;
997     }
998 
999     /* Push obj and count to the stack. */
1000     if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length()))
1001         return false;
1002 
1003     checkStack();
1004 
1005     /* Write the header for obj. */
1006     return out.writePair(SCTAG_MAP_OBJECT, 0);
1007 }
1008 
1009 bool
traverseSet(HandleObject obj)1010 JSStructuredCloneWriter::traverseSet(HandleObject obj)
1011 {
1012     AutoValueVector keys(context());
1013     {
1014         // If there is no wrapper, the compartment munging is a no-op.
1015         RootedObject unwrapped(context(), CheckedUnwrap(obj));
1016         MOZ_ASSERT(unwrapped);
1017         JSAutoCompartment ac(context(), unwrapped);
1018         if (!SetObject::keys(context(), unwrapped, &keys))
1019             return false;
1020     }
1021     if (!context()->compartment()->wrap(context(), keys))
1022         return false;
1023 
1024     for (size_t i = keys.length(); i > 0; --i) {
1025         if (!entries.append(keys[i - 1]))
1026             return false;
1027     }
1028 
1029     /* Push obj and count to the stack. */
1030     if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length()))
1031         return false;
1032 
1033     checkStack();
1034 
1035     /* Write the header for obj. */
1036     return out.writePair(SCTAG_SET_OBJECT, 0);
1037 }
1038 
1039 // Objects are written as a "preorder" traversal of the object graph: object
1040 // "headers" (the class tag and any data needed for initial construction) are
1041 // visited first, then the children are recursed through (where children are
1042 // properties, Set or Map entries, etc.). So for example
1043 //
1044 //     m = new Map();
1045 //     m.set(key1 = {}, value1 = {})
1046 //
1047 // would be stored as
1048 //
1049 //     <Map tag>
1050 //     <key1 class tag>
1051 //     <value1 class tag>
1052 //     <end-of-children marker for key1>
1053 //     <end-of-children marker for value1>
1054 //     <end-of-children marker for Map>
1055 //
1056 // Notice how the end-of-children marker for key1 is sandwiched between the
1057 // value1 beginning and end.
1058 bool
traverseSavedFrame(HandleObject obj)1059 JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj)
1060 {
1061     RootedObject unwrapped(context(), js::CheckedUnwrap(obj));
1062     MOZ_ASSERT(unwrapped && unwrapped->is<SavedFrame>());
1063 
1064     RootedSavedFrame savedFrame(context(), &unwrapped->as<SavedFrame>());
1065 
1066     RootedObject parent(context(), savedFrame->getParent());
1067     if (!context()->compartment()->wrap(context(), &parent))
1068         return false;
1069 
1070     if (!objs.append(ObjectValue(*obj)) ||
1071         !entries.append(parent ? ObjectValue(*parent) : NullValue()) ||
1072         !counts.append(1))
1073     {
1074         return false;
1075     }
1076 
1077     checkStack();
1078 
1079     // Write the SavedFrame tag and the SavedFrame's principals.
1080 
1081     if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsSystem) {
1082         if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1083                            SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM))
1084         {
1085             return false;
1086         };
1087     } else if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsNotSystem) {
1088         if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1089                            SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM))
1090         {
1091             return false;
1092         }
1093     } else {
1094         if (auto principals = savedFrame->getPrincipals()) {
1095             if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
1096                 !principals->write(context(), this))
1097             {
1098                 return false;
1099             }
1100         } else {
1101             if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS))
1102                 return false;
1103         }
1104     }
1105 
1106     // Write the SavedFrame's reserved slots, except for the parent, which is
1107     // queued on objs for further traversal.
1108 
1109     RootedValue val(context());
1110 
1111     val = StringValue(savedFrame->getSource());
1112     if (!startWrite(val))
1113         return false;
1114 
1115     val = NumberValue(savedFrame->getLine());
1116     if (!startWrite(val))
1117         return false;
1118 
1119     val = NumberValue(savedFrame->getColumn());
1120     if (!startWrite(val))
1121         return false;
1122 
1123     auto name = savedFrame->getFunctionDisplayName();
1124     val = name ? StringValue(name) : NullValue();
1125     if (!startWrite(val))
1126         return false;
1127 
1128     auto cause = savedFrame->getAsyncCause();
1129     val = cause ? StringValue(cause) : NullValue();
1130     if (!startWrite(val))
1131         return false;
1132 
1133     return true;
1134 }
1135 
1136 bool
startWrite(HandleValue v)1137 JSStructuredCloneWriter::startWrite(HandleValue v)
1138 {
1139     assertSameCompartment(context(), v);
1140 
1141     if (v.isString()) {
1142         return writeString(SCTAG_STRING, v.toString());
1143     } else if (v.isInt32()) {
1144         return out.writePair(SCTAG_INT32, v.toInt32());
1145     } else if (v.isDouble()) {
1146         return out.writeDouble(v.toDouble());
1147     } else if (v.isBoolean()) {
1148         return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
1149     } else if (v.isNull()) {
1150         return out.writePair(SCTAG_NULL, 0);
1151     } else if (v.isUndefined()) {
1152         return out.writePair(SCTAG_UNDEFINED, 0);
1153     } else if (v.isObject()) {
1154         RootedObject obj(context(), &v.toObject());
1155 
1156         bool backref;
1157         if (!startObject(obj, &backref))
1158             return false;
1159         if (backref)
1160             return true;
1161 
1162         ESClassValue cls;
1163         if (!GetBuiltinClass(context(), obj, &cls))
1164             return false;
1165 
1166         if (cls == ESClass_RegExp) {
1167             RegExpGuard re(context());
1168             if (!RegExpToShared(context(), obj, &re))
1169                 return false;
1170             return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
1171                    writeString(SCTAG_STRING, re->getSource());
1172         } else if (cls == ESClass_Date) {
1173             RootedValue unboxed(context());
1174             if (!Unbox(context(), obj, &unboxed))
1175                 return false;
1176             return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
1177         } else if (JS_IsTypedArrayObject(obj)) {
1178             return writeTypedArray(obj);
1179         } else if (JS_IsDataViewObject(obj)) {
1180             return writeDataView(obj);
1181         } else if (JS_IsArrayBufferObject(obj) && JS_ArrayBufferHasData(obj)) {
1182             return writeArrayBuffer(obj);
1183         } else if (JS_IsSharedArrayBufferObject(obj)) {
1184             return writeSharedArrayBuffer(obj);
1185         } else if (cls == ESClass_Object) {
1186             return traverseObject(obj);
1187         } else if (cls == ESClass_Array) {
1188             return traverseObject(obj);
1189         } else if (cls == ESClass_Boolean) {
1190             RootedValue unboxed(context());
1191             if (!Unbox(context(), obj, &unboxed))
1192                 return false;
1193             return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
1194         } else if (cls == ESClass_Number) {
1195             RootedValue unboxed(context());
1196             if (!Unbox(context(), obj, &unboxed))
1197                 return false;
1198             return out.writePair(SCTAG_NUMBER_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
1199         } else if (cls == ESClass_String) {
1200             RootedValue unboxed(context());
1201             if (!Unbox(context(), obj, &unboxed))
1202                 return false;
1203             return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
1204         } else if (cls == ESClass_Map) {
1205             return traverseMap(obj);
1206         } else if (cls == ESClass_Set) {
1207             return traverseSet(obj);
1208         } else if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
1209             return traverseSavedFrame(obj);
1210         }
1211 
1212         if (callbacks && callbacks->write)
1213             return callbacks->write(context(), this, obj, closure);
1214         /* else fall through */
1215     }
1216 
1217     JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
1218     return false;
1219 }
1220 
1221 bool
writeTransferMap()1222 JSStructuredCloneWriter::writeTransferMap()
1223 {
1224     if (transferableObjects.empty())
1225         return true;
1226 
1227     if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD))
1228         return false;
1229 
1230     if (!out.write(transferableObjects.length()))
1231         return false;
1232 
1233     for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1234         JSObject* obj = tr.front();
1235 
1236         if (!memory.put(obj, memory.count()))
1237             return false;
1238 
1239         // Emit a placeholder pointer. We will steal the data and neuter the
1240         // transferable later, in the case of ArrayBufferObject.
1241         if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED))
1242             return false;
1243         if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents or to SharedArrayRawBuffer.
1244             return false;
1245         if (!out.write(0)) // extraData
1246             return false;
1247     }
1248 
1249     return true;
1250 }
1251 
1252 bool
transferOwnership()1253 JSStructuredCloneWriter::transferOwnership()
1254 {
1255     if (transferableObjects.empty())
1256         return true;
1257 
1258     // Walk along the transferables and the transfer map at the same time,
1259     // grabbing out pointers from the transferables and stuffing them into the
1260     // transfer map.
1261     uint64_t* point = out.rawBuffer();
1262     MOZ_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER);
1263     point++;
1264     MOZ_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length());
1265     point++;
1266 
1267     for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1268         RootedObject obj(context(), tr.front());
1269 
1270         uint32_t tag;
1271         JS::TransferableOwnership ownership;
1272         void* content;
1273         uint64_t extraData;
1274 
1275 #if DEBUG
1276         SCInput::getPair(point, &tag, (uint32_t*) &ownership);
1277         MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1278         MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
1279 #endif
1280 
1281         ESClassValue cls;
1282         if (!GetBuiltinClass(context(), obj, &cls))
1283             return false;
1284 
1285         if (cls == ESClass_ArrayBuffer) {
1286             // The current setup of the array buffer inheritance hierarchy doesn't
1287             // lend itself well to generic manipulation via proxies.
1288             Rooted<ArrayBufferObject*> arrayBuffer(context(), &CheckedUnwrap(obj)->as<ArrayBufferObject>());
1289             JSAutoCompartment ac(context(), arrayBuffer);
1290             size_t nbytes = arrayBuffer->byteLength();
1291 
1292             // Structured cloning currently only has optimizations for mapped
1293             // and malloc'd buffers, not asm.js-ified buffers.
1294             bool hasStealableContents = arrayBuffer->hasStealableContents() &&
1295                                         (arrayBuffer->isMapped() || arrayBuffer->hasMallocedContents());
1296 
1297             ArrayBufferObject::BufferContents bufContents =
1298                 ArrayBufferObject::stealContents(context(), arrayBuffer, hasStealableContents);
1299             if (!bufContents)
1300                 return false; // Destructor will clean up the already-transferred data.
1301 
1302             content = bufContents.data();
1303             tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
1304             if (bufContents.kind() == ArrayBufferObject::MAPPED)
1305                 ownership = JS::SCTAG_TMO_MAPPED_DATA;
1306             else
1307                 ownership = JS::SCTAG_TMO_ALLOC_DATA;
1308             extraData = nbytes;
1309         } else if (cls == ESClass_SharedArrayBuffer) {
1310             Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
1311             SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
1312 
1313             // Avoids a race condition where the parent thread frees the buffer
1314             // before the child has accepted the transferable.
1315             rawbuf->addReference();
1316 
1317             tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER;
1318             ownership = JS::SCTAG_TMO_SHARED_BUFFER;
1319             content = rawbuf;
1320             extraData = 0;
1321         } else {
1322             if (!callbacks || !callbacks->writeTransfer)
1323                 return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
1324             if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData))
1325                 return false;
1326             MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1327         }
1328 
1329         LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership));
1330         LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content));
1331         LittleEndian::writeUint64(point++, extraData);
1332     }
1333 
1334     MOZ_ASSERT(point <= out.rawBuffer() + out.count());
1335     MOZ_ASSERT_IF(point < out.rawBuffer() + out.count(),
1336                   uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER);
1337 
1338     return true;
1339 }
1340 
1341 bool
write(HandleValue v)1342 JSStructuredCloneWriter::write(HandleValue v)
1343 {
1344     if (!startWrite(v))
1345         return false;
1346 
1347     while (!counts.empty()) {
1348         RootedObject obj(context(), &objs.back().toObject());
1349         AutoCompartment ac(context(), obj);
1350         if (counts.back()) {
1351             counts.back()--;
1352             RootedValue key(context(), entries.back());
1353             entries.popBack();
1354             checkStack();
1355 
1356             ESClassValue cls;
1357             if (!GetBuiltinClass(context(), obj, &cls))
1358                 return false;
1359 
1360             if (cls == ESClass_Map) {
1361                 counts.back()--;
1362                 RootedValue val(context(), entries.back());
1363                 entries.popBack();
1364                 checkStack();
1365 
1366                 if (!startWrite(key) || !startWrite(val))
1367                     return false;
1368             } else if (cls == ESClass_Set || SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
1369                 if (!startWrite(key))
1370                     return false;
1371             } else {
1372                 RootedId id(context());
1373                 if (!ValueToId<CanGC>(context(), key, &id))
1374                   return false;
1375                 MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
1376 
1377                 /*
1378                  * If obj still has an own property named id, write it out.
1379                  * The cost of re-checking could be avoided by using
1380                  * NativeIterators.
1381                  */
1382                 bool found;
1383                 if (!HasOwnProperty(context(), obj, id, &found))
1384                     return false;
1385 
1386                 if (found) {
1387                     RootedValue val(context());
1388                     if (!startWrite(key) ||
1389                         !GetProperty(context(), obj, obj, id, &val) ||
1390                         !startWrite(val))
1391                     {
1392                         return false;
1393                     }
1394                 }
1395             }
1396         } else {
1397             out.writePair(SCTAG_END_OF_KEYS, 0);
1398             objs.popBack();
1399             counts.popBack();
1400         }
1401     }
1402 
1403     memory.clear();
1404     return transferOwnership();
1405 }
1406 
1407 bool
checkDouble(double d)1408 JSStructuredCloneReader::checkDouble(double d)
1409 {
1410     jsval_layout l;
1411     l.asDouble = d;
1412     if (!JSVAL_IS_DOUBLE_IMPL(l)) {
1413         JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1414                              JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
1415         return false;
1416     }
1417     return true;
1418 }
1419 
1420 namespace {
1421 
1422 template <typename CharT>
1423 class Chars {
1424     JSContext* cx;
1425     CharT* p;
1426   public:
Chars(JSContext * cx)1427     explicit Chars(JSContext* cx) : cx(cx), p(nullptr) {}
~Chars()1428     ~Chars() { js_free(p); }
1429 
allocate(size_t len)1430     bool allocate(size_t len) {
1431         MOZ_ASSERT(!p);
1432         // We're going to null-terminate!
1433         p = cx->pod_malloc<CharT>(len + 1);
1434         if (p) {
1435             p[len] = CharT(0);
1436             return true;
1437         }
1438         return false;
1439     }
get()1440     CharT* get() { return p; }
forget()1441     void forget() { p = nullptr; }
1442 };
1443 
1444 } /* anonymous namespace */
1445 
1446 template <typename CharT>
1447 JSString*
readStringImpl(uint32_t nchars)1448 JSStructuredCloneReader::readStringImpl(uint32_t nchars)
1449 {
1450     if (nchars > JSString::MAX_LENGTH) {
1451         JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1452                              JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
1453         return nullptr;
1454     }
1455     Chars<CharT> chars(context());
1456     if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
1457         return nullptr;
1458     JSString* str = NewString<CanGC>(context(), chars.get(), nchars);
1459     if (str)
1460         chars.forget();
1461     return str;
1462 }
1463 
1464 JSString*
readString(uint32_t data)1465 JSStructuredCloneReader::readString(uint32_t data)
1466 {
1467     uint32_t nchars = data & JS_BITMASK(31);
1468     bool latin1 = data & (1 << 31);
1469     return latin1 ? readStringImpl<Latin1Char>(nchars) : readStringImpl<char16_t>(nchars);
1470 }
1471 
1472 static uint32_t
TagToV1ArrayType(uint32_t tag)1473 TagToV1ArrayType(uint32_t tag)
1474 {
1475     MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX);
1476     return tag - SCTAG_TYPED_ARRAY_V1_MIN;
1477 }
1478 
1479 bool
readTypedArray(uint32_t arrayType,uint32_t nelems,MutableHandleValue vp,bool v1Read)1480 JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp,
1481                                         bool v1Read)
1482 {
1483     if (arrayType > Scalar::Uint8Clamped) {
1484         JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1485                              JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type");
1486         return false;
1487     }
1488 
1489     // Push a placeholder onto the allObjs list to stand in for the typed array
1490     uint32_t placeholderIndex = allObjs.length();
1491     Value dummy = UndefinedValue();
1492     if (!allObjs.append(dummy))
1493         return false;
1494 
1495     // Read the ArrayBuffer object and its contents (but no properties)
1496     RootedValue v(context());
1497     uint32_t byteOffset;
1498     if (v1Read) {
1499         if (!readV1ArrayBuffer(arrayType, nelems, &v))
1500             return false;
1501         byteOffset = 0;
1502     } else {
1503         if (!startRead(&v))
1504             return false;
1505         uint64_t n;
1506         if (!in.read(&n))
1507             return false;
1508         byteOffset = n;
1509     }
1510     RootedObject buffer(context(), &v.toObject());
1511     RootedObject obj(context(), nullptr);
1512 
1513     switch (arrayType) {
1514       case Scalar::Int8:
1515         obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1516         break;
1517       case Scalar::Uint8:
1518         obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1519         break;
1520       case Scalar::Int16:
1521         obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1522         break;
1523       case Scalar::Uint16:
1524         obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1525         break;
1526       case Scalar::Int32:
1527         obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1528         break;
1529       case Scalar::Uint32:
1530         obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1531         break;
1532       case Scalar::Float32:
1533         obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1534         break;
1535       case Scalar::Float64:
1536         obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1537         break;
1538       case Scalar::Uint8Clamped:
1539         obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems);
1540         break;
1541       default:
1542         MOZ_CRASH("Can't happen: arrayType range checked above");
1543     }
1544 
1545     if (!obj)
1546         return false;
1547     vp.setObject(*obj);
1548 
1549     allObjs[placeholderIndex].set(vp);
1550 
1551     return true;
1552 }
1553 
1554 bool
readDataView(uint32_t byteLength,MutableHandleValue vp)1555 JSStructuredCloneReader::readDataView(uint32_t byteLength, MutableHandleValue vp)
1556 {
1557     // Push a placeholder onto the allObjs list to stand in for the DataView.
1558     uint32_t placeholderIndex = allObjs.length();
1559     Value dummy = UndefinedValue();
1560     if (!allObjs.append(dummy))
1561         return false;
1562 
1563     // Read the ArrayBuffer object and its contents (but no properties).
1564     RootedValue v(context());
1565     if (!startRead(&v))
1566         return false;
1567 
1568     // Read byteOffset.
1569     uint64_t n;
1570     if (!in.read(&n))
1571         return false;
1572     uint32_t byteOffset = n;
1573 
1574     RootedObject buffer(context(), &v.toObject());
1575     RootedObject obj(context(), JS_NewDataView(context(), buffer, byteOffset, byteLength));
1576     if (!obj)
1577         return false;
1578     vp.setObject(*obj);
1579 
1580     allObjs[placeholderIndex].set(vp);
1581 
1582     return true;
1583 }
1584 
1585 bool
readArrayBuffer(uint32_t nbytes,MutableHandleValue vp)1586 JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, MutableHandleValue vp)
1587 {
1588     JSObject* obj = ArrayBufferObject::create(context(), nbytes);
1589     if (!obj)
1590         return false;
1591     vp.setObject(*obj);
1592     ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
1593     MOZ_ASSERT(buffer.byteLength() == nbytes);
1594     return in.readArray(buffer.dataPointer(), nbytes);
1595 }
1596 
1597 /*
1598  * Read in the data for a structured clone version 1 ArrayBuffer, performing
1599  * endianness-conversion while reading.
1600  */
1601 bool
readV1ArrayBuffer(uint32_t arrayType,uint32_t nelems,MutableHandleValue vp)1602 JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
1603                                            MutableHandleValue vp)
1604 {
1605     MOZ_ASSERT(arrayType <= Scalar::Uint8Clamped);
1606 
1607     uint32_t nbytes = nelems << TypedArrayShift(static_cast<Scalar::Type>(arrayType));
1608     JSObject* obj = ArrayBufferObject::create(context(), nbytes);
1609     if (!obj)
1610         return false;
1611     vp.setObject(*obj);
1612     ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
1613     MOZ_ASSERT(buffer.byteLength() == nbytes);
1614 
1615     switch (arrayType) {
1616       case Scalar::Int8:
1617       case Scalar::Uint8:
1618       case Scalar::Uint8Clamped:
1619         return in.readArray((uint8_t*) buffer.dataPointer(), nelems);
1620       case Scalar::Int16:
1621       case Scalar::Uint16:
1622         return in.readArray((uint16_t*) buffer.dataPointer(), nelems);
1623       case Scalar::Int32:
1624       case Scalar::Uint32:
1625       case Scalar::Float32:
1626         return in.readArray((uint32_t*) buffer.dataPointer(), nelems);
1627       case Scalar::Float64:
1628         return in.readArray((uint64_t*) buffer.dataPointer(), nelems);
1629       default:
1630         MOZ_CRASH("Can't happen: arrayType range checked by caller");
1631     }
1632 }
1633 
1634 static bool
PrimitiveToObject(JSContext * cx,MutableHandleValue vp)1635 PrimitiveToObject(JSContext* cx, MutableHandleValue vp)
1636 {
1637     JSObject* obj = js::PrimitiveToObject(cx, vp);
1638     if (!obj)
1639         return false;
1640 
1641     vp.setObject(*obj);
1642     return true;
1643 }
1644 
1645 bool
startRead(MutableHandleValue vp)1646 JSStructuredCloneReader::startRead(MutableHandleValue vp)
1647 {
1648     uint32_t tag, data;
1649 
1650     if (!in.readPair(&tag, &data))
1651         return false;
1652 
1653     switch (tag) {
1654       case SCTAG_NULL:
1655         vp.setNull();
1656         break;
1657 
1658       case SCTAG_UNDEFINED:
1659         vp.setUndefined();
1660         break;
1661 
1662       case SCTAG_INT32:
1663         vp.setInt32(data);
1664         break;
1665 
1666       case SCTAG_BOOLEAN:
1667       case SCTAG_BOOLEAN_OBJECT:
1668         vp.setBoolean(!!data);
1669         if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp))
1670             return false;
1671         break;
1672 
1673       case SCTAG_STRING:
1674       case SCTAG_STRING_OBJECT: {
1675         JSString* str = readString(data);
1676         if (!str)
1677             return false;
1678         vp.setString(str);
1679         if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp))
1680             return false;
1681         break;
1682       }
1683 
1684       case SCTAG_NUMBER_OBJECT: {
1685         double d;
1686         if (!in.readDouble(&d) || !checkDouble(d))
1687             return false;
1688         vp.setDouble(d);
1689         if (!PrimitiveToObject(context(), vp))
1690             return false;
1691         break;
1692       }
1693 
1694       case SCTAG_DATE_OBJECT: {
1695         double d;
1696         if (!in.readDouble(&d) || !checkDouble(d))
1697             return false;
1698         JS::ClippedTime t = JS::TimeClip(d);
1699         if (!NumbersAreIdentical(d, t.toDouble())) {
1700             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1701                                  JSMSG_SC_BAD_SERIALIZED_DATA, "date");
1702             return false;
1703         }
1704         JSObject* obj = NewDateObjectMsec(context(), t);
1705         if (!obj)
1706             return false;
1707         vp.setObject(*obj);
1708         break;
1709       }
1710 
1711       case SCTAG_REGEXP_OBJECT: {
1712         RegExpFlag flags = RegExpFlag(data);
1713         uint32_t tag2, stringData;
1714         if (!in.readPair(&tag2, &stringData))
1715             return false;
1716         if (tag2 != SCTAG_STRING) {
1717             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1718                                  JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
1719             return false;
1720         }
1721         JSString* str = readString(stringData);
1722         if (!str)
1723             return false;
1724 
1725         RootedAtom atom(context(), AtomizeString(context(), str));
1726         if (!atom)
1727             return false;
1728 
1729         RegExpObject* reobj = RegExpObject::createNoStatics(context(), atom, flags, nullptr,
1730                                                             context()->tempLifoAlloc());
1731         if (!reobj)
1732             return false;
1733         vp.setObject(*reobj);
1734         break;
1735       }
1736 
1737       case SCTAG_ARRAY_OBJECT:
1738       case SCTAG_OBJECT_OBJECT: {
1739         JSObject* obj = (tag == SCTAG_ARRAY_OBJECT)
1740                         ? (JSObject*) NewDenseEmptyArray(context())
1741                         : (JSObject*) NewBuiltinClassInstance<PlainObject>(context());
1742         if (!obj || !objs.append(ObjectValue(*obj)))
1743             return false;
1744         vp.setObject(*obj);
1745         break;
1746       }
1747 
1748       case SCTAG_BACK_REFERENCE_OBJECT: {
1749         if (data >= allObjs.length()) {
1750             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1751                                  JSMSG_SC_BAD_SERIALIZED_DATA,
1752                                  "invalid back reference in input");
1753             return false;
1754         }
1755         vp.set(allObjs[data]);
1756         return true;
1757       }
1758 
1759       case SCTAG_TRANSFER_MAP_HEADER:
1760       case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
1761         // We should be past all the transfer map tags.
1762         JS_ReportErrorNumber(context(), GetErrorMessage, NULL,
1763                              JSMSG_SC_BAD_SERIALIZED_DATA,
1764                              "invalid input");
1765         return false;
1766 
1767       case SCTAG_ARRAY_BUFFER_OBJECT:
1768         if (!readArrayBuffer(data, vp))
1769             return false;
1770         break;
1771 
1772       case SCTAG_TYPED_ARRAY_OBJECT: {
1773         // readTypedArray adds the array to allObjs.
1774         uint64_t arrayType;
1775         if (!in.read(&arrayType))
1776             return false;
1777         return readTypedArray(arrayType, data, vp);
1778       }
1779 
1780       case SCTAG_DATA_VIEW_OBJECT: {
1781         // readDataView adds the array to allObjs.
1782         return readDataView(data, vp);
1783       }
1784 
1785       case SCTAG_MAP_OBJECT: {
1786         JSObject* obj = MapObject::create(context());
1787         if (!obj || !objs.append(ObjectValue(*obj)))
1788             return false;
1789         vp.setObject(*obj);
1790         break;
1791       }
1792 
1793       case SCTAG_SET_OBJECT: {
1794         JSObject* obj = SetObject::create(context());
1795         if (!obj || !objs.append(ObjectValue(*obj)))
1796             return false;
1797         vp.setObject(*obj);
1798         break;
1799       }
1800 
1801       case SCTAG_SAVED_FRAME_OBJECT: {
1802         auto obj = readSavedFrame(data);
1803         if (!obj || !objs.append(ObjectValue(*obj)))
1804             return false;
1805         vp.setObject(*obj);
1806         break;
1807       }
1808 
1809       default: {
1810         if (tag <= SCTAG_FLOAT_MAX) {
1811             double d = ReinterpretPairAsDouble(tag, data);
1812             if (!checkDouble(d))
1813                 return false;
1814             vp.setNumber(d);
1815             break;
1816         }
1817 
1818         if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
1819             // A v1-format typed array
1820             // readTypedArray adds the array to allObjs
1821             return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
1822         }
1823 
1824         if (!callbacks || !callbacks->read) {
1825             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1826                                  JSMSG_SC_BAD_SERIALIZED_DATA, "unsupported type");
1827             return false;
1828         }
1829         JSObject* obj = callbacks->read(context(), this, tag, data, closure);
1830         if (!obj)
1831             return false;
1832         vp.setObject(*obj);
1833       }
1834     }
1835 
1836     if (vp.isObject() && !allObjs.append(vp))
1837         return false;
1838 
1839     return true;
1840 }
1841 
1842 bool
readTransferMap()1843 JSStructuredCloneReader::readTransferMap()
1844 {
1845     JSContext* cx = context();
1846     uint64_t* headerPos = in.tell();
1847 
1848     uint32_t tag, data;
1849     if (!in.getPair(&tag, &data))
1850         return in.reportTruncated();
1851 
1852     if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
1853         return true;
1854 
1855     uint64_t numTransferables;
1856     MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
1857     if (!in.read(&numTransferables))
1858         return false;
1859 
1860     for (uint64_t i = 0; i < numTransferables; i++) {
1861         uint64_t* pos = in.tell();
1862 
1863         if (!in.readPair(&tag, &data))
1864             return false;
1865 
1866         MOZ_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1867         RootedObject obj(cx);
1868 
1869         void* content;
1870         if (!in.readPtr(&content))
1871             return false;
1872 
1873         uint64_t extraData;
1874         if (!in.read(&extraData))
1875             return false;
1876 
1877         if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
1878             size_t nbytes = extraData;
1879             MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
1880                        data == JS::SCTAG_TMO_MAPPED_DATA);
1881             if (data == JS::SCTAG_TMO_ALLOC_DATA)
1882                 obj = JS_NewArrayBufferWithContents(cx, nbytes, content);
1883             else if (data == JS::SCTAG_TMO_MAPPED_DATA)
1884                 obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content);
1885         } else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) {
1886             MOZ_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER);
1887             obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer*)content);
1888         } else {
1889             if (!callbacks || !callbacks->readTransfer) {
1890                 ReportErrorTransferable(cx, callbacks, JS_SCERR_TRANSFERABLE);
1891                 return false;
1892             }
1893             if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj))
1894                 return false;
1895             MOZ_ASSERT(obj);
1896             MOZ_ASSERT(!cx->isExceptionPending());
1897         }
1898 
1899         // On failure, the buffer will still own the data (since its ownership
1900         // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
1901         // DiscardTransferables.
1902         if (!obj)
1903             return false;
1904 
1905         // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
1906         // buffer.
1907         *pos = PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED);
1908         MOZ_ASSERT(headerPos < pos && pos < in.end());
1909 
1910         if (!allObjs.append(ObjectValue(*obj)))
1911             return false;
1912     }
1913 
1914     // Mark the whole transfer map as consumed.
1915     MOZ_ASSERT(headerPos <= in.tell());
1916 #ifdef DEBUG
1917     SCInput::getPair(headerPos, &tag, &data);
1918     MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
1919     MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
1920 #endif
1921     *headerPos = PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED);
1922 
1923     return true;
1924 }
1925 
1926 JSObject*
readSavedFrame(uint32_t principalsTag)1927 JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag)
1928 {
1929     RootedSavedFrame savedFrame(context(), SavedFrame::create(context()));
1930     if (!savedFrame)
1931         return nullptr;
1932 
1933     JSPrincipals* principals;
1934     if (principalsTag == SCTAG_JSPRINCIPALS) {
1935         if (!context()->runtime()->readPrincipals) {
1936             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1937                                  JSMSG_SC_UNSUPPORTED_TYPE);
1938             return nullptr;
1939         }
1940 
1941         if (!context()->runtime()->readPrincipals(context(), this, &principals))
1942             return nullptr;
1943     } else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
1944         principals = &ReconstructedSavedFramePrincipals::IsSystem;
1945         principals->refcount++;
1946     } else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
1947         principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
1948         principals->refcount++;
1949     } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
1950         principals = nullptr;
1951     } else {
1952         JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
1953                              JSMSG_SC_BAD_SERIALIZED_DATA, "bad SavedFrame principals");
1954         return nullptr;
1955     }
1956     savedFrame->initPrincipalsAlreadyHeld(principals);
1957 
1958     RootedValue source(context());
1959     if (!startRead(&source) || !source.isString())
1960         return nullptr;
1961     auto atomSource = AtomizeString(context(), source.toString());
1962     if (!atomSource)
1963         return nullptr;
1964     savedFrame->initSource(atomSource);
1965 
1966     RootedValue lineVal(context());
1967     uint32_t line;
1968     if (!startRead(&lineVal) || !lineVal.isNumber() || !ToUint32(context(), lineVal, &line))
1969         return nullptr;
1970     savedFrame->initLine(line);
1971 
1972     RootedValue columnVal(context());
1973     uint32_t column;
1974     if (!startRead(&columnVal) || !columnVal.isNumber() || !ToUint32(context(), columnVal, &column))
1975         return nullptr;
1976     savedFrame->initColumn(column);
1977 
1978     RootedValue name(context());
1979     if (!startRead(&name) || !(name.isString() || name.isNull()))
1980         return nullptr;
1981     JSAtom* atomName = nullptr;
1982     if (name.isString()) {
1983         atomName = AtomizeString(context(), name.toString());
1984         if (!atomName)
1985             return nullptr;
1986     }
1987     savedFrame->initFunctionDisplayName(atomName);
1988 
1989     RootedValue cause(context());
1990     if (!startRead(&cause) || !(cause.isString() || cause.isNull()))
1991         return nullptr;
1992     JSAtom* atomCause = nullptr;
1993     if (cause.isString()) {
1994         atomCause = AtomizeString(context(), cause.toString());
1995         if (!atomCause)
1996             return nullptr;
1997     }
1998     savedFrame->initAsyncCause(atomCause);
1999 
2000     return savedFrame;
2001 }
2002 
2003 // Perform the whole recursive reading procedure.
2004 bool
read(MutableHandleValue vp)2005 JSStructuredCloneReader::read(MutableHandleValue vp)
2006 {
2007     if (!readTransferMap())
2008         return false;
2009 
2010     // Start out by reading in the main object and pushing it onto the 'objs'
2011     // stack. The data related to this object and its descendants extends from
2012     // here to the SCTAG_END_OF_KEYS at the end of the stream.
2013     if (!startRead(vp))
2014         return false;
2015 
2016     // Stop when the stack shows that all objects have been read.
2017     while (objs.length() != 0) {
2018         // What happens depends on the top obj on the objs stack.
2019         RootedObject obj(context(), &objs.back().toObject());
2020 
2021         uint32_t tag, data;
2022         if (!in.getPair(&tag, &data))
2023             return false;
2024 
2025         if (tag == SCTAG_END_OF_KEYS) {
2026             // Pop the current obj off the stack, since we are done with it and
2027             // its children.
2028             MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2029             objs.popBack();
2030             continue;
2031         }
2032 
2033         // The input stream contains a sequence of "child" values, whose
2034         // interpretation depends on the type of obj. These values can be
2035         // anything, and startRead() will push onto 'objs' for any non-leaf
2036         // value (i.e., anything that may contain children).
2037         //
2038         // startRead() will allocate the (empty) object, but note that when
2039         // startRead() returns, 'key' is not yet initialized with any of its
2040         // properties. Those will be filled in by returning to the head of this
2041         // loop, processing the first child obj, and continuing until all
2042         // children have been fully created.
2043         //
2044         // Note that this means the ordering in the stream is a little funky
2045         // for things like Map. See the comment above startWrite() for an
2046         // example.
2047         RootedValue key(context());
2048         if (!startRead(&key))
2049             return false;
2050 
2051         if (key.isNull() &&
2052             !(obj->is<MapObject>() || obj->is<SetObject>() || obj->is<SavedFrame>()))
2053         {
2054             // Backwards compatibility: Null formerly indicated the end of
2055             // object properties.
2056             objs.popBack();
2057             continue;
2058         }
2059 
2060         // Set object: the values between obj header (from startRead()) and
2061         // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
2062         if (obj->is<SetObject>()) {
2063             if (!SetObject::add(context(), obj, key))
2064                 return false;
2065             continue;
2066         }
2067 
2068         // SavedFrame object: there is one following value, the parent
2069         // SavedFrame, which is either null or another SavedFrame object.
2070         if (obj->is<SavedFrame>()) {
2071             SavedFrame* parentFrame;
2072             if (key.isNull())
2073                 parentFrame = nullptr;
2074             else if (key.isObject() && key.toObject().is<SavedFrame>())
2075                 parentFrame = &key.toObject().as<SavedFrame>();
2076             else
2077                 return false;
2078 
2079             obj->as<SavedFrame>().initParent(parentFrame);
2080             continue;
2081         }
2082 
2083         // Everything else uses a series of key,value,key,value,... Value
2084         // objects.
2085         RootedValue val(context());
2086         if (!startRead(&val))
2087             return false;
2088 
2089         if (obj->is<MapObject>()) {
2090             // For a Map, store those <key,value> pairs in the contained map
2091             // data structure.
2092             if (!MapObject::set(context(), obj, key, val))
2093                 return false;
2094         } else {
2095             // For any other Object, interpret them as plain properties.
2096             RootedId id(context());
2097             if (!ValueToId<CanGC>(context(), key, &id))
2098                 return false;
2099 
2100             if (!DefineProperty(context(), obj, id, val))
2101                 return false;
2102          }
2103     }
2104 
2105     allObjs.clear();
2106 
2107     return true;
2108 }
2109 
2110 using namespace js;
2111 
2112 JS_PUBLIC_API(bool)
JS_ReadStructuredClone(JSContext * cx,uint64_t * buf,size_t nbytes,uint32_t version,MutableHandleValue vp,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)2113 JS_ReadStructuredClone(JSContext* cx, uint64_t* buf, size_t nbytes,
2114                        uint32_t version, MutableHandleValue vp,
2115                        const JSStructuredCloneCallbacks* optionalCallbacks,
2116                        void* closure)
2117 {
2118     AssertHeapIsIdle(cx);
2119     CHECK_REQUEST(cx);
2120 
2121     if (version > JS_STRUCTURED_CLONE_VERSION) {
2122         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_CLONE_VERSION);
2123         return false;
2124     }
2125     const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
2126     return ReadStructuredClone(cx, buf, nbytes, vp, callbacks, closure);
2127 }
2128 
2129 JS_PUBLIC_API(bool)
JS_WriteStructuredClone(JSContext * cx,HandleValue value,uint64_t ** bufp,size_t * nbytesp,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure,HandleValue transferable)2130 JS_WriteStructuredClone(JSContext* cx, HandleValue value, uint64_t** bufp, size_t* nbytesp,
2131                         const JSStructuredCloneCallbacks* optionalCallbacks,
2132                         void* closure, HandleValue transferable)
2133 {
2134     AssertHeapIsIdle(cx);
2135     CHECK_REQUEST(cx);
2136     assertSameCompartment(cx, value);
2137 
2138     const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
2139     return WriteStructuredClone(cx, value, bufp, nbytesp, callbacks, closure, transferable);
2140 }
2141 
2142 JS_PUBLIC_API(bool)
JS_ClearStructuredClone(uint64_t * data,size_t nbytes,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure,bool freeData)2143 JS_ClearStructuredClone(uint64_t* data, size_t nbytes,
2144                         const JSStructuredCloneCallbacks* optionalCallbacks,
2145                         void* closure, bool freeData)
2146 {
2147     DiscardTransferables(data, nbytes, optionalCallbacks, closure);
2148     if (freeData) {
2149       js_free(data);
2150     }
2151     return true;
2152 }
2153 
2154 JS_PUBLIC_API(bool)
JS_StructuredCloneHasTransferables(const uint64_t * data,size_t nbytes,bool * hasTransferable)2155 JS_StructuredCloneHasTransferables(const uint64_t* data, size_t nbytes,
2156                                    bool* hasTransferable)
2157 {
2158     *hasTransferable = StructuredCloneHasTransferObjects(data, nbytes);
2159     return true;
2160 }
2161 
2162 JS_PUBLIC_API(bool)
JS_StructuredClone(JSContext * cx,HandleValue value,MutableHandleValue vp,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)2163 JS_StructuredClone(JSContext* cx, HandleValue value, MutableHandleValue vp,
2164                    const JSStructuredCloneCallbacks* optionalCallbacks,
2165                    void* closure)
2166 {
2167     AssertHeapIsIdle(cx);
2168     CHECK_REQUEST(cx);
2169 
2170     // Strings are associated with zones, not compartments,
2171     // so we copy the string by wrapping it.
2172     if (value.isString()) {
2173       RootedString strValue(cx, value.toString());
2174       if (!cx->compartment()->wrap(cx, &strValue)) {
2175         return false;
2176       }
2177       vp.setString(strValue);
2178       return true;
2179     }
2180 
2181     const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
2182 
2183     JSAutoStructuredCloneBuffer buf;
2184     {
2185         // If we use Maybe<AutoCompartment> here, G++ can't tell that the
2186         // destructor is only called when Maybe::construct was called, and
2187         // we get warnings about using uninitialized variables.
2188         if (value.isObject()) {
2189             AutoCompartment ac(cx, &value.toObject());
2190             if (!buf.write(cx, value, callbacks, closure))
2191                 return false;
2192         } else {
2193             if (!buf.write(cx, value, callbacks, closure))
2194                 return false;
2195         }
2196     }
2197 
2198     return buf.read(cx, vp, callbacks, closure);
2199 }
2200 
JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer && other)2201 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other)
2202 {
2203     ownTransferables_ = other.ownTransferables_;
2204     other.steal(&data_, &nbytes_, &version_, &callbacks_, &closure_);
2205 }
2206 
2207 JSAutoStructuredCloneBuffer&
operator =(JSAutoStructuredCloneBuffer && other)2208 JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer&& other)
2209 {
2210     MOZ_ASSERT(&other != this);
2211     clear();
2212     ownTransferables_ = other.ownTransferables_;
2213     other.steal(&data_, &nbytes_, &version_, &callbacks_, &closure_);
2214     return *this;
2215 }
2216 
2217 void
clear(const JSStructuredCloneCallbacks * optionalCallbacks,void * optionalClosure)2218 JSAutoStructuredCloneBuffer::clear(const JSStructuredCloneCallbacks* optionalCallbacks,
2219                                    void* optionalClosure)
2220 {
2221     if (!data_)
2222         return;
2223 
2224     const JSStructuredCloneCallbacks* callbacks =
2225         optionalCallbacks ?  optionalCallbacks : callbacks_;
2226     void* closure = optionalClosure ?  optionalClosure : closure_;
2227 
2228     if (ownTransferables_ == OwnsTransferablesIfAny)
2229         DiscardTransferables(data_, nbytes_, callbacks, closure);
2230     ownTransferables_ = NoTransferables;
2231     js_free(data_);
2232     data_ = nullptr;
2233     nbytes_ = 0;
2234     version_ = 0;
2235 }
2236 
2237 bool
copy(const uint64_t * srcData,size_t nbytes,uint32_t version,const JSStructuredCloneCallbacks * callbacks,void * closure)2238 JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32_t version,
2239                                   const JSStructuredCloneCallbacks* callbacks,
2240                                   void* closure)
2241 {
2242     // transferable objects cannot be copied
2243     if (StructuredCloneHasTransferObjects(data_, nbytes_))
2244         return false;
2245 
2246     uint64_t* newData = static_cast<uint64_t*>(js_malloc(nbytes));
2247     if (!newData)
2248         return false;
2249 
2250     js_memcpy(newData, srcData, nbytes);
2251 
2252     clear();
2253     data_ = newData;
2254     nbytes_ = nbytes;
2255     version_ = version;
2256     callbacks_ = callbacks;
2257     closure_ = closure;
2258     ownTransferables_ = NoTransferables;
2259     return true;
2260 }
2261 
2262 void
adopt(uint64_t * data,size_t nbytes,uint32_t version,const JSStructuredCloneCallbacks * callbacks,void * closure)2263 JSAutoStructuredCloneBuffer::adopt(uint64_t* data, size_t nbytes, uint32_t version,
2264                                    const JSStructuredCloneCallbacks* callbacks,
2265                                    void* closure)
2266 {
2267     clear();
2268     data_ = data;
2269     nbytes_ = nbytes;
2270     version_ = version;
2271     callbacks_ = callbacks;
2272     closure_ = closure;
2273     ownTransferables_ = OwnsTransferablesIfAny;
2274 }
2275 
2276 void
steal(uint64_t ** datap,size_t * nbytesp,uint32_t * versionp,const JSStructuredCloneCallbacks ** callbacks,void ** closure)2277 JSAutoStructuredCloneBuffer::steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp,
2278                                    const JSStructuredCloneCallbacks** callbacks,
2279                                    void** closure)
2280 {
2281     *datap = data_;
2282     *nbytesp = nbytes_;
2283     if (versionp)
2284         *versionp = version_;
2285     if (callbacks)
2286         *callbacks = callbacks_;
2287     if (closure)
2288         *closure = closure_;
2289 
2290     data_ = nullptr;
2291     nbytes_ = 0;
2292     version_ = 0;
2293     callbacks_ = 0;
2294     closure_ = 0;
2295     ownTransferables_ = NoTransferables;
2296 }
2297 
2298 bool
read(JSContext * cx,MutableHandleValue vp,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)2299 JSAutoStructuredCloneBuffer::read(JSContext* cx, MutableHandleValue vp,
2300                                   const JSStructuredCloneCallbacks* optionalCallbacks,
2301                                   void* closure)
2302 {
2303     MOZ_ASSERT(cx);
2304     MOZ_ASSERT(data_);
2305     return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp,
2306                                     optionalCallbacks, closure);
2307 }
2308 
2309 bool
write(JSContext * cx,HandleValue value,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)2310 JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value,
2311                                    const JSStructuredCloneCallbacks* optionalCallbacks,
2312                                    void* closure)
2313 {
2314     HandleValue transferable = UndefinedHandleValue;
2315     return write(cx, value, transferable, optionalCallbacks, closure);
2316 }
2317 
2318 bool
write(JSContext * cx,HandleValue value,HandleValue transferable,const JSStructuredCloneCallbacks * optionalCallbacks,void * closure)2319 JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value,
2320                                    HandleValue transferable,
2321                                    const JSStructuredCloneCallbacks* optionalCallbacks,
2322                                    void* closure)
2323 {
2324     clear();
2325     bool ok = JS_WriteStructuredClone(cx, value, &data_, &nbytes_,
2326                                       optionalCallbacks, closure,
2327                                       transferable);
2328 
2329     if (ok) {
2330         ownTransferables_ = OwnsTransferablesIfAny;
2331     } else {
2332         data_ = nullptr;
2333         nbytes_ = 0;
2334         version_ = JS_STRUCTURED_CLONE_VERSION;
2335         ownTransferables_ = NoTransferables;
2336     }
2337     return ok;
2338 }
2339 
2340 JS_PUBLIC_API(bool)
JS_ReadUint32Pair(JSStructuredCloneReader * r,uint32_t * p1,uint32_t * p2)2341 JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2)
2342 {
2343     return r->input().readPair((uint32_t*) p1, (uint32_t*) p2);
2344 }
2345 
2346 JS_PUBLIC_API(bool)
JS_ReadBytes(JSStructuredCloneReader * r,void * p,size_t len)2347 JS_ReadBytes(JSStructuredCloneReader* r, void* p, size_t len)
2348 {
2349     return r->input().readBytes(p, len);
2350 }
2351 
2352 JS_PUBLIC_API(bool)
JS_ReadTypedArray(JSStructuredCloneReader * r,MutableHandleValue vp)2353 JS_ReadTypedArray(JSStructuredCloneReader* r, MutableHandleValue vp)
2354 {
2355     uint32_t tag, nelems;
2356     if (!r->input().readPair(&tag, &nelems))
2357         return false;
2358     if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
2359         return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp, true);
2360     } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
2361         uint64_t arrayType;
2362         if (!r->input().read(&arrayType))
2363             return false;
2364         return r->readTypedArray(arrayType, nelems, vp);
2365     } else {
2366         JS_ReportErrorNumber(r->context(), GetErrorMessage, nullptr,
2367                              JSMSG_SC_BAD_SERIALIZED_DATA, "expected type array");
2368         return false;
2369     }
2370 }
2371 
2372 JS_PUBLIC_API(bool)
JS_WriteUint32Pair(JSStructuredCloneWriter * w,uint32_t tag,uint32_t data)2373 JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag, uint32_t data)
2374 {
2375     return w->output().writePair(tag, data);
2376 }
2377 
2378 JS_PUBLIC_API(bool)
JS_WriteBytes(JSStructuredCloneWriter * w,const void * p,size_t len)2379 JS_WriteBytes(JSStructuredCloneWriter* w, const void* p, size_t len)
2380 {
2381     return w->output().writeBytes(p, len);
2382 }
2383 
2384 JS_PUBLIC_API(bool)
JS_WriteString(JSStructuredCloneWriter * w,HandleString str)2385 JS_WriteString(JSStructuredCloneWriter* w, HandleString str)
2386 {
2387     return w->writeString(SCTAG_STRING, str);
2388 }
2389 
2390 JS_PUBLIC_API(bool)
JS_WriteTypedArray(JSStructuredCloneWriter * w,HandleValue v)2391 JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v)
2392 {
2393     MOZ_ASSERT(v.isObject());
2394     assertSameCompartment(w->context(), v);
2395     RootedObject obj(w->context(), &v.toObject());
2396     return w->writeTypedArray(obj);
2397 }
2398