StructuredClone.cpp (139463B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * This file implements the structured data algorithms of 9 * https://html.spec.whatwg.org/multipage/structured-data.html 10 * 11 * The spec is in two parts: 12 * 13 * - StructuredSerialize examines a JS value and produces a graph of Records. 14 * - StructuredDeserialize walks the Records and produces a new JS value. 15 * 16 * The differences between our implementation and the spec are minor: 17 * 18 * - We call the two phases "write" and "read". 19 * - Our algorithms use an explicit work stack, rather than recursion. 20 * - Serialized data is a flat array of bytes, not a (possibly cyclic) graph 21 * of "Records". 22 * - As a consequence, we handle non-treelike object graphs differently. 23 * We serialize objects that appear in multiple places in the input as 24 * backreferences, using sequential integer indexes. 25 * See `JSStructuredCloneReader::allObjs`, our take on the "memory" map 26 * in the spec's StructuredDeserialize. 27 */ 28 29 #include "js/StructuredClone.h" 30 31 #include "mozilla/Casting.h" 32 #include "mozilla/CheckedInt.h" 33 #include "mozilla/EndianUtils.h" 34 #include "mozilla/FloatingPoint.h" 35 #include "mozilla/Maybe.h" 36 #include "mozilla/ScopeExit.h" 37 38 #include <algorithm> 39 #include <memory> 40 #include <utility> 41 42 #include "jsdate.h" 43 44 #include "builtin/DataViewObject.h" 45 #include "builtin/MapObject.h" 46 #include "gc/GC.h" // AutoSelectGCHeap 47 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject 48 #include "js/ArrayBuffer.h" // JS::{ArrayBufferHasData,DetachArrayBuffer,IsArrayBufferObject,New{,Mapped}ArrayBufferWithContents,ReleaseMappedArrayBufferContents} 49 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin 50 #include "js/Date.h" 51 #include "js/experimental/TypedData.h" // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer 52 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 53 #include "js/GCAPI.h" 54 #include "js/GCHashTable.h" 55 #include "js/Object.h" // JS::GetBuiltinClass 56 #include "js/PropertyAndElement.h" // JS_GetElement 57 #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags 58 #include "js/ScalarType.h" // js::Scalar::Type 59 #include "js/SharedArrayBuffer.h" // JS::IsSharedArrayBufferObject 60 #include "js/Wrapper.h" 61 #include "util/DifferentialTesting.h" 62 #include "vm/BigIntType.h" 63 #include "vm/ErrorObject.h" 64 #include "vm/JSContext.h" 65 #include "vm/PlainObject.h" // js::PlainObject 66 #include "vm/RegExpObject.h" 67 #include "vm/SavedFrame.h" 68 #include "vm/SharedArrayObject.h" 69 #include "vm/TypedArrayObject.h" 70 #include "wasm/WasmJS.h" 71 72 #include "vm/ArrayObject-inl.h" 73 #include "vm/Compartment-inl.h" 74 #include "vm/ErrorObject-inl.h" 75 #include "vm/JSContext-inl.h" 76 #include "vm/JSObject-inl.h" 77 #include "vm/NativeObject-inl.h" 78 #include "vm/ObjectOperations-inl.h" 79 #include "vm/Realm-inl.h" 80 #include "vm/StringType-inl.h" 81 82 using namespace js; 83 84 using JS::CanonicalizeNaN; 85 using JS::GetBuiltinClass; 86 using JS::RegExpFlag; 87 using JS::RegExpFlags; 88 using JS::RootedValueVector; 89 using mozilla::AssertedCast; 90 using mozilla::BitwiseCast; 91 using mozilla::Maybe; 92 using mozilla::NativeEndian; 93 using mozilla::NumbersAreIdentical; 94 95 // When you make updates here, make sure you consider whether you need to bump 96 // the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You 97 // will likely need to increment the version if anything at all changes in the 98 // serialization format. 99 // 100 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should 101 // have a stable ID, it need not be at the end of the list and should not be 102 // used for sizing data structures. 103 104 enum StructuredDataType : uint32_t { 105 // Structured data types provided by the engine 106 SCTAG_FLOAT_MAX = 0xFFF00000, 107 SCTAG_HEADER = 0xFFF10000, 108 SCTAG_NULL = 0xFFFF0000, 109 SCTAG_UNDEFINED, 110 SCTAG_BOOLEAN, 111 SCTAG_INT32, 112 SCTAG_STRING, 113 SCTAG_DATE_OBJECT, 114 SCTAG_REGEXP_OBJECT, 115 SCTAG_ARRAY_OBJECT, 116 SCTAG_OBJECT_OBJECT, 117 SCTAG_ARRAY_BUFFER_OBJECT_V2, // Old version, for backwards compatibility. 118 SCTAG_BOOLEAN_OBJECT, 119 SCTAG_STRING_OBJECT, 120 SCTAG_NUMBER_OBJECT, 121 SCTAG_BACK_REFERENCE_OBJECT, 122 SCTAG_DO_NOT_USE_1, // Required for backwards compatibility 123 SCTAG_DO_NOT_USE_2, // Required for backwards compatibility 124 SCTAG_TYPED_ARRAY_OBJECT_V2, // Old version, for backwards compatibility. 125 SCTAG_MAP_OBJECT, 126 SCTAG_SET_OBJECT, 127 SCTAG_END_OF_KEYS, 128 SCTAG_DO_NOT_USE_3, // Required for backwards compatibility 129 SCTAG_DATA_VIEW_OBJECT_V2, // Old version, for backwards compatibility. 130 SCTAG_SAVED_FRAME_OBJECT, 131 132 // No new tags before principals. 133 SCTAG_JSPRINCIPALS, 134 SCTAG_NULL_JSPRINCIPALS, 135 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM, 136 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM, 137 138 SCTAG_SHARED_ARRAY_BUFFER_OBJECT, 139 SCTAG_SHARED_WASM_MEMORY_OBJECT, 140 141 SCTAG_BIGINT, 142 SCTAG_BIGINT_OBJECT, 143 144 SCTAG_ARRAY_BUFFER_OBJECT, 145 SCTAG_TYPED_ARRAY_OBJECT, 146 SCTAG_DATA_VIEW_OBJECT, 147 148 SCTAG_ERROR_OBJECT, 149 150 SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT, 151 SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT, 152 SCTAG_IMMUTABLE_ARRAY_BUFFER_OBJECT, 153 154 SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, 155 SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8, 156 SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8, 157 SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16, 158 SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16, 159 SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32, 160 SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32, 161 SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32, 162 SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64, 163 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = 164 SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped, 165 // BigInt64 and BigUint64 are not supported in the v1 format. 166 SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED, 167 168 // Define a separate range of numbers for Transferable-only tags, since 169 // they are not used for persistent clone buffers and therefore do not 170 // require bumping JS_STRUCTURED_CLONE_VERSION. 171 SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200, 172 SCTAG_TRANSFER_MAP_PENDING_ENTRY, 173 SCTAG_TRANSFER_MAP_ARRAY_BUFFER, 174 SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER, 175 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES, 176 177 SCTAG_END_OF_BUILTIN_TYPES 178 }; 179 180 /* 181 * Format of transfer map: 182 * - <SCTAG_TRANSFER_MAP_HEADER, UNREAD|TRANSFERRING|TRANSFERRED> 183 * - numTransferables (64 bits) 184 * - array of: 185 * - <SCTAG_TRANSFER_MAP_*, TransferableOwnership> pointer (64 186 * bits) 187 * - extraData (64 bits), eg byte length for ArrayBuffers 188 * - any data written for custom transferables 189 */ 190 191 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the 192 // contents have been read out yet or not. TRANSFERRING is for the case where we 193 // have started but not completed reading, which due to errors could mean that 194 // there are things still owned by the clone buffer that need to be released, so 195 // discarding should not just be skipped. 196 enum TransferableMapHeader { 197 SCTAG_TM_UNREAD = 0, 198 SCTAG_TM_TRANSFERRING, 199 SCTAG_TM_TRANSFERRED, 200 201 SCTAG_TM_END 202 }; 203 204 static inline uint64_t PairToUInt64(uint32_t tag, uint32_t data) { 205 return uint64_t(data) | (uint64_t(tag) << 32); 206 } 207 208 namespace js { 209 210 template <typename T, typename AllocPolicy> 211 struct BufferIterator { 212 using BufferList = mozilla::BufferList<AllocPolicy>; 213 214 explicit BufferIterator(const BufferList& buffer) 215 : mBuffer(buffer), mIter(buffer.Iter()) { 216 static_assert(8 % sizeof(T) == 0); 217 } 218 219 explicit BufferIterator(const JSStructuredCloneData& data) 220 : mBuffer(data.bufList_), mIter(data.Start()) {} 221 222 BufferIterator& operator=(const BufferIterator& other) { 223 MOZ_ASSERT(&mBuffer == &other.mBuffer); 224 mIter = other.mIter; 225 return *this; 226 } 227 228 [[nodiscard]] bool advance(size_t size = sizeof(T)) { 229 return mIter.AdvanceAcrossSegments(mBuffer, size); 230 } 231 232 BufferIterator operator++(int) { 233 BufferIterator ret = *this; 234 if (!advance(sizeof(T))) { 235 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete"); 236 } 237 return ret; 238 } 239 240 BufferIterator& operator+=(size_t size) { 241 if (!advance(size)) { 242 MOZ_ASSERT(false, "Failed to read StructuredCloneData. Data incomplete"); 243 } 244 return *this; 245 } 246 247 size_t operator-(const BufferIterator& other) const { 248 MOZ_ASSERT(&mBuffer == &other.mBuffer); 249 return mBuffer.RangeLength(other.mIter, mIter); 250 } 251 252 bool operator==(const BufferIterator& other) const { 253 return mBuffer.Start() == other.mBuffer.Start() && mIter == other.mIter; 254 } 255 bool operator!=(const BufferIterator& other) const { 256 return !(*this == other); 257 } 258 259 bool done() const { return mIter.Done(); } 260 261 [[nodiscard]] bool readBytes(char* outData, size_t size) { 262 return mBuffer.ReadBytes(mIter, outData, size); 263 } 264 265 void write(const T& data) { 266 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T))); 267 *reinterpret_cast<T*>(mIter.Data()) = data; 268 } 269 270 T peek() const { 271 MOZ_ASSERT(mIter.HasRoomFor(sizeof(T))); 272 return *reinterpret_cast<T*>(mIter.Data()); 273 } 274 275 bool canPeek() const { return mIter.HasRoomFor(sizeof(T)); } 276 277 const BufferList& mBuffer; 278 typename BufferList::IterImpl mIter; 279 }; 280 281 SharedArrayRawBufferRefs& SharedArrayRawBufferRefs::operator=( 282 SharedArrayRawBufferRefs&& other) { 283 takeOwnership(std::move(other)); 284 return *this; 285 } 286 287 SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); } 288 289 bool SharedArrayRawBufferRefs::acquire(JSContext* cx, 290 SharedArrayRawBuffer* rawbuf) { 291 if (!refs_.append(rawbuf)) { 292 ReportOutOfMemory(cx); 293 return false; 294 } 295 296 if (!rawbuf->addReference()) { 297 refs_.popBack(); 298 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 299 JSMSG_SC_SAB_REFCNT_OFLO); 300 return false; 301 } 302 303 return true; 304 } 305 306 bool SharedArrayRawBufferRefs::acquireAll( 307 JSContext* cx, const SharedArrayRawBufferRefs& that) { 308 if (!refs_.reserve(refs_.length() + that.refs_.length())) { 309 ReportOutOfMemory(cx); 310 return false; 311 } 312 313 for (auto ref : that.refs_) { 314 if (!ref->addReference()) { 315 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 316 JSMSG_SC_SAB_REFCNT_OFLO); 317 return false; 318 } 319 MOZ_ALWAYS_TRUE(refs_.append(ref)); 320 } 321 322 return true; 323 } 324 325 void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs&& other) { 326 MOZ_ASSERT(refs_.empty()); 327 refs_ = std::move(other.refs_); 328 } 329 330 void SharedArrayRawBufferRefs::releaseAll() { 331 for (auto ref : refs_) { 332 ref->dropReference(); 333 } 334 refs_.clear(); 335 } 336 337 // SCOutput provides an interface to write raw data -- eg uint64_ts, doubles, 338 // arrays of bytes -- into a structured clone data output stream. It also knows 339 // how to free any transferable data within that stream. 340 // 341 // Note that it contains a full JSStructuredCloneData object, which holds the 342 // callbacks necessary to read/write/transfer/free the data. For the purpose of 343 // this class, only the freeTransfer callback is relevant; the rest of the 344 // callbacks are used by the higher-level JSStructuredCloneWriter interface. 345 struct SCOutput { 346 public: 347 using Iter = BufferIterator<uint64_t, SystemAllocPolicy>; 348 349 SCOutput(JSContext* cx, JS::StructuredCloneScope scope); 350 351 JSContext* context() const { return cx; } 352 JS::StructuredCloneScope scope() const { return buf.scope(); } 353 void sameProcessScopeRequired() { buf.sameProcessScopeRequired(); } 354 355 [[nodiscard]] bool write(uint64_t u); 356 [[nodiscard]] bool writePair(uint32_t tag, uint32_t data); 357 [[nodiscard]] bool writeDouble(double d); 358 [[nodiscard]] bool writeBytes(const void* p, size_t nbytes); 359 [[nodiscard]] bool writeChars(const Latin1Char* p, size_t nchars); 360 [[nodiscard]] bool writeChars(const char16_t* p, size_t nchars); 361 362 template <class T> 363 [[nodiscard]] bool writeArray(const T* p, size_t nelems); 364 365 void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure, 366 OwnTransferablePolicy policy) { 367 buf.setCallbacks(callbacks, closure, policy); 368 } 369 void extractBuffer(JSStructuredCloneData* data) { *data = std::move(buf); } 370 371 uint64_t tell() const { return buf.Size(); } 372 uint64_t count() const { return buf.Size() / sizeof(uint64_t); } 373 Iter iter() { return Iter(buf); } 374 375 size_t offset(Iter dest) { return dest - iter(); } 376 377 JSContext* cx; 378 JSStructuredCloneData buf; 379 }; 380 381 class SCInput { 382 public: 383 using BufferIterator = js::BufferIterator<uint64_t, SystemAllocPolicy>; 384 385 SCInput(JSContext* cx, const JSStructuredCloneData& data); 386 387 JSContext* context() const { return cx; } 388 389 static void getPtr(uint64_t data, void** ptr); 390 static void getPair(uint64_t data, uint32_t* tagp, uint32_t* datap); 391 392 [[nodiscard]] bool read(uint64_t* p); 393 [[nodiscard]] bool readPair(uint32_t* tagp, uint32_t* datap); 394 [[nodiscard]] bool readDouble(double* p); 395 [[nodiscard]] bool readBytes(void* p, size_t nbytes); 396 [[nodiscard]] bool readChars(Latin1Char* p, size_t nchars); 397 [[nodiscard]] bool readChars(char16_t* p, size_t nchars); 398 [[nodiscard]] bool readPtr(void**); 399 400 [[nodiscard]] bool get(uint64_t* p); 401 [[nodiscard]] bool getPair(uint32_t* tagp, uint32_t* datap); 402 403 const BufferIterator& tell() const { return point; } 404 void seekTo(const BufferIterator& pos) { point = pos; } 405 [[nodiscard]] bool seekBy(size_t pos) { 406 if (!point.advance(pos)) { 407 reportTruncated(); 408 return false; 409 } 410 return true; 411 } 412 413 template <class T> 414 [[nodiscard]] bool readArray(T* p, size_t nelems); 415 416 bool reportTruncated() { 417 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 418 JSMSG_SC_BAD_SERIALIZED_DATA, "truncated"); 419 return false; 420 } 421 422 private: 423 void staticAssertions() { 424 static_assert(sizeof(char16_t) == 2); 425 static_assert(sizeof(uint32_t) == 4); 426 } 427 428 JSContext* cx; 429 BufferIterator point; 430 }; 431 432 } // namespace js 433 434 struct JSStructuredCloneReader { 435 public: 436 explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope, 437 const JS::CloneDataPolicy& cloneDataPolicy, 438 const JSStructuredCloneCallbacks* cb, 439 void* cbClosure); 440 441 SCInput& input() { return in; } 442 bool read(MutableHandleValue vp, size_t nbytes); 443 444 private: 445 JSContext* context() { return in.context(); } 446 447 bool readHeader(); 448 bool readTransferMap(); 449 450 [[nodiscard]] bool readUint32(uint32_t* num); 451 452 enum ShouldAtomizeStrings : bool { 453 DontAtomizeStrings = false, 454 AtomizeStrings = true 455 }; 456 457 template <typename CharT> 458 JSString* readStringImpl(uint32_t nchars, ShouldAtomizeStrings atomize); 459 JSString* readString(uint32_t data, ShouldAtomizeStrings atomize); 460 461 BigInt* readBigInt(uint32_t data); 462 463 [[nodiscard]] bool readTypedArray(uint32_t arrayType, uint64_t nelems, 464 MutableHandleValue vp, bool v1Read = false); 465 466 [[nodiscard]] bool readDataView(uint64_t byteLength, MutableHandleValue vp); 467 468 [[nodiscard]] bool readArrayBuffer(StructuredDataType type, uint32_t data, 469 MutableHandleValue vp); 470 [[nodiscard]] bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, 471 MutableHandleValue vp); 472 473 [[nodiscard]] bool readSharedArrayBuffer(StructuredDataType type, 474 MutableHandleValue vp); 475 476 [[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes, 477 MutableHandleValue vp); 478 479 // A serialized SavedFrame contains primitive values in a header followed by 480 // an optional parent frame that is read recursively. 481 [[nodiscard]] JSObject* readSavedFrameHeader(uint32_t principalsTag); 482 [[nodiscard]] bool readSavedFrameFields(Handle<SavedFrame*> frameObj, 483 HandleValue parent, bool* state); 484 485 // A serialized Error contains primitive values in a header followed by 486 // 'cause', 'errors', and 'stack' fields that are read recursively. 487 [[nodiscard]] JSObject* readErrorHeader(uint32_t type); 488 [[nodiscard]] bool readErrorFields(Handle<ErrorObject*> errorObj, 489 HandleValue cause, bool* state); 490 491 [[nodiscard]] bool readMapField(Handle<MapObject*> mapObj, HandleValue key); 492 493 [[nodiscard]] bool readObjectField(HandleObject obj, HandleValue key); 494 495 [[nodiscard]] bool startRead( 496 MutableHandleValue vp, 497 ShouldAtomizeStrings atomizeStrings = DontAtomizeStrings); 498 499 SCInput& in; 500 501 // The widest scope that the caller will accept, where 502 // SameProcess is the widest (it can store anything it wants) 503 // and DifferentProcess is the narrowest (it cannot contain pointers and must 504 // be valid cross-process.) 505 // 506 // Although this can be initially set to other "unresolved" values (eg 507 // Unassigned), by the end of a successful readHeader() this will be resolved 508 // to SameProcess or DifferentProcess. 509 JS::StructuredCloneScope allowedScope; 510 511 const JS::CloneDataPolicy cloneDataPolicy; 512 513 // Stack of objects with properties remaining to be read. 514 RootedValueVector objs; 515 516 // Maintain a stack of state values for the `objs` stack. Since this is only 517 // needed for a very small subset of objects (those with a known set of 518 // object children), the state information is stored as a stack of 519 // <object, state> pairs where the object determines which element of the 520 // `objs` stack that it corresponds to. So when reading from the `objs` stack, 521 // the state will be retrieved only if the top object on `objState` matches 522 // the top object of `objs`. 523 // 524 // Currently, the only state needed is a boolean indicating whether the fields 525 // have been read yet. 526 Rooted<GCVector<std::pair<HeapPtr<JSObject*>, bool>, 8>> objState; 527 528 // Array of all objects read during this deserialization, for resolving 529 // backreferences. 530 // 531 // For backreferences to work correctly, objects must be added to this 532 // array in exactly the order expected by the version of the Writer that 533 // created the serialized data, even across years and format versions. This 534 // is usually no problem, since both algorithms do a single linear pass 535 // over the serialized data. There is one hitch; see readTypedArray. 536 // 537 // The values in this vector are objects, except it can temporarily have 538 // one `undefined` placeholder value (the readTypedArray hack). 539 RootedValueVector allObjs; 540 541 size_t numItemsRead; 542 543 // The user defined callbacks that will be used for cloning. 544 const JSStructuredCloneCallbacks* callbacks; 545 546 // Any value passed to JS_ReadStructuredClone. 547 void* closure; 548 549 // The heap to use for allocating common GC things. This starts out as the 550 // nursery (the default) but may switch to the tenured heap if nursery 551 // collection occurs, as nursery allocation is pointless after the 552 // deserialized root object is tenured. 553 // 554 // This is only used for the most common kind, e.g. plain objects, strings 555 // and a couple of others. 556 AutoSelectGCHeap gcHeap; 557 558 friend bool JS_ReadString(JSStructuredCloneReader* r, 559 JS::MutableHandleString str); 560 friend bool JS_ReadTypedArray(JSStructuredCloneReader* r, 561 MutableHandleValue vp); 562 563 // Provide a way to detect whether any of the clone data is never used. When 564 // "tail" data (currently, this is only stored data for Transferred 565 // ArrayBuffers in the DifferentProcess scope) is read, record the first and 566 // last positions. At the end of deserialization, make sure there's nothing 567 // between the end of the main data and the beginning of the tail, nor after 568 // the end of the tail. 569 mozilla::Maybe<SCInput::BufferIterator> tailStartPos; 570 mozilla::Maybe<SCInput::BufferIterator> tailEndPos; 571 }; 572 573 struct JSStructuredCloneWriter { 574 public: 575 explicit JSStructuredCloneWriter(JSContext* cx, 576 JS::StructuredCloneScope scope, 577 const JS::CloneDataPolicy& cloneDataPolicy, 578 const JSStructuredCloneCallbacks* cb, 579 void* cbClosure, const Value& tVal) 580 : out(cx, scope), 581 callbacks(cb), 582 closure(cbClosure), 583 objs(cx), 584 counts(cx), 585 objectEntries(cx), 586 otherEntries(cx), 587 memory(cx), 588 transferable(cx, tVal), 589 transferableObjects(cx, TransferableObjectsList(cx)), 590 cloneDataPolicy(cloneDataPolicy) { 591 out.setCallbacks(cb, cbClosure, 592 OwnTransferablePolicy::OwnsTransferablesIfAny); 593 } 594 595 bool init() { 596 return parseTransferable() && writeHeader() && writeTransferMap(); 597 } 598 599 bool write(HandleValue v); 600 601 SCOutput& output() { return out; } 602 603 void extractBuffer(JSStructuredCloneData* newData) { 604 out.extractBuffer(newData); 605 } 606 607 private: 608 JSStructuredCloneWriter() = delete; 609 JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete; 610 611 JSContext* context() { return out.context(); } 612 613 bool writeHeader(); 614 bool writeTransferMap(); 615 616 bool writeString(uint32_t tag, JSString* str); 617 bool writeBigInt(uint32_t tag, BigInt* bi); 618 bool writeArrayBuffer(HandleObject obj); 619 bool writeTypedArray(HandleObject obj); 620 bool writeDataView(HandleObject obj); 621 bool writeSharedArrayBuffer(HandleObject obj); 622 bool writeSharedWasmMemory(HandleObject obj); 623 bool startObject(HandleObject obj, bool* backref); 624 bool writePrimitive(HandleValue v); 625 bool startWrite(HandleValue v); 626 bool traverseObject(HandleObject obj, ESClass cls); 627 bool traverseMap(HandleObject obj); 628 bool traverseSet(HandleObject obj); 629 bool traverseSavedFrame(HandleObject obj); 630 bool traverseError(HandleObject obj); 631 632 template <typename... Args> 633 bool reportDataCloneError(uint32_t errorId, Args&&... aArgs); 634 635 bool parseTransferable(); 636 bool transferOwnership(); 637 638 inline void checkStack(); 639 640 SCOutput out; 641 642 // The user defined callbacks that will be used to signal cloning, in some 643 // cases. 644 const JSStructuredCloneCallbacks* callbacks; 645 646 // Any value passed to the callbacks. 647 void* closure; 648 649 // Vector of objects with properties remaining to be written. 650 // 651 // NB: These can span multiple compartments, so the compartment must be 652 // entered before any manipulation is performed. 653 RootedValueVector objs; 654 655 // counts[i] is the number of entries of objs[i] remaining to be written. 656 // counts.length() == objs.length() and sum(counts) == entries.length(). 657 Vector<size_t> counts; 658 659 // For JSObject: Property IDs as value 660 RootedIdVector objectEntries; 661 662 // For Map: Key followed by value 663 // For Set: Key 664 // For SavedFrame: parent SavedFrame 665 // For Error: cause, errors, stack 666 RootedValueVector otherEntries; 667 668 // The "memory" list described in the HTML5 internal structured cloning 669 // algorithm. memory is a superset of objs; items are never removed from 670 // Memory until a serialization operation is finished 671 using CloneMemory = GCHashMap<JSObject*, uint32_t, 672 StableCellHasher<JSObject*>, SystemAllocPolicy>; 673 Rooted<CloneMemory> memory; 674 675 // Set of transferable objects 676 RootedValue transferable; 677 using TransferableObjectsList = GCVector<JSObject*>; 678 Rooted<TransferableObjectsList> transferableObjects; 679 680 const JS::CloneDataPolicy cloneDataPolicy; 681 682 friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str); 683 friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v); 684 friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj); 685 }; 686 687 JS_PUBLIC_API uint64_t js::GetSCOffset(JSStructuredCloneWriter* writer) { 688 MOZ_ASSERT(writer); 689 return writer->output().count() * sizeof(uint64_t); 690 } 691 692 static_assert(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN); 693 static_assert(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX); 694 static_assert(Scalar::Int8 == 0); 695 696 template <typename... Args> 697 static void ReportDataCloneError(JSContext* cx, 698 const JSStructuredCloneCallbacks* callbacks, 699 uint32_t errorId, void* closure, 700 Args&&... aArgs) { 701 unsigned errorNumber; 702 switch (errorId) { 703 case JS_SCERR_DUP_TRANSFERABLE: 704 errorNumber = JSMSG_SC_DUP_TRANSFERABLE; 705 break; 706 707 case JS_SCERR_TRANSFERABLE: 708 errorNumber = JSMSG_SC_NOT_TRANSFERABLE; 709 break; 710 711 case JS_SCERR_UNSUPPORTED_TYPE: 712 errorNumber = JSMSG_SC_UNSUPPORTED_TYPE; 713 break; 714 715 case JS_SCERR_SHMEM_TRANSFERABLE: 716 errorNumber = JSMSG_SC_SHMEM_TRANSFERABLE; 717 break; 718 719 case JS_SCERR_TRANSFERABLE_TWICE: 720 errorNumber = JSMSG_SC_TRANSFERABLE_TWICE; 721 break; 722 723 case JS_SCERR_TYPED_ARRAY_DETACHED: 724 errorNumber = JSMSG_TYPED_ARRAY_DETACHED; 725 break; 726 727 case JS_SCERR_WASM_NO_TRANSFER: 728 errorNumber = JSMSG_WASM_NO_TRANSFER; 729 break; 730 731 case JS_SCERR_NOT_CLONABLE: 732 errorNumber = JSMSG_SC_NOT_CLONABLE; 733 break; 734 735 case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP: 736 errorNumber = JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP; 737 break; 738 739 default: 740 MOZ_CRASH("Unkown errorId"); 741 break; 742 } 743 744 if (callbacks && callbacks->reportError) { 745 MOZ_RELEASE_ASSERT(!cx->isExceptionPending()); 746 747 JSErrorReport report; 748 report.errorNumber = errorNumber; 749 // Get js error message if it's possible and propagate it through callback. 750 if (JS_ExpandErrorArgumentsASCII(cx, GetErrorMessage, errorNumber, &report, 751 std::forward<Args>(aArgs)...) && 752 report.message()) { 753 callbacks->reportError(cx, errorId, closure, report.message().c_str()); 754 } else { 755 ReportOutOfMemory(cx); 756 757 callbacks->reportError(cx, errorId, closure, ""); 758 } 759 760 return; 761 } 762 763 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, 764 std::forward<Args>(aArgs)...); 765 } 766 767 bool WriteStructuredClone(JSContext* cx, HandleValue v, 768 JSStructuredCloneData* bufp, 769 JS::StructuredCloneScope scope, 770 const JS::CloneDataPolicy& cloneDataPolicy, 771 const JSStructuredCloneCallbacks* cb, void* cbClosure, 772 const Value& transferable) { 773 JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure, 774 transferable); 775 if (!w.init()) { 776 return false; 777 } 778 if (!w.write(v)) { 779 return false; 780 } 781 w.extractBuffer(bufp); 782 return true; 783 } 784 785 bool ReadStructuredClone(JSContext* cx, const JSStructuredCloneData& data, 786 JS::StructuredCloneScope scope, MutableHandleValue vp, 787 const JS::CloneDataPolicy& cloneDataPolicy, 788 const JSStructuredCloneCallbacks* cb, 789 void* cbClosure) { 790 if (data.Size() % 8) { 791 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 792 JSMSG_SC_BAD_SERIALIZED_DATA, "misaligned"); 793 return false; 794 } 795 SCInput in(cx, data); 796 JSStructuredCloneReader r(in, scope, cloneDataPolicy, cb, cbClosure); 797 return r.read(vp, data.Size()); 798 } 799 800 static bool StructuredCloneHasTransferObjects( 801 const JSStructuredCloneData& data) { 802 if (data.Size() < sizeof(uint64_t)) { 803 return false; 804 } 805 806 uint64_t u; 807 BufferIterator<uint64_t, SystemAllocPolicy> iter(data); 808 MOZ_ALWAYS_TRUE(iter.readBytes(reinterpret_cast<char*>(&u), sizeof(u))); 809 uint32_t tag = uint32_t(u >> 32); 810 return (tag == SCTAG_TRANSFER_MAP_HEADER); 811 } 812 813 namespace js { 814 815 SCInput::SCInput(JSContext* cx, const JSStructuredCloneData& data) 816 : cx(cx), point(data) { 817 static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment % 8 == 0, 818 "structured clone buffer reads should be aligned"); 819 MOZ_ASSERT(data.Size() % 8 == 0); 820 } 821 822 bool SCInput::read(uint64_t* p) { 823 if (!point.canPeek()) { 824 *p = 0; // initialize to shut GCC up 825 return reportTruncated(); 826 } 827 *p = NativeEndian::swapFromLittleEndian(point.peek()); 828 MOZ_ALWAYS_TRUE(point.advance()); 829 return true; 830 } 831 832 bool SCInput::readPair(uint32_t* tagp, uint32_t* datap) { 833 uint64_t u; 834 bool ok = read(&u); 835 if (ok) { 836 *tagp = uint32_t(u >> 32); 837 *datap = uint32_t(u); 838 } 839 return ok; 840 } 841 842 bool SCInput::get(uint64_t* p) { 843 if (!point.canPeek()) { 844 return reportTruncated(); 845 } 846 *p = NativeEndian::swapFromLittleEndian(point.peek()); 847 return true; 848 } 849 850 bool SCInput::getPair(uint32_t* tagp, uint32_t* datap) { 851 uint64_t u = 0; 852 if (!get(&u)) { 853 return false; 854 } 855 856 *tagp = uint32_t(u >> 32); 857 *datap = uint32_t(u); 858 return true; 859 } 860 861 void SCInput::getPair(uint64_t data, uint32_t* tagp, uint32_t* datap) { 862 uint64_t u = NativeEndian::swapFromLittleEndian(data); 863 *tagp = uint32_t(u >> 32); 864 *datap = uint32_t(u); 865 } 866 867 bool SCInput::readDouble(double* p) { 868 uint64_t u; 869 if (!read(&u)) { 870 return false; 871 } 872 *p = CanonicalizeNaN(mozilla::BitwiseCast<double>(u)); 873 return true; 874 } 875 876 template <typename T> 877 static void swapFromLittleEndianInPlace(T* ptr, size_t nelems) { 878 if (nelems > 0) { 879 NativeEndian::swapFromLittleEndianInPlace(ptr, nelems); 880 } 881 } 882 883 template <> 884 void swapFromLittleEndianInPlace(uint8_t* ptr, size_t nelems) {} 885 886 // Data is packed into an integral number of uint64_t words. Compute the 887 // padding required to finish off the final word. 888 static size_t ComputePadding(size_t nelems, size_t elemSize) { 889 // We want total length mod 8, where total length is nelems * sizeof(T), 890 // but that might overflow. So reduce nelems to nelems mod 8, since we are 891 // going to be doing a mod 8 later anyway. 892 size_t leftoverLength = (nelems % sizeof(uint64_t)) * elemSize; 893 return (-leftoverLength) & (sizeof(uint64_t) - 1); 894 } 895 896 template <class T> 897 bool SCInput::readArray(T* p, size_t nelems) { 898 if (!nelems) { 899 return true; 900 } 901 902 static_assert(sizeof(uint64_t) % sizeof(T) == 0); 903 904 // Fail if nelems is so huge that computing the full size will overflow. 905 mozilla::CheckedInt<size_t> size = 906 mozilla::CheckedInt<size_t>(nelems) * sizeof(T); 907 if (!size.isValid()) { 908 return reportTruncated(); 909 } 910 911 if (!point.readBytes(reinterpret_cast<char*>(p), size.value())) { 912 // To avoid any way in which uninitialized data could escape, zero the array 913 // if filling it failed. 914 std::uninitialized_fill_n(p, nelems, 0); 915 return reportTruncated(); 916 } 917 918 swapFromLittleEndianInPlace(p, nelems); 919 920 point += ComputePadding(nelems, sizeof(T)); 921 922 return true; 923 } 924 925 bool SCInput::readBytes(void* p, size_t nbytes) { 926 return readArray((uint8_t*)p, nbytes); 927 } 928 929 bool SCInput::readChars(Latin1Char* p, size_t nchars) { 930 static_assert(sizeof(Latin1Char) == sizeof(uint8_t), 931 "Latin1Char must fit in 1 byte"); 932 return readBytes(p, nchars); 933 } 934 935 bool SCInput::readChars(char16_t* p, size_t nchars) { 936 MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t)); 937 return readArray((uint16_t*)p, nchars); 938 } 939 940 void SCInput::getPtr(uint64_t data, void** ptr) { 941 *ptr = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data)); 942 } 943 944 bool SCInput::readPtr(void** p) { 945 uint64_t u; 946 if (!read(&u)) { 947 return false; 948 } 949 *p = reinterpret_cast<void*>(u); 950 return true; 951 } 952 953 SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope) 954 : cx(cx), buf(scope) {} 955 956 bool SCOutput::write(uint64_t u) { 957 uint64_t v = NativeEndian::swapToLittleEndian(u); 958 if (!buf.AppendBytes(reinterpret_cast<char*>(&v), sizeof(u))) { 959 ReportOutOfMemory(context()); 960 return false; 961 } 962 return true; 963 } 964 965 bool SCOutput::writePair(uint32_t tag, uint32_t data) { 966 // As it happens, the tag word appears after the data word in the output. 967 // This is because exponents occupy the last 2 bytes of doubles on the 968 // little-endian platforms we care most about. 969 // 970 // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1). 971 // PairToUInt64 produces the number 0xFFFF000200000001. 972 // That is written out as the bytes 01 00 00 00 02 00 FF FF. 973 return write(PairToUInt64(tag, data)); 974 } 975 976 static inline double ReinterpretPairAsDouble(uint32_t tag, uint32_t data) { 977 return BitwiseCast<double>(PairToUInt64(tag, data)); 978 } 979 980 bool SCOutput::writeDouble(double d) { 981 return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d))); 982 } 983 984 template <class T> 985 bool SCOutput::writeArray(const T* p, size_t nelems) { 986 static_assert(8 % sizeof(T) == 0); 987 static_assert(sizeof(uint64_t) % sizeof(T) == 0); 988 989 if (nelems == 0) { 990 return true; 991 } 992 993 for (size_t i = 0; i < nelems; i++) { 994 T value = NativeEndian::swapToLittleEndian(p[i]); 995 if (!buf.AppendBytes(reinterpret_cast<char*>(&value), sizeof(value))) { 996 ReportOutOfMemory(context()); 997 return false; 998 } 999 } 1000 1001 // Zero-pad to 8 bytes boundary. 1002 size_t padbytes = ComputePadding(nelems, sizeof(T)); 1003 char zeroes[sizeof(uint64_t)] = {0}; 1004 if (!buf.AppendBytes(zeroes, padbytes)) { 1005 ReportOutOfMemory(context()); 1006 return false; 1007 } 1008 1009 return true; 1010 } 1011 1012 template <> 1013 bool SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems) { 1014 if (nelems == 0) { 1015 return true; 1016 } 1017 1018 if (!buf.AppendBytes(reinterpret_cast<const char*>(p), nelems)) { 1019 ReportOutOfMemory(context()); 1020 return false; 1021 } 1022 1023 // zero-pad to 8 bytes boundary 1024 size_t padbytes = ComputePadding(nelems, 1); 1025 char zeroes[sizeof(uint64_t)] = {0}; 1026 if (!buf.AppendBytes(zeroes, padbytes)) { 1027 ReportOutOfMemory(context()); 1028 return false; 1029 } 1030 1031 return true; 1032 } 1033 1034 bool SCOutput::writeBytes(const void* p, size_t nbytes) { 1035 return writeArray((const uint8_t*)p, nbytes); 1036 } 1037 1038 bool SCOutput::writeChars(const char16_t* p, size_t nchars) { 1039 static_assert(sizeof(char16_t) == sizeof(uint16_t), 1040 "required so that treating char16_t[] memory as uint16_t[] " 1041 "memory is permissible"); 1042 return writeArray((const uint16_t*)p, nchars); 1043 } 1044 1045 bool SCOutput::writeChars(const Latin1Char* p, size_t nchars) { 1046 static_assert(sizeof(Latin1Char) == sizeof(uint8_t), 1047 "Latin1Char must fit in 1 byte"); 1048 return writeBytes(p, nchars); 1049 } 1050 1051 } // namespace js 1052 1053 JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); } 1054 1055 // If the buffer contains Transferables, free them. Note that custom 1056 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to 1057 // delete their transferables. 1058 void JSStructuredCloneData::discardTransferables() { 1059 if (!Size()) { 1060 return; 1061 } 1062 1063 if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) { 1064 return; 1065 } 1066 1067 // DifferentProcess clones cannot contain pointers, so nothing needs to be 1068 // released. 1069 if (scope() == JS::StructuredCloneScope::DifferentProcess) { 1070 return; 1071 } 1072 1073 FreeTransferStructuredCloneOp freeTransfer = nullptr; 1074 if (callbacks_) { 1075 freeTransfer = callbacks_->freeTransfer; 1076 } 1077 1078 auto point = BufferIterator<uint64_t, SystemAllocPolicy>(*this); 1079 if (point.done()) { 1080 return; // Empty buffer 1081 } 1082 1083 uint32_t tag, data; 1084 MOZ_RELEASE_ASSERT(point.canPeek()); 1085 SCInput::getPair(point.peek(), &tag, &data); 1086 MOZ_ALWAYS_TRUE(point.advance()); 1087 1088 if (tag == SCTAG_HEADER) { 1089 if (point.done()) { 1090 return; 1091 } 1092 1093 MOZ_RELEASE_ASSERT(point.canPeek()); 1094 SCInput::getPair(point.peek(), &tag, &data); 1095 MOZ_ALWAYS_TRUE(point.advance()); 1096 } 1097 1098 if (tag != SCTAG_TRANSFER_MAP_HEADER) { 1099 return; 1100 } 1101 1102 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) { 1103 return; 1104 } 1105 1106 // freeTransfer should not GC 1107 JS::AutoSuppressGCAnalysis nogc; 1108 1109 if (point.done()) { 1110 return; 1111 } 1112 1113 MOZ_RELEASE_ASSERT(point.canPeek()); 1114 uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek()); 1115 MOZ_ALWAYS_TRUE(point.advance()); 1116 while (numTransferables--) { 1117 if (!point.canPeek()) { 1118 return; 1119 } 1120 1121 uint32_t ownership; 1122 SCInput::getPair(point.peek(), &tag, &ownership); 1123 MOZ_ALWAYS_TRUE(point.advance()); 1124 MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY); 1125 if (!point.canPeek()) { 1126 return; 1127 } 1128 1129 void* content; 1130 SCInput::getPtr(point.peek(), &content); 1131 MOZ_ALWAYS_TRUE(point.advance()); 1132 if (!point.canPeek()) { 1133 return; 1134 } 1135 1136 uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek()); 1137 MOZ_ALWAYS_TRUE(point.advance()); 1138 1139 if (ownership < JS::SCTAG_TMO_FIRST_OWNED) { 1140 continue; 1141 } 1142 1143 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) { 1144 js_free(content); 1145 } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) { 1146 JS::ReleaseMappedArrayBufferContents(content, extraData); 1147 } else if (freeTransfer) { 1148 freeTransfer(tag, JS::TransferableOwnership(ownership), content, 1149 extraData, closure_); 1150 } else { 1151 MOZ_ASSERT(false, "unknown ownership"); 1152 } 1153 } 1154 } 1155 1156 static_assert(JSString::MAX_LENGTH < UINT32_MAX); 1157 1158 bool JSStructuredCloneWriter::parseTransferable() { 1159 // NOTE: The transferables set is tested for non-emptiness at various 1160 // junctures in structured cloning, so this set must be initialized 1161 // by this method in all non-error cases. 1162 MOZ_ASSERT(transferableObjects.empty(), 1163 "parseTransferable called with stale data"); 1164 1165 if (transferable.isNull() || transferable.isUndefined()) { 1166 return true; 1167 } 1168 1169 if (!transferable.isObject()) { 1170 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 1171 } 1172 1173 JSContext* cx = context(); 1174 RootedObject array(cx, &transferable.toObject()); 1175 bool isArray; 1176 if (!JS::IsArrayObject(cx, array, &isArray)) { 1177 return false; 1178 } 1179 if (!isArray) { 1180 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 1181 } 1182 1183 uint32_t length; 1184 if (!JS::GetArrayLength(cx, array, &length)) { 1185 return false; 1186 } 1187 1188 // Initialize the set for the provided array's length. 1189 if (!transferableObjects.reserve(length)) { 1190 return false; 1191 } 1192 1193 if (length == 0) { 1194 return true; 1195 } 1196 1197 RootedValue v(context()); 1198 RootedObject tObj(context()); 1199 1200 Rooted<GCHashSet<js::HeapPtr<JSObject*>, 1201 js::StableCellHasher<js::HeapPtr<JSObject*>>, 1202 SystemAllocPolicy>> 1203 seen(context()); 1204 1205 for (uint32_t i = 0; i < length; ++i) { 1206 if (!CheckForInterrupt(cx)) { 1207 return false; 1208 } 1209 1210 if (!JS_GetElement(cx, array, i, &v)) { 1211 return false; 1212 } 1213 1214 if (!v.isObject()) { 1215 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 1216 } 1217 tObj = &v.toObject(); 1218 1219 RootedObject unwrappedObj(cx, CheckedUnwrapStatic(tObj)); 1220 if (!unwrappedObj) { 1221 ReportAccessDenied(cx); 1222 return false; 1223 } 1224 1225 // Shared memory cannot be transferred because it is not possible (nor 1226 // desirable) to detach the memory in agents that already hold a 1227 // reference to it. 1228 1229 if (unwrappedObj->is<SharedArrayBufferObject>()) { 1230 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE); 1231 } 1232 1233 else if (unwrappedObj->is<WasmMemoryObject>()) { 1234 if (unwrappedObj->as<WasmMemoryObject>().isShared()) { 1235 return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE); 1236 } 1237 } 1238 1239 // External array buffers may be able to be transferred in the future, 1240 // but that is not currently implemented. 1241 // 1242 // Immutable array buffers can't be transferred, because they can't be 1243 // detached. 1244 1245 else if (unwrappedObj->is<ArrayBufferObject>()) { 1246 if (unwrappedObj->as<ArrayBufferObject>().isExternal() || 1247 unwrappedObj->as<ArrayBufferObject>().isImmutable()) { 1248 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 1249 } 1250 } 1251 1252 else { 1253 if (!out.buf.callbacks_ || !out.buf.callbacks_->canTransfer) { 1254 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 1255 } 1256 1257 JSAutoRealm ar(cx, unwrappedObj); 1258 bool sameProcessScopeRequired = false; 1259 if (!out.buf.callbacks_->canTransfer( 1260 cx, unwrappedObj, &sameProcessScopeRequired, out.buf.closure_)) { 1261 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 1262 } 1263 1264 if (sameProcessScopeRequired) { 1265 output().sameProcessScopeRequired(); 1266 } 1267 } 1268 1269 // No duplicates allowed. Normally the transferable list is very short, but 1270 // some users are passing >10k. Switch to a hash-based lookup when the 1271 // linear list starts getting long. 1272 constexpr uint32_t MAX_LINEAR = 10; 1273 1274 // Switch from a linear scan to a set lookup, initializing the set with all 1275 // objects seen so far. 1276 if (i == MAX_LINEAR) { 1277 for (JSObject* obj : transferableObjects) { 1278 if (!seen.putNew(obj)) { 1279 seen.clear(); // Fall back to linear scan on OOM. 1280 break; 1281 } 1282 } 1283 } 1284 1285 if (seen.empty()) { 1286 if (std::find(transferableObjects.begin(), transferableObjects.end(), 1287 tObj) != transferableObjects.end()) { 1288 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE); 1289 } 1290 } else { 1291 MOZ_ASSERT(seen.count() == i); // All objs are distinct up to this point. 1292 auto p = seen.lookupForAdd(tObj); 1293 if (p) { 1294 return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE); 1295 } 1296 if (!seen.add(p, tObj)) { 1297 seen.clear(); // Fall back to linear scan on OOM. 1298 } 1299 } 1300 1301 if (!transferableObjects.append(tObj)) { 1302 return false; 1303 } 1304 } 1305 1306 return true; 1307 } 1308 1309 template <typename... Args> 1310 bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId, 1311 Args&&... aArgs) { 1312 ReportDataCloneError(context(), out.buf.callbacks_, errorId, out.buf.closure_, 1313 std::forward<Args>(aArgs)...); 1314 return false; 1315 } 1316 1317 bool JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) { 1318 JSLinearString* linear = str->ensureLinear(context()); 1319 if (!linear) { 1320 return false; 1321 } 1322 1323 #if FUZZING_JS_FUZZILLI 1324 if (js::SupportDifferentialTesting()) { 1325 // TODO we could always output a twoByteChar string 1326 return true; 1327 } 1328 #endif 1329 1330 static_assert(JSString::MAX_LENGTH < (1 << 30), 1331 "String length must fit in 30 bits"); 1332 1333 // Try to share the underlying StringBuffer without copying the contents. 1334 bool useBuffer = linear->hasStringBuffer() && 1335 output().scope() == JS::StructuredCloneScope::SameProcess; 1336 1337 uint32_t length = linear->length(); 1338 bool isLatin1 = linear->hasLatin1Chars(); 1339 uint32_t lengthAndBits = 1340 length | (uint32_t(isLatin1) << 31) | (uint32_t(useBuffer) << 30); 1341 if (!out.writePair(tag, lengthAndBits)) { 1342 return false; 1343 } 1344 1345 if (useBuffer) { 1346 mozilla::StringBuffer* buffer = linear->stringBuffer(); 1347 if (!out.buf.stringBufferRefsHeld_.emplaceBack(buffer)) { 1348 ReportOutOfMemory(context()); 1349 return false; 1350 } 1351 uintptr_t p = reinterpret_cast<uintptr_t>(buffer); 1352 return out.writeBytes(&p, sizeof(p)); 1353 } 1354 1355 JS::AutoCheckCannotGC nogc; 1356 return linear->hasLatin1Chars() 1357 ? out.writeChars(linear->latin1Chars(nogc), length) 1358 : out.writeChars(linear->twoByteChars(nogc), length); 1359 } 1360 1361 bool JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) { 1362 bool signBit = bi->isNegative(); 1363 size_t length = bi->digitLength(); 1364 // The length must fit in 31 bits to leave room for a sign bit. 1365 if (length > size_t(INT32_MAX)) { 1366 return false; 1367 } 1368 uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31); 1369 1370 if (!out.writePair(tag, lengthAndSign)) { 1371 return false; 1372 } 1373 return out.writeArray(bi->digits().data(), length); 1374 } 1375 1376 inline void JSStructuredCloneWriter::checkStack() { 1377 #ifdef DEBUG 1378 // To avoid making serialization O(n^2), limit stack-checking at 10. 1379 const size_t MAX = 10; 1380 1381 size_t limit = std::min(counts.length(), MAX); 1382 MOZ_ASSERT(objs.length() == counts.length()); 1383 size_t total = 0; 1384 for (size_t i = 0; i < limit; i++) { 1385 MOZ_ASSERT(total + counts[i] >= total); 1386 total += counts[i]; 1387 } 1388 if (counts.length() <= MAX) { 1389 MOZ_ASSERT(total == objectEntries.length() + otherEntries.length()); 1390 } else { 1391 MOZ_ASSERT(total <= objectEntries.length() + otherEntries.length()); 1392 } 1393 1394 size_t j = objs.length(); 1395 for (size_t i = 0; i < limit; i++) { 1396 --j; 1397 MOZ_ASSERT(memory.has(&objs[j].toObject())); 1398 } 1399 #endif 1400 } 1401 1402 /* 1403 * Write out a typed array. Note that post-v1 structured clone buffers do not 1404 * perform endianness conversion on stored data, so multibyte typed arrays 1405 * cannot be deserialized into a different endianness machine. Endianness 1406 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and 1407 * Int16Array views of the same ArrayBuffer, should the data bytes be 1408 * byte-swapped when writing or not? The Int8Array requires them to not be 1409 * swapped; the Int16Array requires that they are. 1410 */ 1411 bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj) { 1412 Rooted<TypedArrayObject*> tarr(context(), 1413 obj->maybeUnwrapAs<TypedArrayObject>()); 1414 JSAutoRealm ar(context(), tarr); 1415 1416 #ifdef FUZZING_JS_FUZZILLI 1417 if (js::SupportDifferentialTesting() && !tarr->hasBuffer()) { 1418 // fake oom because differential testing will fail 1419 fprintf(stderr, "[unhandlable oom]"); 1420 _exit(-1); 1421 return false; 1422 } 1423 #endif 1424 1425 if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) { 1426 return false; 1427 } 1428 1429 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) { 1430 return false; 1431 } 1432 1433 mozilla::Maybe<size_t> nelems = tarr->length(); 1434 if (!nelems) { 1435 return reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED); 1436 } 1437 1438 // Auto-length TypedArrays are tagged by storing `-1` for the length. We still 1439 // need to query the length to check for detached or out-of-bounds lengths. 1440 bool isAutoLength = tarr->is<ResizableTypedArrayObject>() && 1441 tarr->as<ResizableTypedArrayObject>().isAutoLength(); 1442 uint64_t length = isAutoLength ? uint64_t(-1) : uint64_t(*nelems); 1443 if (!out.write(length)) { 1444 return false; 1445 } 1446 1447 // Write out the ArrayBuffer tag and contents 1448 RootedValue val(context(), tarr->bufferValue()); 1449 if (!startWrite(val)) { 1450 return false; 1451 } 1452 1453 uint64_t byteOffset = tarr->byteOffset().valueOr(0); 1454 return out.write(byteOffset); 1455 } 1456 1457 bool JSStructuredCloneWriter::writeDataView(HandleObject obj) { 1458 Rooted<DataViewObject*> view(context(), obj->maybeUnwrapAs<DataViewObject>()); 1459 JSAutoRealm ar(context(), view); 1460 1461 if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, 0)) { 1462 return false; 1463 } 1464 1465 mozilla::Maybe<size_t> byteLength = view->byteLength(); 1466 if (!byteLength) { 1467 return reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED); 1468 } 1469 1470 // Auto-length DataViews are tagged by storing `-1` for the length. We still 1471 // need to query the length to check for detached or out-of-bounds lengths. 1472 bool isAutoLength = view->is<ResizableDataViewObject>() && 1473 view->as<ResizableDataViewObject>().isAutoLength(); 1474 uint64_t length = isAutoLength ? uint64_t(-1) : uint64_t(*byteLength); 1475 if (!out.write(length)) { 1476 return false; 1477 } 1478 1479 // Write out the ArrayBuffer tag and contents 1480 RootedValue val(context(), view->bufferValue()); 1481 if (!startWrite(val)) { 1482 return false; 1483 } 1484 1485 uint64_t byteOffset = view->byteOffset().valueOr(0); 1486 return out.write(byteOffset); 1487 } 1488 1489 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) { 1490 Rooted<ArrayBufferObject*> buffer(context(), 1491 obj->maybeUnwrapAs<ArrayBufferObject>()); 1492 JSAutoRealm ar(context(), buffer); 1493 1494 StructuredDataType type = 1495 buffer->isResizable() ? SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT 1496 : buffer->isImmutable() ? SCTAG_IMMUTABLE_ARRAY_BUFFER_OBJECT 1497 : SCTAG_ARRAY_BUFFER_OBJECT; 1498 1499 if (!out.writePair(type, 0)) { 1500 return false; 1501 } 1502 1503 uint64_t byteLength = buffer->byteLength(); 1504 if (!out.write(byteLength)) { 1505 return false; 1506 } 1507 1508 if (buffer->isResizable()) { 1509 uint64_t maxByteLength = 1510 buffer->as<ResizableArrayBufferObject>().maxByteLength(); 1511 if (!out.write(maxByteLength)) { 1512 return false; 1513 } 1514 } 1515 1516 return out.writeBytes(buffer->dataPointer(), byteLength); 1517 } 1518 1519 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj) { 1520 MOZ_ASSERT(obj->canUnwrapAs<SharedArrayBufferObject>()); 1521 1522 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) { 1523 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled() 1524 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP 1525 : JS_SCERR_NOT_CLONABLE; 1526 reportDataCloneError(error, "SharedArrayBuffer"); 1527 return false; 1528 } 1529 1530 output().sameProcessScopeRequired(); 1531 1532 // We must not transmit SAB pointers (including for WebAssembly.Memory) 1533 // cross-process. The cloneDataPolicy should have guarded against this; 1534 // since it did not then throw, with a very explicit message. 1535 1536 if (output().scope() > JS::StructuredCloneScope::SameProcess) { 1537 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 1538 JSMSG_SC_SHMEM_POLICY); 1539 return false; 1540 } 1541 1542 Rooted<SharedArrayBufferObject*> sharedArrayBuffer( 1543 context(), obj->maybeUnwrapAs<SharedArrayBufferObject>()); 1544 SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject(); 1545 1546 if (!out.buf.refsHeld_.acquire(context(), rawbuf)) { 1547 return false; 1548 } 1549 1550 // We must serialize the length so that the buffer object arrives in the 1551 // receiver with the same length, and not with the length read from the 1552 // rawbuf - that length can be different, and it can change at any time. 1553 1554 StructuredDataType type = !sharedArrayBuffer->isGrowable() 1555 ? SCTAG_SHARED_ARRAY_BUFFER_OBJECT 1556 : SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT; 1557 1558 intptr_t p = reinterpret_cast<intptr_t>(rawbuf); 1559 uint64_t byteLength = sharedArrayBuffer->byteLengthOrMaxByteLength(); 1560 if (!(out.writePair(type, /* unused data word */ 0) && 1561 out.writeBytes(&byteLength, sizeof(byteLength)) && 1562 out.writeBytes(&p, sizeof(p)))) { 1563 return false; 1564 } 1565 1566 if (callbacks && callbacks->sabCloned && 1567 !callbacks->sabCloned(context(), /*receiving=*/false, closure)) { 1568 return false; 1569 } 1570 1571 return true; 1572 } 1573 1574 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj) { 1575 MOZ_ASSERT(obj->canUnwrapAs<WasmMemoryObject>()); 1576 1577 // Check the policy here so that we can report a sane error. 1578 if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) { 1579 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled() 1580 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP 1581 : JS_SCERR_NOT_CLONABLE; 1582 reportDataCloneError(error, "WebAssembly.Memory"); 1583 return false; 1584 } 1585 1586 // If this changes, might need to change what we write. 1587 MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS == 3); 1588 1589 Rooted<WasmMemoryObject*> memoryObj(context(), 1590 &obj->unwrapAs<WasmMemoryObject>()); 1591 Rooted<SharedArrayBufferObject*> sab( 1592 context(), &memoryObj->buffer().as<SharedArrayBufferObject>()); 1593 1594 return out.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT, 0) && 1595 out.writePair(SCTAG_BOOLEAN, memoryObj->isHuge()) && 1596 writeSharedArrayBuffer(sab); 1597 } 1598 1599 bool JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref) { 1600 // Handle cycles in the object graph. 1601 CloneMemory::AddPtr p = memory.lookupForAdd(obj); 1602 if ((*backref = p.found())) { 1603 return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value()); 1604 } 1605 if (!memory.add(p, obj, memory.count())) { 1606 ReportOutOfMemory(context()); 1607 return false; 1608 } 1609 1610 if (memory.count() == UINT32_MAX) { 1611 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 1612 JSMSG_NEED_DIET, "object graph to serialize"); 1613 return false; 1614 } 1615 1616 return true; 1617 } 1618 1619 static bool TryAppendNativeProperties(JSContext* cx, HandleObject obj, 1620 MutableHandleIdVector entries, 1621 size_t* properties, bool* optimized) { 1622 *optimized = false; 1623 1624 if (!obj->is<NativeObject>()) { 1625 return true; 1626 } 1627 1628 Handle<NativeObject*> nobj = obj.as<NativeObject>(); 1629 if (nobj->isIndexed() || nobj->is<TypedArrayObject>() || 1630 nobj->getClass()->getNewEnumerate() || nobj->getClass()->getEnumerate()) { 1631 return true; 1632 } 1633 1634 *optimized = true; 1635 1636 size_t count = 0; 1637 // We iterate from the last to the first property, so the property names 1638 // are already in reverse order. 1639 for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) { 1640 jsid id = iter->key(); 1641 1642 // Ignore symbols and non-enumerable properties. 1643 if (!iter->enumerable() || id.isSymbol()) { 1644 continue; 1645 } 1646 1647 MOZ_ASSERT(id.isString()); 1648 if (!entries.append(id)) { 1649 return false; 1650 } 1651 1652 count++; 1653 } 1654 1655 // Add dense element ids in reverse order. 1656 for (uint32_t i = nobj->getDenseInitializedLength(); i > 0; --i) { 1657 if (nobj->getDenseElement(i - 1).isMagic(JS_ELEMENTS_HOLE)) { 1658 continue; 1659 } 1660 1661 if (!entries.append(PropertyKey::Int(i - 1))) { 1662 return false; 1663 } 1664 1665 count++; 1666 } 1667 1668 *properties = count; 1669 return true; 1670 } 1671 1672 // Objects are written as a "preorder" traversal of the object graph: object 1673 // "headers" (the class tag and any data needed for initial construction) are 1674 // visited first, then the children are recursed through (where children are 1675 // properties, Set or Map entries, etc.). So for example 1676 // 1677 // obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} } 1678 // 1679 // would be stored as: 1680 // 1681 // <Object tag for obj1> 1682 // <key1 data> 1683 // <Object tag for key1's value> 1684 // <key1.1 data> 1685 // <val1.1 data> 1686 // <key1.2 data> 1687 // <val1.2 data> 1688 // <end-of-children marker for key1's value> 1689 // <key2 data> 1690 // <Object tag for key2's value> 1691 // <end-of-children marker for key2's value> 1692 // <end-of-children marker for obj1> 1693 // 1694 // This nests nicely (ie, an entire recursive value starts with its tag and 1695 // ends with its end-of-children marker) and so it can be presented indented. 1696 // But see traverseMap below for how this looks different for Maps. 1697 bool JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls) { 1698 size_t count; 1699 bool optimized = false; 1700 if (!js::SupportDifferentialTesting()) { 1701 if (!TryAppendNativeProperties(context(), obj, &objectEntries, &count, 1702 &optimized)) { 1703 return false; 1704 } 1705 } 1706 1707 if (!optimized) { 1708 // Get enumerable property ids and put them in reverse order so that they 1709 // will come off the stack in forward order. 1710 RootedIdVector properties(context()); 1711 if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties)) { 1712 return false; 1713 } 1714 1715 for (size_t i = properties.length(); i > 0; --i) { 1716 jsid id = properties[i - 1]; 1717 1718 MOZ_ASSERT(id.isString() || id.isInt()); 1719 if (!objectEntries.append(id)) { 1720 return false; 1721 } 1722 } 1723 1724 count = properties.length(); 1725 } 1726 1727 // Push obj and count to the stack. 1728 if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) { 1729 return false; 1730 } 1731 1732 checkStack(); 1733 1734 #if DEBUG 1735 ESClass cls2; 1736 if (!GetBuiltinClass(context(), obj, &cls2)) { 1737 return false; 1738 } 1739 MOZ_ASSERT(cls2 == cls); 1740 #endif 1741 1742 // Write the header for obj. 1743 if (cls == ESClass::Array) { 1744 uint32_t length = 0; 1745 if (!JS::GetArrayLength(context(), obj, &length)) { 1746 return false; 1747 } 1748 1749 return out.writePair(SCTAG_ARRAY_OBJECT, 1750 NativeEndian::swapToLittleEndian(length)); 1751 } 1752 1753 return out.writePair(SCTAG_OBJECT_OBJECT, 0); 1754 } 1755 1756 // Use the same basic setup as for traverseObject, but now keys can themselves 1757 // be complex objects. Keys and values are visited first via startWrite(), then 1758 // the key's children (if any) are handled, then the value's children. 1759 // 1760 // m = new Map(); 1761 // m.set(key1 = ..., value1 = ...) 1762 // 1763 // where key1 and value2 are both objects would be stored as 1764 // 1765 // <Map tag> 1766 // <key1 class tag> 1767 // <value1 class tag> 1768 // ...key1 fields... 1769 // <end-of-children marker for key1> 1770 // ...value1 fields... 1771 // <end-of-children marker for value1> 1772 // <end-of-children marker for Map> 1773 // 1774 // Notice how the end-of-children marker for key1 is sandwiched between the 1775 // value1 beginning and end. 1776 bool JSStructuredCloneWriter::traverseMap(HandleObject obj) { 1777 Rooted<GCVector<Value>> newEntries(context(), GCVector<Value>(context())); 1778 { 1779 // If there is no wrapper, the compartment munging is a no-op. 1780 Rooted<MapObject*> unwrapped(context(), obj->maybeUnwrapAs<MapObject>()); 1781 MOZ_ASSERT(unwrapped); 1782 JSAutoRealm ar(context(), unwrapped); 1783 if (!unwrapped->getKeysAndValuesInterleaved(&newEntries)) { 1784 return false; 1785 } 1786 } 1787 if (!context()->compartment()->wrap(context(), &newEntries)) { 1788 return false; 1789 } 1790 1791 for (size_t i = newEntries.length(); i > 0; --i) { 1792 if (!otherEntries.append(newEntries[i - 1])) { 1793 return false; 1794 } 1795 } 1796 1797 // Push obj and count to the stack. 1798 if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length())) { 1799 return false; 1800 } 1801 1802 checkStack(); 1803 1804 // Write the header for obj. 1805 return out.writePair(SCTAG_MAP_OBJECT, 0); 1806 } 1807 1808 // Similar to traverseMap, only there is a single value instead of a key and 1809 // value, and thus no interleaving is possible: a value will be fully emitted 1810 // before the next value is begun. 1811 bool JSStructuredCloneWriter::traverseSet(HandleObject obj) { 1812 Rooted<GCVector<Value>> keys(context(), GCVector<Value>(context())); 1813 { 1814 // If there is no wrapper, the compartment munging is a no-op. 1815 Rooted<SetObject*> unwrapped(context(), obj->maybeUnwrapAs<SetObject>()); 1816 MOZ_ASSERT(unwrapped); 1817 JSAutoRealm ar(context(), unwrapped); 1818 if (!unwrapped->keys(&keys)) { 1819 return false; 1820 } 1821 } 1822 if (!context()->compartment()->wrap(context(), &keys)) { 1823 return false; 1824 } 1825 1826 for (size_t i = keys.length(); i > 0; --i) { 1827 if (!otherEntries.append(keys[i - 1])) { 1828 return false; 1829 } 1830 } 1831 1832 // Push obj and count to the stack. 1833 if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length())) { 1834 return false; 1835 } 1836 1837 checkStack(); 1838 1839 // Write the header for obj. 1840 return out.writePair(SCTAG_SET_OBJECT, 0); 1841 } 1842 1843 bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj) { 1844 Rooted<SavedFrame*> savedFrame(context(), obj->maybeUnwrapAs<SavedFrame>()); 1845 MOZ_ASSERT(savedFrame); 1846 1847 RootedObject parent(context(), savedFrame->getParent()); 1848 if (!context()->compartment()->wrap(context(), &parent)) { 1849 return false; 1850 } 1851 1852 if (!objs.append(ObjectValue(*obj)) || 1853 !otherEntries.append(parent ? ObjectValue(*parent) : NullValue()) || 1854 !counts.append(1)) { 1855 return false; 1856 } 1857 1858 checkStack(); 1859 1860 // Write the SavedFrame tag and the SavedFrame's principals. 1861 1862 if (savedFrame->getPrincipals() == 1863 &ReconstructedSavedFramePrincipals::IsSystem) { 1864 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, 1865 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM)) { 1866 return false; 1867 }; 1868 } else if (savedFrame->getPrincipals() == 1869 &ReconstructedSavedFramePrincipals::IsNotSystem) { 1870 if (!out.writePair( 1871 SCTAG_SAVED_FRAME_OBJECT, 1872 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM)) { 1873 return false; 1874 } 1875 } else { 1876 if (auto principals = savedFrame->getPrincipals()) { 1877 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) || 1878 !principals->write(context(), this)) { 1879 return false; 1880 } 1881 } else { 1882 if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS)) { 1883 return false; 1884 } 1885 } 1886 } 1887 1888 // Write the SavedFrame's reserved slots, except for the parent, which is 1889 // queued on objs for further traversal. 1890 1891 RootedValue val(context()); 1892 1893 val = BooleanValue(savedFrame->getMutedErrors()); 1894 if (!writePrimitive(val)) { 1895 return false; 1896 } 1897 1898 context()->markAtom(savedFrame->getSource()); 1899 val = StringValue(savedFrame->getSource()); 1900 if (!writePrimitive(val)) { 1901 return false; 1902 } 1903 1904 val = NumberValue(savedFrame->getLine()); 1905 if (!writePrimitive(val)) { 1906 return false; 1907 } 1908 1909 val = NumberValue(*savedFrame->getColumn().addressOfValueForTranscode()); 1910 if (!writePrimitive(val)) { 1911 return false; 1912 } 1913 1914 auto name = savedFrame->getFunctionDisplayName(); 1915 if (name) { 1916 context()->markAtom(name); 1917 } 1918 val = name ? StringValue(name) : NullValue(); 1919 if (!writePrimitive(val)) { 1920 return false; 1921 } 1922 1923 auto cause = savedFrame->getAsyncCause(); 1924 if (cause) { 1925 context()->markAtom(cause); 1926 } 1927 val = cause ? StringValue(cause) : NullValue(); 1928 if (!writePrimitive(val)) { 1929 return false; 1930 } 1931 1932 return true; 1933 } 1934 1935 // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal 1936 // 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] ) 1937 // 1938 // Step 17. Otherwise, if value has an [[ErrorData]] internal slot and 1939 // value is not a platform object, then: 1940 // 1941 // Note: This contains custom extensions for handling non-standard properties. 1942 bool JSStructuredCloneWriter::traverseError(HandleObject obj) { 1943 JSContext* cx = context(); 1944 1945 // 1. Let name be ? Get(value, "name"). 1946 RootedValue name(cx); 1947 if (!GetProperty(cx, obj, obj, cx->names().name, &name)) { 1948 return false; 1949 } 1950 1951 // 2. If name is not one of "Error", "EvalError", "RangeError", 1952 // "ReferenceError", "SyntaxError", "TypeError", or "URIError", 1953 // (not yet specified: or "AggregateError") 1954 // then set name to "Error". 1955 JSExnType type = JSEXN_ERR; 1956 if (name.isString()) { 1957 JSLinearString* linear = name.toString()->ensureLinear(cx); 1958 if (!linear) { 1959 return false; 1960 } 1961 1962 if (EqualStrings(linear, cx->names().Error)) { 1963 type = JSEXN_ERR; 1964 } else if (EqualStrings(linear, cx->names().EvalError)) { 1965 type = JSEXN_EVALERR; 1966 } else if (EqualStrings(linear, cx->names().RangeError)) { 1967 type = JSEXN_RANGEERR; 1968 } else if (EqualStrings(linear, cx->names().ReferenceError)) { 1969 type = JSEXN_REFERENCEERR; 1970 } else if (EqualStrings(linear, cx->names().SyntaxError)) { 1971 type = JSEXN_SYNTAXERR; 1972 } else if (EqualStrings(linear, cx->names().TypeError)) { 1973 type = JSEXN_TYPEERR; 1974 } else if (EqualStrings(linear, cx->names().URIError)) { 1975 type = JSEXN_URIERR; 1976 } else if (EqualStrings(linear, cx->names().AggregateError)) { 1977 type = JSEXN_AGGREGATEERR; 1978 } 1979 } 1980 1981 // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message"). 1982 RootedId messageId(cx, NameToId(cx->names().message)); 1983 Rooted<Maybe<PropertyDescriptor>> messageDesc(cx); 1984 if (!GetOwnPropertyDescriptor(cx, obj, messageId, &messageDesc)) { 1985 return false; 1986 } 1987 1988 // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false, 1989 // and ? ToString(valueMessageDesc.[[Value]]) otherwise. 1990 RootedString message(cx); 1991 if (messageDesc.isSome() && messageDesc->isDataDescriptor()) { 1992 RootedValue messageVal(cx, messageDesc->value()); 1993 message = ToString<CanGC>(cx, messageVal); 1994 if (!message) { 1995 return false; 1996 } 1997 } 1998 1999 // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]: 2000 // message }. 2001 2002 if (!objs.append(ObjectValue(*obj))) { 2003 return false; 2004 } 2005 2006 Rooted<ErrorObject*> unwrapped(cx, obj->maybeUnwrapAs<ErrorObject>()); 2007 MOZ_ASSERT(unwrapped); 2008 2009 // Non-standard: Serialize |stack|. 2010 // The Error stack property is saved as SavedFrames. 2011 RootedValue stack(cx, NullValue()); 2012 RootedObject stackObj(cx, unwrapped->stack()); 2013 if (stackObj) { 2014 MOZ_ASSERT(stackObj->canUnwrapAs<SavedFrame>()); 2015 stack.setObject(*stackObj); 2016 if (!cx->compartment()->wrap(cx, &stack)) { 2017 return false; 2018 } 2019 } 2020 if (!otherEntries.append(stack)) { 2021 return false; 2022 } 2023 2024 // Serialize |errors| 2025 if (type == JSEXN_AGGREGATEERR) { 2026 RootedValue errors(cx); 2027 if (!GetProperty(cx, obj, obj, cx->names().errors, &errors)) { 2028 return false; 2029 } 2030 if (!otherEntries.append(errors)) { 2031 return false; 2032 } 2033 } else { 2034 if (!otherEntries.append(NullValue())) { 2035 return false; 2036 } 2037 } 2038 2039 // Non-standard: Serialize |cause|. Because this property 2040 // might be missing we also write "hasCause" later. 2041 RootedId causeId(cx, NameToId(cx->names().cause)); 2042 Rooted<Maybe<PropertyDescriptor>> causeDesc(cx); 2043 if (!GetOwnPropertyDescriptor(cx, obj, causeId, &causeDesc)) { 2044 return false; 2045 } 2046 2047 Rooted<Maybe<Value>> cause(cx); 2048 if (causeDesc.isSome() && causeDesc->isDataDescriptor()) { 2049 cause = mozilla::Some(causeDesc->value()); 2050 } 2051 if (!cx->compartment()->wrap(cx, &cause)) { 2052 return false; 2053 } 2054 if (!otherEntries.append(cause.get().valueOr(NullValue()))) { 2055 return false; 2056 } 2057 2058 // |cause| + |errors| + |stack|, pushed in reverse order 2059 if (!counts.append(3)) { 2060 return false; 2061 } 2062 2063 checkStack(); 2064 2065 if (!out.writePair(SCTAG_ERROR_OBJECT, type)) { 2066 return false; 2067 } 2068 2069 RootedValue val(cx, message ? StringValue(message) : NullValue()); 2070 if (!writePrimitive(val)) { 2071 return false; 2072 } 2073 2074 // hasCause 2075 val = BooleanValue(cause.isSome()); 2076 if (!writePrimitive(val)) { 2077 return false; 2078 } 2079 2080 // Non-standard: Also serialize fileName, lineNumber and columnNumber. 2081 { 2082 JSAutoRealm ar(cx, unwrapped); 2083 val = StringValue(unwrapped->fileName(cx)); 2084 } 2085 if (!cx->compartment()->wrap(cx, &val) || !writePrimitive(val)) { 2086 return false; 2087 } 2088 2089 val = Int32Value(unwrapped->lineNumber()); 2090 if (!writePrimitive(val)) { 2091 return false; 2092 } 2093 2094 val = Int32Value(*unwrapped->columnNumber().addressOfValueForTranscode()); 2095 return writePrimitive(val); 2096 } 2097 2098 bool JSStructuredCloneWriter::writePrimitive(HandleValue v) { 2099 MOZ_ASSERT(v.isPrimitive()); 2100 context()->check(v); 2101 2102 if (v.isString()) { 2103 return writeString(SCTAG_STRING, v.toString()); 2104 } else if (v.isInt32()) { 2105 if (js::SupportDifferentialTesting()) { 2106 return out.writeDouble(v.toInt32()); 2107 } 2108 return out.writePair(SCTAG_INT32, v.toInt32()); 2109 } else if (v.isDouble()) { 2110 return out.writeDouble(v.toDouble()); 2111 } else if (v.isBoolean()) { 2112 return out.writePair(SCTAG_BOOLEAN, v.toBoolean()); 2113 } else if (v.isNull()) { 2114 return out.writePair(SCTAG_NULL, 0); 2115 } else if (v.isUndefined()) { 2116 return out.writePair(SCTAG_UNDEFINED, 0); 2117 } else if (v.isBigInt()) { 2118 return writeBigInt(SCTAG_BIGINT, v.toBigInt()); 2119 } 2120 2121 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE); 2122 } 2123 2124 bool JSStructuredCloneWriter::startWrite(HandleValue v) { 2125 context()->check(v); 2126 2127 if (v.isPrimitive()) { 2128 return writePrimitive(v); 2129 } 2130 2131 if (!v.isObject()) { 2132 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE); 2133 } 2134 2135 RootedObject obj(context(), &v.toObject()); 2136 2137 bool backref; 2138 if (!startObject(obj, &backref)) { 2139 return false; 2140 } 2141 if (backref) { 2142 return true; 2143 } 2144 2145 ESClass cls; 2146 if (!GetBuiltinClass(context(), obj, &cls)) { 2147 return false; 2148 } 2149 2150 switch (cls) { 2151 case ESClass::Object: 2152 case ESClass::Array: 2153 return traverseObject(obj, cls); 2154 case ESClass::Number: { 2155 RootedValue unboxed(context()); 2156 if (!Unbox(context(), obj, &unboxed)) { 2157 return false; 2158 } 2159 return out.writePair(SCTAG_NUMBER_OBJECT, 0) && 2160 out.writeDouble(unboxed.toNumber()); 2161 } 2162 case ESClass::String: { 2163 RootedValue unboxed(context()); 2164 if (!Unbox(context(), obj, &unboxed)) { 2165 return false; 2166 } 2167 return writeString(SCTAG_STRING_OBJECT, unboxed.toString()); 2168 } 2169 case ESClass::Boolean: { 2170 RootedValue unboxed(context()); 2171 if (!Unbox(context(), obj, &unboxed)) { 2172 return false; 2173 } 2174 return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean()); 2175 } 2176 case ESClass::RegExp: { 2177 RegExpShared* re = RegExpToShared(context(), obj); 2178 if (!re) { 2179 return false; 2180 } 2181 return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags().value()) && 2182 writeString(SCTAG_STRING, re->getSource()); 2183 } 2184 case ESClass::ArrayBuffer: { 2185 if (JS::IsArrayBufferObject(obj) && JS::ArrayBufferHasData(obj)) { 2186 return writeArrayBuffer(obj); 2187 } 2188 break; 2189 } 2190 case ESClass::SharedArrayBuffer: 2191 if (JS::IsSharedArrayBufferObject(obj)) { 2192 return writeSharedArrayBuffer(obj); 2193 } 2194 break; 2195 case ESClass::Date: { 2196 RootedValue unboxed(context()); 2197 if (!Unbox(context(), obj, &unboxed)) { 2198 return false; 2199 } 2200 return out.writePair(SCTAG_DATE_OBJECT, 0) && 2201 out.writeDouble(unboxed.toNumber()); 2202 } 2203 case ESClass::Set: 2204 return traverseSet(obj); 2205 case ESClass::Map: 2206 return traverseMap(obj); 2207 case ESClass::Error: 2208 return traverseError(obj); 2209 case ESClass::BigInt: { 2210 RootedValue unboxed(context()); 2211 if (!Unbox(context(), obj, &unboxed)) { 2212 return false; 2213 } 2214 return writeBigInt(SCTAG_BIGINT_OBJECT, unboxed.toBigInt()); 2215 } 2216 case ESClass::Promise: 2217 case ESClass::MapIterator: 2218 case ESClass::SetIterator: 2219 case ESClass::Arguments: 2220 case ESClass::Function: 2221 break; 2222 case ESClass::Other: { 2223 if (obj->canUnwrapAs<TypedArrayObject>()) { 2224 return writeTypedArray(obj); 2225 } 2226 if (obj->canUnwrapAs<DataViewObject>()) { 2227 return writeDataView(obj); 2228 } 2229 if (wasm::IsSharedWasmMemoryObject(obj)) { 2230 return writeSharedWasmMemory(obj); 2231 } 2232 if (obj->canUnwrapAs<SavedFrame>()) { 2233 return traverseSavedFrame(obj); 2234 } 2235 break; 2236 } 2237 } 2238 2239 if (out.buf.callbacks_ && out.buf.callbacks_->write) { 2240 bool sameProcessScopeRequired = false; 2241 if (!out.buf.callbacks_->write(context(), this, obj, 2242 &sameProcessScopeRequired, 2243 out.buf.closure_)) { 2244 return false; 2245 } 2246 2247 if (sameProcessScopeRequired) { 2248 output().sameProcessScopeRequired(); 2249 } 2250 2251 return true; 2252 } 2253 2254 return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE); 2255 } 2256 2257 bool JSStructuredCloneWriter::writeHeader() { 2258 return out.writePair(SCTAG_HEADER, (uint32_t)output().scope()); 2259 } 2260 2261 bool JSStructuredCloneWriter::writeTransferMap() { 2262 if (transferableObjects.empty()) { 2263 return true; 2264 } 2265 2266 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) { 2267 return false; 2268 } 2269 2270 if (!out.write(transferableObjects.length())) { 2271 return false; 2272 } 2273 2274 RootedObject obj(context()); 2275 for (auto* o : transferableObjects) { 2276 obj = o; 2277 if (!memory.put(obj, memory.count())) { 2278 ReportOutOfMemory(context()); 2279 return false; 2280 } 2281 2282 // Emit a placeholder pointer. We defer stealing the data until later 2283 // (and, if necessary, detaching this object if it's an ArrayBuffer). 2284 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, 2285 JS::SCTAG_TMO_UNFILLED)) { 2286 return false; 2287 } 2288 if (!out.write(0)) { // Pointer to ArrayBuffer contents. 2289 return false; 2290 } 2291 if (!out.write(0)) { // extraData 2292 return false; 2293 } 2294 } 2295 2296 return true; 2297 } 2298 2299 bool JSStructuredCloneWriter::transferOwnership() { 2300 if (transferableObjects.empty()) { 2301 return true; 2302 } 2303 2304 // Walk along the transferables and the transfer map at the same time, 2305 // grabbing out pointers from the transferables and stuffing them into the 2306 // transfer map. 2307 auto point = out.iter(); 2308 MOZ_RELEASE_ASSERT(point.canPeek()); 2309 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) == 2310 SCTAG_HEADER); 2311 point++; 2312 MOZ_RELEASE_ASSERT(point.canPeek()); 2313 MOZ_ASSERT(uint32_t(NativeEndian::swapFromLittleEndian(point.peek()) >> 32) == 2314 SCTAG_TRANSFER_MAP_HEADER); 2315 point++; 2316 MOZ_RELEASE_ASSERT(point.canPeek()); 2317 MOZ_ASSERT(NativeEndian::swapFromLittleEndian(point.peek()) == 2318 transferableObjects.length()); 2319 point++; 2320 2321 JSContext* cx = context(); 2322 RootedObject obj(cx); 2323 JS::StructuredCloneScope scope = output().scope(); 2324 for (auto* o : transferableObjects) { 2325 obj = o; 2326 2327 uint32_t tag; 2328 JS::TransferableOwnership ownership; 2329 void* content; 2330 uint64_t extraData; 2331 2332 #if DEBUG 2333 SCInput::getPair(point.peek(), &tag, (uint32_t*)&ownership); 2334 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY); 2335 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED); 2336 #endif 2337 2338 ESClass cls; 2339 if (!GetBuiltinClass(cx, obj, &cls)) { 2340 return false; 2341 } 2342 2343 if (cls == ESClass::ArrayBuffer) { 2344 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER; 2345 2346 // The current setup of the array buffer inheritance hierarchy doesn't 2347 // lend itself well to generic manipulation via proxies. 2348 Rooted<ArrayBufferObject*> arrayBuffer( 2349 cx, obj->maybeUnwrapAs<ArrayBufferObject>()); 2350 JSAutoRealm ar(cx, arrayBuffer); 2351 2352 MOZ_ASSERT(!arrayBuffer->isImmutable(), 2353 "Immutable array buffers can't be transferred"); 2354 2355 if (arrayBuffer->isDetached()) { 2356 reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED); 2357 return false; 2358 } 2359 2360 if (arrayBuffer->isPreparedForAsmJS()) { 2361 reportDataCloneError(JS_SCERR_WASM_NO_TRANSFER); 2362 return false; 2363 } 2364 2365 if (scope == JS::StructuredCloneScope::DifferentProcess || 2366 scope == JS::StructuredCloneScope::DifferentProcessForIndexedDB || 2367 arrayBuffer->isResizable()) { 2368 // Write Transferred ArrayBuffers in DifferentProcess scope at 2369 // the end of the clone buffer, and store the offset within the 2370 // buffer to where the ArrayBuffer was written. Note that this 2371 // will invalidate the current position iterator. 2372 // 2373 // Resizable ArrayBuffers need to store two extra data, the byte length 2374 // and the maximum byte length, but the current transferables format 2375 // supports only a single additional datum. Therefore resizable buffers 2376 // currently go through this slower code path. 2377 2378 size_t pointOffset = out.offset(point); 2379 tag = SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER; 2380 ownership = JS::SCTAG_TMO_UNOWNED; 2381 content = nullptr; 2382 extraData = out.tell() - 2383 pointOffset; // Offset from tag to current end of buffer 2384 if (!writeArrayBuffer(arrayBuffer)) { 2385 ReportOutOfMemory(cx); 2386 return false; 2387 } 2388 2389 // Must refresh the point iterator after its collection has 2390 // been modified. 2391 point = out.iter(); 2392 point += pointOffset; 2393 2394 if (!JS::DetachArrayBuffer(cx, arrayBuffer)) { 2395 return false; 2396 } 2397 } else { 2398 size_t nbytes = arrayBuffer->byteLength(); 2399 2400 using BufferContents = ArrayBufferObject::BufferContents; 2401 2402 BufferContents bufContents = 2403 ArrayBufferObject::extractStructuredCloneContents(cx, arrayBuffer); 2404 if (!bufContents) { 2405 return false; // out of memory 2406 } 2407 2408 content = bufContents.data(); 2409 if (bufContents.kind() == ArrayBufferObject::MAPPED) { 2410 ownership = JS::SCTAG_TMO_MAPPED_DATA; 2411 } else { 2412 MOZ_ASSERT( 2413 bufContents.kind() == 2414 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA || 2415 bufContents.kind() == 2416 ArrayBufferObject::MALLOCED_UNKNOWN_ARENA, 2417 "failing to handle new ArrayBuffer kind?"); 2418 ownership = JS::SCTAG_TMO_ALLOC_DATA; 2419 } 2420 extraData = nbytes; 2421 } 2422 } else { 2423 if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) { 2424 return reportDataCloneError(JS_SCERR_TRANSFERABLE); 2425 } 2426 if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag, 2427 &ownership, &content, 2428 &extraData)) { 2429 return false; 2430 } 2431 MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY); 2432 } 2433 2434 point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership))); 2435 MOZ_ALWAYS_TRUE(point.advance()); 2436 point.write( 2437 NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content))); 2438 MOZ_ALWAYS_TRUE(point.advance()); 2439 point.write(NativeEndian::swapToLittleEndian(extraData)); 2440 MOZ_ALWAYS_TRUE(point.advance()); 2441 } 2442 2443 #if DEBUG 2444 // Make sure there aren't any more transfer map entries after the expected 2445 // number we read out. 2446 if (!point.done()) { 2447 uint32_t tag, data; 2448 SCInput::getPair(point.peek(), &tag, &data); 2449 MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER || 2450 tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES); 2451 } 2452 #endif 2453 return true; 2454 } 2455 2456 bool JSStructuredCloneWriter::write(HandleValue v) { 2457 if (!startWrite(v)) { 2458 return false; 2459 } 2460 2461 RootedObject obj(context()); 2462 RootedValue key(context()); 2463 RootedValue val(context()); 2464 RootedId id(context()); 2465 2466 RootedValue cause(context()); 2467 RootedValue errors(context()); 2468 RootedValue stack(context()); 2469 2470 while (!counts.empty()) { 2471 obj = &objs.back().toObject(); 2472 context()->check(obj); 2473 if (counts.back()) { 2474 counts.back()--; 2475 2476 ESClass cls; 2477 if (!GetBuiltinClass(context(), obj, &cls)) { 2478 return false; 2479 } 2480 2481 if (cls == ESClass::Map) { 2482 key = otherEntries.popCopy(); 2483 checkStack(); 2484 2485 counts.back()--; 2486 val = otherEntries.popCopy(); 2487 checkStack(); 2488 2489 if (!startWrite(key) || !startWrite(val)) { 2490 return false; 2491 } 2492 } else if (cls == ESClass::Set || obj->canUnwrapAs<SavedFrame>()) { 2493 key = otherEntries.popCopy(); 2494 checkStack(); 2495 2496 if (!startWrite(key)) { 2497 return false; 2498 } 2499 } else if (cls == ESClass::Error) { 2500 cause = otherEntries.popCopy(); 2501 checkStack(); 2502 2503 counts.back()--; 2504 errors = otherEntries.popCopy(); 2505 checkStack(); 2506 2507 counts.back()--; 2508 stack = otherEntries.popCopy(); 2509 checkStack(); 2510 2511 if (!startWrite(cause) || !startWrite(errors) || !startWrite(stack)) { 2512 return false; 2513 } 2514 } else { 2515 id = objectEntries.popCopy(); 2516 key = IdToValue(id); 2517 checkStack(); 2518 2519 // If obj still has an own property named id, write it out. 2520 bool found; 2521 if (GetOwnPropertyPure(context(), obj, id, val.address(), &found)) { 2522 if (found) { 2523 if (!writePrimitive(key) || !startWrite(val)) { 2524 return false; 2525 } 2526 } 2527 continue; 2528 } 2529 2530 if (!HasOwnProperty(context(), obj, id, &found)) { 2531 return false; 2532 } 2533 2534 if (found) { 2535 #if FUZZING_JS_FUZZILLI 2536 // supress calls into user code 2537 if (js::SupportDifferentialTesting()) { 2538 fprintf(stderr, "Differential testing: cannot call GetProperty\n"); 2539 return false; 2540 } 2541 #endif 2542 2543 if (!writePrimitive(key) || 2544 !GetProperty(context(), obj, obj, id, &val) || !startWrite(val)) { 2545 return false; 2546 } 2547 } 2548 } 2549 } else { 2550 if (!out.writePair(SCTAG_END_OF_KEYS, 0)) { 2551 return false; 2552 } 2553 objs.popBack(); 2554 counts.popBack(); 2555 } 2556 } 2557 2558 memory.clear(); 2559 return transferOwnership(); 2560 } 2561 2562 JSStructuredCloneReader::JSStructuredCloneReader( 2563 SCInput& in, JS::StructuredCloneScope scope, 2564 const JS::CloneDataPolicy& cloneDataPolicy, 2565 const JSStructuredCloneCallbacks* cb, void* cbClosure) 2566 : in(in), 2567 allowedScope(scope), 2568 cloneDataPolicy(cloneDataPolicy), 2569 objs(in.context()), 2570 objState(in.context(), in.context()), 2571 allObjs(in.context()), 2572 numItemsRead(0), 2573 callbacks(cb), 2574 closure(cbClosure), 2575 gcHeap(in.context()) { 2576 // Avoid the need to bounds check by keeping a never-matching element at the 2577 // base of the `objState` stack. This append() will always succeed because 2578 // the objState vector has a nonzero MinInlineCapacity. 2579 MOZ_ALWAYS_TRUE(objState.append(std::make_pair(nullptr, true))); 2580 } 2581 2582 template <typename CharT> 2583 JSString* JSStructuredCloneReader::readStringImpl( 2584 uint32_t nchars, ShouldAtomizeStrings atomize) { 2585 if (atomize) { 2586 AtomStringChars<CharT> chars; 2587 if (!chars.maybeAlloc(context(), nchars) || 2588 !in.readChars(chars.data(), nchars)) { 2589 return nullptr; 2590 } 2591 return chars.toAtom(context(), nchars); 2592 } 2593 2594 // Uses `StringChars::unsafeData()` because `readChars` can report an error, 2595 // which can trigger a GC. 2596 StringChars<CharT> chars(context()); 2597 if (!chars.maybeAlloc(context(), nchars, gcHeap) || 2598 !in.readChars(chars.unsafeData(), nchars)) { 2599 return nullptr; 2600 } 2601 return chars.template toStringDontDeflate<CanGC>(context(), nchars, gcHeap); 2602 } 2603 2604 JSString* JSStructuredCloneReader::readString(uint32_t data, 2605 ShouldAtomizeStrings atomize) { 2606 uint32_t nchars = data & BitMask(30); 2607 bool latin1 = data & (1 << 31); 2608 bool hasBuffer = data & (1 << 30); 2609 2610 if (nchars > JSString::MAX_LENGTH) { 2611 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2612 JSMSG_SC_BAD_SERIALIZED_DATA, "string length"); 2613 return nullptr; 2614 } 2615 2616 if (hasBuffer) { 2617 if (allowedScope > JS::StructuredCloneScope::SameProcess) { 2618 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2619 JSMSG_SC_BAD_SERIALIZED_DATA, 2620 "invalid scope for string buffer"); 2621 return nullptr; 2622 } 2623 2624 uintptr_t p; 2625 if (!in.readBytes(&p, sizeof(p))) { 2626 in.reportTruncated(); 2627 return nullptr; 2628 } 2629 RefPtr<mozilla::StringBuffer> buffer( 2630 reinterpret_cast<mozilla::StringBuffer*>(p)); 2631 JSContext* cx = context(); 2632 if (atomize) { 2633 if (latin1) { 2634 return AtomizeChars(cx, static_cast<Latin1Char*>(buffer->Data()), 2635 nchars); 2636 } 2637 return AtomizeChars(cx, static_cast<char16_t*>(buffer->Data()), nchars); 2638 } 2639 if (latin1) { 2640 Rooted<JSString::OwnedChars<Latin1Char>> owned(cx, std::move(buffer), 2641 nchars); 2642 return JSLinearString::newValidLength<CanGC, Latin1Char>(cx, &owned, 2643 gcHeap); 2644 } 2645 Rooted<JSString::OwnedChars<char16_t>> owned(cx, std::move(buffer), nchars); 2646 return JSLinearString::newValidLength<CanGC, char16_t>(cx, &owned, gcHeap); 2647 } 2648 2649 return latin1 ? readStringImpl<Latin1Char>(nchars, atomize) 2650 : readStringImpl<char16_t>(nchars, atomize); 2651 } 2652 2653 [[nodiscard]] bool JSStructuredCloneReader::readUint32(uint32_t* num) { 2654 Rooted<Value> lineVal(context()); 2655 if (!startRead(&lineVal)) { 2656 return false; 2657 } 2658 if (!lineVal.isInt32()) { 2659 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2660 JSMSG_SC_BAD_SERIALIZED_DATA, "integer required"); 2661 return false; 2662 } 2663 *num = uint32_t(lineVal.toInt32()); 2664 return true; 2665 } 2666 2667 BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) { 2668 size_t length = data & BitMask(31); 2669 bool isNegative = data & (1 << 31); 2670 if (length == 0) { 2671 return BigInt::zero(context()); 2672 } 2673 RootedBigInt result(context(), BigInt::createUninitialized( 2674 context(), length, isNegative, gcHeap)); 2675 if (!result) { 2676 return nullptr; 2677 } 2678 if (!in.readArray(result->digits().data(), length)) { 2679 return nullptr; 2680 } 2681 return JS::BigInt::destructivelyTrimHighZeroDigits(context(), result); 2682 } 2683 2684 static uint32_t TagToV1ArrayType(uint32_t tag) { 2685 MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && 2686 tag <= SCTAG_TYPED_ARRAY_V1_MAX); 2687 return tag - SCTAG_TYPED_ARRAY_V1_MIN; 2688 } 2689 2690 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType, 2691 uint64_t nelems, 2692 MutableHandleValue vp, 2693 bool v1Read) { 2694 if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::Float16)) { 2695 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2696 JSMSG_SC_BAD_SERIALIZED_DATA, 2697 "unhandled typed array element type"); 2698 return false; 2699 } 2700 2701 // Push a placeholder onto the allObjs list to stand in for the typed array. 2702 uint32_t placeholderIndex = allObjs.length(); 2703 Value dummy = UndefinedValue(); 2704 if (!allObjs.append(dummy)) { 2705 return false; 2706 } 2707 2708 // Auto-length TypedArrays are tagged by using `-1` for their length. 2709 bool isAutoLength = nelems == uint64_t(-1); 2710 2711 // Zero |nelems| if it was only used as a tag. 2712 if (isAutoLength) { 2713 nelems = 0; 2714 } 2715 2716 // Read the ArrayBuffer object and its contents (but no properties) 2717 RootedValue v(context()); 2718 uint64_t byteOffset; 2719 if (v1Read) { 2720 MOZ_ASSERT(!isAutoLength, "v1Read can't produce auto-length TypedArrays"); 2721 if (!readV1ArrayBuffer(arrayType, nelems, &v)) { 2722 return false; 2723 } 2724 byteOffset = 0; 2725 } else { 2726 if (!startRead(&v)) { 2727 return false; 2728 } 2729 if (!in.read(&byteOffset)) { 2730 return false; 2731 } 2732 } 2733 2734 // Ensure invalid 64-bit values won't be truncated below. 2735 if (nelems > ArrayBufferObject::ByteLengthLimit || 2736 byteOffset > ArrayBufferObject::ByteLengthLimit) { 2737 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2738 JSMSG_SC_BAD_SERIALIZED_DATA, 2739 "invalid typed array length or offset"); 2740 return false; 2741 } 2742 2743 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) { 2744 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2745 JSMSG_SC_BAD_SERIALIZED_DATA, 2746 "typed array must be backed by an ArrayBuffer"); 2747 return false; 2748 } 2749 2750 RootedObject buffer(context(), &v.toObject()); 2751 RootedObject obj(context(), nullptr); 2752 2753 // Negative values represent an absent length parameter. 2754 int64_t length = isAutoLength ? -1 : int64_t(nelems); 2755 2756 switch (arrayType) { 2757 #define CREATE_FROM_BUFFER(ExternalType, NativeType, Name) \ 2758 case Scalar::Name: \ 2759 obj = JS::TypedArray<Scalar::Name>::fromBuffer(context(), buffer, \ 2760 byteOffset, length) \ 2761 .asObject(); \ 2762 break; 2763 2764 JS_FOR_EACH_TYPED_ARRAY(CREATE_FROM_BUFFER) 2765 #undef CREATE_FROM_BUFFER 2766 2767 default: 2768 MOZ_CRASH("Can't happen: arrayType range checked above"); 2769 } 2770 2771 if (!obj) { 2772 return false; 2773 } 2774 vp.setObject(*obj); 2775 2776 allObjs[placeholderIndex].set(vp); 2777 2778 return true; 2779 } 2780 2781 bool JSStructuredCloneReader::readDataView(uint64_t byteLength, 2782 MutableHandleValue vp) { 2783 // Push a placeholder onto the allObjs list to stand in for the DataView. 2784 uint32_t placeholderIndex = allObjs.length(); 2785 Value dummy = UndefinedValue(); 2786 if (!allObjs.append(dummy)) { 2787 return false; 2788 } 2789 2790 // Read the ArrayBuffer object and its contents (but no properties). 2791 RootedValue v(context()); 2792 if (!startRead(&v)) { 2793 return false; 2794 } 2795 if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) { 2796 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2797 JSMSG_SC_BAD_SERIALIZED_DATA, 2798 "DataView must be backed by an ArrayBuffer"); 2799 return false; 2800 } 2801 2802 // Read byteOffset. 2803 uint64_t byteOffset; 2804 if (!in.read(&byteOffset)) { 2805 return false; 2806 } 2807 2808 // Auto-length DataViews are tagged by using `-1` for their byte length. 2809 bool isAutoLength = byteLength == uint64_t(-1); 2810 2811 // Zero |byteLength| if it was only used as a tag. 2812 if (isAutoLength) { 2813 byteLength = 0; 2814 } 2815 2816 // Ensure invalid 64-bit values won't be truncated below. 2817 if (byteLength > ArrayBufferObject::ByteLengthLimit || 2818 byteOffset > ArrayBufferObject::ByteLengthLimit) { 2819 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2820 JSMSG_SC_BAD_SERIALIZED_DATA, 2821 "invalid DataView length or offset"); 2822 return false; 2823 } 2824 2825 RootedObject buffer(context(), &v.toObject()); 2826 RootedObject obj(context()); 2827 if (!isAutoLength) { 2828 obj = JS_NewDataView(context(), buffer, byteOffset, byteLength); 2829 } else { 2830 obj = js::NewDataView(context(), buffer, byteOffset); 2831 } 2832 if (!obj) { 2833 return false; 2834 } 2835 vp.setObject(*obj); 2836 2837 allObjs[placeholderIndex].set(vp); 2838 2839 return true; 2840 } 2841 2842 bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type, 2843 uint32_t data, 2844 MutableHandleValue vp) { 2845 // V2 stores the length in |data|. The current version stores the 2846 // length separately to allow larger length values. 2847 uint64_t nbytes = 0; 2848 uint64_t maxbytes = 0; 2849 if (type == SCTAG_ARRAY_BUFFER_OBJECT || 2850 type == SCTAG_IMMUTABLE_ARRAY_BUFFER_OBJECT) { 2851 if (!in.read(&nbytes)) { 2852 return false; 2853 } 2854 } else if (type == SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT) { 2855 if (!in.read(&nbytes)) { 2856 return false; 2857 } 2858 if (!in.read(&maxbytes)) { 2859 return false; 2860 } 2861 } else { 2862 MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2); 2863 nbytes = data; 2864 } 2865 2866 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t 2867 // below, so we have to check this here. 2868 if (nbytes > ArrayBufferObject::ByteLengthLimit || 2869 maxbytes > ArrayBufferObject::ByteLengthLimit) { 2870 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2871 JSMSG_BAD_ARRAY_LENGTH); 2872 return false; 2873 } 2874 2875 JSObject* obj; 2876 if (type == SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT) { 2877 obj = ResizableArrayBufferObject::createZeroed(context(), size_t(nbytes), 2878 size_t(maxbytes)); 2879 } else if (type == SCTAG_IMMUTABLE_ARRAY_BUFFER_OBJECT) { 2880 MOZ_ASSERT(maxbytes == 0); 2881 obj = ImmutableArrayBufferObject::createZeroed(context(), size_t(nbytes)); 2882 } else { 2883 MOZ_ASSERT(maxbytes == 0); 2884 obj = ArrayBufferObject::createZeroed(context(), size_t(nbytes)); 2885 } 2886 if (!obj) { 2887 return false; 2888 } 2889 vp.setObject(*obj); 2890 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>(); 2891 MOZ_ASSERT(buffer.byteLength() == nbytes); 2892 return in.readArray(buffer.dataPointer(), nbytes); 2893 } 2894 2895 bool JSStructuredCloneReader::readSharedArrayBuffer(StructuredDataType type, 2896 MutableHandleValue vp) { 2897 MOZ_ASSERT(type == SCTAG_SHARED_ARRAY_BUFFER_OBJECT || 2898 type == SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT); 2899 2900 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() || 2901 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) { 2902 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled() 2903 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP 2904 : JS_SCERR_NOT_CLONABLE; 2905 ReportDataCloneError(context(), callbacks, error, closure, 2906 "SharedArrayBuffer"); 2907 return false; 2908 } 2909 2910 uint64_t byteLength; 2911 if (!in.readBytes(&byteLength, sizeof(byteLength))) { 2912 return in.reportTruncated(); 2913 } 2914 2915 // The maximum ArrayBuffer size depends on the platform, and we cast to size_t 2916 // below, so we have to check this here. 2917 if (byteLength > ArrayBufferObject::ByteLengthLimit) { 2918 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2919 JSMSG_BAD_ARRAY_LENGTH); 2920 return false; 2921 } 2922 2923 intptr_t p; 2924 if (!in.readBytes(&p, sizeof(p))) { 2925 return in.reportTruncated(); 2926 } 2927 2928 bool isGrowable = type == SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT; 2929 2930 SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p); 2931 MOZ_RELEASE_ASSERT(rawbuf->isWasm() || isGrowable == rawbuf->isGrowableJS()); 2932 2933 // There's no guarantee that the receiving agent has enabled shared memory 2934 // even if the transmitting agent has done so. Ideally we'd check at the 2935 // transmission point, but that's tricky, and it will be a very rare problem 2936 // in any case. Just fail at the receiving end if we can't handle it. 2937 2938 if (!context() 2939 ->realm() 2940 ->creationOptions() 2941 .getSharedMemoryAndAtomicsEnabled()) { 2942 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2943 JSMSG_SC_SAB_DISABLED); 2944 return false; 2945 } 2946 2947 // The new object will have a new reference to the rawbuf. 2948 2949 if (!rawbuf->addReference()) { 2950 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 2951 JSMSG_SC_SAB_REFCNT_OFLO); 2952 return false; 2953 } 2954 2955 RootedObject obj(context()); 2956 if (!isGrowable) { 2957 obj = SharedArrayBufferObject::New(context(), rawbuf, byteLength); 2958 } else { 2959 obj = SharedArrayBufferObject::NewGrowable(context(), rawbuf, byteLength); 2960 } 2961 if (!obj) { 2962 rawbuf->dropReference(); 2963 return false; 2964 } 2965 2966 // `rawbuf` is now owned by `obj`. 2967 2968 if (callbacks && callbacks->sabCloned && 2969 !callbacks->sabCloned(context(), /*receiving=*/true, closure)) { 2970 return false; 2971 } 2972 2973 vp.setObject(*obj); 2974 return true; 2975 } 2976 2977 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes, 2978 MutableHandleValue vp) { 2979 JSContext* cx = context(); 2980 if (nbytes != 0) { 2981 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2982 JSMSG_SC_BAD_SERIALIZED_DATA, 2983 "invalid shared wasm memory tag"); 2984 return false; 2985 } 2986 2987 if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() || 2988 !cloneDataPolicy.areSharedMemoryObjectsAllowed()) { 2989 auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled() 2990 ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP 2991 : JS_SCERR_NOT_CLONABLE; 2992 ReportDataCloneError(cx, callbacks, error, closure, "WebAssembly.Memory"); 2993 return false; 2994 } 2995 2996 // Read the isHuge flag 2997 RootedValue isHuge(cx); 2998 if (!startRead(&isHuge)) { 2999 return false; 3000 } 3001 3002 // Read the SharedArrayBuffer object. 3003 RootedValue payload(cx); 3004 if (!startRead(&payload)) { 3005 return false; 3006 } 3007 if (!payload.isObject() || 3008 !payload.toObject().is<SharedArrayBufferObject>() || 3009 payload.toObject().as<SharedArrayBufferObject>().isGrowable()) { 3010 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3011 JSMSG_SC_BAD_SERIALIZED_DATA, 3012 "shared wasm memory must be backed by a " 3013 "non-growable SharedArrayBuffer"); 3014 return false; 3015 } 3016 3017 Rooted<ArrayBufferObjectMaybeShared*> sab( 3018 cx, &payload.toObject().as<SharedArrayBufferObject>()); 3019 3020 // Construct the memory. 3021 RootedObject proto( 3022 cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmMemory)); 3023 if (!proto) { 3024 return false; 3025 } 3026 RootedObject memory( 3027 cx, WasmMemoryObject::create(cx, sab, isHuge.toBoolean(), proto)); 3028 if (!memory) { 3029 return false; 3030 } 3031 3032 vp.setObject(*memory); 3033 return true; 3034 } 3035 3036 /* 3037 * Read in the data for a structured clone version 1 ArrayBuffer, performing 3038 * endianness-conversion while reading. 3039 */ 3040 bool JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, 3041 uint32_t nelems, 3042 MutableHandleValue vp) { 3043 if (arrayType > Scalar::Uint8Clamped) { 3044 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3045 JSMSG_SC_BAD_SERIALIZED_DATA, 3046 "invalid TypedArray type"); 3047 return false; 3048 } 3049 3050 mozilla::CheckedInt<size_t> nbytes = 3051 mozilla::CheckedInt<size_t>(nelems) * 3052 TypedArrayElemSize(static_cast<Scalar::Type>(arrayType)); 3053 if (!nbytes.isValid() || nbytes.value() > UINT32_MAX) { 3054 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3055 JSMSG_SC_BAD_SERIALIZED_DATA, 3056 "invalid typed array size"); 3057 return false; 3058 } 3059 3060 JSObject* obj = ArrayBufferObject::createZeroed(context(), nbytes.value()); 3061 if (!obj) { 3062 return false; 3063 } 3064 vp.setObject(*obj); 3065 ArrayBufferObject& buffer = obj->as<ArrayBufferObject>(); 3066 MOZ_ASSERT(buffer.byteLength() == nbytes); 3067 3068 switch (arrayType) { 3069 case Scalar::Int8: 3070 case Scalar::Uint8: 3071 case Scalar::Uint8Clamped: 3072 return in.readArray((uint8_t*)buffer.dataPointer(), nelems); 3073 case Scalar::Int16: 3074 case Scalar::Uint16: 3075 return in.readArray((uint16_t*)buffer.dataPointer(), nelems); 3076 case Scalar::Int32: 3077 case Scalar::Uint32: 3078 case Scalar::Float32: 3079 return in.readArray((uint32_t*)buffer.dataPointer(), nelems); 3080 case Scalar::Float64: 3081 case Scalar::BigInt64: 3082 case Scalar::BigUint64: 3083 return in.readArray((uint64_t*)buffer.dataPointer(), nelems); 3084 default: 3085 MOZ_CRASH("Can't happen: arrayType range checked by caller"); 3086 } 3087 } 3088 3089 static bool PrimitiveToObject(JSContext* cx, MutableHandleValue vp) { 3090 JSObject* obj = js::PrimitiveToObject(cx, vp); 3091 if (!obj) { 3092 return false; 3093 } 3094 3095 vp.setObject(*obj); 3096 return true; 3097 } 3098 3099 bool JSStructuredCloneReader::startRead(MutableHandleValue vp, 3100 ShouldAtomizeStrings atomizeStrings) { 3101 uint32_t tag, data; 3102 bool alreadAppended = false; 3103 3104 AutoCheckRecursionLimit recursion(in.context()); 3105 if (!recursion.check(in.context())) { 3106 return false; 3107 } 3108 3109 if (!in.readPair(&tag, &data)) { 3110 return false; 3111 } 3112 3113 numItemsRead++; 3114 3115 switch (tag) { 3116 case SCTAG_NULL: 3117 vp.setNull(); 3118 break; 3119 3120 case SCTAG_UNDEFINED: 3121 vp.setUndefined(); 3122 break; 3123 3124 case SCTAG_INT32: 3125 vp.setInt32(data); 3126 break; 3127 3128 case SCTAG_BOOLEAN: 3129 case SCTAG_BOOLEAN_OBJECT: 3130 vp.setBoolean(!!data); 3131 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) { 3132 return false; 3133 } 3134 break; 3135 3136 case SCTAG_STRING: 3137 case SCTAG_STRING_OBJECT: { 3138 JSString* str = readString(data, atomizeStrings); 3139 if (!str) { 3140 return false; 3141 } 3142 vp.setString(str); 3143 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) { 3144 return false; 3145 } 3146 break; 3147 } 3148 3149 case SCTAG_NUMBER_OBJECT: { 3150 double d; 3151 if (!in.readDouble(&d)) { 3152 return false; 3153 } 3154 vp.setDouble(CanonicalizeNaN(d)); 3155 if (!PrimitiveToObject(context(), vp)) { 3156 return false; 3157 } 3158 break; 3159 } 3160 3161 case SCTAG_BIGINT: 3162 case SCTAG_BIGINT_OBJECT: { 3163 RootedBigInt bi(context(), readBigInt(data)); 3164 if (!bi) { 3165 return false; 3166 } 3167 vp.setBigInt(bi); 3168 if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) { 3169 return false; 3170 } 3171 break; 3172 } 3173 3174 case SCTAG_DATE_OBJECT: { 3175 double d; 3176 if (!in.readDouble(&d)) { 3177 return false; 3178 } 3179 JS::ClippedTime t = JS::TimeClip(d); 3180 if (!NumbersAreIdentical(d, t.toDouble())) { 3181 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3182 JSMSG_SC_BAD_SERIALIZED_DATA, "date"); 3183 return false; 3184 } 3185 JSObject* obj = NewDateObjectMsec(context(), t); 3186 if (!obj) { 3187 return false; 3188 } 3189 vp.setObject(*obj); 3190 break; 3191 } 3192 3193 case SCTAG_REGEXP_OBJECT: { 3194 if ((data & RegExpFlag::AllFlags) != data) { 3195 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3196 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp"); 3197 return false; 3198 } 3199 3200 RegExpFlags flags(AssertedCast<uint8_t>(data)); 3201 3202 uint32_t tag2, stringData; 3203 if (!in.readPair(&tag2, &stringData)) { 3204 return false; 3205 } 3206 if (tag2 != SCTAG_STRING) { 3207 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3208 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp"); 3209 return false; 3210 } 3211 3212 JSString* str = readString(stringData, AtomizeStrings); 3213 if (!str) { 3214 return false; 3215 } 3216 3217 Rooted<JSAtom*> atom(context(), &str->asAtom()); 3218 3219 NewObjectKind kind = 3220 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject; 3221 RegExpObject* reobj = RegExpObject::create(context(), atom, flags, kind); 3222 if (!reobj) { 3223 return false; 3224 } 3225 vp.setObject(*reobj); 3226 break; 3227 } 3228 3229 case SCTAG_ARRAY_OBJECT: 3230 case SCTAG_OBJECT_OBJECT: { 3231 NewObjectKind kind = 3232 gcHeap == gc::Heap::Tenured ? TenuredObject : GenericObject; 3233 JSObject* obj; 3234 if (tag == SCTAG_ARRAY_OBJECT) { 3235 obj = NewDenseUnallocatedArray( 3236 context(), NativeEndian::swapFromLittleEndian(data), kind); 3237 } else { 3238 obj = NewPlainObject(context(), kind); 3239 } 3240 if (!obj || !objs.append(ObjectValue(*obj))) { 3241 return false; 3242 } 3243 3244 vp.setObject(*obj); 3245 break; 3246 } 3247 3248 case SCTAG_BACK_REFERENCE_OBJECT: { 3249 if (data >= allObjs.length() || !allObjs[data].isObject()) { 3250 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3251 JSMSG_SC_BAD_SERIALIZED_DATA, 3252 "invalid back reference in input"); 3253 return false; 3254 } 3255 vp.set(allObjs[data]); 3256 return true; 3257 } 3258 3259 case SCTAG_TRANSFER_MAP_HEADER: 3260 case SCTAG_TRANSFER_MAP_PENDING_ENTRY: 3261 // We should be past all the transfer map tags. 3262 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3263 JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input"); 3264 return false; 3265 3266 case SCTAG_ARRAY_BUFFER_OBJECT_V2: 3267 case SCTAG_ARRAY_BUFFER_OBJECT: 3268 case SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT: 3269 case SCTAG_IMMUTABLE_ARRAY_BUFFER_OBJECT: 3270 if (!readArrayBuffer(StructuredDataType(tag), data, vp)) { 3271 return false; 3272 } 3273 break; 3274 3275 case SCTAG_SHARED_ARRAY_BUFFER_OBJECT: 3276 case SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT: 3277 if (!readSharedArrayBuffer(StructuredDataType(tag), vp)) { 3278 return false; 3279 } 3280 break; 3281 3282 case SCTAG_SHARED_WASM_MEMORY_OBJECT: 3283 if (!readSharedWasmMemory(data, vp)) { 3284 return false; 3285 } 3286 break; 3287 3288 case SCTAG_TYPED_ARRAY_OBJECT_V2: { 3289 // readTypedArray adds the array to allObjs. 3290 // V2 stores the length (nelems) in |data| and the arrayType separately. 3291 uint64_t arrayType; 3292 if (!in.read(&arrayType)) { 3293 return false; 3294 } 3295 uint64_t nelems = data; 3296 return readTypedArray(arrayType, nelems, vp); 3297 } 3298 3299 case SCTAG_TYPED_ARRAY_OBJECT: { 3300 // readTypedArray adds the array to allObjs. 3301 // The current version stores the array type in |data| and the length 3302 // (nelems) separately to support large TypedArrays. 3303 uint32_t arrayType = data; 3304 uint64_t nelems; 3305 if (!in.read(&nelems)) { 3306 return false; 3307 } 3308 return readTypedArray(arrayType, nelems, vp); 3309 } 3310 3311 case SCTAG_DATA_VIEW_OBJECT_V2: { 3312 // readDataView adds the array to allObjs. 3313 uint64_t byteLength = data; 3314 return readDataView(byteLength, vp); 3315 } 3316 3317 case SCTAG_DATA_VIEW_OBJECT: { 3318 // readDataView adds the array to allObjs. 3319 uint64_t byteLength; 3320 if (!in.read(&byteLength)) { 3321 return false; 3322 } 3323 return readDataView(byteLength, vp); 3324 } 3325 3326 case SCTAG_MAP_OBJECT: { 3327 JSObject* obj = MapObject::create(context()); 3328 if (!obj || !objs.append(ObjectValue(*obj))) { 3329 return false; 3330 } 3331 vp.setObject(*obj); 3332 break; 3333 } 3334 3335 case SCTAG_SET_OBJECT: { 3336 JSObject* obj = SetObject::create(context()); 3337 if (!obj || !objs.append(ObjectValue(*obj))) { 3338 return false; 3339 } 3340 vp.setObject(*obj); 3341 break; 3342 } 3343 3344 case SCTAG_SAVED_FRAME_OBJECT: { 3345 auto* obj = readSavedFrameHeader(data); 3346 if (!obj || !objs.append(ObjectValue(*obj)) || 3347 !objState.append(std::make_pair(obj, false))) { 3348 return false; 3349 } 3350 vp.setObject(*obj); 3351 break; 3352 } 3353 3354 case SCTAG_ERROR_OBJECT: { 3355 auto* obj = readErrorHeader(data); 3356 if (!obj || !objs.append(ObjectValue(*obj)) || 3357 !objState.append(std::make_pair(obj, false))) { 3358 return false; 3359 } 3360 vp.setObject(*obj); 3361 break; 3362 } 3363 3364 case SCTAG_END_OF_KEYS: 3365 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3366 JSMSG_SC_BAD_SERIALIZED_DATA, 3367 "truncated input"); 3368 return false; 3369 break; 3370 3371 default: { 3372 if (tag <= SCTAG_FLOAT_MAX) { 3373 double d = ReinterpretPairAsDouble(tag, data); 3374 vp.setNumber(CanonicalizeNaN(d)); 3375 break; 3376 } 3377 3378 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) { 3379 // A v1-format typed array 3380 // readTypedArray adds the array to allObjs 3381 return readTypedArray(TagToV1ArrayType(tag), data, vp, true); 3382 } 3383 3384 if (!callbacks || !callbacks->read) { 3385 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3386 JSMSG_SC_BAD_SERIALIZED_DATA, 3387 "unsupported type"); 3388 return false; 3389 } 3390 3391 // callbacks->read() might read other objects from the buffer. 3392 // In startWrite we always write the object itself before calling 3393 // the custom function. We should do the same here to keep 3394 // indexing consistent. 3395 uint32_t placeholderIndex = allObjs.length(); 3396 Value dummy = UndefinedValue(); 3397 if (!allObjs.append(dummy)) { 3398 return false; 3399 } 3400 JSObject* obj = 3401 callbacks->read(context(), this, cloneDataPolicy, tag, data, closure); 3402 if (!obj) { 3403 return false; 3404 } 3405 vp.setObject(*obj); 3406 allObjs[placeholderIndex].set(vp); 3407 alreadAppended = true; 3408 } 3409 } 3410 3411 if (!alreadAppended && vp.isObject() && !allObjs.append(vp)) { 3412 return false; 3413 } 3414 3415 return true; 3416 } 3417 3418 bool JSStructuredCloneReader::readHeader() { 3419 uint32_t tag, data; 3420 if (!in.getPair(&tag, &data)) { 3421 return in.reportTruncated(); 3422 } 3423 3424 JS::StructuredCloneScope storedScope; 3425 if (tag == SCTAG_HEADER) { 3426 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); 3427 storedScope = JS::StructuredCloneScope(data); 3428 } else { 3429 // Old structured clone buffer. We must have read it from disk. 3430 storedScope = JS::StructuredCloneScope::DifferentProcessForIndexedDB; 3431 } 3432 3433 // Backward compatibility with old structured clone buffers. Value '0' was 3434 // used for SameProcessSameThread scope. 3435 if ((int)storedScope == 0) { 3436 storedScope = JS::StructuredCloneScope::SameProcess; 3437 } 3438 3439 if (storedScope < JS::StructuredCloneScope::SameProcess || 3440 storedScope > JS::StructuredCloneScope::DifferentProcessForIndexedDB) { 3441 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3442 JSMSG_SC_BAD_SERIALIZED_DATA, 3443 "invalid structured clone scope"); 3444 return false; 3445 } 3446 3447 if (allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) { 3448 // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB 3449 // clones are incorrect. Treat them as if they were DifferentProcess. 3450 allowedScope = JS::StructuredCloneScope::DifferentProcess; 3451 return true; 3452 } 3453 3454 if (storedScope < allowedScope) { 3455 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3456 JSMSG_SC_BAD_SERIALIZED_DATA, 3457 "incompatible structured clone scope"); 3458 return false; 3459 } 3460 3461 return true; 3462 } 3463 3464 bool JSStructuredCloneReader::readTransferMap() { 3465 JSContext* cx = context(); 3466 auto headerPos = in.tell(); 3467 3468 uint32_t tag, data; 3469 if (!in.getPair(&tag, &data)) { 3470 return in.reportTruncated(); 3471 } 3472 3473 if (tag != SCTAG_TRANSFER_MAP_HEADER) { 3474 // No transfer map header found. 3475 return true; 3476 } 3477 3478 if (data >= SCTAG_TM_END) { 3479 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3480 JSMSG_SC_BAD_SERIALIZED_DATA, 3481 "invalid transfer map header"); 3482 return false; 3483 } 3484 auto transferState = static_cast<TransferableMapHeader>(data); 3485 3486 if (transferState == SCTAG_TM_TRANSFERRED) { 3487 return true; 3488 } 3489 3490 if (transferState == SCTAG_TM_TRANSFERRING) { 3491 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE_TWICE, closure); 3492 return false; 3493 } 3494 3495 headerPos.write( 3496 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRING)); 3497 3498 uint64_t numTransferables; 3499 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); 3500 if (!in.read(&numTransferables)) { 3501 return false; 3502 } 3503 3504 for (uint64_t i = 0; i < numTransferables; i++) { 3505 auto pos = in.tell(); 3506 3507 if (!in.readPair(&tag, &data)) { 3508 return false; 3509 } 3510 3511 if (tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY) { 3512 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure); 3513 return false; 3514 } 3515 3516 RootedObject obj(cx); 3517 3518 void* content; 3519 if (!in.readPtr(&content)) { 3520 return false; 3521 } 3522 3523 uint64_t extraData; 3524 if (!in.read(&extraData)) { 3525 return false; 3526 } 3527 3528 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) { 3529 MOZ_ASSERT(allowedScope <= JS::StructuredCloneScope::LastResolvedScope); 3530 if (allowedScope == JS::StructuredCloneScope::DifferentProcess) { 3531 // Transferred ArrayBuffers in a DifferentProcess clone buffer 3532 // are treated as if they weren't Transferred at all. We should 3533 // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER. 3534 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure); 3535 return false; 3536 } 3537 3538 MOZ_RELEASE_ASSERT(extraData <= ArrayBufferObject::ByteLengthLimit); 3539 size_t nbytes = extraData; 3540 3541 MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA || 3542 data == JS::SCTAG_TMO_MAPPED_DATA); 3543 if (data == JS::SCTAG_TMO_ALLOC_DATA) { 3544 // When the ArrayBuffer can't be allocated, |content| will be free'ed 3545 // in `JSStructuredCloneData::discardTransferables()`. 3546 obj = JS::NewArrayBufferWithContents( 3547 cx, nbytes, content, 3548 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory); 3549 } else if (data == JS::SCTAG_TMO_MAPPED_DATA) { 3550 obj = JS::NewMappedArrayBufferWithContents(cx, nbytes, content); 3551 } 3552 } else if (tag == SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER) { 3553 auto savedPos = in.tell(); 3554 auto guard = mozilla::MakeScopeExit([&] { in.seekTo(savedPos); }); 3555 in.seekTo(pos); 3556 if (!in.seekBy(static_cast<size_t>(extraData))) { 3557 return false; 3558 } 3559 3560 if (tailStartPos.isNothing()) { 3561 tailStartPos = mozilla::Some(in.tell()); 3562 } 3563 3564 uint32_t tag, data; 3565 if (!in.readPair(&tag, &data)) { 3566 return false; 3567 } 3568 if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 && 3569 tag != SCTAG_ARRAY_BUFFER_OBJECT && 3570 tag != SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT && 3571 tag != SCTAG_IMMUTABLE_ARRAY_BUFFER_OBJECT) { 3572 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure); 3573 return false; 3574 } 3575 RootedValue val(cx); 3576 if (!readArrayBuffer(StructuredDataType(tag), data, &val)) { 3577 return false; 3578 } 3579 obj = &val.toObject(); 3580 tailEndPos = mozilla::Some(in.tell()); 3581 } else { 3582 if (!callbacks || !callbacks->readTransfer) { 3583 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure); 3584 return false; 3585 } 3586 if (!callbacks->readTransfer(cx, this, cloneDataPolicy, tag, content, 3587 extraData, closure, &obj)) { 3588 if (!cx->isExceptionPending()) { 3589 ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure); 3590 } 3591 return false; 3592 } 3593 MOZ_ASSERT(obj); 3594 MOZ_ASSERT(!cx->isExceptionPending()); 3595 } 3596 3597 // On failure, the buffer will still own the data (since its ownership 3598 // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by 3599 // DiscardTransferables. 3600 if (!obj) { 3601 return false; 3602 } 3603 3604 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input 3605 // buffer. 3606 pos.write(PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED)); 3607 MOZ_ASSERT(!pos.done()); 3608 3609 if (!allObjs.append(ObjectValue(*obj))) { 3610 return false; 3611 } 3612 } 3613 3614 // Mark the whole transfer map as consumed. 3615 #ifdef DEBUG 3616 SCInput::getPair(headerPos.peek(), &tag, &data); 3617 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER); 3618 MOZ_ASSERT(TransferableMapHeader(data) == SCTAG_TM_TRANSFERRING); 3619 #endif 3620 headerPos.write( 3621 PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED)); 3622 3623 return true; 3624 } 3625 3626 JSObject* JSStructuredCloneReader::readSavedFrameHeader( 3627 uint32_t principalsTag) { 3628 Rooted<SavedFrame*> savedFrame(context(), SavedFrame::create(context())); 3629 if (!savedFrame) { 3630 return nullptr; 3631 } 3632 3633 JSPrincipals* principals; 3634 if (principalsTag == SCTAG_JSPRINCIPALS) { 3635 if (!context()->runtime()->readPrincipals) { 3636 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3637 JSMSG_SC_UNSUPPORTED_TYPE); 3638 return nullptr; 3639 } 3640 3641 if (!context()->runtime()->readPrincipals(context(), this, &principals)) { 3642 return nullptr; 3643 } 3644 } else if (principalsTag == 3645 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) { 3646 principals = &ReconstructedSavedFramePrincipals::IsSystem; 3647 principals->refcount++; 3648 } else if (principalsTag == 3649 SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) { 3650 principals = &ReconstructedSavedFramePrincipals::IsNotSystem; 3651 principals->refcount++; 3652 } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) { 3653 principals = nullptr; 3654 } else { 3655 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3656 JSMSG_SC_BAD_SERIALIZED_DATA, 3657 "bad SavedFrame principals"); 3658 return nullptr; 3659 } 3660 3661 RootedValue mutedErrors(context()); 3662 RootedValue source(context()); 3663 { 3664 // Read a |mutedErrors| boolean followed by a |source| string. 3665 // The |mutedErrors| boolean is present in all new structured-clone data, 3666 // but in older data it will be absent and only the |source| string will be 3667 // found. 3668 if (!startRead(&mutedErrors, AtomizeStrings)) { 3669 return nullptr; 3670 } 3671 3672 if (mutedErrors.isBoolean()) { 3673 if (!startRead(&source, AtomizeStrings)) { 3674 return nullptr; 3675 } 3676 if (!source.isString()) { 3677 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3678 JSMSG_SC_BAD_SERIALIZED_DATA, 3679 "bad source string"); 3680 return nullptr; 3681 } 3682 } else if (mutedErrors.isString()) { 3683 // Backwards compatibility: Handle missing |mutedErrors| boolean, 3684 // this is actually just a |source| string. 3685 source = mutedErrors; 3686 mutedErrors.setBoolean(true); // Safe default value. 3687 } else { 3688 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3689 JSMSG_SC_BAD_SERIALIZED_DATA, 3690 "invalid mutedErrors"); 3691 return nullptr; 3692 } 3693 } 3694 3695 savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals, 3696 mutedErrors.toBoolean()); 3697 3698 savedFrame->initSource(&source.toString()->asAtom()); 3699 3700 uint32_t line; 3701 if (!readUint32(&line)) { 3702 return nullptr; 3703 } 3704 savedFrame->initLine(line); 3705 3706 JS::TaggedColumnNumberOneOrigin column; 3707 if (!readUint32(column.addressOfValueForTranscode())) { 3708 return nullptr; 3709 } 3710 savedFrame->initColumn(column); 3711 3712 // Don't specify a source ID when reading a cloned saved frame, as these IDs 3713 // are only valid within a specific process. 3714 savedFrame->initSourceId(0); 3715 3716 RootedValue name(context()); 3717 if (!startRead(&name, AtomizeStrings)) { 3718 return nullptr; 3719 } 3720 if (!(name.isString() || name.isNull())) { 3721 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3722 JSMSG_SC_BAD_SERIALIZED_DATA, 3723 "invalid saved frame cause"); 3724 return nullptr; 3725 } 3726 JSAtom* atomName = nullptr; 3727 if (name.isString()) { 3728 atomName = &name.toString()->asAtom(); 3729 } 3730 3731 savedFrame->initFunctionDisplayName(atomName); 3732 3733 RootedValue cause(context()); 3734 if (!startRead(&cause, AtomizeStrings)) { 3735 return nullptr; 3736 } 3737 if (!(cause.isString() || cause.isNull())) { 3738 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3739 JSMSG_SC_BAD_SERIALIZED_DATA, 3740 "invalid saved frame cause"); 3741 return nullptr; 3742 } 3743 JSAtom* atomCause = nullptr; 3744 if (cause.isString()) { 3745 atomCause = &cause.toString()->asAtom(); 3746 } 3747 savedFrame->initAsyncCause(atomCause); 3748 3749 return savedFrame; 3750 } 3751 3752 // SavedFrame object: there is one child value, the parent SavedFrame, 3753 // which is either null or another SavedFrame object. 3754 bool JSStructuredCloneReader::readSavedFrameFields(Handle<SavedFrame*> frameObj, 3755 HandleValue parent, 3756 bool* state) { 3757 if (*state) { 3758 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3759 JSMSG_SC_BAD_SERIALIZED_DATA, 3760 "multiple SavedFrame parents"); 3761 return false; 3762 } 3763 3764 SavedFrame* parentFrame; 3765 if (parent.isNull()) { 3766 parentFrame = nullptr; 3767 } else if (parent.isObject() && parent.toObject().is<SavedFrame>()) { 3768 parentFrame = &parent.toObject().as<SavedFrame>(); 3769 } else { 3770 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3771 JSMSG_SC_BAD_SERIALIZED_DATA, 3772 "invalid SavedFrame parent"); 3773 return false; 3774 } 3775 3776 frameObj->initParent(parentFrame); 3777 *state = true; 3778 return true; 3779 } 3780 3781 JSObject* JSStructuredCloneReader::readErrorHeader(uint32_t type) { 3782 JSContext* cx = context(); 3783 3784 switch (type) { 3785 case JSEXN_ERR: 3786 case JSEXN_EVALERR: 3787 case JSEXN_RANGEERR: 3788 case JSEXN_REFERENCEERR: 3789 case JSEXN_SYNTAXERR: 3790 case JSEXN_TYPEERR: 3791 case JSEXN_URIERR: 3792 case JSEXN_AGGREGATEERR: 3793 break; 3794 default: 3795 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3796 JSMSG_SC_BAD_SERIALIZED_DATA, 3797 "invalid error type"); 3798 return nullptr; 3799 } 3800 3801 RootedString message(cx); 3802 { 3803 RootedValue messageVal(cx); 3804 if (!startRead(&messageVal)) { 3805 return nullptr; 3806 } 3807 if (messageVal.isString()) { 3808 message = messageVal.toString(); 3809 } else if (!messageVal.isNull()) { 3810 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3811 JSMSG_SC_BAD_SERIALIZED_DATA, 3812 "invalid 'message' field for Error object"); 3813 return nullptr; 3814 } 3815 } 3816 3817 // We have to set |cause| to something if it exists, otherwise the shape 3818 // would be wrong. The actual value will be overwritten later. 3819 RootedValue val(cx); 3820 if (!startRead(&val)) { 3821 return nullptr; 3822 } 3823 bool hasCause = ToBoolean(val); 3824 Rooted<Maybe<Value>> cause(cx, mozilla::Nothing()); 3825 if (hasCause) { 3826 cause = mozilla::Some(BooleanValue(true)); 3827 } 3828 3829 if (!startRead(&val)) { 3830 return nullptr; 3831 } 3832 if (!val.isString()) { 3833 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3834 JSMSG_SC_BAD_SERIALIZED_DATA, 3835 "invalid 'fileName' field for Error object"); 3836 return nullptr; 3837 } 3838 RootedString fileName(cx, val.toString()); 3839 3840 uint32_t lineNumber; 3841 JS::ColumnNumberOneOrigin columnNumber; 3842 if (!readUint32(&lineNumber) || 3843 !readUint32(columnNumber.addressOfValueForTranscode())) { 3844 return nullptr; 3845 } 3846 3847 // The |cause| and |stack| slots of the objects might be overwritten later. 3848 // For AggregateErrors the |errors| property will be added. 3849 RootedObject errorObj( 3850 cx, ErrorObject::create(cx, static_cast<JSExnType>(type), nullptr, 3851 fileName, 0, lineNumber, columnNumber, nullptr, 3852 message, cause)); 3853 if (!errorObj) { 3854 return nullptr; 3855 } 3856 3857 return errorObj; 3858 } 3859 3860 // Error objects have 3 fields, some or all of them null: cause, 3861 // errors, and stack. 3862 bool JSStructuredCloneReader::readErrorFields(Handle<ErrorObject*> errorObj, 3863 HandleValue cause, bool* state) { 3864 JSContext* cx = context(); 3865 if (*state) { 3866 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3867 JSMSG_SC_BAD_SERIALIZED_DATA, 3868 "unexpected child value seen for Error object"); 3869 return false; 3870 } 3871 3872 RootedValue errors(cx); 3873 RootedValue stack(cx); 3874 if (!startRead(&errors) || !startRead(&stack)) { 3875 return false; 3876 } 3877 3878 bool hasCause = errorObj->getCause().isSome(); 3879 if (hasCause) { 3880 errorObj->setCauseSlot(cause); 3881 } else if (!cause.isNull()) { 3882 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3883 JSMSG_SC_BAD_SERIALIZED_DATA, 3884 "invalid 'cause' field for Error object"); 3885 return false; 3886 } 3887 3888 if (errorObj->type() == JSEXN_AGGREGATEERR) { 3889 if (!DefineDataProperty(context(), errorObj, cx->names().errors, errors, 3890 0)) { 3891 return false; 3892 } 3893 } else if (!errors.isNull()) { 3894 JS_ReportErrorNumberASCII( 3895 cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, 3896 "unexpected 'errors' field seen for non-AggregateError"); 3897 return false; 3898 } 3899 3900 if (stack.isObject()) { 3901 RootedObject stackObj(cx, &stack.toObject()); 3902 if (!stackObj->is<SavedFrame>()) { 3903 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3904 JSMSG_SC_BAD_SERIALIZED_DATA, 3905 "invalid 'stack' field for Error object"); 3906 return false; 3907 } 3908 errorObj->setStackSlot(stack); 3909 } else if (!stack.isNull()) { 3910 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3911 JSMSG_SC_BAD_SERIALIZED_DATA, 3912 "invalid 'stack' field for Error object"); 3913 return false; 3914 } 3915 3916 *state = true; 3917 return true; 3918 } 3919 3920 // Read a value and treat as a key,value pair. 3921 bool JSStructuredCloneReader::readMapField(Handle<MapObject*> mapObj, 3922 HandleValue key) { 3923 RootedValue val(context()); 3924 if (!startRead(&val)) { 3925 return false; 3926 } 3927 return mapObj->set(context(), key, val); 3928 } 3929 3930 // Read a value and treat as a key,value pair. Interpret as a plain property 3931 // value. 3932 bool JSStructuredCloneReader::readObjectField(HandleObject obj, 3933 HandleValue key) { 3934 if (!key.isString() && !key.isInt32()) { 3935 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 3936 JSMSG_SC_BAD_SERIALIZED_DATA, 3937 "property key expected"); 3938 return false; 3939 } 3940 3941 RootedValue val(context()); 3942 if (!startRead(&val)) { 3943 return false; 3944 } 3945 3946 RootedId id(context()); 3947 if (!PrimitiveValueToId<CanGC>(context(), key, &id)) { 3948 return false; 3949 } 3950 3951 // Fast path for adding a new property to a plain object. The property names 3952 // we see here should be unique, but we check for duplicates to guard against 3953 // corrupt or malicious data. 3954 if (id.isString() && obj->is<PlainObject>() && 3955 MOZ_LIKELY(!obj->as<PlainObject>().contains(context(), id))) { 3956 return AddDataPropertyToPlainObject(context(), obj.as<PlainObject>(), id, 3957 val); 3958 } 3959 3960 // Fast path for adding an array element. The index shouldn't exceed the 3961 // array's length, but we check for this in `addDenseElementNoLengthChange` to 3962 // guard against corrupt or malicious data. 3963 if (id.isInt() && obj->is<ArrayObject>()) { 3964 ArrayObject* arr = &obj->as<ArrayObject>(); 3965 switch (arr->addDenseElementNoLengthChange(context(), id.toInt(), val)) { 3966 case DenseElementResult::Failure: 3967 return false; 3968 case DenseElementResult::Success: 3969 return true; 3970 case DenseElementResult::Incomplete: 3971 // Fall-through to slow path. 3972 break; 3973 } 3974 } 3975 3976 return DefineDataProperty(context(), obj, id, val); 3977 } 3978 3979 // Perform the whole recursive reading procedure. 3980 bool JSStructuredCloneReader::read(MutableHandleValue vp, size_t nbytes) { 3981 if (!readHeader()) { 3982 return false; 3983 } 3984 MOZ_ASSERT(allowedScope <= JS::StructuredCloneScope::LastResolvedScope, 3985 "allowedScope should have been resolved by now"); 3986 3987 if (!readTransferMap()) { 3988 return false; 3989 } 3990 3991 MOZ_ASSERT(objs.length() == 0); 3992 MOZ_ASSERT(objState.length() == 1); 3993 3994 // Start out by reading in the main object and pushing it onto the 'objs' 3995 // stack. The data related to this object and its descendants extends from 3996 // here to the SCTAG_END_OF_KEYS at the end of the stream. 3997 if (!startRead(vp)) { 3998 return false; 3999 } 4000 4001 // Stop when the stack shows that all objects have been read. 4002 while (objs.length() != 0) { 4003 // What happens depends on the top obj on the objs stack. 4004 RootedObject obj(context(), &objs.back().toObject()); 4005 4006 uint32_t tag, data; 4007 if (!in.getPair(&tag, &data)) { 4008 return false; 4009 } 4010 4011 if (tag == SCTAG_END_OF_KEYS) { 4012 // Pop the current obj off the stack, since we are done with it and 4013 // its children. 4014 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); 4015 objs.popBack(); 4016 if (objState.back().first == obj) { 4017 objState.popBack(); 4018 } 4019 continue; 4020 } 4021 4022 // Remember the index of the current top of the state stack, which will 4023 // correspond to the state for `obj` iff `obj` is a type that uses state. 4024 // startRead() may push additional entries before the state is accessed and 4025 // updated while filling in the object's data. 4026 size_t objStateIdx = objState.length() - 1; 4027 4028 // The input stream contains a sequence of "child" values, whose 4029 // interpretation depends on the type of obj. These values can be 4030 // anything, and startRead() will push onto 'objs' for any non-leaf 4031 // value (i.e., anything that may contain children). 4032 // 4033 // startRead() will allocate the (empty) object, but note that when 4034 // startRead() returns, 'key' is not yet initialized with any of its 4035 // properties. Those will be filled in by returning to the head of this 4036 // loop, processing the first child obj, and continuing until all 4037 // children have been fully created. 4038 // 4039 // Note that this means the ordering in the stream is a little funky for 4040 // things like Map. See the comment above traverseMap() for an example. 4041 4042 bool expectKeyValuePairs = 4043 !(obj->is<MapObject>() || obj->is<SetObject>() || 4044 obj->is<SavedFrame>() || obj->is<ErrorObject>()); 4045 4046 RootedValue key(context()); 4047 ShouldAtomizeStrings atomize = 4048 expectKeyValuePairs ? AtomizeStrings : DontAtomizeStrings; 4049 if (!startRead(&key, atomize)) { 4050 return false; 4051 } 4052 4053 if (key.isNull() && expectKeyValuePairs) { 4054 // Backwards compatibility: Null formerly indicated the end of 4055 // object properties. 4056 4057 // No legacy objects used the state stack. 4058 MOZ_ASSERT(objState[objStateIdx].first() != obj); 4059 4060 objs.popBack(); 4061 continue; 4062 } 4063 4064 context()->check(key); 4065 4066 if (obj->is<SetObject>()) { 4067 // Set object: the values between obj header (from startRead()) and 4068 // SCTAG_END_OF_KEYS are all interpreted as values to add to the set. 4069 if (!obj->as<SetObject>().add(context(), key)) { 4070 return false; 4071 } 4072 } else if (obj->is<MapObject>()) { 4073 Rooted<MapObject*> mapObj(context(), &obj->as<MapObject>()); 4074 if (!readMapField(mapObj, key)) { 4075 return false; 4076 } 4077 } else if (obj->is<SavedFrame>()) { 4078 Rooted<SavedFrame*> frameObj(context(), &obj->as<SavedFrame>()); 4079 MOZ_ASSERT(objState[objStateIdx].first() == obj); 4080 bool state = objState[objStateIdx].second(); 4081 if (!readSavedFrameFields(frameObj, key, &state)) { 4082 return false; 4083 } 4084 objState[objStateIdx].second() = state; 4085 } else if (obj->is<ErrorObject>()) { 4086 Rooted<ErrorObject*> errorObj(context(), &obj->as<ErrorObject>()); 4087 MOZ_ASSERT(objState[objStateIdx].first() == obj); 4088 bool state = objState[objStateIdx].second(); 4089 if (!readErrorFields(errorObj, key, &state)) { 4090 return false; 4091 } 4092 objState[objStateIdx].second() = state; 4093 } else { 4094 MOZ_ASSERT(expectKeyValuePairs); 4095 // Everything else uses a series of key,value,key,value,... Value 4096 // objects. 4097 if (!readObjectField(obj, key)) { 4098 return false; 4099 } 4100 } 4101 } 4102 4103 allObjs.clear(); 4104 4105 // For fuzzing, it is convenient to allow extra data at the end 4106 // of the input buffer so that more possible inputs are considered 4107 // valid. 4108 #ifndef FUZZING 4109 bool extraData; 4110 if (tailStartPos.isSome()) { 4111 // in.tell() is the end of the main data. If "tail" data was consumed, 4112 // then check whether there's any data between the main data and the 4113 // beginning of the tail, or after the last read point in the tail. 4114 extraData = (in.tell() != *tailStartPos || !tailEndPos->done()); 4115 } else { 4116 extraData = !in.tell().done(); 4117 } 4118 if (extraData) { 4119 JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, 4120 JSMSG_SC_BAD_SERIALIZED_DATA, 4121 "extra data after end"); 4122 return false; 4123 } 4124 #endif 4125 return true; 4126 } 4127 4128 JS_PUBLIC_API bool JS_ReadStructuredClone( 4129 JSContext* cx, const JSStructuredCloneData& buf, uint32_t version, 4130 JS::StructuredCloneScope scope, MutableHandleValue vp, 4131 const JS::CloneDataPolicy& cloneDataPolicy, 4132 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) { 4133 AssertHeapIsIdle(); 4134 CHECK_THREAD(cx); 4135 4136 if (version > JS_STRUCTURED_CLONE_VERSION) { 4137 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4138 JSMSG_SC_BAD_CLONE_VERSION); 4139 return false; 4140 } 4141 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks; 4142 return ReadStructuredClone(cx, buf, scope, vp, cloneDataPolicy, callbacks, 4143 closure); 4144 } 4145 4146 JS_PUBLIC_API bool JS_WriteStructuredClone( 4147 JSContext* cx, HandleValue value, JSStructuredCloneData* bufp, 4148 JS::StructuredCloneScope scope, const JS::CloneDataPolicy& cloneDataPolicy, 4149 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure, 4150 HandleValue transferable) { 4151 AssertHeapIsIdle(); 4152 CHECK_THREAD(cx); 4153 cx->check(value); 4154 4155 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks; 4156 return WriteStructuredClone(cx, value, bufp, scope, cloneDataPolicy, 4157 callbacks, closure, transferable); 4158 } 4159 4160 JS_PUBLIC_API bool JS_StructuredCloneHasTransferables( 4161 JSStructuredCloneData& data, bool* hasTransferable) { 4162 *hasTransferable = StructuredCloneHasTransferObjects(data); 4163 return true; 4164 } 4165 4166 JS_PUBLIC_API bool JS_StructuredClone( 4167 JSContext* cx, HandleValue value, MutableHandleValue vp, 4168 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) { 4169 AssertHeapIsIdle(); 4170 CHECK_THREAD(cx); 4171 4172 // Strings are associated with zones, not compartments, 4173 // so we copy the string by wrapping it. 4174 if (value.isString()) { 4175 RootedString strValue(cx, value.toString()); 4176 if (!cx->compartment()->wrap(cx, &strValue)) { 4177 return false; 4178 } 4179 vp.setString(strValue); 4180 return true; 4181 } 4182 4183 const JSStructuredCloneCallbacks* callbacks = optionalCallbacks; 4184 4185 JSAutoStructuredCloneBuffer buf(JS::StructuredCloneScope::SameProcess, 4186 callbacks, closure); 4187 { 4188 if (value.isObject()) { 4189 RootedObject obj(cx, &value.toObject()); 4190 obj = CheckedUnwrapStatic(obj); 4191 if (!obj) { 4192 ReportAccessDenied(cx); 4193 return false; 4194 } 4195 AutoRealm ar(cx, obj); 4196 RootedValue unwrappedVal(cx, ObjectValue(*obj)); 4197 if (!buf.write(cx, unwrappedVal, callbacks, closure)) { 4198 return false; 4199 } 4200 } else { 4201 if (!buf.write(cx, value, callbacks, closure)) { 4202 return false; 4203 } 4204 } 4205 } 4206 4207 return buf.read(cx, vp, JS::CloneDataPolicy(), callbacks, closure); 4208 } 4209 4210 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer( 4211 JSAutoStructuredCloneBuffer&& other) 4212 : data_(other.scope()) { 4213 version_ = other.version_; 4214 other.giveTo(&data_); 4215 } 4216 4217 JSAutoStructuredCloneBuffer& JSAutoStructuredCloneBuffer::operator=( 4218 JSAutoStructuredCloneBuffer&& other) { 4219 MOZ_ASSERT(&other != this); 4220 MOZ_ASSERT(scope() == other.scope()); 4221 clear(); 4222 version_ = other.version_; 4223 other.giveTo(&data_); 4224 return *this; 4225 } 4226 4227 void JSAutoStructuredCloneBuffer::clear() { 4228 data_.discardTransferables(); 4229 data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables; 4230 data_.refsHeld_.releaseAll(); 4231 data_.stringBufferRefsHeld_.clear(); 4232 data_.Clear(); 4233 version_ = 0; 4234 } 4235 4236 void JSAutoStructuredCloneBuffer::adopt( 4237 JSStructuredCloneData&& data, uint32_t version, 4238 const JSStructuredCloneCallbacks* callbacks, void* closure) { 4239 clear(); 4240 data_ = std::move(data); 4241 version_ = version; 4242 data_.setCallbacks(callbacks, closure, 4243 OwnTransferablePolicy::OwnsTransferablesIfAny); 4244 } 4245 4246 void JSAutoStructuredCloneBuffer::giveTo(JSStructuredCloneData* data) { 4247 *data = std::move(data_); 4248 version_ = 0; 4249 data_.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables); 4250 data_.Clear(); 4251 } 4252 4253 bool JSAutoStructuredCloneBuffer::read( 4254 JSContext* cx, MutableHandleValue vp, 4255 const JS::CloneDataPolicy& cloneDataPolicy, 4256 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) { 4257 MOZ_ASSERT(cx); 4258 return !!JS_ReadStructuredClone( 4259 cx, data_, version_, data_.scope(), vp, cloneDataPolicy, 4260 optionalCallbacks ? optionalCallbacks : data_.callbacks_, 4261 optionalCallbacks ? closure : data_.closure_); 4262 } 4263 4264 bool JSAutoStructuredCloneBuffer::write( 4265 JSContext* cx, HandleValue value, 4266 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) { 4267 HandleValue transferable = UndefinedHandleValue; 4268 return write(cx, value, transferable, JS::CloneDataPolicy(), 4269 optionalCallbacks ? optionalCallbacks : data_.callbacks_, 4270 optionalCallbacks ? closure : data_.closure_); 4271 } 4272 4273 bool JSAutoStructuredCloneBuffer::write( 4274 JSContext* cx, HandleValue value, HandleValue transferable, 4275 const JS::CloneDataPolicy& cloneDataPolicy, 4276 const JSStructuredCloneCallbacks* optionalCallbacks, void* closure) { 4277 clear(); 4278 bool ok = JS_WriteStructuredClone( 4279 cx, value, &data_, data_.scopeForInternalWriting(), cloneDataPolicy, 4280 optionalCallbacks ? optionalCallbacks : data_.callbacks_, 4281 optionalCallbacks ? closure : data_.closure_, transferable); 4282 if (!ok) { 4283 version_ = JS_STRUCTURED_CLONE_VERSION; 4284 } 4285 return ok; 4286 } 4287 4288 JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, 4289 uint32_t* p2) { 4290 return r->input().readPair((uint32_t*)p1, (uint32_t*)p2); 4291 } 4292 4293 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p, 4294 size_t len) { 4295 return r->input().readBytes(p, len); 4296 } 4297 4298 JS_PUBLIC_API bool JS_ReadString(JSStructuredCloneReader* r, 4299 MutableHandleString str) { 4300 uint32_t tag, data; 4301 if (!r->input().readPair(&tag, &data)) { 4302 return false; 4303 } 4304 4305 if (tag == SCTAG_STRING) { 4306 if (JSString* s = 4307 r->readString(data, JSStructuredCloneReader::DontAtomizeStrings)) { 4308 str.set(s); 4309 return true; 4310 } 4311 return false; 4312 } 4313 4314 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr, 4315 JSMSG_SC_BAD_SERIALIZED_DATA, "expected string"); 4316 return false; 4317 } 4318 4319 JS_PUBLIC_API bool JS_ReadDouble(JSStructuredCloneReader* r, double* v) { 4320 return r->input().readDouble(v); 4321 } 4322 4323 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r, 4324 MutableHandleValue vp) { 4325 uint32_t tag, data; 4326 if (!r->input().readPair(&tag, &data)) { 4327 return false; 4328 } 4329 4330 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) { 4331 return r->readTypedArray(TagToV1ArrayType(tag), data, vp, true); 4332 } 4333 4334 if (tag == SCTAG_TYPED_ARRAY_OBJECT_V2) { 4335 // V2 stores the length (nelems) in |data| and the arrayType separately. 4336 uint64_t arrayType; 4337 if (!r->input().read(&arrayType)) { 4338 return false; 4339 } 4340 uint64_t nelems = data; 4341 return r->readTypedArray(arrayType, nelems, vp); 4342 } 4343 4344 if (tag == SCTAG_TYPED_ARRAY_OBJECT) { 4345 // The current version stores the array type in |data| and the length 4346 // (nelems) separately to support large TypedArrays. 4347 uint32_t arrayType = data; 4348 uint64_t nelems; 4349 if (!r->input().read(&nelems)) { 4350 return false; 4351 } 4352 return r->readTypedArray(arrayType, nelems, vp); 4353 } 4354 4355 JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr, 4356 JSMSG_SC_BAD_SERIALIZED_DATA, 4357 "expected type array"); 4358 return false; 4359 } 4360 4361 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag, 4362 uint32_t data) { 4363 return w->output().writePair(tag, data); 4364 } 4365 4366 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p, 4367 size_t len) { 4368 return w->output().writeBytes(p, len); 4369 } 4370 4371 JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w, 4372 HandleString str) { 4373 return w->writeString(SCTAG_STRING, str); 4374 } 4375 4376 JS_PUBLIC_API bool JS_WriteDouble(JSStructuredCloneWriter* w, double v) { 4377 return w->output().writeDouble(v); 4378 } 4379 4380 JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w, 4381 HandleValue v) { 4382 MOZ_ASSERT(v.isObject()); 4383 w->context()->check(v); 4384 RootedObject obj(w->context(), &v.toObject()); 4385 4386 // startWrite can write everything, thus we should check here 4387 // and report error if the user passes a wrong type. 4388 if (!obj->canUnwrapAs<TypedArrayObject>()) { 4389 ReportAccessDenied(w->context()); 4390 return false; 4391 } 4392 4393 // We should use startWrite instead of writeTypedArray, because 4394 // typed array is an object, we should add it to the |memory| 4395 // (allObjs) list. Directly calling writeTypedArray won't add it. 4396 return w->startWrite(v); 4397 } 4398 4399 JS_PUBLIC_API bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, 4400 HandleObject obj) { 4401 w->memory.remove(w->memory.lookup(obj)); 4402 4403 return true; 4404 } 4405 4406 JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope( 4407 JSStructuredCloneWriter* w) { 4408 return w->output().scope(); 4409 }