Line data Source code
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/CheckedInt.h"
33 : #include "mozilla/EndianUtils.h"
34 : #include "mozilla/FloatingPoint.h"
35 :
36 : #include <algorithm>
37 :
38 : #include "jsapi.h"
39 : #include "jscntxt.h"
40 : #include "jsdate.h"
41 : #include "jswrapper.h"
42 :
43 : #include "builtin/DataViewObject.h"
44 : #include "builtin/MapObject.h"
45 : #include "js/Date.h"
46 : #include "js/GCHashTable.h"
47 : #include "vm/RegExpObject.h"
48 : #include "vm/SavedFrame.h"
49 : #include "vm/SharedArrayObject.h"
50 : #include "vm/TypedArrayObject.h"
51 : #include "vm/WrapperObject.h"
52 :
53 : #include "jscntxtinlines.h"
54 : #include "jsobjinlines.h"
55 :
56 : using namespace js;
57 :
58 : using mozilla::BitwiseCast;
59 : using mozilla::IsNaN;
60 : using mozilla::LittleEndian;
61 : using mozilla::NativeEndian;
62 : using mozilla::NumbersAreIdentical;
63 : using JS::CanonicalizeNaN;
64 :
65 : // When you make updates here, make sure you consider whether you need to bump the
66 : // value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You will
67 : // likely need to increment the version if anything at all changes in the serialization
68 : // format.
69 : //
70 : // Note that SCTAG_END_OF_KEYS is written into the serialized form and should have
71 : // a stable ID, it need not be at the end of the list and should not be used for
72 : // sizing data structures.
73 :
74 : enum StructuredDataType : uint32_t {
75 : /* Structured data types provided by the engine */
76 : SCTAG_FLOAT_MAX = 0xFFF00000,
77 : SCTAG_HEADER = 0xFFF10000,
78 : SCTAG_NULL = 0xFFFF0000,
79 : SCTAG_UNDEFINED,
80 : SCTAG_BOOLEAN,
81 : SCTAG_INT32,
82 : SCTAG_STRING,
83 : SCTAG_DATE_OBJECT,
84 : SCTAG_REGEXP_OBJECT,
85 : SCTAG_ARRAY_OBJECT,
86 : SCTAG_OBJECT_OBJECT,
87 : SCTAG_ARRAY_BUFFER_OBJECT,
88 : SCTAG_BOOLEAN_OBJECT,
89 : SCTAG_STRING_OBJECT,
90 : SCTAG_NUMBER_OBJECT,
91 : SCTAG_BACK_REFERENCE_OBJECT,
92 : SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
93 : SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
94 : SCTAG_TYPED_ARRAY_OBJECT,
95 : SCTAG_MAP_OBJECT,
96 : SCTAG_SET_OBJECT,
97 : SCTAG_END_OF_KEYS,
98 : SCTAG_DO_NOT_USE_3, // Required for backwards compatibility
99 : SCTAG_DATA_VIEW_OBJECT,
100 : SCTAG_SAVED_FRAME_OBJECT,
101 :
102 : // No new tags before principals.
103 : SCTAG_JSPRINCIPALS,
104 : SCTAG_NULL_JSPRINCIPALS,
105 : SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
106 : SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
107 :
108 : SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
109 :
110 : SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
111 : SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
112 : SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
113 : SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
114 : SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
115 : SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
116 : SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
117 : SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
118 : SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
119 : SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
120 : SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::MaxTypedArrayViewType - 1,
121 :
122 : /*
123 : * Define a separate range of numbers for Transferable-only tags, since
124 : * they are not used for persistent clone buffers and therefore do not
125 : * require bumping JS_STRUCTURED_CLONE_VERSION.
126 : */
127 : SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
128 : SCTAG_TRANSFER_MAP_PENDING_ENTRY,
129 : SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
130 : SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER,
131 : SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
132 :
133 : SCTAG_END_OF_BUILTIN_TYPES
134 : };
135 :
136 : /*
137 : * Format of transfer map:
138 : * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
139 : * numTransferables (64 bits)
140 : * array of:
141 : * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
142 : * pointer (64 bits)
143 : * extraData (64 bits), eg byte length for ArrayBuffers
144 : */
145 :
146 : // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
147 : // contents have been read out yet or not.
148 : enum TransferableMapHeader {
149 : SCTAG_TM_UNREAD = 0,
150 : SCTAG_TM_TRANSFERRED
151 : };
152 :
153 : static inline uint64_t
154 1318 : PairToUInt64(uint32_t tag, uint32_t data)
155 : {
156 1318 : return uint64_t(data) | (uint64_t(tag) << 32);
157 : }
158 :
159 : namespace js {
160 :
161 : template<typename T, typename AllocPolicy>
162 : struct BufferIterator {
163 : typedef mozilla::BufferList<AllocPolicy> BufferList;
164 :
165 101 : explicit BufferIterator(BufferList& buffer)
166 : : mBuffer(buffer)
167 101 : , mIter(buffer.Iter())
168 : {
169 : JS_STATIC_ASSERT(8 % sizeof(T) == 0);
170 101 : }
171 :
172 72 : BufferIterator(const BufferIterator& other)
173 72 : : mBuffer(other.mBuffer)
174 72 : , mIter(other.mIter)
175 : {
176 72 : }
177 :
178 0 : BufferIterator& operator=(const BufferIterator& other)
179 : {
180 0 : MOZ_ASSERT(&mBuffer == &other.mBuffer);
181 0 : mIter = other.mIter;
182 0 : return *this;
183 : }
184 :
185 0 : BufferIterator operator++(int) {
186 0 : BufferIterator ret = *this;
187 0 : if (!mIter.AdvanceAcrossSegments(mBuffer, sizeof(T))) {
188 0 : MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
189 : }
190 0 : return ret;
191 : }
192 :
193 613 : BufferIterator& operator+=(size_t size) {
194 613 : if (!mIter.AdvanceAcrossSegments(mBuffer, size)) {
195 0 : MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
196 : }
197 613 : return *this;
198 : }
199 :
200 0 : size_t operator-(const BufferIterator& other) {
201 0 : MOZ_ASSERT(&mBuffer == &other.mBuffer);
202 0 : return mBuffer.RangeLength(other.mIter, mIter);
203 : }
204 :
205 1313 : void next() {
206 1313 : if (!mIter.AdvanceAcrossSegments(mBuffer, sizeof(T))) {
207 0 : MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete");
208 : }
209 1313 : }
210 :
211 2068 : bool done() const {
212 2068 : return mIter.Done();
213 : }
214 :
215 613 : bool readBytes(char* outData, size_t size) {
216 613 : return mBuffer.ReadBytes(mIter, outData, size);
217 : }
218 :
219 0 : void write(const T& data) {
220 0 : MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
221 0 : *reinterpret_cast<T*>(mIter.Data()) = data;
222 0 : }
223 :
224 2068 : T peek() const {
225 2068 : MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
226 2068 : return *reinterpret_cast<T*>(mIter.Data());
227 : }
228 :
229 : BufferList& mBuffer;
230 : typename BufferList::IterImpl mIter;
231 : };
232 :
233 : SharedArrayRawBufferRefs&
234 248 : SharedArrayRawBufferRefs::operator=(SharedArrayRawBufferRefs&& other)
235 : {
236 248 : takeOwnership(Move(other));
237 248 : return *this;
238 : }
239 :
240 1254 : SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs()
241 : {
242 627 : releaseAll();
243 627 : }
244 :
245 : bool
246 0 : SharedArrayRawBufferRefs::acquire(JSContext* cx, SharedArrayRawBuffer* rawbuf)
247 : {
248 0 : if (!refs_.append(rawbuf)) {
249 0 : ReportOutOfMemory(cx);
250 0 : return false;
251 : }
252 :
253 0 : if (!rawbuf->addReference()) {
254 0 : refs_.popBack();
255 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SAB_REFCNT_OFLO);
256 0 : return false;
257 : }
258 :
259 0 : return true;
260 : }
261 :
262 : bool
263 0 : SharedArrayRawBufferRefs::acquireAll(JSContext* cx, const SharedArrayRawBufferRefs& that)
264 : {
265 0 : if (!refs_.reserve(refs_.length() + that.refs_.length())) {
266 0 : ReportOutOfMemory(cx);
267 0 : return false;
268 : }
269 :
270 0 : for (auto ref : that.refs_) {
271 0 : if (!ref->addReference()) {
272 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SAB_REFCNT_OFLO);
273 0 : return false;
274 : }
275 0 : MOZ_ALWAYS_TRUE(refs_.append(ref));
276 : }
277 :
278 0 : return true;
279 : }
280 :
281 : void
282 322 : SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs&& other)
283 : {
284 322 : MOZ_ASSERT(refs_.empty());
285 322 : refs_ = Move(other.refs_);
286 322 : }
287 :
288 : void
289 656 : SharedArrayRawBufferRefs::releaseAll()
290 : {
291 656 : for (auto ref : refs_)
292 0 : ref->dropReference();
293 656 : refs_.clear();
294 656 : }
295 :
296 74 : struct SCOutput {
297 : public:
298 : using Iter = BufferIterator<uint64_t, TempAllocPolicy>;
299 :
300 : explicit SCOutput(JSContext* cx);
301 :
302 8396 : JSContext* context() const { return cx; }
303 :
304 : bool write(uint64_t u);
305 : bool writePair(uint32_t tag, uint32_t data);
306 : bool writeDouble(double d);
307 : bool writeBytes(const void* p, size_t nbytes);
308 : bool writeChars(const Latin1Char* p, size_t nchars);
309 : bool writeChars(const char16_t* p, size_t nchars);
310 : bool writePtr(const void*);
311 :
312 : template <class T>
313 : bool writeArray(const T* p, size_t nbytes);
314 :
315 : bool extractBuffer(JSStructuredCloneData* data);
316 : void discardTransferables(const JSStructuredCloneCallbacks* cb, void* cbClosure);
317 :
318 0 : uint64_t tell() const { return buf.Size(); }
319 74 : uint64_t count() const { return buf.Size() / sizeof(uint64_t); }
320 0 : Iter iter() {
321 0 : return BufferIterator<uint64_t, TempAllocPolicy>(buf);
322 : }
323 :
324 0 : size_t offset(Iter dest) {
325 0 : return dest - iter();
326 : }
327 :
328 : private:
329 : JSContext* cx;
330 : mozilla::BufferList<TempAllocPolicy> buf;
331 : };
332 :
333 : class SCInput {
334 : typedef js::BufferIterator<uint64_t, SystemAllocPolicy> BufferIterator;
335 :
336 : public:
337 : SCInput(JSContext* cx, JSStructuredCloneData& data);
338 :
339 4625 : JSContext* context() const { return cx; }
340 :
341 : static void getPtr(uint64_t data, void** ptr);
342 : static void getPair(uint64_t data, uint32_t* tagp, uint32_t* datap);
343 :
344 : bool read(uint64_t* p);
345 : bool readNativeEndian(uint64_t* p);
346 : bool readPair(uint32_t* tagp, uint32_t* datap);
347 : bool readDouble(double* p);
348 : bool readBytes(void* p, size_t nbytes);
349 : bool readChars(Latin1Char* p, size_t nchars);
350 : bool readChars(char16_t* p, size_t nchars);
351 : bool readPtr(void**);
352 :
353 : bool get(uint64_t* p);
354 : bool getPair(uint32_t* tagp, uint32_t* datap);
355 :
356 72 : const BufferIterator& tell() const { return point; }
357 0 : void seekTo(const BufferIterator& pos) { point = pos; }
358 0 : void seekBy(size_t pos) { point += pos; }
359 :
360 : template <class T>
361 : bool readArray(T* p, size_t nelems);
362 :
363 0 : bool reportTruncated() {
364 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
365 0 : "truncated");
366 0 : return false;
367 : }
368 :
369 : private:
370 : void staticAssertions() {
371 : JS_STATIC_ASSERT(sizeof(char16_t) == 2);
372 : JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
373 : }
374 :
375 : JSContext* cx;
376 : BufferIterator point;
377 : };
378 :
379 : } /* namespace js */
380 :
381 72 : struct JSStructuredCloneReader {
382 : public:
383 72 : explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope,
384 : const JSStructuredCloneCallbacks* cb,
385 : void* cbClosure)
386 72 : : in(in), allowedScope(scope), objs(in.context()), allObjs(in.context()),
387 72 : callbacks(cb), closure(cbClosure) { }
388 :
389 3 : SCInput& input() { return in; }
390 : bool read(MutableHandleValue vp);
391 :
392 : private:
393 4481 : JSContext* context() { return in.context(); }
394 :
395 : bool readHeader();
396 : bool readTransferMap();
397 :
398 : template <typename CharT>
399 : JSString* readStringImpl(uint32_t nchars);
400 : JSString* readString(uint32_t data);
401 :
402 : bool checkDouble(double d);
403 : bool readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp,
404 : bool v1Read = false);
405 : bool readDataView(uint32_t byteLength, MutableHandleValue vp);
406 : bool readArrayBuffer(uint32_t nbytes, MutableHandleValue vp);
407 : bool readSharedArrayBuffer(uint32_t nbytes, MutableHandleValue vp);
408 : bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp);
409 : JSObject* readSavedFrame(uint32_t principalsTag);
410 : bool startRead(MutableHandleValue vp);
411 :
412 : SCInput& in;
413 :
414 : // The widest scope that the caller will accept, where
415 : // SameProcessSameThread is the widest (it can store anything it wants) and
416 : // DifferentProcess is the narrowest (it cannot contain pointers and must
417 : // be valid cross-process.)
418 : JS::StructuredCloneScope allowedScope;
419 :
420 : // The scope the buffer was generated for (what sort of buffer it is.) The
421 : // scope is not just a permissions thing; it also affects the storage
422 : // format (eg a Transferred ArrayBuffer can be stored as a pointer for
423 : // SameProcessSameThread but must have its contents in the clone buffer for
424 : // DifferentProcess.)
425 : JS::StructuredCloneScope storedScope;
426 :
427 : // Stack of objects with properties remaining to be read.
428 : AutoValueVector objs;
429 :
430 : // Stack of all objects read during this deserialization
431 : AutoValueVector allObjs;
432 :
433 : // The user defined callbacks that will be used for cloning.
434 : const JSStructuredCloneCallbacks* callbacks;
435 :
436 : // Any value passed to JS_ReadStructuredClone.
437 : void* closure;
438 :
439 : friend bool JS_ReadTypedArray(JSStructuredCloneReader* r, MutableHandleValue vp);
440 : };
441 :
442 : struct JSStructuredCloneWriter {
443 : public:
444 74 : explicit JSStructuredCloneWriter(JSContext* cx,
445 : JS::StructuredCloneScope scope,
446 : JS::CloneDataPolicy cloneDataPolicy,
447 : const JSStructuredCloneCallbacks* cb,
448 : void* cbClosure,
449 : const Value& tVal)
450 74 : : out(cx), scope(scope), objs(out.context()),
451 : counts(out.context()), entries(out.context()),
452 148 : memory(out.context()), callbacks(cb),
453 148 : closure(cbClosure), transferable(out.context(), tVal),
454 148 : transferableObjects(out.context(), GCHashSet<JSObject*>(cx)),
455 444 : cloneDataPolicy(cloneDataPolicy)
456 74 : {}
457 :
458 : ~JSStructuredCloneWriter();
459 :
460 74 : bool init() {
461 74 : if (!memory.init()) {
462 0 : ReportOutOfMemory(context());
463 0 : return false;
464 : }
465 74 : return parseTransferable() && writeHeader() && writeTransferMap();
466 : }
467 :
468 : bool write(HandleValue v);
469 :
470 4 : SCOutput& output() { return out; }
471 :
472 74 : bool extractBuffer(JSStructuredCloneData* data) {
473 74 : bool success = out.extractBuffer(data);
474 74 : if (success) {
475 : // Move the SharedArrayRawBuf references here, SCOutput::extractBuffer
476 : // moves the serialized data.
477 74 : data->refsHeld_.takeOwnership(Move(refsHeld));
478 74 : data->setOptionalCallbacks(callbacks, closure,
479 74 : OwnTransferablePolicy::OwnsTransferablesIfAny);
480 : }
481 74 : return success;
482 : }
483 :
484 0 : JS::StructuredCloneScope cloneScope() const { return scope; }
485 :
486 : private:
487 : JSStructuredCloneWriter() = delete;
488 : JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
489 :
490 7952 : JSContext* context() { return out.context(); }
491 :
492 : bool writeHeader();
493 : bool writeTransferMap();
494 :
495 : bool writeString(uint32_t tag, JSString* str);
496 : bool writeArrayBuffer(HandleObject obj);
497 : bool writeTypedArray(HandleObject obj);
498 : bool writeDataView(HandleObject obj);
499 : bool writeSharedArrayBuffer(HandleObject obj);
500 : bool startObject(HandleObject obj, bool* backref);
501 : bool startWrite(HandleValue v);
502 : bool traverseObject(HandleObject obj);
503 : bool traverseMap(HandleObject obj);
504 : bool traverseSet(HandleObject obj);
505 : bool traverseSavedFrame(HandleObject obj);
506 :
507 : bool reportDataCloneError(uint32_t errorId);
508 :
509 : bool parseTransferable();
510 : bool transferOwnership();
511 :
512 : inline void checkStack();
513 :
514 : SCOutput out;
515 :
516 : // The (address space, thread) scope within which this clone is valid.
517 : JS::StructuredCloneScope scope;
518 :
519 : // Vector of objects with properties remaining to be written.
520 : //
521 : // NB: These can span multiple compartments, so the compartment must be
522 : // entered before any manipulation is performed.
523 : AutoValueVector objs;
524 :
525 : // counts[i] is the number of entries of objs[i] remaining to be written.
526 : // counts.length() == objs.length() and sum(counts) == entries.length().
527 : Vector<size_t> counts;
528 :
529 : // For JSObject: Property IDs as value
530 : // For Map: Key followed by value
531 : // For Set: Key
532 : // For SavedFrame: parent SavedFrame
533 : AutoValueVector entries;
534 :
535 : // The "memory" list described in the HTML5 internal structured cloning
536 : // algorithm. memory is a superset of objs; items are never removed from
537 : // Memory until a serialization operation is finished
538 : using CloneMemory = GCHashMap<JSObject*,
539 : uint32_t,
540 : MovableCellHasher<JSObject*>,
541 : SystemAllocPolicy>;
542 : Rooted<CloneMemory> memory;
543 :
544 : // The user defined callbacks that will be used for cloning.
545 : const JSStructuredCloneCallbacks* callbacks;
546 :
547 : // Any value passed to JS_WriteStructuredClone.
548 : void* closure;
549 :
550 : // Set of transferable objects
551 : RootedValue transferable;
552 : Rooted<GCHashSet<JSObject*>> transferableObjects;
553 :
554 : const JS::CloneDataPolicy cloneDataPolicy;
555 :
556 : // SharedArrayRawBuffers whose reference counts we have incremented.
557 : SharedArrayRawBufferRefs refsHeld;
558 :
559 : friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
560 : friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
561 : friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj);
562 : };
563 :
564 : JS_FRIEND_API(uint64_t)
565 0 : js::GetSCOffset(JSStructuredCloneWriter* writer)
566 : {
567 0 : MOZ_ASSERT(writer);
568 0 : return writer->output().count() * sizeof(uint64_t);
569 : }
570 :
571 : JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
572 : JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
573 : JS_STATIC_ASSERT(Scalar::Int8 == 0);
574 :
575 : static void
576 0 : ReportDataCloneError(JSContext* cx,
577 : const JSStructuredCloneCallbacks* callbacks,
578 : uint32_t errorId)
579 : {
580 0 : if (callbacks && callbacks->reportError) {
581 0 : callbacks->reportError(cx, errorId);
582 0 : return;
583 : }
584 :
585 0 : switch (errorId) {
586 : case JS_SCERR_DUP_TRANSFERABLE:
587 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_DUP_TRANSFERABLE);
588 0 : break;
589 :
590 : case JS_SCERR_TRANSFERABLE:
591 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE);
592 0 : break;
593 :
594 : case JS_SCERR_UNSUPPORTED_TYPE:
595 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
596 0 : break;
597 :
598 : case JS_SCERR_SAB_TRANSFERABLE:
599 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SAB_TRANSFERABLE);
600 0 : break;
601 :
602 : default:
603 0 : MOZ_CRASH("Unkown errorId");
604 : break;
605 : }
606 : }
607 :
608 : bool
609 74 : WriteStructuredClone(JSContext* cx, HandleValue v, JSStructuredCloneData* bufp,
610 : JS::StructuredCloneScope scope,
611 : JS::CloneDataPolicy cloneDataPolicy,
612 : const JSStructuredCloneCallbacks* cb, void* cbClosure,
613 : const Value& transferable)
614 : {
615 148 : JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure, transferable);
616 148 : return w.init() && w.write(v) && w.extractBuffer(bufp);
617 : }
618 :
619 : bool
620 72 : ReadStructuredClone(JSContext* cx, JSStructuredCloneData& data,
621 : JS::StructuredCloneScope scope, MutableHandleValue vp,
622 : const JSStructuredCloneCallbacks* cb, void* cbClosure)
623 : {
624 72 : SCInput in(cx, data);
625 144 : JSStructuredCloneReader r(in, scope, cb, cbClosure);
626 144 : return r.read(vp);
627 : }
628 :
629 : // If the given buffer contains Transferables, free them. Note that custom
630 : // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
631 : // delete their transferables.
632 : template<typename AllocPolicy>
633 : static void
634 29 : DiscardTransferables(mozilla::BufferList<AllocPolicy>& buffer,
635 : const JSStructuredCloneCallbacks* cb, void* cbClosure)
636 : {
637 29 : auto point = BufferIterator<uint64_t, AllocPolicy>(buffer);
638 29 : if (point.done())
639 29 : return; // Empty buffer
640 :
641 : uint32_t tag, data;
642 29 : SCInput::getPair(point.peek(), &tag, &data);
643 29 : point.next();
644 :
645 29 : if (tag == SCTAG_HEADER) {
646 29 : if (point.done())
647 0 : return;
648 :
649 29 : SCInput::getPair(point.peek(), &tag, &data);
650 29 : point.next();
651 : }
652 :
653 29 : if (tag != SCTAG_TRANSFER_MAP_HEADER)
654 29 : return;
655 :
656 0 : if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
657 0 : return;
658 :
659 : // freeTransfer should not GC
660 0 : JS::AutoSuppressGCAnalysis nogc;
661 :
662 0 : if (point.done())
663 0 : return;
664 :
665 0 : uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek());
666 0 : point.next();
667 0 : while (numTransferables--) {
668 0 : if (point.done())
669 0 : return;
670 :
671 : uint32_t ownership;
672 0 : SCInput::getPair(point.peek(), &tag, &ownership);
673 0 : point.next();
674 0 : MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
675 0 : if (point.done())
676 0 : return;
677 :
678 : void* content;
679 0 : SCInput::getPtr(point.peek(), &content);
680 0 : point.next();
681 0 : if (point.done())
682 0 : return;
683 :
684 0 : uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek());
685 0 : point.next();
686 :
687 0 : if (ownership < JS::SCTAG_TMO_FIRST_OWNED)
688 0 : continue;
689 :
690 0 : if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
691 0 : js_free(content);
692 0 : } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
693 0 : JS_ReleaseMappedArrayBufferContents(content, extraData);
694 0 : } else if (cb && cb->freeTransfer) {
695 0 : cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure);
696 : } else {
697 0 : MOZ_ASSERT(false, "unknown ownership");
698 : }
699 : }
700 : }
701 :
702 : static bool
703 0 : StructuredCloneHasTransferObjects(const JSStructuredCloneData& data)
704 : {
705 0 : auto iter = data.Iter();
706 :
707 0 : if (data.Size() < sizeof(uint64_t))
708 0 : return false;
709 :
710 : uint64_t u;
711 0 : data.ReadBytes(iter, reinterpret_cast<char*>(&u), sizeof(u));
712 0 : uint32_t tag = uint32_t(u >> 32);
713 0 : return (tag == SCTAG_TRANSFER_MAP_HEADER);
714 : }
715 :
716 : namespace js {
717 :
718 72 : SCInput::SCInput(JSContext* cx, JSStructuredCloneData& data)
719 72 : : cx(cx), point(data)
720 : {
721 :
722 : static_assert(JSStructuredCloneData::kSegmentAlignment % 8 == 0,
723 : "structured clone buffer reads should be aligned");
724 72 : MOZ_ASSERT(data.Size() % 8 == 0);
725 72 : }
726 :
727 : bool
728 1255 : SCInput::read(uint64_t* p)
729 : {
730 1255 : if (point.done()) {
731 0 : *p = 0; /* initialize to shut GCC up */
732 0 : return reportTruncated();
733 : }
734 1255 : *p = NativeEndian::swapFromLittleEndian(point.peek());
735 1255 : point.next();
736 1255 : return true;
737 : }
738 :
739 : bool
740 0 : SCInput::readNativeEndian(uint64_t* p)
741 : {
742 0 : if (point.done()) {
743 0 : *p = 0; /* initialize to shut GCC up */
744 0 : return reportTruncated();
745 : }
746 0 : *p = point.peek();
747 0 : point.next();
748 0 : return true;
749 : }
750 :
751 : bool
752 1239 : SCInput::readPair(uint32_t* tagp, uint32_t* datap)
753 : {
754 : uint64_t u;
755 1239 : bool ok = read(&u);
756 1239 : if (ok) {
757 1239 : *tagp = uint32_t(u >> 32);
758 1239 : *datap = uint32_t(u);
759 : }
760 1239 : return ok;
761 : }
762 :
763 : bool
764 755 : SCInput::get(uint64_t* p)
765 : {
766 755 : if (point.done())
767 0 : return reportTruncated();
768 755 : *p = NativeEndian::swapFromLittleEndian(point.peek());
769 755 : return true;
770 : }
771 :
772 : bool
773 755 : SCInput::getPair(uint32_t* tagp, uint32_t* datap)
774 : {
775 755 : uint64_t u = 0;
776 755 : if (!get(&u))
777 0 : return false;
778 :
779 755 : *tagp = uint32_t(u >> 32);
780 755 : *datap = uint32_t(u);
781 755 : return true;
782 : }
783 :
784 : void
785 58 : SCInput::getPair(uint64_t data, uint32_t* tagp, uint32_t* datap)
786 : {
787 58 : uint64_t u = NativeEndian::swapFromLittleEndian(data);
788 58 : *tagp = uint32_t(u >> 32);
789 58 : *datap = uint32_t(u);
790 58 : }
791 :
792 : bool
793 16 : SCInput::readDouble(double* p)
794 : {
795 : union {
796 : uint64_t u;
797 : double d;
798 : } pun;
799 16 : if (!read(&pun.u))
800 0 : return false;
801 16 : *p = CanonicalizeNaN(pun.d);
802 16 : return true;
803 : }
804 :
805 : template <typename T>
806 : static void
807 25 : swapFromLittleEndianInPlace(T* ptr, size_t nelems)
808 : {
809 25 : if (nelems > 0)
810 25 : NativeEndian::swapFromLittleEndianInPlace(ptr, nelems);
811 25 : }
812 :
813 : template <>
814 : void
815 588 : swapFromLittleEndianInPlace(uint8_t* ptr, size_t nelems)
816 588 : {}
817 :
818 : // Data is packed into an integral number of uint64_t words. Compute the
819 : // padding required to finish off the final word.
820 : static size_t
821 1268 : ComputePadding(size_t nelems, size_t elemSize)
822 : {
823 : // We want total length mod 8, where total length is nelems * sizeof(T),
824 : // but that might overflow. So reduce nelems to nelems mod 8, since we are
825 : // going to be doing a mod 8 later anyway.
826 1268 : size_t leftoverLength = (nelems % sizeof(uint64_t)) * elemSize;
827 1268 : return (-leftoverLength) & (sizeof(uint64_t) - 1);
828 : }
829 :
830 : template <class T>
831 : bool
832 621 : SCInput::readArray(T* p, size_t nelems)
833 : {
834 621 : if (!nelems)
835 8 : return true;
836 :
837 : JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
838 :
839 : /*
840 : * Fail if nelems is so huge that computing the full size will overflow.
841 : */
842 613 : mozilla::CheckedInt<size_t> size = mozilla::CheckedInt<size_t>(nelems) * sizeof(T);
843 613 : if (!size.isValid())
844 0 : return reportTruncated();
845 :
846 613 : if (!point.readBytes(reinterpret_cast<char*>(p), size.value()))
847 0 : return false;
848 :
849 613 : swapFromLittleEndianInPlace(p, nelems);
850 :
851 613 : point += ComputePadding(nelems, sizeof(T));
852 :
853 613 : return true;
854 : }
855 :
856 : bool
857 596 : SCInput::readBytes(void* p, size_t nbytes)
858 : {
859 596 : return readArray((uint8_t*) p, nbytes);
860 : }
861 :
862 : bool
863 594 : SCInput::readChars(Latin1Char* p, size_t nchars)
864 : {
865 : static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
866 594 : return readBytes(p, nchars);
867 : }
868 :
869 : bool
870 25 : SCInput::readChars(char16_t* p, size_t nchars)
871 : {
872 : MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
873 25 : return readArray((uint16_t*) p, nchars);
874 : }
875 :
876 : void
877 0 : SCInput::getPtr(uint64_t data, void** ptr)
878 : {
879 : // No endianness conversion is used for pointers, since they are not sent
880 : // across address spaces anyway.
881 0 : *ptr = reinterpret_cast<void*>(data);
882 0 : }
883 :
884 : bool
885 0 : SCInput::readPtr(void** p)
886 : {
887 : uint64_t u;
888 0 : if (!readNativeEndian(&u))
889 0 : return false;
890 0 : *p = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(u));
891 0 : return true;
892 : }
893 :
894 74 : SCOutput::SCOutput(JSContext* cx)
895 : : cx(cx)
896 74 : , buf(0, 0, 4096, cx)
897 : {
898 74 : }
899 :
900 : bool
901 1334 : SCOutput::write(uint64_t u)
902 : {
903 1334 : uint64_t v = NativeEndian::swapToLittleEndian(u);
904 1334 : return buf.WriteBytes(reinterpret_cast<char*>(&v), sizeof(u));
905 : }
906 :
907 : bool
908 1301 : SCOutput::writePair(uint32_t tag, uint32_t data)
909 : {
910 : /*
911 : * As it happens, the tag word appears after the data word in the output.
912 : * This is because exponents occupy the last 2 bytes of doubles on the
913 : * little-endian platforms we care most about.
914 : *
915 : * For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
916 : * PairToUInt64 produces the number 0xFFFF000200000001.
917 : * That is written out as the bytes 01 00 00 00 02 00 FF FF.
918 : */
919 1301 : return write(PairToUInt64(tag, data));
920 : }
921 :
922 : static inline double
923 17 : ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
924 : {
925 17 : return BitwiseCast<double>(PairToUInt64(tag, data));
926 : }
927 :
928 : bool
929 33 : SCOutput::writeDouble(double d)
930 : {
931 33 : return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
932 : }
933 :
934 : template <class T>
935 : bool
936 25 : SCOutput::writeArray(const T* p, size_t nelems)
937 : {
938 : JS_STATIC_ASSERT(8 % sizeof(T) == 0);
939 : JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
940 :
941 25 : if (nelems == 0)
942 0 : return true;
943 :
944 885 : for (size_t i = 0; i < nelems; i++) {
945 860 : T value = NativeEndian::swapToLittleEndian(p[i]);
946 860 : if (!buf.WriteBytes(reinterpret_cast<char*>(&value), sizeof(value)))
947 0 : return false;
948 : }
949 :
950 : // Zero-pad to 8 bytes boundary.
951 25 : size_t padbytes = ComputePadding(nelems, sizeof(T));
952 25 : char zeroes[sizeof(uint64_t)] = { 0 };
953 25 : if (!buf.WriteBytes(zeroes, padbytes))
954 0 : return false;
955 :
956 25 : return true;
957 : }
958 :
959 : template <>
960 : bool
961 637 : SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems)
962 : {
963 637 : if (nelems == 0)
964 7 : return true;
965 :
966 630 : if (!buf.WriteBytes(reinterpret_cast<const char*>(p), nelems))
967 0 : return false;
968 :
969 : // zero-pad to 8 bytes boundary
970 630 : size_t padbytes = ComputePadding(nelems, 1);
971 630 : char zeroes[sizeof(uint64_t)] = { 0 };
972 630 : if (!buf.WriteBytes(zeroes, padbytes))
973 0 : return false;
974 :
975 630 : return true;
976 : }
977 :
978 : bool
979 637 : SCOutput::writeBytes(const void* p, size_t nbytes)
980 : {
981 637 : return writeArray((const uint8_t*) p, nbytes);
982 : }
983 :
984 : bool
985 25 : SCOutput::writeChars(const char16_t* p, size_t nchars)
986 : {
987 : static_assert(sizeof(char16_t) == sizeof(uint16_t),
988 : "required so that treating char16_t[] memory as uint16_t[] "
989 : "memory is permissible");
990 25 : return writeArray((const uint16_t*) p, nchars);
991 : }
992 :
993 : bool
994 635 : SCOutput::writeChars(const Latin1Char* p, size_t nchars)
995 : {
996 : static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
997 635 : return writeBytes(p, nchars);
998 : }
999 :
1000 : bool
1001 0 : SCOutput::writePtr(const void* p)
1002 : {
1003 0 : return write(reinterpret_cast<uint64_t>(p));
1004 : }
1005 :
1006 : bool
1007 74 : SCOutput::extractBuffer(JSStructuredCloneData* data)
1008 : {
1009 : bool success;
1010 : mozilla::BufferList<SystemAllocPolicy> out =
1011 148 : buf.MoveFallible<SystemAllocPolicy>(&success);
1012 74 : if (!success) {
1013 0 : ReportOutOfMemory(cx);
1014 0 : return false;
1015 : }
1016 74 : *data = JSStructuredCloneData(Move(out));
1017 74 : return true;
1018 : }
1019 :
1020 : void
1021 0 : SCOutput::discardTransferables(const JSStructuredCloneCallbacks* cb, void* cbClosure)
1022 : {
1023 0 : DiscardTransferables(buf, cb, cbClosure);
1024 0 : }
1025 :
1026 : } /* namespace js */
1027 :
1028 1106 : JSStructuredCloneData::~JSStructuredCloneData()
1029 : {
1030 553 : if (!Size())
1031 394 : return;
1032 159 : if (ownTransferables_ == OwnTransferablePolicy::OwnsTransferablesIfAny)
1033 0 : DiscardTransferables(*this, callbacks_, closure_);
1034 553 : }
1035 :
1036 : JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
1037 :
1038 148 : JSStructuredCloneWriter::~JSStructuredCloneWriter()
1039 : {
1040 : // Free any transferable data left lying around in the buffer
1041 74 : if (out.count()) {
1042 0 : out.discardTransferables(callbacks, closure);
1043 : }
1044 74 : }
1045 :
1046 : bool
1047 74 : JSStructuredCloneWriter::parseTransferable()
1048 : {
1049 : // NOTE: The transferables set is tested for non-emptiness at various
1050 : // junctures in structured cloning, so this set must be initialized
1051 : // by this method in all non-error cases.
1052 74 : MOZ_ASSERT(!transferableObjects.initialized(),
1053 : "parseTransferable called with stale data");
1054 :
1055 74 : if (transferable.isNull() || transferable.isUndefined())
1056 74 : return transferableObjects.init(0);
1057 :
1058 0 : if (!transferable.isObject())
1059 0 : return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1060 :
1061 0 : JSContext* cx = context();
1062 0 : RootedObject array(cx, &transferable.toObject());
1063 : bool isArray;
1064 0 : if (!JS_IsArrayObject(cx, array, &isArray))
1065 0 : return false;
1066 0 : if (!isArray)
1067 0 : return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1068 :
1069 : uint32_t length;
1070 0 : if (!JS_GetArrayLength(cx, array, &length))
1071 0 : return false;
1072 :
1073 : // Initialize the set for the provided array's length.
1074 0 : if (!transferableObjects.init(length))
1075 0 : return false;
1076 :
1077 0 : if (length == 0)
1078 0 : return true;
1079 :
1080 0 : RootedValue v(context());
1081 0 : RootedObject tObj(context());
1082 :
1083 0 : for (uint32_t i = 0; i < length; ++i) {
1084 0 : if (!CheckForInterrupt(cx))
1085 0 : return false;
1086 :
1087 0 : if (!JS_GetElement(cx, array, i, &v))
1088 0 : return false;
1089 :
1090 0 : if (!v.isObject())
1091 0 : return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1092 0 : tObj = &v.toObject();
1093 :
1094 0 : if (tObj->is<SharedArrayBufferObject>())
1095 0 : return reportDataCloneError(JS_SCERR_SAB_TRANSFERABLE);
1096 :
1097 : // No duplicates allowed
1098 0 : auto p = transferableObjects.lookupForAdd(tObj);
1099 0 : if (p)
1100 0 : return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
1101 :
1102 0 : if (!transferableObjects.add(p, tObj))
1103 0 : return false;
1104 : }
1105 :
1106 0 : return true;
1107 : }
1108 :
1109 : bool
1110 0 : JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId)
1111 : {
1112 0 : ReportDataCloneError(context(), callbacks, errorId);
1113 0 : return false;
1114 : }
1115 :
1116 : bool
1117 660 : JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str)
1118 : {
1119 660 : JSLinearString* linear = str->ensureLinear(context());
1120 660 : if (!linear)
1121 0 : return false;
1122 :
1123 : static_assert(JSString::MAX_LENGTH <= INT32_MAX, "String length must fit in 31 bits");
1124 :
1125 660 : uint32_t length = linear->length();
1126 660 : uint32_t lengthAndEncoding = length | (uint32_t(linear->hasLatin1Chars()) << 31);
1127 660 : if (!out.writePair(tag, lengthAndEncoding))
1128 0 : return false;
1129 :
1130 1320 : JS::AutoCheckCannotGC nogc;
1131 660 : return linear->hasLatin1Chars()
1132 685 : ? out.writeChars(linear->latin1Chars(nogc), length)
1133 685 : : out.writeChars(linear->twoByteChars(nogc), length);
1134 : }
1135 :
1136 : inline void
1137 653 : JSStructuredCloneWriter::checkStack()
1138 : {
1139 : #ifdef DEBUG
1140 : /* To avoid making serialization O(n^2), limit stack-checking at 10. */
1141 653 : const size_t MAX = 10;
1142 :
1143 653 : size_t limit = Min(counts.length(), MAX);
1144 653 : MOZ_ASSERT(objs.length() == counts.length());
1145 653 : size_t total = 0;
1146 2051 : for (size_t i = 0; i < limit; i++) {
1147 1398 : MOZ_ASSERT(total + counts[i] >= total);
1148 1398 : total += counts[i];
1149 : }
1150 653 : if (counts.length() <= MAX)
1151 653 : MOZ_ASSERT(total == entries.length());
1152 : else
1153 0 : MOZ_ASSERT(total <= entries.length());
1154 :
1155 653 : size_t j = objs.length();
1156 2051 : for (size_t i = 0; i < limit; i++) {
1157 1398 : --j;
1158 1398 : MOZ_ASSERT(memory.has(&objs[j].toObject()));
1159 : }
1160 : #endif
1161 653 : }
1162 :
1163 : /*
1164 : * Write out a typed array. Note that post-v1 structured clone buffers do not
1165 : * perform endianness conversion on stored data, so multibyte typed arrays
1166 : * cannot be deserialized into a different endianness machine. Endianness
1167 : * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
1168 : * Int16Array views of the same ArrayBuffer, should the data bytes be
1169 : * byte-swapped when writing or not? The Int8Array requires them to not be
1170 : * swapped; the Int16Array requires that they are.
1171 : */
1172 : bool
1173 0 : JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
1174 : {
1175 0 : Rooted<TypedArrayObject*> tarr(context(), &CheckedUnwrap(obj)->as<TypedArrayObject>());
1176 0 : JSAutoCompartment ac(context(), tarr);
1177 :
1178 0 : if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
1179 0 : return false;
1180 :
1181 0 : if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
1182 0 : return false;
1183 0 : uint64_t type = tarr->type();
1184 0 : if (!out.write(type))
1185 0 : return false;
1186 :
1187 : // Write out the ArrayBuffer tag and contents
1188 0 : RootedValue val(context(), TypedArrayObject::bufferValue(tarr));
1189 0 : if (!startWrite(val))
1190 0 : return false;
1191 :
1192 0 : return out.write(tarr->byteOffset());
1193 : }
1194 :
1195 : bool
1196 0 : JSStructuredCloneWriter::writeDataView(HandleObject obj)
1197 : {
1198 0 : Rooted<DataViewObject*> view(context(), &CheckedUnwrap(obj)->as<DataViewObject>());
1199 0 : JSAutoCompartment ac(context(), view);
1200 :
1201 0 : if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, view->byteLength()))
1202 0 : return false;
1203 :
1204 : // Write out the ArrayBuffer tag and contents
1205 0 : RootedValue val(context(), DataViewObject::bufferValue(view));
1206 0 : if (!startWrite(val))
1207 0 : return false;
1208 :
1209 0 : return out.write(view->byteOffset());
1210 : }
1211 :
1212 : bool
1213 0 : JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj)
1214 : {
1215 0 : ArrayBufferObject& buffer = CheckedUnwrap(obj)->as<ArrayBufferObject>();
1216 0 : JSAutoCompartment ac(context(), &buffer);
1217 :
1218 0 : return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
1219 0 : out.writeBytes(buffer.dataPointer(), buffer.byteLength());
1220 : }
1221 :
1222 : bool
1223 0 : JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj)
1224 : {
1225 0 : if (!cloneDataPolicy.isSharedArrayBufferAllowed()) {
1226 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_NOT_CLONABLE,
1227 0 : "SharedArrayBuffer");
1228 0 : return false;
1229 : }
1230 :
1231 : // We must not transfer buffer pointers cross-process. The cloneDataPolicy
1232 : // should guard against this; check that it does.
1233 :
1234 0 : MOZ_RELEASE_ASSERT(scope <= JS::StructuredCloneScope::SameProcessDifferentThread);
1235 :
1236 0 : Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
1237 0 : SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
1238 :
1239 0 : if (!refsHeld.acquire(context(), rawbuf))
1240 0 : return false;
1241 :
1242 0 : intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
1243 0 : return out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT, static_cast<uint32_t>(sizeof(p))) &&
1244 0 : out.writeBytes(&p, sizeof(p));
1245 : }
1246 :
1247 : bool
1248 154 : JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref)
1249 : {
1250 : /* Handle cycles in the object graph. */
1251 154 : CloneMemory::AddPtr p = memory.lookupForAdd(obj);
1252 154 : if ((*backref = p.found()))
1253 0 : return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
1254 154 : if (!memory.add(p, obj, memory.count())) {
1255 0 : ReportOutOfMemory(context());
1256 0 : return false;
1257 : }
1258 :
1259 154 : if (memory.count() == UINT32_MAX) {
1260 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_NEED_DIET,
1261 0 : "object graph to serialize");
1262 0 : return false;
1263 : }
1264 :
1265 154 : return true;
1266 : }
1267 :
1268 : bool
1269 137 : JSStructuredCloneWriter::traverseObject(HandleObject obj)
1270 : {
1271 : /*
1272 : * Get enumerable property ids and put them in reverse order so that they
1273 : * will come off the stack in forward order.
1274 : */
1275 274 : AutoIdVector properties(context());
1276 137 : if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties))
1277 0 : return false;
1278 :
1279 653 : for (size_t i = properties.length(); i > 0; --i) {
1280 516 : MOZ_ASSERT(JSID_IS_STRING(properties[i - 1]) || JSID_IS_INT(properties[i - 1]));
1281 1032 : RootedValue val(context(), IdToValue(properties[i - 1]));
1282 516 : if (!entries.append(val))
1283 0 : return false;
1284 : }
1285 :
1286 : /* Push obj and count to the stack. */
1287 137 : if (!objs.append(ObjectValue(*obj)) || !counts.append(properties.length()))
1288 0 : return false;
1289 :
1290 137 : checkStack();
1291 :
1292 : /* Write the header for obj. */
1293 : ESClass cls;
1294 137 : if (!GetBuiltinClass(context(), obj, &cls))
1295 0 : return false;
1296 137 : return out.writePair(cls == ESClass::Array ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
1297 : }
1298 :
1299 : bool
1300 0 : JSStructuredCloneWriter::traverseMap(HandleObject obj)
1301 : {
1302 0 : Rooted<GCVector<Value>> newEntries(context(), GCVector<Value>(context()));
1303 : {
1304 : // If there is no wrapper, the compartment munging is a no-op.
1305 0 : RootedObject unwrapped(context(), CheckedUnwrap(obj));
1306 0 : MOZ_ASSERT(unwrapped);
1307 0 : JSAutoCompartment ac(context(), unwrapped);
1308 0 : if (!MapObject::getKeysAndValuesInterleaved(context(), unwrapped, &newEntries))
1309 0 : return false;
1310 : }
1311 0 : if (!context()->compartment()->wrap(context(), &newEntries))
1312 0 : return false;
1313 :
1314 0 : for (size_t i = newEntries.length(); i > 0; --i) {
1315 0 : if (!entries.append(newEntries[i - 1]))
1316 0 : return false;
1317 : }
1318 :
1319 : /* Push obj and count to the stack. */
1320 0 : if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length()))
1321 0 : return false;
1322 :
1323 0 : checkStack();
1324 :
1325 : /* Write the header for obj. */
1326 0 : return out.writePair(SCTAG_MAP_OBJECT, 0);
1327 : }
1328 :
1329 : bool
1330 0 : JSStructuredCloneWriter::traverseSet(HandleObject obj)
1331 : {
1332 0 : Rooted<GCVector<Value>> keys(context(), GCVector<Value>(context()));
1333 : {
1334 : // If there is no wrapper, the compartment munging is a no-op.
1335 0 : RootedObject unwrapped(context(), CheckedUnwrap(obj));
1336 0 : MOZ_ASSERT(unwrapped);
1337 0 : JSAutoCompartment ac(context(), unwrapped);
1338 0 : if (!SetObject::keys(context(), unwrapped, &keys))
1339 0 : return false;
1340 : }
1341 0 : if (!context()->compartment()->wrap(context(), &keys))
1342 0 : return false;
1343 :
1344 0 : for (size_t i = keys.length(); i > 0; --i) {
1345 0 : if (!entries.append(keys[i - 1]))
1346 0 : return false;
1347 : }
1348 :
1349 : /* Push obj and count to the stack. */
1350 0 : if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length()))
1351 0 : return false;
1352 :
1353 0 : checkStack();
1354 :
1355 : /* Write the header for obj. */
1356 0 : return out.writePair(SCTAG_SET_OBJECT, 0);
1357 : }
1358 :
1359 : // Objects are written as a "preorder" traversal of the object graph: object
1360 : // "headers" (the class tag and any data needed for initial construction) are
1361 : // visited first, then the children are recursed through (where children are
1362 : // properties, Set or Map entries, etc.). So for example
1363 : //
1364 : // m = new Map();
1365 : // m.set(key1 = {}, value1 = {})
1366 : //
1367 : // would be stored as
1368 : //
1369 : // <Map tag>
1370 : // <key1 class tag>
1371 : // <value1 class tag>
1372 : // <end-of-children marker for key1>
1373 : // <end-of-children marker for value1>
1374 : // <end-of-children marker for Map>
1375 : //
1376 : // Notice how the end-of-children marker for key1 is sandwiched between the
1377 : // value1 beginning and end.
1378 : bool
1379 0 : JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj)
1380 : {
1381 0 : RootedObject unwrapped(context(), js::CheckedUnwrap(obj));
1382 0 : MOZ_ASSERT(unwrapped && unwrapped->is<SavedFrame>());
1383 :
1384 0 : RootedSavedFrame savedFrame(context(), &unwrapped->as<SavedFrame>());
1385 :
1386 0 : RootedObject parent(context(), savedFrame->getParent());
1387 0 : if (!context()->compartment()->wrap(context(), &parent))
1388 0 : return false;
1389 :
1390 0 : if (!objs.append(ObjectValue(*obj)) ||
1391 0 : !entries.append(parent ? ObjectValue(*parent) : NullValue()) ||
1392 0 : !counts.append(1))
1393 : {
1394 0 : return false;
1395 : }
1396 :
1397 0 : checkStack();
1398 :
1399 : // Write the SavedFrame tag and the SavedFrame's principals.
1400 :
1401 0 : if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsSystem) {
1402 0 : if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1403 : SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM))
1404 : {
1405 0 : return false;
1406 : };
1407 0 : } else if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsNotSystem) {
1408 0 : if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
1409 : SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM))
1410 : {
1411 0 : return false;
1412 : }
1413 : } else {
1414 0 : if (auto principals = savedFrame->getPrincipals()) {
1415 0 : if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
1416 0 : !principals->write(context(), this))
1417 : {
1418 0 : return false;
1419 : }
1420 : } else {
1421 0 : if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS))
1422 0 : return false;
1423 : }
1424 : }
1425 :
1426 : // Write the SavedFrame's reserved slots, except for the parent, which is
1427 : // queued on objs for further traversal.
1428 :
1429 0 : RootedValue val(context());
1430 :
1431 0 : context()->markAtom(savedFrame->getSource());
1432 0 : val = StringValue(savedFrame->getSource());
1433 0 : if (!startWrite(val))
1434 0 : return false;
1435 :
1436 0 : val = NumberValue(savedFrame->getLine());
1437 0 : if (!startWrite(val))
1438 0 : return false;
1439 :
1440 0 : val = NumberValue(savedFrame->getColumn());
1441 0 : if (!startWrite(val))
1442 0 : return false;
1443 :
1444 0 : auto name = savedFrame->getFunctionDisplayName();
1445 0 : if (name)
1446 0 : context()->markAtom(name);
1447 0 : val = name ? StringValue(name) : NullValue();
1448 0 : if (!startWrite(val))
1449 0 : return false;
1450 :
1451 0 : auto cause = savedFrame->getAsyncCause();
1452 0 : if (cause)
1453 0 : context()->markAtom(cause);
1454 0 : val = cause ? StringValue(cause) : NullValue();
1455 0 : if (!startWrite(val))
1456 0 : return false;
1457 :
1458 0 : return true;
1459 : }
1460 :
1461 : bool
1462 1106 : JSStructuredCloneWriter::startWrite(HandleValue v)
1463 : {
1464 1106 : assertSameCompartment(context(), v);
1465 :
1466 1106 : if (v.isString()) {
1467 660 : return writeString(SCTAG_STRING, v.toString());
1468 446 : } else if (v.isInt32()) {
1469 113 : return out.writePair(SCTAG_INT32, v.toInt32());
1470 333 : } else if (v.isDouble()) {
1471 17 : return out.writeDouble(v.toDouble());
1472 316 : } else if (v.isBoolean()) {
1473 76 : return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
1474 240 : } else if (v.isNull()) {
1475 75 : return out.writePair(SCTAG_NULL, 0);
1476 165 : } else if (v.isUndefined()) {
1477 11 : return out.writePair(SCTAG_UNDEFINED, 0);
1478 154 : } else if (v.isObject()) {
1479 154 : RootedObject obj(context(), &v.toObject());
1480 :
1481 : bool backref;
1482 154 : if (!startObject(obj, &backref))
1483 0 : return false;
1484 154 : if (backref)
1485 0 : return true;
1486 :
1487 : ESClass cls;
1488 154 : if (!GetBuiltinClass(context(), obj, &cls))
1489 0 : return false;
1490 :
1491 154 : if (cls == ESClass::RegExp) {
1492 0 : RegExpShared* re = RegExpToShared(context(), obj);
1493 0 : if (!re)
1494 0 : return false;
1495 0 : return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
1496 0 : writeString(SCTAG_STRING, re->getSource());
1497 154 : } else if (cls == ESClass::Date) {
1498 32 : RootedValue unboxed(context());
1499 16 : if (!Unbox(context(), obj, &unboxed))
1500 0 : return false;
1501 16 : return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
1502 138 : } else if (JS_IsTypedArrayObject(obj)) {
1503 0 : return writeTypedArray(obj);
1504 138 : } else if (JS_IsDataViewObject(obj)) {
1505 0 : return writeDataView(obj);
1506 138 : } else if (JS_IsArrayBufferObject(obj) && JS_ArrayBufferHasData(obj)) {
1507 0 : return writeArrayBuffer(obj);
1508 138 : } else if (JS_IsSharedArrayBufferObject(obj)) {
1509 0 : return writeSharedArrayBuffer(obj);
1510 138 : } else if (cls == ESClass::Object) {
1511 118 : return traverseObject(obj);
1512 20 : } else if (cls == ESClass::Array) {
1513 19 : return traverseObject(obj);
1514 1 : } else if (cls == ESClass::Boolean) {
1515 0 : RootedValue unboxed(context());
1516 0 : if (!Unbox(context(), obj, &unboxed))
1517 0 : return false;
1518 0 : return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
1519 1 : } else if (cls == ESClass::Number) {
1520 0 : RootedValue unboxed(context());
1521 0 : if (!Unbox(context(), obj, &unboxed))
1522 0 : return false;
1523 0 : return out.writePair(SCTAG_NUMBER_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
1524 1 : } else if (cls == ESClass::String) {
1525 0 : RootedValue unboxed(context());
1526 0 : if (!Unbox(context(), obj, &unboxed))
1527 0 : return false;
1528 0 : return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
1529 1 : } else if (cls == ESClass::Map) {
1530 0 : return traverseMap(obj);
1531 1 : } else if (cls == ESClass::Set) {
1532 0 : return traverseSet(obj);
1533 1 : } else if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
1534 0 : return traverseSavedFrame(obj);
1535 : }
1536 :
1537 1 : if (callbacks && callbacks->write)
1538 1 : return callbacks->write(context(), this, obj, closure);
1539 : /* else fall through */
1540 : }
1541 :
1542 0 : return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
1543 : }
1544 :
1545 : bool
1546 74 : JSStructuredCloneWriter::writeHeader()
1547 : {
1548 74 : return out.writePair(SCTAG_HEADER, (uint32_t)scope);
1549 : }
1550 :
1551 : bool
1552 74 : JSStructuredCloneWriter::writeTransferMap()
1553 : {
1554 74 : if (transferableObjects.empty())
1555 74 : return true;
1556 :
1557 0 : if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD))
1558 0 : return false;
1559 :
1560 0 : if (!out.write(transferableObjects.count()))
1561 0 : return false;
1562 :
1563 0 : RootedObject obj(context());
1564 0 : for (auto tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1565 0 : obj = tr.front();
1566 0 : if (!memory.put(obj, memory.count())) {
1567 0 : ReportOutOfMemory(context());
1568 0 : return false;
1569 : }
1570 :
1571 : // Emit a placeholder pointer. We defer stealing the data until later
1572 : // (and, if necessary, detaching this object if it's an ArrayBuffer).
1573 0 : if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED))
1574 0 : return false;
1575 0 : if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents.
1576 0 : return false;
1577 0 : if (!out.write(0)) // extraData
1578 0 : return false;
1579 : }
1580 :
1581 0 : return true;
1582 : }
1583 :
1584 : bool
1585 74 : JSStructuredCloneWriter::transferOwnership()
1586 : {
1587 74 : if (transferableObjects.empty())
1588 74 : return true;
1589 :
1590 : // Walk along the transferables and the transfer map at the same time,
1591 : // grabbing out pointers from the transferables and stuffing them into the
1592 : // transfer map.
1593 0 : auto point = out.iter();
1594 0 : MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) == SCTAG_HEADER);
1595 0 : point++;
1596 0 : MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) == SCTAG_TRANSFER_MAP_HEADER);
1597 0 : point++;
1598 0 : MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) == transferableObjects.count());
1599 0 : point++;
1600 :
1601 0 : JSContext* cx = context();
1602 0 : RootedObject obj(cx);
1603 0 : for (auto tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
1604 0 : obj = tr.front();
1605 :
1606 : uint32_t tag;
1607 : JS::TransferableOwnership ownership;
1608 : void* content;
1609 : uint64_t extraData;
1610 :
1611 : #if DEBUG
1612 0 : SCInput::getPair(point.peek(), &tag, (uint32_t*) &ownership);
1613 0 : MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1614 0 : MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
1615 : #endif
1616 :
1617 : ESClass cls;
1618 0 : if (!GetBuiltinClass(cx, obj, &cls))
1619 0 : return false;
1620 :
1621 0 : if (cls == ESClass::ArrayBuffer) {
1622 0 : tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
1623 :
1624 : // The current setup of the array buffer inheritance hierarchy doesn't
1625 : // lend itself well to generic manipulation via proxies.
1626 0 : Rooted<ArrayBufferObject*> arrayBuffer(cx, &CheckedUnwrap(obj)->as<ArrayBufferObject>());
1627 0 : JSAutoCompartment ac(cx, arrayBuffer);
1628 0 : size_t nbytes = arrayBuffer->byteLength();
1629 :
1630 0 : if (arrayBuffer->isWasm() || arrayBuffer->isPreparedForAsmJS()) {
1631 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
1632 0 : return false;
1633 : }
1634 :
1635 0 : if (scope == JS::StructuredCloneScope::DifferentProcess) {
1636 : // Write Transferred ArrayBuffers in DifferentProcess scope at
1637 : // the end of the clone buffer, and store the offset within the
1638 : // buffer to where the ArrayBuffer was written. Note that this
1639 : // will invalidate the current position iterator.
1640 :
1641 0 : size_t pointOffset = out.offset(point);
1642 0 : tag = SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER;
1643 0 : ownership = JS::SCTAG_TMO_UNOWNED;
1644 0 : content = nullptr;
1645 0 : extraData = out.tell() - pointOffset; // Offset from tag to current end of buffer
1646 0 : if (!writeArrayBuffer(arrayBuffer))
1647 0 : return false;
1648 :
1649 : // Must refresh the point iterator after its collection has
1650 : // been modified.
1651 0 : point = out.iter();
1652 0 : point += pointOffset;
1653 :
1654 0 : if (!JS_DetachArrayBuffer(cx, arrayBuffer))
1655 0 : return false;
1656 : } else {
1657 0 : bool hasStealableContents = arrayBuffer->hasStealableContents();
1658 :
1659 : ArrayBufferObject::BufferContents bufContents =
1660 0 : ArrayBufferObject::stealContents(cx, arrayBuffer, hasStealableContents);
1661 0 : if (!bufContents)
1662 0 : return false; // already transferred data
1663 :
1664 0 : content = bufContents.data();
1665 0 : if (bufContents.kind() == ArrayBufferObject::MAPPED)
1666 0 : ownership = JS::SCTAG_TMO_MAPPED_DATA;
1667 : else
1668 0 : ownership = JS::SCTAG_TMO_ALLOC_DATA;
1669 0 : extraData = nbytes;
1670 : }
1671 : } else {
1672 0 : if (!callbacks || !callbacks->writeTransfer)
1673 0 : return reportDataCloneError(JS_SCERR_TRANSFERABLE);
1674 0 : if (!callbacks->writeTransfer(cx, obj, closure, &tag, &ownership, &content, &extraData))
1675 0 : return false;
1676 0 : MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
1677 : }
1678 :
1679 0 : point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
1680 0 : point.next();
1681 0 : point.write(NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content)));
1682 0 : point.next();
1683 0 : point.write(NativeEndian::swapToLittleEndian(extraData));
1684 0 : point.next();
1685 : }
1686 :
1687 : #if DEBUG
1688 : // Make sure there aren't any more transfer map entries after the expected
1689 : // number we read out.
1690 0 : if (!point.done()) {
1691 : uint32_t tag, data;
1692 0 : SCInput::getPair(point.peek(), &tag, &data);
1693 0 : MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER || tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
1694 : }
1695 : #endif
1696 0 : return true;
1697 : }
1698 :
1699 : bool
1700 74 : JSStructuredCloneWriter::write(HandleValue v)
1701 : {
1702 74 : if (!startWrite(v))
1703 0 : return false;
1704 :
1705 1380 : while (!counts.empty()) {
1706 1306 : RootedObject obj(context(), &objs.back().toObject());
1707 1306 : AutoCompartment ac(context(), obj);
1708 653 : if (counts.back()) {
1709 516 : counts.back()--;
1710 1032 : RootedValue key(context(), entries.back());
1711 516 : entries.popBack();
1712 516 : checkStack();
1713 :
1714 : ESClass cls;
1715 516 : if (!GetBuiltinClass(context(), obj, &cls))
1716 0 : return false;
1717 :
1718 516 : if (cls == ESClass::Map) {
1719 0 : counts.back()--;
1720 0 : RootedValue val(context(), entries.back());
1721 0 : entries.popBack();
1722 0 : checkStack();
1723 :
1724 0 : if (!startWrite(key) || !startWrite(val))
1725 0 : return false;
1726 516 : } else if (cls == ESClass::Set || SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
1727 0 : if (!startWrite(key))
1728 0 : return false;
1729 : } else {
1730 1032 : RootedId id(context());
1731 516 : if (!ValueToId<CanGC>(context(), key, &id))
1732 0 : return false;
1733 516 : MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
1734 :
1735 : /*
1736 : * If obj still has an own property named id, write it out.
1737 : * The cost of re-checking could be avoided by using
1738 : * NativeIterators.
1739 : */
1740 : bool found;
1741 516 : if (!HasOwnProperty(context(), obj, id, &found))
1742 0 : return false;
1743 :
1744 516 : if (found) {
1745 1032 : RootedValue val(context());
1746 4644 : if (!startWrite(key) ||
1747 5160 : !GetProperty(context(), obj, obj, id, &val) ||
1748 1548 : !startWrite(val))
1749 : {
1750 0 : return false;
1751 : }
1752 : }
1753 : }
1754 : } else {
1755 137 : if (!out.writePair(SCTAG_END_OF_KEYS, 0))
1756 0 : return false;
1757 137 : objs.popBack();
1758 137 : counts.popBack();
1759 : }
1760 : }
1761 :
1762 74 : memory.clear();
1763 74 : return transferOwnership();
1764 : }
1765 :
1766 : bool
1767 33 : JSStructuredCloneReader::checkDouble(double d)
1768 : {
1769 33 : if (!JS::IsCanonicalized(d)) {
1770 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
1771 0 : "unrecognized NaN");
1772 0 : return false;
1773 : }
1774 33 : return true;
1775 : }
1776 :
1777 : namespace {
1778 :
1779 : template <typename CharT>
1780 : class Chars {
1781 : JSContext* cx;
1782 : CharT* p;
1783 : public:
1784 619 : explicit Chars(JSContext* cx) : cx(cx), p(nullptr) {}
1785 619 : ~Chars() { js_free(p); }
1786 :
1787 619 : bool allocate(size_t len) {
1788 619 : MOZ_ASSERT(!p);
1789 : // We're going to null-terminate!
1790 619 : p = cx->pod_malloc<CharT>(len + 1);
1791 619 : if (p) {
1792 619 : p[len] = CharT(0);
1793 619 : return true;
1794 : }
1795 0 : return false;
1796 : }
1797 1238 : CharT* get() { return p; }
1798 619 : void forget() { p = nullptr; }
1799 : };
1800 :
1801 : } /* anonymous namespace */
1802 :
1803 : template <typename CharT>
1804 : JSString*
1805 619 : JSStructuredCloneReader::readStringImpl(uint32_t nchars)
1806 : {
1807 619 : if (nchars > JSString::MAX_LENGTH) {
1808 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
1809 : "string length");
1810 0 : return nullptr;
1811 : }
1812 1238 : Chars<CharT> chars(context());
1813 619 : if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
1814 0 : return nullptr;
1815 619 : JSString* str = NewString<CanGC>(context(), chars.get(), nchars);
1816 619 : if (str)
1817 619 : chars.forget();
1818 619 : return str;
1819 : }
1820 :
1821 : JSString*
1822 619 : JSStructuredCloneReader::readString(uint32_t data)
1823 : {
1824 619 : uint32_t nchars = data & JS_BITMASK(31);
1825 619 : bool latin1 = data & (1 << 31);
1826 619 : return latin1 ? readStringImpl<Latin1Char>(nchars) : readStringImpl<char16_t>(nchars);
1827 : }
1828 :
1829 : static uint32_t
1830 0 : TagToV1ArrayType(uint32_t tag)
1831 : {
1832 0 : MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX);
1833 0 : return tag - SCTAG_TYPED_ARRAY_V1_MIN;
1834 : }
1835 :
1836 : bool
1837 0 : JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp,
1838 : bool v1Read)
1839 : {
1840 0 : if (arrayType > Scalar::Uint8Clamped) {
1841 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
1842 0 : "unhandled typed array element type");
1843 0 : return false;
1844 : }
1845 :
1846 : // Push a placeholder onto the allObjs list to stand in for the typed array
1847 0 : uint32_t placeholderIndex = allObjs.length();
1848 0 : Value dummy = UndefinedValue();
1849 0 : if (!allObjs.append(dummy))
1850 0 : return false;
1851 :
1852 : // Read the ArrayBuffer object and its contents (but no properties)
1853 0 : RootedValue v(context());
1854 : uint32_t byteOffset;
1855 0 : if (v1Read) {
1856 0 : if (!readV1ArrayBuffer(arrayType, nelems, &v))
1857 0 : return false;
1858 0 : byteOffset = 0;
1859 : } else {
1860 0 : if (!startRead(&v))
1861 0 : return false;
1862 : uint64_t n;
1863 0 : if (!in.read(&n))
1864 0 : return false;
1865 0 : byteOffset = n;
1866 : }
1867 0 : RootedObject buffer(context(), &v.toObject());
1868 0 : RootedObject obj(context(), nullptr);
1869 :
1870 0 : switch (arrayType) {
1871 : case Scalar::Int8:
1872 0 : obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1873 0 : break;
1874 : case Scalar::Uint8:
1875 0 : obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1876 0 : break;
1877 : case Scalar::Int16:
1878 0 : obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1879 0 : break;
1880 : case Scalar::Uint16:
1881 0 : obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1882 0 : break;
1883 : case Scalar::Int32:
1884 0 : obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1885 0 : break;
1886 : case Scalar::Uint32:
1887 0 : obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1888 0 : break;
1889 : case Scalar::Float32:
1890 0 : obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1891 0 : break;
1892 : case Scalar::Float64:
1893 0 : obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
1894 0 : break;
1895 : case Scalar::Uint8Clamped:
1896 0 : obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems);
1897 0 : break;
1898 : default:
1899 0 : MOZ_CRASH("Can't happen: arrayType range checked above");
1900 : }
1901 :
1902 0 : if (!obj)
1903 0 : return false;
1904 0 : vp.setObject(*obj);
1905 :
1906 0 : allObjs[placeholderIndex].set(vp);
1907 :
1908 0 : return true;
1909 : }
1910 :
1911 : bool
1912 0 : JSStructuredCloneReader::readDataView(uint32_t byteLength, MutableHandleValue vp)
1913 : {
1914 : // Push a placeholder onto the allObjs list to stand in for the DataView.
1915 0 : uint32_t placeholderIndex = allObjs.length();
1916 0 : Value dummy = UndefinedValue();
1917 0 : if (!allObjs.append(dummy))
1918 0 : return false;
1919 :
1920 : // Read the ArrayBuffer object and its contents (but no properties).
1921 0 : RootedValue v(context());
1922 0 : if (!startRead(&v))
1923 0 : return false;
1924 :
1925 : // Read byteOffset.
1926 : uint64_t n;
1927 0 : if (!in.read(&n))
1928 0 : return false;
1929 0 : uint32_t byteOffset = n;
1930 :
1931 0 : RootedObject buffer(context(), &v.toObject());
1932 0 : RootedObject obj(context(), JS_NewDataView(context(), buffer, byteOffset, byteLength));
1933 0 : if (!obj)
1934 0 : return false;
1935 0 : vp.setObject(*obj);
1936 :
1937 0 : allObjs[placeholderIndex].set(vp);
1938 :
1939 0 : return true;
1940 : }
1941 :
1942 : bool
1943 0 : JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, MutableHandleValue vp)
1944 : {
1945 0 : JSObject* obj = ArrayBufferObject::create(context(), nbytes);
1946 0 : if (!obj)
1947 0 : return false;
1948 0 : vp.setObject(*obj);
1949 0 : ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
1950 0 : MOZ_ASSERT(buffer.byteLength() == nbytes);
1951 0 : return in.readArray(buffer.dataPointer(), nbytes);
1952 : }
1953 :
1954 : bool
1955 0 : JSStructuredCloneReader::readSharedArrayBuffer(uint32_t nbytes, MutableHandleValue vp)
1956 : {
1957 : intptr_t p;
1958 0 : in.readBytes(&p, sizeof(p));
1959 :
1960 0 : SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p);
1961 :
1962 : // There's no guarantee that the receiving agent has enabled shared memory
1963 : // even if the transmitting agent has done so. Ideally we'd check at the
1964 : // transmission point, but that's tricky, and it will be a very rare problem
1965 : // in any case. Just fail at the receiving end if we can't handle it.
1966 :
1967 0 : if (!context()->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
1968 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_SAB_DISABLED);
1969 0 : return false;
1970 : }
1971 :
1972 : // We must not transfer buffer pointers cross-process. The cloneDataPolicy
1973 : // in the sender should guard against this; check that it does.
1974 :
1975 0 : MOZ_RELEASE_ASSERT(storedScope <= JS::StructuredCloneScope::SameProcessDifferentThread);
1976 :
1977 : // The new object will have a new reference to the rawbuf.
1978 :
1979 0 : if (!rawbuf->addReference()) {
1980 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_SAB_REFCNT_OFLO);
1981 0 : return false;
1982 : }
1983 :
1984 0 : JSObject* obj = SharedArrayBufferObject::New(context(), rawbuf);
1985 :
1986 0 : if (!obj) {
1987 0 : rawbuf->dropReference();
1988 0 : return false;
1989 : }
1990 :
1991 0 : vp.setObject(*obj);
1992 0 : return true;
1993 : }
1994 :
1995 : /*
1996 : * Read in the data for a structured clone version 1 ArrayBuffer, performing
1997 : * endianness-conversion while reading.
1998 : */
1999 : bool
2000 0 : JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
2001 : MutableHandleValue vp)
2002 : {
2003 0 : MOZ_ASSERT(arrayType <= Scalar::Uint8Clamped);
2004 :
2005 0 : uint32_t nbytes = nelems << TypedArrayShift(static_cast<Scalar::Type>(arrayType));
2006 0 : JSObject* obj = ArrayBufferObject::create(context(), nbytes);
2007 0 : if (!obj)
2008 0 : return false;
2009 0 : vp.setObject(*obj);
2010 0 : ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
2011 0 : MOZ_ASSERT(buffer.byteLength() == nbytes);
2012 :
2013 0 : switch (arrayType) {
2014 : case Scalar::Int8:
2015 : case Scalar::Uint8:
2016 : case Scalar::Uint8Clamped:
2017 0 : return in.readArray((uint8_t*) buffer.dataPointer(), nelems);
2018 : case Scalar::Int16:
2019 : case Scalar::Uint16:
2020 0 : return in.readArray((uint16_t*) buffer.dataPointer(), nelems);
2021 : case Scalar::Int32:
2022 : case Scalar::Uint32:
2023 : case Scalar::Float32:
2024 0 : return in.readArray((uint32_t*) buffer.dataPointer(), nelems);
2025 : case Scalar::Float64:
2026 0 : return in.readArray((uint64_t*) buffer.dataPointer(), nelems);
2027 : default:
2028 0 : MOZ_CRASH("Can't happen: arrayType range checked by caller");
2029 : }
2030 : }
2031 :
2032 : static bool
2033 0 : PrimitiveToObject(JSContext* cx, MutableHandleValue vp)
2034 : {
2035 0 : JSObject* obj = js::PrimitiveToObject(cx, vp);
2036 0 : if (!obj)
2037 0 : return false;
2038 :
2039 0 : vp.setObject(*obj);
2040 0 : return true;
2041 : }
2042 :
2043 : bool
2044 1038 : JSStructuredCloneReader::startRead(MutableHandleValue vp)
2045 : {
2046 : uint32_t tag, data;
2047 :
2048 1038 : if (!in.readPair(&tag, &data))
2049 0 : return false;
2050 :
2051 1038 : switch (tag) {
2052 : case SCTAG_NULL:
2053 71 : vp.setNull();
2054 71 : break;
2055 :
2056 : case SCTAG_UNDEFINED:
2057 11 : vp.setUndefined();
2058 11 : break;
2059 :
2060 : case SCTAG_INT32:
2061 102 : vp.setInt32(data);
2062 102 : break;
2063 :
2064 : case SCTAG_BOOLEAN:
2065 : case SCTAG_BOOLEAN_OBJECT:
2066 73 : vp.setBoolean(!!data);
2067 73 : if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp))
2068 0 : return false;
2069 73 : break;
2070 :
2071 : case SCTAG_STRING:
2072 : case SCTAG_STRING_OBJECT: {
2073 619 : JSString* str = readString(data);
2074 619 : if (!str)
2075 0 : return false;
2076 619 : vp.setString(str);
2077 619 : if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp))
2078 0 : return false;
2079 619 : break;
2080 : }
2081 :
2082 : case SCTAG_NUMBER_OBJECT: {
2083 : double d;
2084 0 : if (!in.readDouble(&d) || !checkDouble(d))
2085 0 : return false;
2086 0 : vp.setDouble(d);
2087 0 : if (!PrimitiveToObject(context(), vp))
2088 0 : return false;
2089 0 : break;
2090 : }
2091 :
2092 : case SCTAG_DATE_OBJECT: {
2093 : double d;
2094 16 : if (!in.readDouble(&d) || !checkDouble(d))
2095 0 : return false;
2096 16 : JS::ClippedTime t = JS::TimeClip(d);
2097 16 : if (!NumbersAreIdentical(d, t.toDouble())) {
2098 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2099 : JSMSG_SC_BAD_SERIALIZED_DATA,
2100 0 : "date");
2101 0 : return false;
2102 : }
2103 16 : JSObject* obj = NewDateObjectMsec(context(), t);
2104 16 : if (!obj)
2105 0 : return false;
2106 16 : vp.setObject(*obj);
2107 16 : break;
2108 : }
2109 :
2110 : case SCTAG_REGEXP_OBJECT: {
2111 0 : RegExpFlag flags = RegExpFlag(data);
2112 : uint32_t tag2, stringData;
2113 0 : if (!in.readPair(&tag2, &stringData))
2114 0 : return false;
2115 0 : if (tag2 != SCTAG_STRING) {
2116 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2117 : JSMSG_SC_BAD_SERIALIZED_DATA,
2118 0 : "regexp");
2119 0 : return false;
2120 : }
2121 0 : JSString* str = readString(stringData);
2122 0 : if (!str)
2123 0 : return false;
2124 :
2125 0 : RootedAtom atom(context(), AtomizeString(context(), str));
2126 0 : if (!atom)
2127 0 : return false;
2128 :
2129 0 : RegExpObject* reobj = RegExpObject::create(context(), atom, flags, nullptr, nullptr,
2130 0 : context()->tempLifoAlloc(), GenericObject);
2131 0 : if (!reobj)
2132 0 : return false;
2133 0 : vp.setObject(*reobj);
2134 0 : break;
2135 : }
2136 :
2137 : case SCTAG_ARRAY_OBJECT:
2138 : case SCTAG_OBJECT_OBJECT: {
2139 128 : JSObject* obj = (tag == SCTAG_ARRAY_OBJECT)
2140 384 : ? (JSObject*) NewDenseEmptyArray(context())
2141 348 : : (JSObject*) NewBuiltinClassInstance<PlainObject>(context());
2142 128 : if (!obj || !objs.append(ObjectValue(*obj)))
2143 0 : return false;
2144 128 : vp.setObject(*obj);
2145 128 : break;
2146 : }
2147 :
2148 : case SCTAG_BACK_REFERENCE_OBJECT: {
2149 0 : if (data >= allObjs.length() || !allObjs[data].isObject()) {
2150 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2151 : JSMSG_SC_BAD_SERIALIZED_DATA,
2152 0 : "invalid back reference in input");
2153 0 : return false;
2154 : }
2155 0 : vp.set(allObjs[data]);
2156 0 : return true;
2157 : }
2158 :
2159 : case SCTAG_TRANSFER_MAP_HEADER:
2160 : case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
2161 : // We should be past all the transfer map tags.
2162 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
2163 0 : "invalid input");
2164 0 : return false;
2165 :
2166 : case SCTAG_ARRAY_BUFFER_OBJECT:
2167 0 : if (!readArrayBuffer(data, vp))
2168 0 : return false;
2169 0 : break;
2170 :
2171 : case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
2172 0 : if (!readSharedArrayBuffer(data, vp))
2173 0 : return false;
2174 0 : break;
2175 :
2176 : case SCTAG_TYPED_ARRAY_OBJECT: {
2177 : // readTypedArray adds the array to allObjs.
2178 : uint64_t arrayType;
2179 0 : if (!in.read(&arrayType))
2180 0 : return false;
2181 0 : return readTypedArray(arrayType, data, vp);
2182 : }
2183 :
2184 : case SCTAG_DATA_VIEW_OBJECT: {
2185 : // readDataView adds the array to allObjs.
2186 0 : return readDataView(data, vp);
2187 : }
2188 :
2189 : case SCTAG_MAP_OBJECT: {
2190 0 : JSObject* obj = MapObject::create(context());
2191 0 : if (!obj || !objs.append(ObjectValue(*obj)))
2192 0 : return false;
2193 0 : vp.setObject(*obj);
2194 0 : break;
2195 : }
2196 :
2197 : case SCTAG_SET_OBJECT: {
2198 0 : JSObject* obj = SetObject::create(context());
2199 0 : if (!obj || !objs.append(ObjectValue(*obj)))
2200 0 : return false;
2201 0 : vp.setObject(*obj);
2202 0 : break;
2203 : }
2204 :
2205 : case SCTAG_SAVED_FRAME_OBJECT: {
2206 0 : auto obj = readSavedFrame(data);
2207 0 : if (!obj || !objs.append(ObjectValue(*obj)))
2208 0 : return false;
2209 0 : vp.setObject(*obj);
2210 0 : break;
2211 : }
2212 :
2213 : default: {
2214 18 : if (tag <= SCTAG_FLOAT_MAX) {
2215 17 : double d = ReinterpretPairAsDouble(tag, data);
2216 17 : if (!checkDouble(d))
2217 0 : return false;
2218 17 : vp.setNumber(d);
2219 17 : break;
2220 : }
2221 :
2222 1 : if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
2223 : // A v1-format typed array
2224 : // readTypedArray adds the array to allObjs
2225 0 : return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
2226 : }
2227 :
2228 1 : if (!callbacks || !callbacks->read) {
2229 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2230 : JSMSG_SC_BAD_SERIALIZED_DATA,
2231 0 : "unsupported type");
2232 0 : return false;
2233 : }
2234 1 : JSObject* obj = callbacks->read(context(), this, tag, data, closure);
2235 1 : if (!obj)
2236 0 : return false;
2237 1 : vp.setObject(*obj);
2238 : }
2239 : }
2240 :
2241 1038 : if (vp.isObject() && !allObjs.append(vp))
2242 0 : return false;
2243 :
2244 1038 : return true;
2245 : }
2246 :
2247 : bool
2248 72 : JSStructuredCloneReader::readHeader()
2249 : {
2250 : uint32_t tag, data;
2251 72 : if (!in.getPair(&tag, &data))
2252 0 : return in.reportTruncated();
2253 :
2254 72 : if (tag != SCTAG_HEADER) {
2255 : // Old structured clone buffer. We must have read it from disk or
2256 : // somewhere, so we can assume it's scope-compatible.
2257 0 : return true;
2258 : }
2259 :
2260 72 : MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2261 72 : storedScope = JS::StructuredCloneScope(data);
2262 72 : if (storedScope < allowedScope) {
2263 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
2264 0 : "incompatible structured clone scope");
2265 0 : return false;
2266 : }
2267 :
2268 72 : return true;
2269 : }
2270 :
2271 : bool
2272 72 : JSStructuredCloneReader::readTransferMap()
2273 : {
2274 72 : JSContext* cx = context();
2275 72 : auto headerPos = in.tell();
2276 :
2277 : uint32_t tag, data;
2278 72 : if (!in.getPair(&tag, &data))
2279 0 : return in.reportTruncated();
2280 :
2281 72 : if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
2282 72 : return true;
2283 :
2284 : uint64_t numTransferables;
2285 0 : MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2286 0 : if (!in.read(&numTransferables))
2287 0 : return false;
2288 :
2289 0 : for (uint64_t i = 0; i < numTransferables; i++) {
2290 0 : auto pos = in.tell();
2291 :
2292 0 : if (!in.readPair(&tag, &data))
2293 0 : return false;
2294 :
2295 0 : MOZ_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY);
2296 0 : RootedObject obj(cx);
2297 :
2298 : void* content;
2299 0 : if (!in.readPtr(&content))
2300 0 : return false;
2301 :
2302 : uint64_t extraData;
2303 0 : if (!in.read(&extraData))
2304 0 : return false;
2305 :
2306 0 : if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
2307 0 : if (storedScope == JS::StructuredCloneScope::DifferentProcess) {
2308 : // Transferred ArrayBuffers in a DifferentProcess clone buffer
2309 : // are treated as if they weren't Transferred at all.
2310 0 : continue;
2311 : }
2312 :
2313 0 : size_t nbytes = extraData;
2314 0 : MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
2315 : data == JS::SCTAG_TMO_MAPPED_DATA);
2316 0 : if (data == JS::SCTAG_TMO_ALLOC_DATA)
2317 0 : obj = JS_NewArrayBufferWithContents(cx, nbytes, content);
2318 0 : else if (data == JS::SCTAG_TMO_MAPPED_DATA)
2319 0 : obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content);
2320 0 : } else if (tag == SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER) {
2321 0 : auto savedPos = in.tell();
2322 0 : auto guard = mozilla::MakeScopeExit([&] {
2323 0 : in.seekTo(savedPos);
2324 0 : });
2325 0 : in.seekTo(pos);
2326 0 : in.seekBy(static_cast<size_t>(extraData));
2327 :
2328 : uint32_t tag, data;
2329 0 : if (!in.readPair(&tag, &data))
2330 0 : return false;
2331 0 : MOZ_ASSERT(tag == SCTAG_ARRAY_BUFFER_OBJECT);
2332 0 : RootedValue val(cx);
2333 0 : if (!readArrayBuffer(data, &val))
2334 0 : return false;
2335 0 : obj = &val.toObject();
2336 : } else {
2337 0 : if (!callbacks || !callbacks->readTransfer) {
2338 0 : ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE);
2339 0 : return false;
2340 : }
2341 0 : if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj))
2342 0 : return false;
2343 0 : MOZ_ASSERT(obj);
2344 0 : MOZ_ASSERT(!cx->isExceptionPending());
2345 : }
2346 :
2347 : // On failure, the buffer will still own the data (since its ownership
2348 : // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
2349 : // DiscardTransferables.
2350 0 : if (!obj)
2351 0 : return false;
2352 :
2353 : // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
2354 : // buffer.
2355 0 : pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED));
2356 0 : MOZ_ASSERT(!pos.done());
2357 :
2358 0 : if (!allObjs.append(ObjectValue(*obj)))
2359 0 : return false;
2360 : }
2361 :
2362 : // Mark the whole transfer map as consumed.
2363 : #ifdef DEBUG
2364 0 : SCInput::getPair(headerPos.peek(), &tag, &data);
2365 0 : MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
2366 0 : MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
2367 : #endif
2368 0 : headerPos.write(PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
2369 :
2370 0 : return true;
2371 : }
2372 :
2373 : JSObject*
2374 0 : JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag)
2375 : {
2376 0 : RootedSavedFrame savedFrame(context(), SavedFrame::create(context()));
2377 0 : if (!savedFrame)
2378 0 : return nullptr;
2379 :
2380 : JSPrincipals* principals;
2381 0 : if (principalsTag == SCTAG_JSPRINCIPALS) {
2382 0 : if (!context()->runtime()->readPrincipals) {
2383 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
2384 0 : JSMSG_SC_UNSUPPORTED_TYPE);
2385 0 : return nullptr;
2386 : }
2387 :
2388 0 : if (!context()->runtime()->readPrincipals(context(), this, &principals))
2389 0 : return nullptr;
2390 0 : } else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
2391 0 : principals = &ReconstructedSavedFramePrincipals::IsSystem;
2392 0 : principals->refcount++;
2393 0 : } else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
2394 0 : principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
2395 0 : principals->refcount++;
2396 0 : } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
2397 0 : principals = nullptr;
2398 : } else {
2399 0 : JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
2400 0 : "bad SavedFrame principals");
2401 0 : return nullptr;
2402 : }
2403 0 : savedFrame->initPrincipalsAlreadyHeld(principals);
2404 :
2405 0 : RootedValue source(context());
2406 0 : if (!startRead(&source) || !source.isString())
2407 0 : return nullptr;
2408 0 : auto atomSource = AtomizeString(context(), source.toString());
2409 0 : if (!atomSource)
2410 0 : return nullptr;
2411 0 : savedFrame->initSource(atomSource);
2412 :
2413 0 : RootedValue lineVal(context());
2414 : uint32_t line;
2415 0 : if (!startRead(&lineVal) || !lineVal.isNumber() || !ToUint32(context(), lineVal, &line))
2416 0 : return nullptr;
2417 0 : savedFrame->initLine(line);
2418 :
2419 0 : RootedValue columnVal(context());
2420 : uint32_t column;
2421 0 : if (!startRead(&columnVal) || !columnVal.isNumber() || !ToUint32(context(), columnVal, &column))
2422 0 : return nullptr;
2423 0 : savedFrame->initColumn(column);
2424 :
2425 0 : RootedValue name(context());
2426 0 : if (!startRead(&name) || !(name.isString() || name.isNull()))
2427 0 : return nullptr;
2428 0 : JSAtom* atomName = nullptr;
2429 0 : if (name.isString()) {
2430 0 : atomName = AtomizeString(context(), name.toString());
2431 0 : if (!atomName)
2432 0 : return nullptr;
2433 : }
2434 :
2435 0 : savedFrame->initFunctionDisplayName(atomName);
2436 :
2437 0 : RootedValue cause(context());
2438 0 : if (!startRead(&cause) || !(cause.isString() || cause.isNull()))
2439 0 : return nullptr;
2440 0 : JSAtom* atomCause = nullptr;
2441 0 : if (cause.isString()) {
2442 0 : atomCause = AtomizeString(context(), cause.toString());
2443 0 : if (!atomCause)
2444 0 : return nullptr;
2445 : }
2446 0 : savedFrame->initAsyncCause(atomCause);
2447 :
2448 0 : return savedFrame;
2449 : }
2450 :
2451 : // Perform the whole recursive reading procedure.
2452 : bool
2453 72 : JSStructuredCloneReader::read(MutableHandleValue vp)
2454 : {
2455 72 : if (!readHeader())
2456 0 : return false;
2457 :
2458 72 : if (!readTransferMap())
2459 0 : return false;
2460 :
2461 : // Start out by reading in the main object and pushing it onto the 'objs'
2462 : // stack. The data related to this object and its descendants extends from
2463 : // here to the SCTAG_END_OF_KEYS at the end of the stream.
2464 72 : if (!startRead(vp))
2465 0 : return false;
2466 :
2467 : // Stop when the stack shows that all objects have been read.
2468 1294 : while (objs.length() != 0) {
2469 : // What happens depends on the top obj on the objs stack.
2470 1094 : RootedObject obj(context(), &objs.back().toObject());
2471 :
2472 : uint32_t tag, data;
2473 611 : if (!in.getPair(&tag, &data))
2474 0 : return false;
2475 :
2476 611 : if (tag == SCTAG_END_OF_KEYS) {
2477 : // Pop the current obj off the stack, since we are done with it and
2478 : // its children.
2479 128 : MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
2480 128 : objs.popBack();
2481 128 : continue;
2482 : }
2483 :
2484 : // The input stream contains a sequence of "child" values, whose
2485 : // interpretation depends on the type of obj. These values can be
2486 : // anything, and startRead() will push onto 'objs' for any non-leaf
2487 : // value (i.e., anything that may contain children).
2488 : //
2489 : // startRead() will allocate the (empty) object, but note that when
2490 : // startRead() returns, 'key' is not yet initialized with any of its
2491 : // properties. Those will be filled in by returning to the head of this
2492 : // loop, processing the first child obj, and continuing until all
2493 : // children have been fully created.
2494 : //
2495 : // Note that this means the ordering in the stream is a little funky
2496 : // for things like Map. See the comment above startWrite() for an
2497 : // example.
2498 966 : RootedValue key(context());
2499 483 : if (!startRead(&key))
2500 0 : return false;
2501 :
2502 483 : if (key.isNull() &&
2503 0 : !(obj->is<MapObject>() || obj->is<SetObject>() || obj->is<SavedFrame>()))
2504 : {
2505 : // Backwards compatibility: Null formerly indicated the end of
2506 : // object properties.
2507 0 : objs.popBack();
2508 0 : continue;
2509 : }
2510 :
2511 : // Set object: the values between obj header (from startRead()) and
2512 : // SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
2513 483 : if (obj->is<SetObject>()) {
2514 0 : if (!SetObject::add(context(), obj, key))
2515 0 : return false;
2516 0 : continue;
2517 : }
2518 :
2519 : // SavedFrame object: there is one following value, the parent
2520 : // SavedFrame, which is either null or another SavedFrame object.
2521 483 : if (obj->is<SavedFrame>()) {
2522 : SavedFrame* parentFrame;
2523 0 : if (key.isNull())
2524 0 : parentFrame = nullptr;
2525 0 : else if (key.isObject() && key.toObject().is<SavedFrame>())
2526 0 : parentFrame = &key.toObject().as<SavedFrame>();
2527 : else
2528 0 : return false;
2529 :
2530 0 : obj->as<SavedFrame>().initParent(parentFrame);
2531 0 : continue;
2532 : }
2533 :
2534 : // Everything else uses a series of key,value,key,value,... Value
2535 : // objects.
2536 966 : RootedValue val(context());
2537 483 : if (!startRead(&val))
2538 0 : return false;
2539 :
2540 483 : if (obj->is<MapObject>()) {
2541 : // For a Map, store those <key,value> pairs in the contained map
2542 : // data structure.
2543 0 : if (!MapObject::set(context(), obj, key, val))
2544 0 : return false;
2545 : } else {
2546 : // For any other Object, interpret them as plain properties.
2547 966 : RootedId id(context());
2548 483 : if (!ValueToId<CanGC>(context(), key, &id))
2549 0 : return false;
2550 :
2551 483 : if (!DefineProperty(context(), obj, id, val))
2552 0 : return false;
2553 : }
2554 : }
2555 :
2556 72 : allObjs.clear();
2557 :
2558 72 : return true;
2559 : }
2560 :
2561 : using namespace js;
2562 :
2563 : JS_PUBLIC_API(bool)
2564 72 : JS_ReadStructuredClone(JSContext* cx, JSStructuredCloneData& buf,
2565 : uint32_t version, JS::StructuredCloneScope scope,
2566 : MutableHandleValue vp,
2567 : const JSStructuredCloneCallbacks* optionalCallbacks,
2568 : void* closure)
2569 : {
2570 72 : AssertHeapIsIdle();
2571 144 : CHECK_REQUEST(cx);
2572 :
2573 72 : if (version > JS_STRUCTURED_CLONE_VERSION) {
2574 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_CLONE_VERSION);
2575 0 : return false;
2576 : }
2577 72 : const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
2578 72 : return ReadStructuredClone(cx, buf, scope, vp, callbacks, closure);
2579 : }
2580 :
2581 : JS_PUBLIC_API(bool)
2582 74 : JS_WriteStructuredClone(JSContext* cx, HandleValue value, JSStructuredCloneData* bufp,
2583 : JS::StructuredCloneScope scope,
2584 : JS::CloneDataPolicy cloneDataPolicy,
2585 : const JSStructuredCloneCallbacks* optionalCallbacks,
2586 : void* closure, HandleValue transferable)
2587 : {
2588 74 : AssertHeapIsIdle();
2589 148 : CHECK_REQUEST(cx);
2590 74 : assertSameCompartment(cx, value);
2591 :
2592 74 : const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
2593 74 : return WriteStructuredClone(cx, value, bufp, scope, cloneDataPolicy, callbacks, closure,
2594 148 : transferable);
2595 : }
2596 :
2597 : JS_PUBLIC_API(bool)
2598 0 : JS_StructuredCloneHasTransferables(JSStructuredCloneData& data,
2599 : bool* hasTransferable)
2600 : {
2601 0 : *hasTransferable = StructuredCloneHasTransferObjects(data);
2602 0 : return true;
2603 : }
2604 :
2605 : JS_PUBLIC_API(bool)
2606 25 : JS_StructuredClone(JSContext* cx, HandleValue value, MutableHandleValue vp,
2607 : const JSStructuredCloneCallbacks* optionalCallbacks,
2608 : void* closure)
2609 : {
2610 25 : AssertHeapIsIdle();
2611 50 : CHECK_REQUEST(cx);
2612 :
2613 : // Strings are associated with zones, not compartments,
2614 : // so we copy the string by wrapping it.
2615 25 : if (value.isString()) {
2616 0 : RootedString strValue(cx, value.toString());
2617 0 : if (!cx->compartment()->wrap(cx, &strValue)) {
2618 0 : return false;
2619 : }
2620 0 : vp.setString(strValue);
2621 0 : return true;
2622 : }
2623 :
2624 25 : const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
2625 :
2626 50 : JSAutoStructuredCloneBuffer buf(JS::StructuredCloneScope::SameProcessSameThread, callbacks, closure);
2627 : {
2628 : // If we use Maybe<AutoCompartment> here, G++ can't tell that the
2629 : // destructor is only called when Maybe::construct was called, and
2630 : // we get warnings about using uninitialized variables.
2631 25 : if (value.isObject()) {
2632 32 : AutoCompartment ac(cx, &value.toObject());
2633 16 : if (!buf.write(cx, value, callbacks, closure))
2634 0 : return false;
2635 : } else {
2636 9 : if (!buf.write(cx, value, callbacks, closure))
2637 0 : return false;
2638 : }
2639 : }
2640 :
2641 25 : return buf.read(cx, vp, callbacks, closure);
2642 : }
2643 :
2644 0 : JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other)
2645 0 : : scope_(other.scope_)
2646 : {
2647 0 : data_.ownTransferables_ = other.data_.ownTransferables_;
2648 0 : other.steal(&data_, &version_, &data_.callbacks_, &data_.closure_);
2649 0 : }
2650 :
2651 : JSAutoStructuredCloneBuffer&
2652 0 : JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer&& other)
2653 : {
2654 0 : MOZ_ASSERT(&other != this);
2655 0 : MOZ_ASSERT(scope_ == other.scope_);
2656 0 : clear();
2657 0 : data_.ownTransferables_ = other.data_.ownTransferables_;
2658 0 : other.steal(&data_, &version_, &data_.callbacks_, &data_.closure_);
2659 0 : return *this;
2660 : }
2661 :
2662 : void
2663 147 : JSAutoStructuredCloneBuffer::clear(const JSStructuredCloneCallbacks* optionalCallbacks,
2664 : void* optionalClosure)
2665 : {
2666 147 : if (!data_.Size())
2667 118 : return;
2668 :
2669 : const JSStructuredCloneCallbacks* callbacks =
2670 29 : optionalCallbacks ? optionalCallbacks : data_.callbacks_;
2671 29 : void* closure = optionalClosure ? optionalClosure : data_.closure_;
2672 :
2673 29 : if (data_.ownTransferables_ == OwnTransferablePolicy::OwnsTransferablesIfAny)
2674 29 : DiscardTransferables(data_, callbacks, closure);
2675 29 : data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
2676 29 : data_.refsHeld_.releaseAll();
2677 29 : data_.Clear();
2678 29 : version_ = 0;
2679 : }
2680 :
2681 : bool
2682 0 : JSAutoStructuredCloneBuffer::copy(JSContext* cx, const JSStructuredCloneData& srcData,
2683 : uint32_t version, const JSStructuredCloneCallbacks* callbacks,
2684 : void* closure)
2685 : {
2686 : // transferable objects cannot be copied
2687 0 : if (StructuredCloneHasTransferObjects(srcData))
2688 0 : return false;
2689 :
2690 0 : clear();
2691 :
2692 0 : auto iter = srcData.Iter();
2693 0 : while (!iter.Done()) {
2694 0 : if (!data_.WriteBytes(iter.Data(), iter.RemainingInSegment()))
2695 0 : return false;
2696 0 : iter.Advance(srcData, iter.RemainingInSegment());
2697 : }
2698 :
2699 0 : version_ = version;
2700 :
2701 0 : if (!data_.refsHeld_.acquireAll(cx, srcData.refsHeld_))
2702 0 : return false;
2703 :
2704 0 : data_.setOptionalCallbacks(callbacks, closure, OwnTransferablePolicy::NoTransferables);
2705 0 : return true;
2706 : }
2707 :
2708 : void
2709 0 : JSAutoStructuredCloneBuffer::adopt(JSStructuredCloneData&& data, uint32_t version,
2710 : const JSStructuredCloneCallbacks* callbacks,
2711 : void* closure)
2712 : {
2713 0 : clear();
2714 0 : data_ = Move(data);
2715 0 : version_ = version;
2716 0 : data_.setOptionalCallbacks(callbacks, closure, OwnTransferablePolicy::OwnsTransferablesIfAny);
2717 0 : }
2718 :
2719 : void
2720 44 : JSAutoStructuredCloneBuffer::steal(JSStructuredCloneData* data, uint32_t* versionp,
2721 : const JSStructuredCloneCallbacks** callbacks,
2722 : void** closure)
2723 : {
2724 44 : if (versionp)
2725 0 : *versionp = version_;
2726 44 : if (callbacks)
2727 0 : *callbacks = data_.callbacks_;
2728 44 : if (closure)
2729 0 : *closure = data_.closure_;
2730 44 : *data = Move(data_);
2731 :
2732 44 : version_ = 0;
2733 44 : data_.setOptionalCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables);
2734 44 : }
2735 :
2736 : bool
2737 29 : JSAutoStructuredCloneBuffer::read(JSContext* cx, MutableHandleValue vp,
2738 : const JSStructuredCloneCallbacks* optionalCallbacks,
2739 : void* closure)
2740 : {
2741 29 : MOZ_ASSERT(cx);
2742 29 : return !!JS_ReadStructuredClone(cx, data_, version_, scope_, vp,
2743 29 : optionalCallbacks, closure);
2744 : }
2745 :
2746 : bool
2747 25 : JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value,
2748 : const JSStructuredCloneCallbacks* optionalCallbacks,
2749 : void* closure)
2750 : {
2751 25 : HandleValue transferable = UndefinedHandleValue;
2752 25 : return write(cx, value, transferable, JS::CloneDataPolicy().denySharedArrayBuffer(), optionalCallbacks, closure);
2753 : }
2754 :
2755 : bool
2756 74 : JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value,
2757 : HandleValue transferable, JS::CloneDataPolicy cloneDataPolicy,
2758 : const JSStructuredCloneCallbacks* optionalCallbacks,
2759 : void* closure)
2760 : {
2761 74 : clear();
2762 74 : bool ok = JS_WriteStructuredClone(cx, value, &data_,
2763 74 : scope_, cloneDataPolicy,
2764 : optionalCallbacks, closure,
2765 74 : transferable);
2766 :
2767 74 : if (ok) {
2768 74 : data_.ownTransferables_ = OwnTransferablePolicy::OwnsTransferablesIfAny;
2769 : } else {
2770 0 : version_ = JS_STRUCTURED_CLONE_VERSION;
2771 0 : data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables;
2772 : }
2773 74 : return ok;
2774 : }
2775 :
2776 : JS_PUBLIC_API(bool)
2777 1 : JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2)
2778 : {
2779 1 : return r->input().readPair((uint32_t*) p1, (uint32_t*) p2);
2780 : }
2781 :
2782 : JS_PUBLIC_API(bool)
2783 2 : JS_ReadBytes(JSStructuredCloneReader* r, void* p, size_t len)
2784 : {
2785 2 : return r->input().readBytes(p, len);
2786 : }
2787 :
2788 : JS_PUBLIC_API(bool)
2789 0 : JS_ReadTypedArray(JSStructuredCloneReader* r, MutableHandleValue vp)
2790 : {
2791 : uint32_t tag, nelems;
2792 0 : if (!r->input().readPair(&tag, &nelems))
2793 0 : return false;
2794 0 : if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
2795 0 : return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp, true);
2796 0 : } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
2797 : uint64_t arrayType;
2798 0 : if (!r->input().read(&arrayType))
2799 0 : return false;
2800 0 : return r->readTypedArray(arrayType, nelems, vp);
2801 : } else {
2802 0 : JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
2803 : JSMSG_SC_BAD_SERIALIZED_DATA,
2804 0 : "expected type array");
2805 0 : return false;
2806 : }
2807 : }
2808 :
2809 : JS_PUBLIC_API(bool)
2810 2 : JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag, uint32_t data)
2811 : {
2812 2 : return w->output().writePair(tag, data);
2813 : }
2814 :
2815 : JS_PUBLIC_API(bool)
2816 2 : JS_WriteBytes(JSStructuredCloneWriter* w, const void* p, size_t len)
2817 : {
2818 2 : return w->output().writeBytes(p, len);
2819 : }
2820 :
2821 : JS_PUBLIC_API(bool)
2822 0 : JS_WriteString(JSStructuredCloneWriter* w, HandleString str)
2823 : {
2824 0 : return w->writeString(SCTAG_STRING, str);
2825 : }
2826 :
2827 : JS_PUBLIC_API(bool)
2828 0 : JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v)
2829 : {
2830 0 : MOZ_ASSERT(v.isObject());
2831 0 : assertSameCompartment(w->context(), v);
2832 0 : RootedObject obj(w->context(), &v.toObject());
2833 0 : return w->writeTypedArray(obj);
2834 : }
2835 :
2836 : JS_PUBLIC_API(bool)
2837 0 : JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj)
2838 : {
2839 0 : w->memory.remove(w->memory.lookup(obj));
2840 :
2841 0 : return true;
2842 : }
2843 :
2844 : JS_PUBLIC_API(JS::StructuredCloneScope)
2845 0 : JS_GetStructuredCloneScope(JSStructuredCloneWriter* w)
2846 : {
2847 0 : return w->cloneScope();
2848 : }
|