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