Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
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 */
30 #include "js/StructuredClone.h"
32 #include "mozilla/Endian.h"
33 #include "mozilla/FloatingPoint.h"
35 #include <algorithm>
37 #include "jsapi.h"
38 #include "jscntxt.h"
39 #include "jsdate.h"
40 #include "jswrapper.h"
42 #include "vm/SharedArrayObject.h"
43 #include "vm/TypedArrayObject.h"
44 #include "vm/WrapperObject.h"
46 #include "jscntxtinlines.h"
47 #include "jsobjinlines.h"
49 using namespace js;
51 using mozilla::IsNaN;
52 using mozilla::LittleEndian;
53 using mozilla::NativeEndian;
54 using JS::CanonicalizeNaN;
56 enum StructuredDataType {
57 /* Structured data types provided by the engine */
58 SCTAG_FLOAT_MAX = 0xFFF00000,
59 SCTAG_NULL = 0xFFFF0000,
60 SCTAG_UNDEFINED,
61 SCTAG_BOOLEAN,
62 SCTAG_INDEX,
63 SCTAG_STRING,
64 SCTAG_DATE_OBJECT,
65 SCTAG_REGEXP_OBJECT,
66 SCTAG_ARRAY_OBJECT,
67 SCTAG_OBJECT_OBJECT,
68 SCTAG_ARRAY_BUFFER_OBJECT,
69 SCTAG_BOOLEAN_OBJECT,
70 SCTAG_STRING_OBJECT,
71 SCTAG_NUMBER_OBJECT,
72 SCTAG_BACK_REFERENCE_OBJECT,
73 SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
74 SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
75 SCTAG_TYPED_ARRAY_OBJECT,
76 SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
77 SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT8,
78 SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT8,
79 SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT16,
80 SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT16,
81 SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT32,
82 SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT32,
83 SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_FLOAT32,
84 SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_FLOAT64,
85 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT8_CLAMPED,
86 SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_MAX - 1,
88 /*
89 * Define a separate range of numbers for Transferable-only tags, since
90 * they are not used for persistent clone buffers and therefore do not
91 * require bumping JS_STRUCTURED_CLONE_VERSION.
92 */
93 SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
94 SCTAG_TRANSFER_MAP_PENDING_ENTRY,
95 SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
96 SCTAG_TRANSFER_MAP_SHARED_BUFFER,
97 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
99 SCTAG_END_OF_BUILTIN_TYPES
100 };
102 /*
103 * Format of transfer map:
104 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
105 * numTransferables (64 bits)
106 * array of:
107 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
108 * pointer (64 bits)
109 * extraData (64 bits), eg byte length for ArrayBuffers
110 */
112 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
113 // contents have been read out yet or not.
114 enum TransferableMapHeader {
115 SCTAG_TM_UNREAD = 0,
116 SCTAG_TM_TRANSFERRED
117 };
119 static inline uint64_t
120 PairToUInt64(uint32_t tag, uint32_t data)
121 {
122 return uint64_t(data) | (uint64_t(tag) << 32);
123 }
125 namespace js {
127 struct SCOutput {
128 public:
129 explicit SCOutput(JSContext *cx);
131 JSContext *context() const { return cx; }
133 bool write(uint64_t u);
134 bool writePair(uint32_t tag, uint32_t data);
135 bool writeDouble(double d);
136 bool writeBytes(const void *p, size_t nbytes);
137 bool writeChars(const jschar *p, size_t nchars);
138 bool writePtr(const void *);
140 template <class T>
141 bool writeArray(const T *p, size_t nbytes);
143 bool extractBuffer(uint64_t **datap, size_t *sizep);
145 uint64_t count() const { return buf.length(); }
146 uint64_t *rawBuffer() { return buf.begin(); }
148 private:
149 JSContext *cx;
150 Vector<uint64_t> buf;
151 };
153 class SCInput {
154 public:
155 SCInput(JSContext *cx, uint64_t *data, size_t nbytes);
157 JSContext *context() const { return cx; }
159 static void getPtr(const uint64_t *buffer, void **ptr);
160 static void getPair(const uint64_t *buffer, uint32_t *tagp, uint32_t *datap);
162 bool read(uint64_t *p);
163 bool readNativeEndian(uint64_t *p);
164 bool readPair(uint32_t *tagp, uint32_t *datap);
165 bool readDouble(double *p);
166 bool readBytes(void *p, size_t nbytes);
167 bool readChars(jschar *p, size_t nchars);
168 bool readPtr(void **);
170 bool get(uint64_t *p);
171 bool getPair(uint32_t *tagp, uint32_t *datap);
173 uint64_t *tell() const { return point; }
174 uint64_t *end() const { return bufEnd; }
176 template <class T>
177 bool readArray(T *p, size_t nelems);
179 bool reportTruncated() {
180 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
181 JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
182 return false;
183 }
185 private:
186 void staticAssertions() {
187 JS_STATIC_ASSERT(sizeof(jschar) == 2);
188 JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
189 JS_STATIC_ASSERT(sizeof(double) == 8);
190 }
192 JSContext *cx;
193 uint64_t *point;
194 uint64_t *bufEnd;
195 };
197 } /* namespace js */
199 struct JSStructuredCloneReader {
200 public:
201 explicit JSStructuredCloneReader(SCInput &in, const JSStructuredCloneCallbacks *cb,
202 void *cbClosure)
203 : in(in), objs(in.context()), allObjs(in.context()),
204 callbacks(cb), closure(cbClosure) { }
206 SCInput &input() { return in; }
207 bool read(Value *vp);
209 private:
210 JSContext *context() { return in.context(); }
212 bool readTransferMap();
214 bool checkDouble(double d);
215 JSString *readString(uint32_t nchars);
216 bool readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, bool v1Read = false);
217 bool readArrayBuffer(uint32_t nbytes, Value *vp);
218 bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp);
219 bool readId(jsid *idp);
220 bool startRead(Value *vp);
222 SCInput ∈
224 // Stack of objects with properties remaining to be read.
225 AutoValueVector objs;
227 // Stack of all objects read during this deserialization
228 AutoValueVector allObjs;
230 // The user defined callbacks that will be used for cloning.
231 const JSStructuredCloneCallbacks *callbacks;
233 // Any value passed to JS_ReadStructuredClone.
234 void *closure;
236 friend bool JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp);
237 };
239 struct JSStructuredCloneWriter {
240 public:
241 explicit JSStructuredCloneWriter(JSContext *cx,
242 const JSStructuredCloneCallbacks *cb,
243 void *cbClosure,
244 jsval tVal)
245 : out(cx), objs(out.context()),
246 counts(out.context()), ids(out.context()),
247 memory(out.context()), callbacks(cb), closure(cbClosure),
248 transferable(out.context(), tVal), transferableObjects(out.context()) { }
250 ~JSStructuredCloneWriter();
252 bool init() { return memory.init() && parseTransferable() && writeTransferMap(); }
254 bool write(const Value &v);
256 SCOutput &output() { return out; }
258 bool extractBuffer(uint64_t **datap, size_t *sizep) {
259 return out.extractBuffer(datap, sizep);
260 }
262 private:
263 JSContext *context() { return out.context(); }
265 bool writeTransferMap();
267 bool writeString(uint32_t tag, JSString *str);
268 bool writeId(jsid id);
269 bool writeArrayBuffer(HandleObject obj);
270 bool writeTypedArray(HandleObject obj);
271 bool startObject(HandleObject obj, bool *backref);
272 bool startWrite(const Value &v);
273 bool traverseObject(HandleObject obj);
275 bool parseTransferable();
276 bool reportErrorTransferable();
277 bool transferOwnership();
279 inline void checkStack();
281 SCOutput out;
283 // Vector of objects with properties remaining to be written.
284 //
285 // NB: These can span multiple compartments, so the compartment must be
286 // entered before any manipulation is performed.
287 AutoValueVector objs;
289 // counts[i] is the number of properties of objs[i] remaining to be written.
290 // counts.length() == objs.length() and sum(counts) == ids.length().
291 Vector<size_t> counts;
293 // Ids of properties remaining to be written.
294 AutoIdVector ids;
296 // The "memory" list described in the HTML5 internal structured cloning algorithm.
297 // memory is a superset of objs; items are never removed from Memory
298 // until a serialization operation is finished
299 typedef AutoObjectUnsigned32HashMap CloneMemory;
300 CloneMemory memory;
302 // The user defined callbacks that will be used for cloning.
303 const JSStructuredCloneCallbacks *callbacks;
305 // Any value passed to JS_WriteStructuredClone.
306 void *closure;
308 // List of transferable objects
309 RootedValue transferable;
310 AutoObjectVector transferableObjects;
312 friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v);
313 };
315 JS_FRIEND_API(uint64_t)
316 js_GetSCOffset(JSStructuredCloneWriter* writer)
317 {
318 JS_ASSERT(writer);
319 return writer->output().count() * sizeof(uint64_t);
320 }
322 JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
323 JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
324 JS_STATIC_ASSERT(ScalarTypeDescr::TYPE_INT8 == 0);
326 static void
327 ReportErrorTransferable(JSContext *cx, const JSStructuredCloneCallbacks *callbacks)
328 {
329 if (callbacks && callbacks->reportError)
330 callbacks->reportError(cx, JS_SCERR_TRANSFERABLE);
331 else
332 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE);
333 }
335 bool
336 WriteStructuredClone(JSContext *cx, HandleValue v, uint64_t **bufp, size_t *nbytesp,
337 const JSStructuredCloneCallbacks *cb, void *cbClosure,
338 jsval transferable)
339 {
340 JSStructuredCloneWriter w(cx, cb, cbClosure, transferable);
341 return w.init() && w.write(v) && w.extractBuffer(bufp, nbytesp);
342 }
344 bool
345 ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, MutableHandleValue vp,
346 const JSStructuredCloneCallbacks *cb, void *cbClosure)
347 {
348 SCInput in(cx, data, nbytes);
349 JSStructuredCloneReader r(in, cb, cbClosure);
350 return r.read(vp.address());
351 }
353 // If the given buffer contains Transferables, free them. Note that custom
354 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
355 // delete their transferables.
356 static void
357 Discard(uint64_t *buffer, size_t nbytes, const JSStructuredCloneCallbacks *cb, void *cbClosure)
358 {
359 JS_ASSERT(nbytes % sizeof(uint64_t) == 0);
360 if (nbytes < sizeof(uint64_t))
361 return; // Empty buffer
363 uint64_t *point = buffer;
364 uint32_t tag, data;
365 SCInput::getPair(point++, &tag, &data);
366 if (tag != SCTAG_TRANSFER_MAP_HEADER)
367 return;
369 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
370 return;
372 // freeTransfer should not GC
373 JS::AutoAssertNoGC nogc;
375 uint64_t numTransferables = LittleEndian::readUint64(point++);
376 while (numTransferables--) {
377 uint32_t ownership;
378 SCInput::getPair(point++, &tag, &ownership);
379 JS_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
381 void *content;
382 SCInput::getPtr(point++, &content);
384 uint64_t extraData = LittleEndian::readUint64(point++);
386 if (ownership < JS::SCTAG_TMO_FIRST_OWNED)
387 continue;
389 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
390 js_free(content);
391 } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
392 JS_ReleaseMappedArrayBufferContents(content, extraData);
393 } else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) {
394 SharedArrayRawBuffer *raw = static_cast<SharedArrayRawBuffer*>(content);
395 if (raw)
396 raw->dropReference();
397 } else if (cb && cb->freeTransfer) {
398 cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure);
399 } else {
400 MOZ_ASSERT(false, "unknown ownership");
401 }
402 }
403 }
405 static void
406 ClearStructuredClone(uint64_t *data, size_t nbytes,
407 const JSStructuredCloneCallbacks *cb, void *cbClosure)
408 {
409 Discard(data, nbytes, cb, cbClosure);
410 js_free(data);
411 }
413 bool
414 StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes, bool *hasTransferable)
415 {
416 *hasTransferable = false;
418 if (data) {
419 uint64_t u = LittleEndian::readUint64(data);
420 uint32_t tag = uint32_t(u >> 32);
421 if (tag == SCTAG_TRANSFER_MAP_HEADER)
422 *hasTransferable = true;
423 }
425 return true;
426 }
428 namespace js {
430 SCInput::SCInput(JSContext *cx, uint64_t *data, size_t nbytes)
431 : cx(cx), point(data), bufEnd(data + nbytes / 8)
432 {
433 // On 32-bit, we sometimes construct an SCInput from an SCOutput buffer,
434 // which is not guaranteed to be 8-byte aligned
435 JS_ASSERT((uintptr_t(data) & (sizeof(int) - 1)) == 0);
436 JS_ASSERT((nbytes & 7) == 0);
437 }
439 bool
440 SCInput::read(uint64_t *p)
441 {
442 if (point == bufEnd) {
443 *p = 0; /* initialize to shut GCC up */
444 return reportTruncated();
445 }
446 *p = LittleEndian::readUint64(point++);
447 return true;
448 }
450 bool
451 SCInput::readNativeEndian(uint64_t *p)
452 {
453 if (point == bufEnd) {
454 *p = 0; /* initialize to shut GCC up */
455 return reportTruncated();
456 }
457 *p = *(point++);
458 return true;
459 }
461 bool
462 SCInput::readPair(uint32_t *tagp, uint32_t *datap)
463 {
464 uint64_t u;
465 bool ok = read(&u);
466 if (ok) {
467 *tagp = uint32_t(u >> 32);
468 *datap = uint32_t(u);
469 }
470 return ok;
471 }
473 bool
474 SCInput::get(uint64_t *p)
475 {
476 if (point == bufEnd)
477 return reportTruncated();
478 *p = LittleEndian::readUint64(point);
479 return true;
480 }
482 bool
483 SCInput::getPair(uint32_t *tagp, uint32_t *datap)
484 {
485 uint64_t u = 0;
486 if (!get(&u))
487 return false;
489 *tagp = uint32_t(u >> 32);
490 *datap = uint32_t(u);
491 return true;
492 }
494 void
495 SCInput::getPair(const uint64_t *p, uint32_t *tagp, uint32_t *datap)
496 {
497 uint64_t u = LittleEndian::readUint64(p);
498 *tagp = uint32_t(u >> 32);
499 *datap = uint32_t(u);
500 }
502 bool
503 SCInput::readDouble(double *p)
504 {
505 union {
506 uint64_t u;
507 double d;
508 } pun;
509 if (!read(&pun.u))
510 return false;
511 *p = CanonicalizeNaN(pun.d);
512 return true;
513 }
515 template <typename T>
516 static void
517 copyAndSwapFromLittleEndian(T *dest, const void *src, size_t nelems)
518 {
519 if (nelems > 0)
520 NativeEndian::copyAndSwapFromLittleEndian(dest, src, nelems);
521 }
523 template <>
524 void
525 copyAndSwapFromLittleEndian(uint8_t *dest, const void *src, size_t nelems)
526 {
527 memcpy(dest, src, nelems);
528 }
530 template <class T>
531 bool
532 SCInput::readArray(T *p, size_t nelems)
533 {
534 JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
536 /*
537 * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
538 * larger than the remaining data.
539 */
540 size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
541 if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point))
542 return reportTruncated();
544 copyAndSwapFromLittleEndian(p, point, nelems);
545 point += nwords;
546 return true;
547 }
549 bool
550 SCInput::readBytes(void *p, size_t nbytes)
551 {
552 return readArray((uint8_t *) p, nbytes);
553 }
555 bool
556 SCInput::readChars(jschar *p, size_t nchars)
557 {
558 JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
559 return readArray((uint16_t *) p, nchars);
560 }
562 void
563 SCInput::getPtr(const uint64_t *p, void **ptr)
564 {
565 // No endianness conversion is used for pointers, since they are not sent
566 // across address spaces anyway.
567 *ptr = reinterpret_cast<void*>(*p);
568 }
570 bool
571 SCInput::readPtr(void **p)
572 {
573 uint64_t u;
574 if (!readNativeEndian(&u))
575 return false;
576 *p = reinterpret_cast<void*>(u);
577 return true;
578 }
580 SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
582 bool
583 SCOutput::write(uint64_t u)
584 {
585 return buf.append(NativeEndian::swapToLittleEndian(u));
586 }
588 bool
589 SCOutput::writePair(uint32_t tag, uint32_t data)
590 {
591 /*
592 * As it happens, the tag word appears after the data word in the output.
593 * This is because exponents occupy the last 2 bytes of doubles on the
594 * little-endian platforms we care most about.
595 *
596 * For example, JSVAL_TRUE is written using writePair(SCTAG_BOOLEAN, 1).
597 * PairToUInt64 produces the number 0xFFFF000200000001.
598 * That is written out as the bytes 01 00 00 00 02 00 FF FF.
599 */
600 return write(PairToUInt64(tag, data));
601 }
603 static inline uint64_t
604 ReinterpretDoubleAsUInt64(double d)
605 {
606 union {
607 double d;
608 uint64_t u;
609 } pun;
610 pun.d = d;
611 return pun.u;
612 }
614 static inline double
615 ReinterpretUInt64AsDouble(uint64_t u)
616 {
617 union {
618 uint64_t u;
619 double d;
620 } pun;
621 pun.u = u;
622 return pun.d;
623 }
625 static inline double
626 ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
627 {
628 return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
629 }
631 bool
632 SCOutput::writeDouble(double d)
633 {
634 return write(ReinterpretDoubleAsUInt64(CanonicalizeNaN(d)));
635 }
637 template <typename T>
638 static void
639 copyAndSwapToLittleEndian(void *dest, const T *src, size_t nelems)
640 {
641 if (nelems > 0)
642 NativeEndian::copyAndSwapToLittleEndian(dest, src, nelems);
643 }
645 template <>
646 void
647 copyAndSwapToLittleEndian(void *dest, const uint8_t *src, size_t nelems)
648 {
649 memcpy(dest, src, nelems);
650 }
652 template <class T>
653 bool
654 SCOutput::writeArray(const T *p, size_t nelems)
655 {
656 JS_ASSERT(8 % sizeof(T) == 0);
657 JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
659 if (nelems == 0)
660 return true;
662 if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
663 js_ReportAllocationOverflow(context());
664 return false;
665 }
666 size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
667 size_t start = buf.length();
668 if (!buf.growByUninitialized(nwords))
669 return false;
671 buf.back() = 0; /* zero-pad to an 8-byte boundary */
673 T *q = (T *) &buf[start];
674 copyAndSwapToLittleEndian(q, p, nelems);
675 return true;
676 }
678 bool
679 SCOutput::writeBytes(const void *p, size_t nbytes)
680 {
681 return writeArray((const uint8_t *) p, nbytes);
682 }
684 bool
685 SCOutput::writeChars(const jschar *p, size_t nchars)
686 {
687 JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
688 return writeArray((const uint16_t *) p, nchars);
689 }
691 bool
692 SCOutput::writePtr(const void *p)
693 {
694 return write(reinterpret_cast<uint64_t>(p));
695 }
697 bool
698 SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
699 {
700 *sizep = buf.length() * sizeof(uint64_t);
701 return (*datap = buf.extractRawBuffer()) != nullptr;
702 }
704 } /* namespace js */
706 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
708 JSStructuredCloneWriter::~JSStructuredCloneWriter()
709 {
710 // Free any transferable data left lying around in the buffer
711 uint64_t *data;
712 size_t size;
713 MOZ_ALWAYS_TRUE(extractBuffer(&data, &size));
714 ClearStructuredClone(data, size, callbacks, closure);
715 }
717 bool
718 JSStructuredCloneWriter::parseTransferable()
719 {
720 MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data");
722 if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable))
723 return true;
725 if (!transferable.isObject())
726 return reportErrorTransferable();
728 JSContext *cx = context();
729 RootedObject array(cx, &transferable.toObject());
730 if (!JS_IsArrayObject(cx, array))
731 return reportErrorTransferable();
733 uint32_t length;
734 if (!JS_GetArrayLength(cx, array, &length)) {
735 return false;
736 }
738 RootedValue v(context());
740 for (uint32_t i = 0; i < length; ++i) {
741 if (!JS_GetElement(cx, array, i, &v))
742 return false;
744 if (!v.isObject())
745 return reportErrorTransferable();
747 RootedObject tObj(context(), CheckedUnwrap(&v.toObject()));
749 if (!tObj) {
750 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
751 return false;
752 }
754 // No duplicates allowed
755 if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) {
756 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_DUP_TRANSFERABLE);
757 return false;
758 }
760 if (!transferableObjects.append(tObj))
761 return false;
762 }
764 return true;
765 }
767 bool
768 JSStructuredCloneWriter::reportErrorTransferable()
769 {
770 ReportErrorTransferable(context(), callbacks);
771 return false;
772 }
774 bool
775 JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
776 {
777 size_t length = str->length();
778 const jschar *chars = str->getChars(context());
779 if (!chars)
780 return false;
781 return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length);
782 }
784 bool
785 JSStructuredCloneWriter::writeId(jsid id)
786 {
787 if (JSID_IS_INT(id))
788 return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
789 JS_ASSERT(JSID_IS_STRING(id));
790 return writeString(SCTAG_STRING, JSID_TO_STRING(id));
791 }
793 inline void
794 JSStructuredCloneWriter::checkStack()
795 {
796 #ifdef DEBUG
797 /* To avoid making serialization O(n^2), limit stack-checking at 10. */
798 const size_t MAX = 10;
800 size_t limit = Min(counts.length(), MAX);
801 JS_ASSERT(objs.length() == counts.length());
802 size_t total = 0;
803 for (size_t i = 0; i < limit; i++) {
804 JS_ASSERT(total + counts[i] >= total);
805 total += counts[i];
806 }
807 if (counts.length() <= MAX)
808 JS_ASSERT(total == ids.length());
809 else
810 JS_ASSERT(total <= ids.length());
812 size_t j = objs.length();
813 for (size_t i = 0; i < limit; i++)
814 JS_ASSERT(memory.has(&objs[--j].toObject()));
815 #endif
816 }
818 /*
819 * Write out a typed array. Note that post-v1 structured clone buffers do not
820 * perform endianness conversion on stored data, so multibyte typed arrays
821 * cannot be deserialized into a different endianness machine. Endianness
822 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
823 * Int16Array views of the same ArrayBuffer, should the data bytes be
824 * byte-swapped when writing or not? The Int8Array requires them to not be
825 * swapped; the Int16Array requires that they are.
826 */
827 bool
828 JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
829 {
830 Rooted<TypedArrayObject*> tarr(context(), &obj->as<TypedArrayObject>());
832 if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
833 return false;
835 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
836 return false;
837 uint64_t type = tarr->type();
838 if (!out.write(type))
839 return false;
841 // Write out the ArrayBuffer tag and contents
842 RootedValue val(context(), TypedArrayObject::bufferValue(tarr));
843 if (!startWrite(val))
844 return false;
846 return out.write(tarr->byteOffset());
847 }
849 bool
850 JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj)
851 {
852 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
854 return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
855 out.writeBytes(buffer.dataPointer(), buffer.byteLength());
856 }
858 bool
859 JSStructuredCloneWriter::startObject(HandleObject obj, bool *backref)
860 {
861 /* Handle cycles in the object graph. */
862 CloneMemory::AddPtr p = memory.lookupForAdd(obj);
863 if ((*backref = p))
864 return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
865 if (!memory.add(p, obj, memory.count()))
866 return false;
868 if (memory.count() == UINT32_MAX) {
869 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
870 JSMSG_NEED_DIET, "object graph to serialize");
871 return false;
872 }
874 return true;
875 }
877 bool
878 JSStructuredCloneWriter::traverseObject(HandleObject obj)
879 {
880 /*
881 * Get enumerable property ids and put them in reverse order so that they
882 * will come off the stack in forward order.
883 */
884 size_t initialLength = ids.length();
885 if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
886 return false;
887 jsid *begin = ids.begin() + initialLength, *end = ids.end();
888 size_t count = size_t(end - begin);
889 Reverse(begin, end);
891 /* Push obj and count to the stack. */
892 if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
893 return false;
894 checkStack();
896 /* Write the header for obj. */
897 return out.writePair(obj->is<ArrayObject>() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
898 }
900 static bool
901 PrimitiveToObject(JSContext *cx, Value *vp)
902 {
903 JSObject *obj = PrimitiveToObject(cx, *vp);
904 if (!obj)
905 return false;
907 vp->setObject(*obj);
908 return true;
909 }
911 bool
912 JSStructuredCloneWriter::startWrite(const Value &v)
913 {
914 assertSameCompartment(context(), v);
916 if (v.isString()) {
917 return writeString(SCTAG_STRING, v.toString());
918 } else if (v.isNumber()) {
919 return out.writeDouble(v.toNumber());
920 } else if (v.isBoolean()) {
921 return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
922 } else if (v.isNull()) {
923 return out.writePair(SCTAG_NULL, 0);
924 } else if (v.isUndefined()) {
925 return out.writePair(SCTAG_UNDEFINED, 0);
926 } else if (v.isObject()) {
927 RootedObject obj(context(), &v.toObject());
929 // The object might be a security wrapper. See if we can clone what's
930 // behind it. If we can, unwrap the object.
931 obj = CheckedUnwrap(obj);
932 if (!obj) {
933 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
934 return false;
935 }
937 AutoCompartment ac(context(), obj);
939 bool backref;
940 if (!startObject(obj, &backref))
941 return false;
942 if (backref)
943 return true;
945 if (obj->is<RegExpObject>()) {
946 RegExpObject &reobj = obj->as<RegExpObject>();
947 return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) &&
948 writeString(SCTAG_STRING, reobj.getSource());
949 } else if (obj->is<DateObject>()) {
950 double d = js_DateGetMsecSinceEpoch(obj);
951 return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
952 } else if (obj->is<TypedArrayObject>()) {
953 return writeTypedArray(obj);
954 } else if (obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().hasData()) {
955 return writeArrayBuffer(obj);
956 } else if (obj->is<JSObject>() || obj->is<ArrayObject>()) {
957 return traverseObject(obj);
958 } else if (obj->is<BooleanObject>()) {
959 return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->as<BooleanObject>().unbox());
960 } else if (obj->is<NumberObject>()) {
961 return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
962 out.writeDouble(obj->as<NumberObject>().unbox());
963 } else if (obj->is<StringObject>()) {
964 return writeString(SCTAG_STRING_OBJECT, obj->as<StringObject>().unbox());
965 }
967 if (callbacks && callbacks->write)
968 return callbacks->write(context(), this, obj, closure);
969 /* else fall through */
970 }
972 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
973 return false;
974 }
976 bool
977 JSStructuredCloneWriter::writeTransferMap()
978 {
979 if (transferableObjects.empty())
980 return true;
982 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD))
983 return false;
985 if (!out.write(transferableObjects.length()))
986 return false;
988 for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
989 JSObject *obj = tr.front();
991 if (!memory.put(obj, memory.count()))
992 return false;
994 // Emit a placeholder pointer. We will steal the data and neuter the
995 // transferable later, in the case of ArrayBufferObject.
996 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED))
997 return false;
998 if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents or to SharedArrayRawBuffer.
999 return false;
1000 if (!out.write(0)) // extraData
1001 return false;
1002 }
1004 return true;
1005 }
1007 bool
1008 JSStructuredCloneWriter::transferOwnership()
1009 {
1010 if (transferableObjects.empty())
1011 return true;
1013 // Walk along the transferables and the transfer map at the same time,
1014 // grabbing out pointers from the transferables and stuffing them into the
1015 // transfer map.
1016 uint64_t *point = out.rawBuffer();
1017 JS_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER);
1018 point++;
1019 JS_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length());
1020 point++;
1022 for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1023 RootedObject obj(context(), tr.front());
1025 uint32_t tag;
1026 JS::TransferableOwnership ownership;
1027 void *content;
1028 uint64_t extraData;
1030 #if DEBUG
1031 SCInput::getPair(point, &tag, (uint32_t*) &ownership);
1032 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1033 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
1034 #endif
1036 if (obj->is<ArrayBufferObject>()) {
1037 size_t nbytes = obj->as<ArrayBufferObject>().byteLength();
1038 content = JS_StealArrayBufferContents(context(), obj);
1039 if (!content)
1040 return false; // Destructor will clean up the already-transferred data
1041 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
1042 if (obj->as<ArrayBufferObject>().isMappedArrayBuffer())
1043 ownership = JS::SCTAG_TMO_MAPPED_DATA;
1044 else
1045 ownership = JS::SCTAG_TMO_ALLOC_DATA;
1046 extraData = nbytes;
1047 } else if (obj->is<SharedArrayBufferObject>()) {
1048 SharedArrayRawBuffer *rawbuf = obj->as<SharedArrayBufferObject>().rawBufferObject();
1050 // Avoids a race condition where the parent thread frees the buffer
1051 // before the child has accepted the transferable.
1052 rawbuf->addReference();
1054 tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER;
1055 ownership = JS::SCTAG_TMO_SHARED_BUFFER;
1056 content = rawbuf;
1057 extraData = 0;
1058 } else {
1059 if (!callbacks || !callbacks->writeTransfer)
1060 return reportErrorTransferable();
1061 if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData))
1062 return false;
1063 JS_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1064 }
1066 LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership));
1067 LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content));
1068 LittleEndian::writeUint64(point++, extraData);
1069 }
1071 JS_ASSERT(point <= out.rawBuffer() + out.count());
1072 JS_ASSERT_IF(point < out.rawBuffer() + out.count(),
1073 uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER);
1075 return true;
1076 }
1078 bool
1079 JSStructuredCloneWriter::write(const Value &v)
1080 {
1081 if (!startWrite(v))
1082 return false;
1084 while (!counts.empty()) {
1085 RootedObject obj(context(), &objs.back().toObject());
1086 AutoCompartment ac(context(), obj);
1087 if (counts.back()) {
1088 counts.back()--;
1089 RootedId id(context(), ids.back());
1090 ids.popBack();
1091 checkStack();
1092 if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
1093 /*
1094 * If obj still has an own property named id, write it out.
1095 * The cost of re-checking could be avoided by using
1096 * NativeIterators.
1097 */
1098 bool found;
1099 if (!HasOwnProperty(context(), obj, id, &found))
1100 return false;
1102 if (found) {
1103 RootedValue val(context());
1104 if (!writeId(id) ||
1105 !JSObject::getGeneric(context(), obj, obj, id, &val) ||
1106 !startWrite(val))
1107 return false;
1108 }
1109 }
1110 } else {
1111 out.writePair(SCTAG_NULL, 0);
1112 objs.popBack();
1113 counts.popBack();
1114 }
1115 }
1117 memory.clear();
1118 return transferOwnership();
1119 }
1121 bool
1122 JSStructuredCloneReader::checkDouble(double d)
1123 {
1124 jsval_layout l;
1125 l.asDouble = d;
1126 if (!JSVAL_IS_DOUBLE_IMPL(l)) {
1127 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1128 JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
1129 return false;
1130 }
1131 return true;
1132 }
1134 namespace {
1136 class Chars {
1137 JSContext *cx;
1138 jschar *p;
1139 public:
1140 Chars(JSContext *cx) : cx(cx), p(nullptr) {}
1141 ~Chars() { js_free(p); }
1143 bool allocate(size_t len) {
1144 JS_ASSERT(!p);
1145 // We're going to null-terminate!
1146 p = cx->pod_malloc<jschar>(len + 1);
1147 if (p) {
1148 p[len] = jschar(0);
1149 return true;
1150 }
1151 return false;
1152 }
1153 jschar *get() { return p; }
1154 void forget() { p = nullptr; }
1155 };
1157 } /* anonymous namespace */
1159 JSString *
1160 JSStructuredCloneReader::readString(uint32_t nchars)
1161 {
1162 if (nchars > JSString::MAX_LENGTH) {
1163 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1164 JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
1165 return nullptr;
1166 }
1167 Chars chars(context());
1168 if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
1169 return nullptr;
1170 JSString *str = js_NewString<CanGC>(context(), chars.get(), nchars);
1171 if (str)
1172 chars.forget();
1173 return str;
1174 }
1176 static uint32_t
1177 TagToV1ArrayType(uint32_t tag)
1178 {
1179 JS_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX);
1180 return tag - SCTAG_TYPED_ARRAY_V1_MIN;
1181 }
1183 bool
1184 JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp,
1185 bool v1Read)
1186 {
1187 if (arrayType > ScalarTypeDescr::TYPE_UINT8_CLAMPED) {
1188 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1189 JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type");
1190 return false;
1191 }
1193 // Push a placeholder onto the allObjs list to stand in for the typed array
1194 uint32_t placeholderIndex = allObjs.length();
1195 Value dummy = JSVAL_NULL;
1196 if (!allObjs.append(dummy))
1197 return false;
1199 // Read the ArrayBuffer object and its contents (but no properties)
1200 RootedValue v(context());
1201 uint32_t byteOffset;
1202 if (v1Read) {
1203 if (!readV1ArrayBuffer(arrayType, nelems, v.address()))
1204 return false;
1205 byteOffset = 0;
1206 } else {
1207 if (!startRead(v.address()))
1208 return false;
1209 uint64_t n;
1210 if (!in.read(&n))
1211 return false;
1212 byteOffset = n;
1213 }
1214 RootedObject buffer(context(), &v.toObject());
1215 RootedObject obj(context(), nullptr);
1217 switch (arrayType) {
1218 case ScalarTypeDescr::TYPE_INT8:
1219 obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1220 break;
1221 case ScalarTypeDescr::TYPE_UINT8:
1222 obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1223 break;
1224 case ScalarTypeDescr::TYPE_INT16:
1225 obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1226 break;
1227 case ScalarTypeDescr::TYPE_UINT16:
1228 obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1229 break;
1230 case ScalarTypeDescr::TYPE_INT32:
1231 obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1232 break;
1233 case ScalarTypeDescr::TYPE_UINT32:
1234 obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1235 break;
1236 case ScalarTypeDescr::TYPE_FLOAT32:
1237 obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1238 break;
1239 case ScalarTypeDescr::TYPE_FLOAT64:
1240 obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1241 break;
1242 case ScalarTypeDescr::TYPE_UINT8_CLAMPED:
1243 obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems);
1244 break;
1245 default:
1246 MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type");
1247 }
1249 if (!obj)
1250 return false;
1251 vp->setObject(*obj);
1253 allObjs[placeholderIndex] = *vp;
1255 return true;
1256 }
1258 bool
1259 JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
1260 {
1261 JSObject *obj = ArrayBufferObject::create(context(), nbytes);
1262 if (!obj)
1263 return false;
1264 vp->setObject(*obj);
1265 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
1266 JS_ASSERT(buffer.byteLength() == nbytes);
1267 return in.readArray(buffer.dataPointer(), nbytes);
1268 }
1270 static size_t
1271 bytesPerTypedArrayElement(uint32_t arrayType)
1272 {
1273 switch (arrayType) {
1274 case ScalarTypeDescr::TYPE_INT8:
1275 case ScalarTypeDescr::TYPE_UINT8:
1276 case ScalarTypeDescr::TYPE_UINT8_CLAMPED:
1277 return sizeof(uint8_t);
1278 case ScalarTypeDescr::TYPE_INT16:
1279 case ScalarTypeDescr::TYPE_UINT16:
1280 return sizeof(uint16_t);
1281 case ScalarTypeDescr::TYPE_INT32:
1282 case ScalarTypeDescr::TYPE_UINT32:
1283 case ScalarTypeDescr::TYPE_FLOAT32:
1284 return sizeof(uint32_t);
1285 case ScalarTypeDescr::TYPE_FLOAT64:
1286 return sizeof(uint64_t);
1287 default:
1288 MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type");
1289 }
1290 }
1292 /*
1293 * Read in the data for a structured clone version 1 ArrayBuffer, performing
1294 * endianness-conversion while reading.
1295 */
1296 bool
1297 JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp)
1298 {
1299 JS_ASSERT(arrayType <= ScalarTypeDescr::TYPE_UINT8_CLAMPED);
1301 uint32_t nbytes = nelems * bytesPerTypedArrayElement(arrayType);
1302 JSObject *obj = ArrayBufferObject::create(context(), nbytes);
1303 if (!obj)
1304 return false;
1305 vp->setObject(*obj);
1306 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
1307 JS_ASSERT(buffer.byteLength() == nbytes);
1309 switch (arrayType) {
1310 case ScalarTypeDescr::TYPE_INT8:
1311 case ScalarTypeDescr::TYPE_UINT8:
1312 case ScalarTypeDescr::TYPE_UINT8_CLAMPED:
1313 return in.readArray((uint8_t*) buffer.dataPointer(), nelems);
1314 case ScalarTypeDescr::TYPE_INT16:
1315 case ScalarTypeDescr::TYPE_UINT16:
1316 return in.readArray((uint16_t*) buffer.dataPointer(), nelems);
1317 case ScalarTypeDescr::TYPE_INT32:
1318 case ScalarTypeDescr::TYPE_UINT32:
1319 case ScalarTypeDescr::TYPE_FLOAT32:
1320 return in.readArray((uint32_t*) buffer.dataPointer(), nelems);
1321 case ScalarTypeDescr::TYPE_FLOAT64:
1322 return in.readArray((uint64_t*) buffer.dataPointer(), nelems);
1323 default:
1324 MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type");
1325 }
1326 }
1328 bool
1329 JSStructuredCloneReader::startRead(Value *vp)
1330 {
1331 uint32_t tag, data;
1333 if (!in.readPair(&tag, &data))
1334 return false;
1335 switch (tag) {
1336 case SCTAG_NULL:
1337 vp->setNull();
1338 break;
1340 case SCTAG_UNDEFINED:
1341 vp->setUndefined();
1342 break;
1344 case SCTAG_BOOLEAN:
1345 case SCTAG_BOOLEAN_OBJECT:
1346 vp->setBoolean(!!data);
1347 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp))
1348 return false;
1349 break;
1351 case SCTAG_STRING:
1352 case SCTAG_STRING_OBJECT: {
1353 JSString *str = readString(data);
1354 if (!str)
1355 return false;
1356 vp->setString(str);
1357 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp))
1358 return false;
1359 break;
1360 }
1362 case SCTAG_NUMBER_OBJECT: {
1363 double d;
1364 if (!in.readDouble(&d) || !checkDouble(d))
1365 return false;
1366 vp->setDouble(d);
1367 if (!PrimitiveToObject(context(), vp))
1368 return false;
1369 break;
1370 }
1372 case SCTAG_DATE_OBJECT: {
1373 double d;
1374 if (!in.readDouble(&d) || !checkDouble(d))
1375 return false;
1376 if (!IsNaN(d) && d != TimeClip(d)) {
1377 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1378 JSMSG_SC_BAD_SERIALIZED_DATA, "date");
1379 return false;
1380 }
1381 JSObject *obj = js_NewDateObjectMsec(context(), d);
1382 if (!obj)
1383 return false;
1384 vp->setObject(*obj);
1385 break;
1386 }
1388 case SCTAG_REGEXP_OBJECT: {
1389 RegExpFlag flags = RegExpFlag(data);
1390 uint32_t tag2, nchars;
1391 if (!in.readPair(&tag2, &nchars))
1392 return false;
1393 if (tag2 != SCTAG_STRING) {
1394 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1395 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
1396 return false;
1397 }
1398 JSString *str = readString(nchars);
1399 if (!str)
1400 return false;
1401 JSFlatString *flat = str->ensureFlat(context());
1402 if (!flat)
1403 return false;
1405 RegExpObject *reobj = RegExpObject::createNoStatics(context(), flat->chars(),
1406 flat->length(), flags, nullptr);
1407 if (!reobj)
1408 return false;
1409 vp->setObject(*reobj);
1410 break;
1411 }
1413 case SCTAG_ARRAY_OBJECT:
1414 case SCTAG_OBJECT_OBJECT: {
1415 JSObject *obj = (tag == SCTAG_ARRAY_OBJECT)
1416 ? NewDenseEmptyArray(context())
1417 : NewBuiltinClassInstance(context(), &JSObject::class_);
1418 if (!obj || !objs.append(ObjectValue(*obj)))
1419 return false;
1420 vp->setObject(*obj);
1421 break;
1422 }
1424 case SCTAG_BACK_REFERENCE_OBJECT: {
1425 if (data >= allObjs.length()) {
1426 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1427 JSMSG_SC_BAD_SERIALIZED_DATA,
1428 "invalid back reference in input");
1429 return false;
1430 }
1431 *vp = allObjs[data];
1432 return true;
1433 }
1435 case SCTAG_TRANSFER_MAP_HEADER:
1436 case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
1437 // We should be past all the transfer map tags.
1438 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
1439 JSMSG_SC_BAD_SERIALIZED_DATA,
1440 "invalid input");
1441 return false;
1443 case SCTAG_ARRAY_BUFFER_OBJECT:
1444 if (!readArrayBuffer(data, vp))
1445 return false;
1446 break;
1448 case SCTAG_TYPED_ARRAY_OBJECT:
1449 // readTypedArray adds the array to allObjs
1450 uint64_t arrayType;
1451 if (!in.read(&arrayType))
1452 return false;
1453 return readTypedArray(arrayType, data, vp);
1454 break;
1456 default: {
1457 if (tag <= SCTAG_FLOAT_MAX) {
1458 double d = ReinterpretPairAsDouble(tag, data);
1459 if (!checkDouble(d))
1460 return false;
1461 vp->setNumber(d);
1462 break;
1463 }
1465 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
1466 // A v1-format typed array
1467 // readTypedArray adds the array to allObjs
1468 return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
1469 }
1471 if (!callbacks || !callbacks->read) {
1472 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1473 JSMSG_SC_BAD_SERIALIZED_DATA, "unsupported type");
1474 return false;
1475 }
1476 JSObject *obj = callbacks->read(context(), this, tag, data, closure);
1477 if (!obj)
1478 return false;
1479 vp->setObject(*obj);
1480 }
1481 }
1483 if (vp->isObject() && !allObjs.append(*vp))
1484 return false;
1486 return true;
1487 }
1489 bool
1490 JSStructuredCloneReader::readId(jsid *idp)
1491 {
1492 uint32_t tag, data;
1493 if (!in.readPair(&tag, &data))
1494 return false;
1496 if (tag == SCTAG_INDEX) {
1497 *idp = INT_TO_JSID(int32_t(data));
1498 return true;
1499 }
1500 if (tag == SCTAG_STRING) {
1501 JSString *str = readString(data);
1502 if (!str)
1503 return false;
1504 JSAtom *atom = AtomizeString(context(), str);
1505 if (!atom)
1506 return false;
1507 *idp = NON_INTEGER_ATOM_TO_JSID(atom);
1508 return true;
1509 }
1510 if (tag == SCTAG_NULL) {
1511 *idp = JSID_VOID;
1512 return true;
1513 }
1514 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
1515 JSMSG_SC_BAD_SERIALIZED_DATA, "id");
1516 return false;
1517 }
1519 bool
1520 JSStructuredCloneReader::readTransferMap()
1521 {
1522 JSContext *cx = context();
1523 uint64_t *headerPos = in.tell();
1525 uint32_t tag, data;
1526 if (!in.getPair(&tag, &data))
1527 return in.reportTruncated();
1529 if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
1530 return true;
1532 uint64_t numTransferables;
1533 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
1534 if (!in.read(&numTransferables))
1535 return false;
1537 for (uint64_t i = 0; i < numTransferables; i++) {
1538 uint64_t *pos = in.tell();
1540 if (!in.readPair(&tag, &data))
1541 return false;
1543 JS_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1544 RootedObject obj(cx);
1546 void *content;
1547 if (!in.readPtr(&content))
1548 return false;
1550 uint64_t extraData;
1551 if (!in.read(&extraData))
1552 return false;
1554 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
1555 size_t nbytes = extraData;
1556 JS_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
1557 data == JS::SCTAG_TMO_MAPPED_DATA);
1558 if (data == JS::SCTAG_TMO_ALLOC_DATA)
1559 obj = JS_NewArrayBufferWithContents(cx, nbytes, content);
1560 else if (data == JS::SCTAG_TMO_MAPPED_DATA)
1561 obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content);
1562 } else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) {
1563 JS_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER);
1564 obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer *)content);
1565 } else {
1566 if (!callbacks || !callbacks->readTransfer) {
1567 ReportErrorTransferable(cx, callbacks);
1568 return false;
1569 }
1570 if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj))
1571 return false;
1572 MOZ_ASSERT(obj);
1573 MOZ_ASSERT(!cx->isExceptionPending());
1574 }
1576 // On failure, the buffer will still own the data (since its ownership will not get set to SCTAG_TMO_UNOWNED),
1577 // so the data will be freed by ClearStructuredClone
1578 if (!obj)
1579 return false;
1581 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
1582 // buffer.
1583 *pos = PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED);
1584 MOZ_ASSERT(headerPos < pos && pos < in.end());
1586 if (!allObjs.append(ObjectValue(*obj)))
1587 return false;
1588 }
1590 // Mark the whole transfer map as consumed.
1591 MOZ_ASSERT(headerPos <= in.tell());
1592 #ifdef DEBUG
1593 SCInput::getPair(headerPos, &tag, &data);
1594 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
1595 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
1596 #endif
1597 *headerPos = PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED);
1599 return true;
1600 }
1602 bool
1603 JSStructuredCloneReader::read(Value *vp)
1604 {
1605 if (!readTransferMap())
1606 return false;
1608 if (!startRead(vp))
1609 return false;
1611 while (objs.length() != 0) {
1612 RootedObject obj(context(), &objs.back().toObject());
1614 RootedId id(context());
1615 if (!readId(id.address()))
1616 return false;
1618 if (JSID_IS_VOID(id)) {
1619 objs.popBack();
1620 } else {
1621 RootedValue v(context());
1622 if (!startRead(v.address()) || !JSObject::defineGeneric(context(), obj, id, v))
1623 return false;
1624 }
1625 }
1627 allObjs.clear();
1629 return true;
1630 }
1632 using namespace js;
1634 JS_PUBLIC_API(bool)
1635 JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes,
1636 uint32_t version, MutableHandleValue vp,
1637 const JSStructuredCloneCallbacks *optionalCallbacks,
1638 void *closure)
1639 {
1640 AssertHeapIsIdle(cx);
1641 CHECK_REQUEST(cx);
1643 if (version > JS_STRUCTURED_CLONE_VERSION) {
1644 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_CLONE_VERSION);
1645 return false;
1646 }
1647 const JSStructuredCloneCallbacks *callbacks =
1648 optionalCallbacks ?
1649 optionalCallbacks :
1650 cx->runtime()->structuredCloneCallbacks;
1651 return ReadStructuredClone(cx, buf, nbytes, vp, callbacks, closure);
1652 }
1654 JS_PUBLIC_API(bool)
1655 JS_WriteStructuredClone(JSContext *cx, HandleValue value, uint64_t **bufp, size_t *nbytesp,
1656 const JSStructuredCloneCallbacks *optionalCallbacks,
1657 void *closure, HandleValue transferable)
1658 {
1659 AssertHeapIsIdle(cx);
1660 CHECK_REQUEST(cx);
1661 assertSameCompartment(cx, value);
1663 const JSStructuredCloneCallbacks *callbacks =
1664 optionalCallbacks ?
1665 optionalCallbacks :
1666 cx->runtime()->structuredCloneCallbacks;
1667 return WriteStructuredClone(cx, value, bufp, nbytesp, callbacks, closure, transferable);
1668 }
1670 JS_PUBLIC_API(bool)
1671 JS_ClearStructuredClone(uint64_t *data, size_t nbytes,
1672 const JSStructuredCloneCallbacks *optionalCallbacks,
1673 void *closure)
1674 {
1675 ClearStructuredClone(data, nbytes, optionalCallbacks, closure);
1676 return true;
1677 }
1679 JS_PUBLIC_API(bool)
1680 JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes,
1681 bool *hasTransferable)
1682 {
1683 bool transferable;
1684 if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable))
1685 return false;
1687 *hasTransferable = transferable;
1688 return true;
1689 }
1691 JS_PUBLIC_API(bool)
1692 JS_StructuredClone(JSContext *cx, HandleValue value, MutableHandleValue vp,
1693 const JSStructuredCloneCallbacks *optionalCallbacks,
1694 void *closure)
1695 {
1696 AssertHeapIsIdle(cx);
1697 CHECK_REQUEST(cx);
1699 // Strings are associated with zones, not compartments,
1700 // so we copy the string by wrapping it.
1701 if (value.isString()) {
1702 RootedString strValue(cx, value.toString());
1703 if (!cx->compartment()->wrap(cx, strValue.address())) {
1704 return false;
1705 }
1706 vp.setString(strValue);
1707 return true;
1708 }
1710 const JSStructuredCloneCallbacks *callbacks =
1711 optionalCallbacks ?
1712 optionalCallbacks :
1713 cx->runtime()->structuredCloneCallbacks;
1715 JSAutoStructuredCloneBuffer buf;
1716 {
1717 // If we use Maybe<AutoCompartment> here, G++ can't tell that the
1718 // destructor is only called when Maybe::construct was called, and
1719 // we get warnings about using uninitialized variables.
1720 if (value.isObject()) {
1721 AutoCompartment ac(cx, &value.toObject());
1722 if (!buf.write(cx, value, callbacks, closure))
1723 return false;
1724 } else {
1725 if (!buf.write(cx, value, callbacks, closure))
1726 return false;
1727 }
1728 }
1730 return buf.read(cx, vp, callbacks, closure);
1731 }
1733 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer &&other)
1734 {
1735 other.steal(&data_, &nbytes_, &version_);
1736 }
1738 JSAutoStructuredCloneBuffer&
1739 JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer &&other)
1740 {
1741 JS_ASSERT(&other != this);
1742 clear();
1743 other.steal(&data_, &nbytes_, &version_);
1744 return *this;
1745 }
1747 void
1748 JSAutoStructuredCloneBuffer::clear()
1749 {
1750 if (data_) {
1751 ClearStructuredClone(data_, nbytes_, callbacks_, closure_);
1752 data_ = nullptr;
1753 nbytes_ = 0;
1754 version_ = 0;
1755 }
1756 }
1758 bool
1759 JSAutoStructuredCloneBuffer::copy(const uint64_t *srcData, size_t nbytes, uint32_t version)
1760 {
1761 // transferable objects cannot be copied
1762 bool hasTransferable;
1763 if (!StructuredCloneHasTransferObjects(data_, nbytes_, &hasTransferable) ||
1764 hasTransferable)
1765 return false;
1767 uint64_t *newData = static_cast<uint64_t *>(js_malloc(nbytes));
1768 if (!newData)
1769 return false;
1771 js_memcpy(newData, srcData, nbytes);
1773 clear();
1774 data_ = newData;
1775 nbytes_ = nbytes;
1776 version_ = version;
1777 return true;
1778 }
1780 void
1781 JSAutoStructuredCloneBuffer::adopt(uint64_t *data, size_t nbytes, uint32_t version)
1782 {
1783 clear();
1784 data_ = data;
1785 nbytes_ = nbytes;
1786 version_ = version;
1787 }
1789 void
1790 JSAutoStructuredCloneBuffer::steal(uint64_t **datap, size_t *nbytesp, uint32_t *versionp)
1791 {
1792 *datap = data_;
1793 *nbytesp = nbytes_;
1794 if (versionp)
1795 *versionp = version_;
1797 data_ = nullptr;
1798 nbytes_ = 0;
1799 version_ = 0;
1800 }
1802 bool
1803 JSAutoStructuredCloneBuffer::read(JSContext *cx, MutableHandleValue vp,
1804 const JSStructuredCloneCallbacks *optionalCallbacks,
1805 void *closure)
1806 {
1807 JS_ASSERT(cx);
1808 JS_ASSERT(data_);
1809 return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp,
1810 optionalCallbacks, closure);
1811 }
1813 bool
1814 JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value,
1815 const JSStructuredCloneCallbacks *optionalCallbacks,
1816 void *closure)
1817 {
1818 HandleValue transferable = UndefinedHandleValue;
1819 return write(cx, value, transferable, optionalCallbacks, closure);
1820 }
1822 bool
1823 JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value,
1824 HandleValue transferable,
1825 const JSStructuredCloneCallbacks *optionalCallbacks,
1826 void *closure)
1827 {
1828 clear();
1829 bool ok = !!JS_WriteStructuredClone(cx, value, &data_, &nbytes_,
1830 optionalCallbacks, closure,
1831 transferable);
1832 if (!ok) {
1833 data_ = nullptr;
1834 nbytes_ = 0;
1835 version_ = JS_STRUCTURED_CLONE_VERSION;
1836 }
1837 return ok;
1838 }
1840 JS_PUBLIC_API(void)
1841 JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks)
1842 {
1843 rt->structuredCloneCallbacks = callbacks;
1844 }
1846 JS_PUBLIC_API(bool)
1847 JS_ReadUint32Pair(JSStructuredCloneReader *r, uint32_t *p1, uint32_t *p2)
1848 {
1849 return r->input().readPair((uint32_t *) p1, (uint32_t *) p2);
1850 }
1852 JS_PUBLIC_API(bool)
1853 JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len)
1854 {
1855 return r->input().readBytes(p, len);
1856 }
1858 JS_PUBLIC_API(bool)
1859 JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp)
1860 {
1861 uint32_t tag, nelems;
1862 if (!r->input().readPair(&tag, &nelems))
1863 return false;
1864 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
1865 return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp.address(), true);
1866 } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
1867 uint64_t arrayType;
1868 if (!r->input().read(&arrayType))
1869 return false;
1870 return r->readTypedArray(arrayType, nelems, vp.address());
1871 } else {
1872 JS_ReportErrorNumber(r->context(), js_GetErrorMessage, nullptr,
1873 JSMSG_SC_BAD_SERIALIZED_DATA, "expected type array");
1874 return false;
1875 }
1876 }
1878 JS_PUBLIC_API(bool)
1879 JS_WriteUint32Pair(JSStructuredCloneWriter *w, uint32_t tag, uint32_t data)
1880 {
1881 return w->output().writePair(tag, data);
1882 }
1884 JS_PUBLIC_API(bool)
1885 JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len)
1886 {
1887 return w->output().writeBytes(p, len);
1888 }
1890 JS_PUBLIC_API(bool)
1891 JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v)
1892 {
1893 JS_ASSERT(v.isObject());
1894 assertSameCompartment(w->context(), v);
1895 RootedObject obj(w->context(), &v.toObject());
1897 // If the object is a security wrapper, see if we're allowed to unwrap it.
1898 // If we aren't, throw.
1899 if (obj->is<WrapperObject>())
1900 obj = CheckedUnwrap(obj);
1901 if (!obj) {
1902 JS_ReportErrorNumber(w->context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
1903 return false;
1904 }
1905 return w->writeTypedArray(obj);
1906 }