/* * Copyright (C) 2009-2021 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "config.h" #include "SerializedScriptValue.h" // #include "BlobRegistry.h" // #include "CryptoKeyAES.h" // #include "CryptoKeyEC.h" // #include "CryptoKeyHMAC.h" // #include "CryptoKeyRSA.h" // #include "CryptoKeyRSAComponents.h" // #include "CryptoKeyRaw.h" // #include "IDBValue.h" // #include "ImageBitmapBacking.h" // #include "JSBlob.h" // #include "JSCryptoKey.h" #include "JSDOMBinding.h" #include "JSDOMConvertBufferSource.h" #include "JSDOMException.h" #include "JSDOMGlobalObject.h" #include "JSDOMMatrix.h" #include "JSDOMPoint.h" #include "JSDOMQuad.h" #include "JSDOMRect.h" #include "JSExecState.h" #include "JSFile.h" #include "JSFileList.h" // #include "JSIDBSerializationGlobalObject.h" // #include "JSImageBitmap.h" // #include "JSImageData.h" // #include "JSMessagePort.h" #include "JSNavigator.h" #include "JSRTCCertificate.h" #include "JSRTCDataChannel.h" #include "ScriptExecutionContext.h" #include "SharedBuffer.h" #include "WebCoreJSClientData.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if USE(CG) #include #endif #if PLATFORM(COCOA) #include #endif #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) #include "JSOffscreenCanvas.h" #include "OffscreenCanvas.h" #endif #if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) || CPU(NEEDS_ALIGNED_ACCESS) #define ASSUME_LITTLE_ENDIAN 0 #else #define ASSUME_LITTLE_ENDIAN 1 #endif namespace WebCore { using namespace JSC; DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(SerializedScriptValue); static const unsigned maximumFilterRecursion = 40000; enum class SerializationReturnCode { SuccessfullyCompleted, StackOverflowError, InterruptedExecutionError, ValidationError, ExistingExceptionError, DataCloneError, UnspecifiedError }; enum WalkerState { StateUnknown, ArrayStartState, ArrayStartVisitMember, ArrayEndVisitMember, ObjectStartState, ObjectStartVisitMember, ObjectEndVisitMember, MapDataStartVisitEntry, MapDataEndVisitKey, MapDataEndVisitValue, SetDataStartVisitEntry, SetDataEndVisitKey }; // These can't be reordered, and any new types must be added to the end of the list // When making changes to these lists please cover your new type(s) in the API test "IndexedDB.StructuredCloneBackwardCompatibility" enum SerializationTag { ArrayTag = 1, ObjectTag = 2, UndefinedTag = 3, NullTag = 4, IntTag = 5, ZeroTag = 6, OneTag = 7, FalseTag = 8, TrueTag = 9, DoubleTag = 10, DateTag = 11, FileTag = 12, FileListTag = 13, ImageDataTag = 14, BlobTag = 15, StringTag = 16, EmptyStringTag = 17, RegExpTag = 18, ObjectReferenceTag = 19, MessagePortReferenceTag = 20, ArrayBufferTag = 21, ArrayBufferViewTag = 22, ArrayBufferTransferTag = 23, TrueObjectTag = 24, FalseObjectTag = 25, StringObjectTag = 26, EmptyStringObjectTag = 27, NumberObjectTag = 28, SetObjectTag = 29, MapObjectTag = 30, NonMapPropertiesTag = 31, NonSetPropertiesTag = 32, #if ENABLE(WEB_CRYPTO) CryptoKeyTag = 33, #endif SharedArrayBufferTag = 34, #if ENABLE(WEBASSEMBLY) WasmModuleTag = 35, #endif DOMPointReadOnlyTag = 36, DOMPointTag = 37, DOMRectReadOnlyTag = 38, DOMRectTag = 39, DOMMatrixReadOnlyTag = 40, DOMMatrixTag = 41, DOMQuadTag = 42, ImageBitmapTransferTag = 43, #if ENABLE(WEB_RTC) RTCCertificateTag = 44, #endif ImageBitmapTag = 45, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) OffscreenCanvasTransferTag = 46, #endif BigIntTag = 47, BigIntObjectTag = 48, #if ENABLE(WEBASSEMBLY) WasmMemoryTag = 49, #endif #if ENABLE(WEB_RTC) RTCDataChannelTransferTag = 50, #endif DOMExceptionTag = 51, ErrorTag = 255 }; enum ArrayBufferViewSubtag { DataViewTag = 0, Int8ArrayTag = 1, Uint8ArrayTag = 2, Uint8ClampedArrayTag = 3, Int16ArrayTag = 4, Uint16ArrayTag = 5, Int32ArrayTag = 6, Uint32ArrayTag = 7, Float32ArrayTag = 8, Float64ArrayTag = 9, BigInt64ArrayTag = 10, BigUint64ArrayTag = 11, }; static unsigned typedArrayElementSize(ArrayBufferViewSubtag tag) { switch (tag) { case DataViewTag: case Int8ArrayTag: case Uint8ArrayTag: case Uint8ClampedArrayTag: return 1; case Int16ArrayTag: case Uint16ArrayTag: return 2; case Int32ArrayTag: case Uint32ArrayTag: case Float32ArrayTag: return 4; case Float64ArrayTag: case BigInt64ArrayTag: case BigUint64ArrayTag: return 8; default: return 0; } } enum class PredefinedColorSpaceTag : uint8_t { SRGB = 0 #if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) , DisplayP3 = 1 #endif }; enum DestinationColorSpaceTag { DestinationColorSpaceSRGBTag = 0, #if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) DestinationColorSpaceLinearSRGBTag = 1, #endif #if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) DestinationColorSpaceDisplayP3Tag = 2, #endif #if PLATFORM(COCOA) DestinationColorSpaceCGColorSpaceNameTag = 3, DestinationColorSpaceCGColorSpacePropertyListTag = 4, #endif }; #if ENABLE(WEB_CRYPTO) const uint32_t currentKeyFormatVersion = 1; enum class CryptoKeyClassSubtag { HMAC = 0, AES = 1, RSA = 2, EC = 3, Raw = 4, }; const uint8_t cryptoKeyClassSubtagMaximumValue = 4; enum class CryptoKeyAsymmetricTypeSubtag { Public = 0, Private = 1 }; const uint8_t cryptoKeyAsymmetricTypeSubtagMaximumValue = 1; enum class CryptoKeyUsageTag { Encrypt = 0, Decrypt = 1, Sign = 2, Verify = 3, DeriveKey = 4, DeriveBits = 5, WrapKey = 6, UnwrapKey = 7 }; const uint8_t cryptoKeyUsageTagMaximumValue = 7; enum class CryptoAlgorithmIdentifierTag { RSAES_PKCS1_v1_5 = 0, RSASSA_PKCS1_v1_5 = 1, RSA_PSS = 2, RSA_OAEP = 3, ECDSA = 4, ECDH = 5, AES_CTR = 6, AES_CBC = 7, AES_GCM = 9, AES_CFB = 10, AES_KW = 11, HMAC = 12, SHA_1 = 14, SHA_224 = 15, SHA_256 = 16, SHA_384 = 17, SHA_512 = 18, HKDF = 20, PBKDF2 = 21, }; const uint8_t cryptoAlgorithmIdentifierTagMaximumValue = 21; static unsigned countUsages(CryptoKeyUsageBitmap usages) { // Fast bit count algorithm for sparse bit maps. unsigned count = 0; while (usages) { usages = usages & (usages - 1); ++count; } return count; } #endif /* CurrentVersion tracks the serialization version so that persistent stores * are able to correctly bail out in the case of encountering newer formats. * * Initial version was 1. * Version 2. added the ObjectReferenceTag and support for serialization of cyclic graphs. * Version 3. added the FalseObjectTag, TrueObjectTag, NumberObjectTag, StringObjectTag * and EmptyStringObjectTag for serialization of Boolean, Number and String objects. * Version 4. added support for serializing non-index properties of arrays. * Version 5. added support for Map and Set types. * Version 6. added support for 8-bit strings. * Version 7. added support for File's lastModified attribute. * Version 8. added support for ImageData's colorSpace attribute. * Version 9. added support for ImageBitmap color space. * Version 10. changed the length (and offsets) of ArrayBuffers (and ArrayBufferViews) from 32 to 64 bits */ static const unsigned CurrentVersion = 10; static const unsigned TerminatorTag = 0xFFFFFFFF; static const unsigned StringPoolTag = 0xFFFFFFFE; static const unsigned NonIndexPropertiesTag = 0xFFFFFFFD; // The high bit of a StringData's length determines the character size. static const unsigned StringDataIs8BitFlag = 0x80000000; /* * Object serialization is performed according to the following grammar, all tags * are recorded as a single uint8_t. * * IndexType (used for the object pool and StringData's constant pool) is the * minimum sized unsigned integer type required to represent the maximum index * in the constant pool. * * SerializedValue :- Value * Value :- Array | Object | Map | Set | Terminal * * Array :- * ArrayTag ()* TerminatorTag * * Object :- * ObjectTag ()* TerminatorTag * * Map :- MapObjectTag MapData * * Set :- SetObjectTag SetData * * MapData :- ()* NonMapPropertiesTag ()* TerminatorTag * SetData :- ()* NonSetPropertiesTag ()* TerminatorTag * * Terminal :- * UndefinedTag * | NullTag * | IntTag * | ZeroTag * | OneTag * | FalseTag * | TrueTag * | FalseObjectTag * | TrueObjectTag * | DoubleTag * | NumberObjectTag * | DateTag * | String * | EmptyStringTag * | EmptyStringObjectTag * | BigInt * | File * | FileList * | ImageData * | Blob * | ObjectReference * | MessagePortReferenceTag * | ArrayBuffer * | ArrayBufferViewTag ArrayBufferViewSubtag (ArrayBuffer | ObjectReference) * | CryptoKeyTag * | DOMPoint * | DOMRect * | DOMMatrix * | DOMQuad * | ImageBitmapTransferTag * | RTCCertificateTag * | ImageBitmapTag DestinationColorSpace () * | OffscreenCanvasTransferTag * | WasmMemoryTag * | RTCDataChannelTransferTag * | DOMExceptionTag * * Inside certificate, data is serialized in this format as per spec: * * * We also add fingerprints to make sure we expose to JavaScript the same information. * * Inside wrapped crypto key, data is serialized in this format: * * CryptoKeyClassSubtag (CryptoKeyHMAC | CryptoKeyAES | CryptoKeyRSA) * * String :- * EmptyStringTag * StringTag StringData * * StringObject: * EmptyStringObjectTag * StringObjectTag StringData * * StringData :- * StringPoolTag * (not (TerminatorTag | StringPoolTag)) // Added to constant pool when seen, string length 0xFFFFFFFF is disallowed * * BigInt :- * BigIntTag BigIntData * BigIntObjectTag BigIntData * * BigIntData :- * * * File :- * FileTag FileData * * FileData :- * * * FileList :- * FileListTag (){length} * * ImageData :- * ImageDataTag * * Blob :- * BlobTag * * RegExp :- * RegExpTag * * ObjectReference :- * ObjectReferenceTag * * ArrayBuffer :- * ArrayBufferTag * ArrayBufferTransferTag * SharedArrayBufferTag * * CryptoKeyHMAC :- * CryptoAlgorithmIdentifierTag // Algorithm tag inner hash function. * * CryptoKeyAES :- * CryptoAlgorithmIdentifierTag * * CryptoKeyRSA :- * CryptoAlgorithmIdentifierTag CryptoAlgorithmIdentifierTag? CryptoKeyAsymmetricTypeSubtag CryptoKeyRSAPublicComponents CryptoKeyRSAPrivateComponents? * * CryptoKeyRSAPublicComponents :- * * * CryptoKeyRSAPrivateComponents :- * FirstPrimeInfo? PrimeInfo{primeCount - 1} * * // CRT data could be computed from prime factors. It is only serialized to reuse a code path that's needed for JWK. * FirstPrimeInfo :- * * * PrimeInfo :- * * * CryptoKeyEC :- * CryptoAlgorithmIdentifierTag CryptoKeyAsymmetricTypeSubtag * * CryptoKeyRaw :- * CryptoAlgorithmIdentifierTag * * DOMPoint :- * DOMPointReadOnlyTag DOMPointData * | DOMPointTag DOMPointData * * DOMPointData :- * * * DOMRect :- * DOMRectReadOnlyTag DOMRectData * | DOMRectTag DOMRectData * * DOMRectData :- * * * DOMMatrix :- * DOMMatrixReadOnlyTag DOMMatrixData * | DOMMatrixTag DOMMatrixData * * DOMMatrixData :- * * | * * DOMQuad :- * DOMQuadTag DOMQuadData * * DOMQuadData :- * * * DestinationColorSpace :- * DestinationColorSpaceSRGBTag * | DestinationColorSpaceLinearSRGBTag * | DestinationColorSpaceDisplayP3Tag * | DestinationColorSpaceCGColorSpaceNameTag {nameDataLength} * | DestinationColorSpaceCGColorSpacePropertyListTag {propertyListDataLength} */ using DeserializationResult = std::pair; class CloneBase { protected: CloneBase(JSGlobalObject* lexicalGlobalObject) : m_lexicalGlobalObject(lexicalGlobalObject) , m_failed(false) { } void fail() { m_failed = true; } JSGlobalObject* m_lexicalGlobalObject; bool m_failed; MarkedArgumentBuffer m_gcBuffer; }; #if ENABLE(WEB_CRYPTO) static bool wrapCryptoKey(JSGlobalObject* lexicalGlobalObject, const Vector& key, Vector& wrappedKey) { auto context = executionContext(lexicalGlobalObject); return context && context->wrapCryptoKey(key, wrappedKey); } static bool unwrapCryptoKey(JSGlobalObject* lexicalGlobalObject, const Vector& wrappedKey, Vector& key) { auto context = executionContext(lexicalGlobalObject); return context && context->unwrapCryptoKey(wrappedKey, key); } #endif #if ASSUME_LITTLE_ENDIAN template static void writeLittleEndian(Vector& buffer, T value) { buffer.append(reinterpret_cast(&value), sizeof(value)); } #else template static void writeLittleEndian(Vector& buffer, T value) { for (unsigned i = 0; i < sizeof(T); i++) { buffer.append(value & 0xFF); value >>= 8; } } #endif template<> void writeLittleEndian(Vector& buffer, uint8_t value) { buffer.append(value); } template static bool writeLittleEndian(Vector& buffer, const T* values, uint32_t length) { if (length > std::numeric_limits::max() / sizeof(T)) return false; #if ASSUME_LITTLE_ENDIAN buffer.append(reinterpret_cast(values), length * sizeof(T)); #else for (unsigned i = 0; i < length; i++) { T value = values[i]; for (unsigned j = 0; j < sizeof(T); j++) { buffer.append(static_cast(value & 0xFF)); value >>= 8; } } #endif return true; } template<> bool writeLittleEndian(Vector& buffer, const uint8_t* values, uint32_t length) { buffer.append(values, length); return true; } class CloneSerializer : CloneBase { public: static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) const Vector>& offscreenCanvases, #endif #if ENABLE(WEB_RTC) const Vector>& rtcDataChannels, #endif #if ENABLE(WEBASSEMBLY) WasmModuleArray& wasmModules, WasmMemoryHandleArray& wasmMemoryHandles, #endif /* Vector& blobHandles,*/ Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers) { CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers, imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, #endif #if ENABLE(WEB_RTC) rtcDataChannels, #endif #if ENABLE(WEBASSEMBLY) wasmModules, wasmMemoryHandles, #endif blobHandles, out, context, sharedBuffers); return serializer.serialize(value); } static bool serialize(StringView string, Vector& out) { writeLittleEndian(out, CurrentVersion); if (string.isEmpty()) { writeLittleEndian(out, EmptyStringTag); return true; } writeLittleEndian(out, StringTag); if (string.is8Bit()) { writeLittleEndian(out, string.length() | StringDataIs8BitFlag); return writeLittleEndian(out, string.characters8(), string.length()); } writeLittleEndian(out, string.length()); return writeLittleEndian(out, string.characters16(), string.length()); } private: typedef HashMap ObjectPool; CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, Vector>& arrayBuffers, /*const Vector>& imageBitmaps,*/ #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) const Vector>& offscreenCanvases, #endif #if ENABLE(WEB_RTC) const Vector>& rtcDataChannels, #endif #if ENABLE(WEBASSEMBLY) WasmModuleArray& wasmModules, WasmMemoryHandleArray& wasmMemoryHandles, #endif /*Vector& blobHandles,*/ Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers) : CloneBase(lexicalGlobalObject) , m_buffer(out) // , m_blobHandles(blobHandles) , m_emptyIdentifier(Identifier::fromString(lexicalGlobalObject->vm(), emptyString())) , m_context(context) , m_sharedBuffers(sharedBuffers) #if ENABLE(WEBASSEMBLY) , m_wasmModules(wasmModules) , m_wasmMemoryHandles(wasmMemoryHandles) #endif { write(CurrentVersion); fillTransferMap(messagePorts, m_transferredMessagePorts); fillTransferMap(arrayBuffers, m_transferredArrayBuffers); // fillTransferMap(imageBitmaps, m_transferredImageBitmaps); // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) // fillTransferMap(offscreenCanvases, m_transferredOffscreenCanvases); // #endif // #if ENABLE(WEB_RTC) // fillTransferMap(rtcDataChannels, m_transferredRTCDataChannels); // #endif } template void fillTransferMap(const Vector>& input, ObjectPool& result) { if (input.isEmpty()) return; JSDOMGlobalObject* globalObject = jsCast(m_lexicalGlobalObject); for (size_t i = 0; i < input.size(); i++) { JSC::JSValue value = toJS(m_lexicalGlobalObject, globalObject, input[i].get()); JSC::JSObject* obj = value.getObject(); if (obj && !result.contains(obj)) result.add(obj, i); } } template void fillTransferMap(const Vector>& input, ObjectPool& result) { if (input.isEmpty()) return; JSDOMGlobalObject* globalObject = jsCast(m_lexicalGlobalObject); for (size_t i = 0; i < input.size(); i++) { JSC::JSValue value = toJS(m_lexicalGlobalObject, globalObject, input[i].get()); JSC::JSObject* obj = value.getObject(); if (obj && !result.contains(obj)) result.add(obj, i); } } SerializationReturnCode serialize(JSValue in); bool isArray(VM& vm, JSValue value) { if (!value.isObject()) return false; JSObject* object = asObject(value); return object->inherits(vm); } bool isMap(VM& vm, JSValue value) { if (!value.isObject()) return false; JSObject* object = asObject(value); return object->inherits(vm); } bool isSet(VM& vm, JSValue value) { if (!value.isObject()) return false; JSObject* object = asObject(value); return object->inherits(vm); } bool checkForDuplicate(JSObject* object) { // Record object for graph reconstruction ObjectPool::const_iterator found = m_objectPool.find(object); // Handle duplicate references if (found != m_objectPool.end()) { write(ObjectReferenceTag); ASSERT(found->value < m_objectPool.size()); writeObjectIndex(found->value); return true; } return false; } void recordObject(JSObject* object) { m_objectPool.add(object, m_objectPool.size()); m_gcBuffer.appendWithCrashOnOverflow(object); } bool startObjectInternal(JSObject* object) { if (checkForDuplicate(object)) return false; recordObject(object); return true; } bool startObject(JSObject* object) { if (!startObjectInternal(object)) return false; write(ObjectTag); return true; } bool startArray(JSArray* array) { if (!startObjectInternal(array)) return false; unsigned length = array->length(); write(ArrayTag); write(length); return true; } bool startSet(JSSet* set) { if (!startObjectInternal(set)) return false; write(SetObjectTag); return true; } bool startMap(JSMap* map) { if (!startObjectInternal(map)) return false; write(MapObjectTag); return true; } void endObject() { write(TerminatorTag); } JSValue getProperty(VM& vm, JSObject* object, const Identifier& propertyName) { PropertySlot slot(object, PropertySlot::InternalMethodType::Get); if (object->methodTable(vm)->getOwnPropertySlot(object, m_lexicalGlobalObject, propertyName, slot)) return slot.getValue(m_lexicalGlobalObject, propertyName); return JSValue(); } void dumpImmediate(JSValue value, SerializationReturnCode& code) { if (value.isNull()) { write(NullTag); return; } if (value.isUndefined()) { write(UndefinedTag); return; } if (value.isNumber()) { if (value.isInt32()) { if (!value.asInt32()) write(ZeroTag); else if (value.asInt32() == 1) write(OneTag); else { write(IntTag); write(static_cast(value.asInt32())); } } else { write(DoubleTag); write(value.asDouble()); } return; } if (value.isBoolean()) { if (value.isTrue()) write(TrueTag); else write(FalseTag); return; } #if USE(BIGINT32) if (value.isBigInt32()) { write(BigIntTag); dumpBigIntData(value); return; } #endif // Make any new primitive extension safe by throwing an error. code = SerializationReturnCode::DataCloneError; } void dumpString(const String& string) { if (string.isEmpty()) write(EmptyStringTag); else { write(StringTag); write(string); } } void dumpStringObject(const String& string) { if (string.isEmpty()) write(EmptyStringObjectTag); else { write(StringObjectTag); write(string); } } void dumpBigIntData(JSValue value) { ASSERT(value.isBigInt()); #if USE(BIGINT32) if (value.isBigInt32()) { dumpBigInt32Data(value.bigInt32AsInt32()); return; } #endif dumpHeapBigIntData(jsCast(value)); } #if USE(BIGINT32) void dumpBigInt32Data(int32_t integer) { write(static_cast(integer < 0)); if (!integer) { write(static_cast(0)); // Length-in-uint64_t return; } write(static_cast(1)); // Length-in-uint64_t int64_t value = static_cast(integer); if (value < 0) value = -value; write(static_cast(value)); } #endif void dumpHeapBigIntData(JSBigInt* bigInt) { write(static_cast(bigInt->sign())); if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) { write(static_cast(bigInt->length())); for (unsigned index = 0; index < bigInt->length(); ++index) write(static_cast(bigInt->digit(index))); } else { ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t)); uint32_t lengthInUint64 = bigInt->length() / 2; if (bigInt->length() & 0x1) ++lengthInUint64; write(lengthInUint64); uint64_t value = 0; for (unsigned index = 0; index < bigInt->length(); ++index) { if (!(index & 0x1)) value = bigInt->digit(index); else { value = (static_cast(bigInt->digit(index)) << 32) | value; write(static_cast(value)); value = 0; } } if (bigInt->length() & 0x1) write(static_cast(value)); } } JSC::JSValue toJSArrayBuffer(ArrayBuffer& arrayBuffer) { auto& vm = m_lexicalGlobalObject->vm(); auto* globalObject = m_lexicalGlobalObject; if (globalObject->inherits(vm)) return toJS(globalObject, jsCast(globalObject), &arrayBuffer); if (auto* buffer = arrayBuffer.m_wrapper.get()) return buffer; return JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(arrayBuffer.sharingMode()), &arrayBuffer); } bool dumpArrayBufferView(JSObject* obj, SerializationReturnCode& code) { VM& vm = m_lexicalGlobalObject->vm(); write(ArrayBufferViewTag); if (obj->inherits(vm)) write(DataViewTag); else if (obj->inherits(vm)) write(Uint8ClampedArrayTag); else if (obj->inherits(vm)) write(Int8ArrayTag); else if (obj->inherits(vm)) write(Uint8ArrayTag); else if (obj->inherits(vm)) write(Int16ArrayTag); else if (obj->inherits(vm)) write(Uint16ArrayTag); else if (obj->inherits(vm)) write(Int32ArrayTag); else if (obj->inherits(vm)) write(Uint32ArrayTag); else if (obj->inherits(vm)) write(Float32ArrayTag); else if (obj->inherits(vm)) write(Float64ArrayTag); else if (obj->inherits(vm)) write(BigInt64ArrayTag); else if (obj->inherits(vm)) write(BigUint64ArrayTag); else return false; RefPtr arrayBufferView = toPossiblySharedArrayBufferView(vm, obj); uint64_t byteOffset = arrayBufferView->byteOffset(); write(byteOffset); uint64_t byteLength = arrayBufferView->byteLength(); write(byteLength); RefPtr arrayBuffer = arrayBufferView->possiblySharedBuffer(); if (!arrayBuffer) { code = SerializationReturnCode::ValidationError; return true; } return dumpIfTerminal(toJSArrayBuffer(*arrayBuffer), code); } // void dumpDOMPoint(const DOMPointReadOnly& point) // { // write(point.x()); // write(point.y()); // write(point.z()); // write(point.w()); // } // void dumpDOMPoint(JSObject* obj) // { // VM& vm = m_lexicalGlobalObject->vm(); // if (obj->inherits(vm)) // write(DOMPointTag); // else // write(DOMPointReadOnlyTag); // dumpDOMPoint(jsCast(obj)->wrapped()); // } // void dumpDOMRect(JSObject* obj) // { // VM& vm = m_lexicalGlobalObject->vm(); // if (obj->inherits(vm)) // write(DOMRectTag); // else // write(DOMRectReadOnlyTag); // auto& rect = jsCast(obj)->wrapped(); // write(rect.x()); // write(rect.y()); // write(rect.width()); // write(rect.height()); // } // void dumpDOMMatrix(JSObject* obj) // { // VM& vm = m_lexicalGlobalObject->vm(); // if (obj->inherits(vm)) // write(DOMMatrixTag); // else // write(DOMMatrixReadOnlyTag); // auto& matrix = jsCast(obj)->wrapped(); // bool is2D = matrix.is2D(); // write(static_cast(is2D)); // if (is2D) { // write(matrix.m11()); // write(matrix.m12()); // write(matrix.m21()); // write(matrix.m22()); // write(matrix.m41()); // write(matrix.m42()); // } else { // write(matrix.m11()); // write(matrix.m12()); // write(matrix.m13()); // write(matrix.m14()); // write(matrix.m21()); // write(matrix.m22()); // write(matrix.m23()); // write(matrix.m24()); // write(matrix.m31()); // write(matrix.m32()); // write(matrix.m33()); // write(matrix.m34()); // write(matrix.m41()); // write(matrix.m42()); // write(matrix.m43()); // write(matrix.m44()); // } // } // void dumpDOMQuad(JSObject* obj) // { // write(DOMQuadTag); // auto& quad = jsCast(obj)->wrapped(); // dumpDOMPoint(quad.p1()); // dumpDOMPoint(quad.p2()); // dumpDOMPoint(quad.p3()); // dumpDOMPoint(quad.p4()); // } // void dumpImageBitmap(JSObject* obj, SerializationReturnCode& code) // { // auto index = m_transferredImageBitmaps.find(obj); // if (index != m_transferredImageBitmaps.end()) { // write(ImageBitmapTransferTag); // write(index->value); // return; // } // auto& imageBitmap = jsCast(obj)->wrapped(); // auto* buffer = imageBitmap.buffer(); // if (!buffer) { // code = SerializationReturnCode::ValidationError; // return; // } // // FIXME: We should try to avoid converting pixel format. // PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, buffer->colorSpace() }; // const IntSize& logicalSize = buffer->truncatedLogicalSize(); // auto pixelBuffer = buffer->getPixelBuffer(format, { IntPoint::zero(), logicalSize }); // if (!pixelBuffer) { // code = SerializationReturnCode::ValidationError; // return; // } // auto arrayBuffer = pixelBuffer->data().possiblySharedBuffer(); // if (!arrayBuffer) { // code = SerializationReturnCode::ValidationError; // return; // } // write(ImageBitmapTag); // write(static_cast(imageBitmap.serializationState().toRaw())); // write(static_cast(logicalSize.width())); // write(static_cast(logicalSize.height())); // write(static_cast(buffer->resolutionScale())); // write(buffer->colorSpace()); // CheckedUint32 byteLength = arrayBuffer->byteLength(); // if (byteLength.hasOverflowed()) { // code = SerializationReturnCode::ValidationError; // return; // } // write(byteLength); // write(static_cast(arrayBuffer->data()), byteLength); // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) void dumpOffscreenCanvas(JSObject* obj, SerializationReturnCode& code) { auto index = m_transferredOffscreenCanvases.find(obj); if (index != m_transferredOffscreenCanvases.end()) { write(OffscreenCanvasTransferTag); write(index->value); return; } code = SerializationReturnCode::DataCloneError; } #endif #if ENABLE(WEB_RTC) void dumpRTCDataChannel(JSObject* obj, SerializationReturnCode& code) { auto index = m_transferredRTCDataChannels.find(obj); if (index != m_transferredRTCDataChannels.end()) { write(RTCDataChannelTransferTag); write(index->value); return; } code = SerializationReturnCode::DataCloneError; } #endif void dumpDOMException(JSObject* obj, SerializationReturnCode& code) { if (auto* exception = JSDOMException::toWrapped(m_lexicalGlobalObject->vm(), obj)) { write(DOMExceptionTag); write(exception->message()); write(exception->name()); return; } code = SerializationReturnCode::DataCloneError; } bool dumpIfTerminal(JSValue value, SerializationReturnCode& code) { if (!value.isCell()) { dumpImmediate(value, code); return true; } ASSERT(value.isCell()); if (value.isString()) { dumpString(asString(value)->value(m_lexicalGlobalObject)); return true; } if (value.isHeapBigInt()) { write(BigIntTag); dumpBigIntData(value); return true; } if (value.isSymbol()) { code = SerializationReturnCode::DataCloneError; return true; } VM& vm = m_lexicalGlobalObject->vm(); if (isArray(vm, value)) return false; if (value.isObject()) { auto* obj = asObject(value); if (auto* dateObject = jsDynamicCast(vm, obj)) { write(DateTag); write(dateObject->internalNumber()); return true; } if (auto* booleanObject = jsDynamicCast(vm, obj)) { if (!startObjectInternal(booleanObject)) // handle duplicates return true; write(booleanObject->internalValue().toBoolean(m_lexicalGlobalObject) ? TrueObjectTag : FalseObjectTag); return true; } if (auto* stringObject = jsDynamicCast(vm, obj)) { if (!startObjectInternal(stringObject)) // handle duplicates return true; String str = asString(stringObject->internalValue())->value(m_lexicalGlobalObject); dumpStringObject(str); return true; } if (auto* numberObject = jsDynamicCast(vm, obj)) { if (!startObjectInternal(numberObject)) // handle duplicates return true; write(NumberObjectTag); write(numberObject->internalValue().asNumber()); return true; } if (auto* bigIntObject = jsDynamicCast(vm, obj)) { if (!startObjectInternal(bigIntObject)) // handle duplicates return true; JSValue bigIntValue = bigIntObject->internalValue(); ASSERT(bigIntValue.isBigInt()); write(BigIntObjectTag); dumpBigIntData(bigIntValue); return true; } if (auto* file = JSFile::toWrapped(vm, obj)) { write(FileTag); write(*file); return true; } if (auto* list = JSFileList::toWrapped(vm, obj)) { write(FileListTag); write(list->length()); for (auto& file : list->files()) write(file.get()); return true; } // if (auto* blob = JSBlob::toWrapped(vm, obj)) { // write(BlobTag); // m_blobHandles.append(blob->handle()); // write(blob->url().string()); // write(blob->type()); // static_assert(sizeof(uint64_t) == sizeof(decltype(blob->size()))); // uint64_t size = blob->size(); // write(size); // return true; // } if (auto* data = JSImageData::toWrapped(vm, obj)) { write(ImageDataTag); write(data->width()); write(data->height()); CheckedUint32 dataLength = data->data().length(); if (dataLength.hasOverflowed()) { code = SerializationReturnCode::DataCloneError; return true; } write(dataLength); write(data->data().data(), dataLength); write(data->colorSpace()); return true; } if (auto* regExp = jsDynamicCast(vm, obj)) { write(RegExpTag); write(regExp->regExp()->pattern()); write(String(JSC::Yarr::flagsString(regExp->regExp()->flags()).data())); return true; } if (obj->inherits(vm)) { auto index = m_transferredMessagePorts.find(obj); if (index != m_transferredMessagePorts.end()) { write(MessagePortReferenceTag); write(index->value); return true; } // MessagePort object could not be found in transferred message ports code = SerializationReturnCode::ValidationError; return true; } if (auto* arrayBuffer = toPossiblySharedArrayBuffer(vm, obj)) { if (arrayBuffer->isDetached()) { code = SerializationReturnCode::ValidationError; return true; } auto index = m_transferredArrayBuffers.find(obj); if (index != m_transferredArrayBuffers.end()) { write(ArrayBufferTransferTag); write(index->value); return true; } if (!startObjectInternal(obj)) // handle duplicates return true; if (arrayBuffer->isShared() && m_context == SerializationContext::WorkerPostMessage) { uint32_t index = m_sharedBuffers.size(); ArrayBufferContents contents; if (arrayBuffer->shareWith(contents)) { write(SharedArrayBufferTag); m_sharedBuffers.append(WTFMove(contents)); write(index); return true; } } write(ArrayBufferTag); uint64_t byteLength = arrayBuffer->byteLength(); write(byteLength); write(static_cast(arrayBuffer->data()), byteLength); return true; } if (obj->inherits(vm)) { if (checkForDuplicate(obj)) return true; bool success = dumpArrayBufferView(obj, code); recordObject(obj); return success; } #if ENABLE(WEB_CRYPTO) if (auto* key = JSCryptoKey::toWrapped(vm, obj)) { write(CryptoKeyTag); Vector serializedKey; Vector dummyBlobHandles; Vector> dummyMessagePorts; Vector> dummyArrayBuffers; #if ENABLE(WEBASSEMBLY) WasmModuleArray dummyModules; WasmMemoryHandleArray dummyMemoryHandles; #endif ArrayBufferContentsArray dummySharedBuffers; CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, {}, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) {}, #endif #if ENABLE(WEB_RTC) {}, #endif #if ENABLE(WEBASSEMBLY) dummyModules, dummyMemoryHandles, #endif dummyBlobHandles, serializedKey, SerializationContext::Default, dummySharedBuffers); rawKeySerializer.write(key); Vector wrappedKey; if (!wrapCryptoKey(m_lexicalGlobalObject, serializedKey, wrappedKey)) return false; write(wrappedKey); return true; } #endif #if ENABLE(WEB_RTC) if (auto* rtcCertificate = JSRTCCertificate::toWrapped(vm, obj)) { write(RTCCertificateTag); write(rtcCertificate->expires()); write(rtcCertificate->pemCertificate()); write(rtcCertificate->origin().toString()); write(rtcCertificate->pemPrivateKey()); write(static_cast(rtcCertificate->getFingerprints().size())); for (const auto& fingerprint : rtcCertificate->getFingerprints()) { write(fingerprint.algorithm); write(fingerprint.value); } return true; } #endif #if ENABLE(WEBASSEMBLY) if (JSWebAssemblyModule* module = jsDynamicCast(vm, obj)) { if (m_context != SerializationContext::WorkerPostMessage && m_context != SerializationContext::WindowPostMessage) return false; uint32_t index = m_wasmModules.size(); m_wasmModules.append(&module->module()); write(WasmModuleTag); write(index); return true; } if (JSWebAssemblyMemory* memory = jsDynamicCast(vm, obj)) { if (memory->memory().sharingMode() != JSC::Wasm::MemorySharingMode::Shared) { code = SerializationReturnCode::DataCloneError; return true; } if (m_context != SerializationContext::WorkerPostMessage) { code = SerializationReturnCode::DataCloneError; return true; } uint32_t index = m_wasmMemoryHandles.size(); m_wasmMemoryHandles.append(&memory->memory().handle()); write(WasmMemoryTag); write(index); return true; } #endif // if (obj->inherits(vm)) { // dumpDOMPoint(obj); // return true; // } // if (obj->inherits(vm)) { // dumpDOMRect(obj); // return true; // } // if (obj->inherits(vm)) { // dumpDOMMatrix(obj); // return true; // } // if (obj->inherits(vm)) { // dumpDOMQuad(obj); // return true; // } // if (obj->inherits(vm, JSImageBitmap::info())) { // dumpImageBitmap(obj, code); // return true; // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) if (obj->inherits(vm, JSOffscreenCanvas::info())) { dumpOffscreenCanvas(obj, code); return true; } #endif #if ENABLE(WEB_RTC) if (obj->inherits(vm, JSRTCDataChannel::info())) { dumpRTCDataChannel(obj, code); return true; } #endif if (obj->inherits(vm, JSDOMException::info())) { dumpDOMException(obj, code); return true; } return false; } // Any other types are expected to serialize as null. write(NullTag); return true; } void write(SerializationTag tag) { writeLittleEndian(m_buffer, static_cast(tag)); } void write(ArrayBufferViewSubtag tag) { writeLittleEndian(m_buffer, static_cast(tag)); } // void write(DestinationColorSpaceTag tag) // { // writeLittleEndian(m_buffer, static_cast(tag)); // } #if ENABLE(WEB_CRYPTO) void write(CryptoKeyClassSubtag tag) { writeLittleEndian(m_buffer, static_cast(tag)); } void write(CryptoKeyAsymmetricTypeSubtag tag) { writeLittleEndian(m_buffer, static_cast(tag)); } void write(CryptoKeyUsageTag tag) { writeLittleEndian(m_buffer, static_cast(tag)); } void write(CryptoAlgorithmIdentifierTag tag) { writeLittleEndian(m_buffer, static_cast(tag)); } #endif void write(uint8_t c) { writeLittleEndian(m_buffer, c); } void write(uint32_t i) { writeLittleEndian(m_buffer, i); } void write(double d) { union { double d; int64_t i; } u; u.d = d; writeLittleEndian(m_buffer, u.i); } void write(int32_t i) { writeLittleEndian(m_buffer, i); } void write(uint64_t i) { writeLittleEndian(m_buffer, i); } void write(uint16_t ch) { writeLittleEndian(m_buffer, ch); } void writeStringIndex(unsigned i) { writeConstantPoolIndex(m_constantPool, i); } void writeObjectIndex(unsigned i) { writeConstantPoolIndex(m_objectPool, i); } template void writeConstantPoolIndex(const T& constantPool, unsigned i) { ASSERT(i < constantPool.size()); if (constantPool.size() <= 0xFF) write(static_cast(i)); else if (constantPool.size() <= 0xFFFF) write(static_cast(i)); else write(static_cast(i)); } void write(const Identifier& ident) { const String& str = ident.string(); StringConstantPool::AddResult addResult = m_constantPool.add(ident.impl(), m_constantPool.size()); if (!addResult.isNewEntry) { write(StringPoolTag); writeStringIndex(addResult.iterator->value); return; } unsigned length = str.length(); // Guard against overflow if (length > (std::numeric_limits::max() - sizeof(uint32_t)) / sizeof(UChar)) { fail(); return; } if (str.is8Bit()) writeLittleEndian(m_buffer, length | StringDataIs8BitFlag); else writeLittleEndian(m_buffer, length); if (!length) return; if (str.is8Bit()) { if (!writeLittleEndian(m_buffer, str.characters8(), length)) fail(); return; } if (!writeLittleEndian(m_buffer, str.characters16(), length)) fail(); } void write(const String& str) { if (str.isNull()) write(m_emptyIdentifier); else write(Identifier::fromString(m_lexicalGlobalObject->vm(), str)); } void write(const Vector& vector) { uint32_t size = vector.size(); write(size); writeLittleEndian(m_buffer, vector.data(), size); } // void write(const File& file) // { // // m_blobHandles.append(file.handle()); // write(file.path()); // write(file.url().string()); // write(file.type()); // write(file.name()); // write(static_cast(file.lastModifiedOverride().value_or(-1))); // } // void write(PredefinedColorSpace colorSpace) // { // switch (colorSpace) { // case PredefinedColorSpace::SRGB: // writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::SRGB)); // break; // #if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) // case PredefinedColorSpace::DisplayP3: // writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::DisplayP3)); // break; // #endif // } // } #if PLATFORM(COCOA) void write(const RetainPtr& data) { uint32_t dataLength = CFDataGetLength(data.get()); write(dataLength); write(CFDataGetBytePtr(data.get()), dataLength); } #endif // void write(DestinationColorSpace destinationColorSpace) // { // if (destinationColorSpace == DestinationColorSpace::SRGB()) { // write(DestinationColorSpaceSRGBTag); // return; // } // #if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) // if (destinationColorSpace == DestinationColorSpace::LinearSRGB()) { // write(DestinationColorSpaceLinearSRGBTag); // return; // } // #endif // #if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) // if (destinationColorSpace == DestinationColorSpace::DisplayP3()) { // write(DestinationColorSpaceDisplayP3Tag); // return; // } // #endif // #if PLATFORM(COCOA) // auto colorSpace = destinationColorSpace.platformColorSpace(); // if (auto name = CGColorSpaceGetName(colorSpace)) { // auto data = adoptCF(CFStringCreateExternalRepresentation(nullptr, name, kCFStringEncodingUTF8, 0)); // if (!data) { // write(DestinationColorSpaceSRGBTag); // return; // } // write(DestinationColorSpaceCGColorSpaceNameTag); // write(data); // return; // } // if (auto propertyList = adoptCF(CGColorSpaceCopyPropertyList(colorSpace))) { // auto data = adoptCF(CFPropertyListCreateData(nullptr, propertyList.get(), kCFPropertyListBinaryFormat_v1_0, 0, nullptr)); // if (!data) { // write(DestinationColorSpaceSRGBTag); // return; // } // write(DestinationColorSpaceCGColorSpacePropertyListTag); // write(data); // return; // } // #endif // ASSERT_NOT_REACHED(); // write(DestinationColorSpaceSRGBTag); // } #if ENABLE(WEB_CRYPTO) void write(CryptoAlgorithmIdentifier algorithm) { switch (algorithm) { case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: write(CryptoAlgorithmIdentifierTag::RSAES_PKCS1_v1_5); break; case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: write(CryptoAlgorithmIdentifierTag::RSASSA_PKCS1_v1_5); break; case CryptoAlgorithmIdentifier::RSA_PSS: write(CryptoAlgorithmIdentifierTag::RSA_PSS); break; case CryptoAlgorithmIdentifier::RSA_OAEP: write(CryptoAlgorithmIdentifierTag::RSA_OAEP); break; case CryptoAlgorithmIdentifier::ECDSA: write(CryptoAlgorithmIdentifierTag::ECDSA); break; case CryptoAlgorithmIdentifier::ECDH: write(CryptoAlgorithmIdentifierTag::ECDH); break; case CryptoAlgorithmIdentifier::AES_CTR: write(CryptoAlgorithmIdentifierTag::AES_CTR); break; case CryptoAlgorithmIdentifier::AES_CBC: write(CryptoAlgorithmIdentifierTag::AES_CBC); break; case CryptoAlgorithmIdentifier::AES_GCM: write(CryptoAlgorithmIdentifierTag::AES_GCM); break; case CryptoAlgorithmIdentifier::AES_CFB: write(CryptoAlgorithmIdentifierTag::AES_CFB); break; case CryptoAlgorithmIdentifier::AES_KW: write(CryptoAlgorithmIdentifierTag::AES_KW); break; case CryptoAlgorithmIdentifier::HMAC: write(CryptoAlgorithmIdentifierTag::HMAC); break; case CryptoAlgorithmIdentifier::SHA_1: write(CryptoAlgorithmIdentifierTag::SHA_1); break; case CryptoAlgorithmIdentifier::SHA_224: write(CryptoAlgorithmIdentifierTag::SHA_224); break; case CryptoAlgorithmIdentifier::SHA_256: write(CryptoAlgorithmIdentifierTag::SHA_256); break; case CryptoAlgorithmIdentifier::SHA_384: write(CryptoAlgorithmIdentifierTag::SHA_384); break; case CryptoAlgorithmIdentifier::SHA_512: write(CryptoAlgorithmIdentifierTag::SHA_512); break; case CryptoAlgorithmIdentifier::HKDF: write(CryptoAlgorithmIdentifierTag::HKDF); break; case CryptoAlgorithmIdentifier::PBKDF2: write(CryptoAlgorithmIdentifierTag::PBKDF2); break; } } void write(CryptoKeyRSAComponents::Type type) { switch (type) { case CryptoKeyRSAComponents::Type::Public: write(CryptoKeyAsymmetricTypeSubtag::Public); return; case CryptoKeyRSAComponents::Type::Private: write(CryptoKeyAsymmetricTypeSubtag::Private); return; } } void write(const CryptoKeyRSAComponents& key) { write(key.type()); write(key.modulus()); write(key.exponent()); if (key.type() == CryptoKeyRSAComponents::Type::Public) return; write(key.privateExponent()); unsigned primeCount = key.hasAdditionalPrivateKeyParameters() ? key.otherPrimeInfos().size() + 2 : 0; write(primeCount); if (!primeCount) return; write(key.firstPrimeInfo().primeFactor); write(key.firstPrimeInfo().factorCRTExponent); write(key.secondPrimeInfo().primeFactor); write(key.secondPrimeInfo().factorCRTExponent); write(key.secondPrimeInfo().factorCRTCoefficient); for (unsigned i = 2; i < primeCount; ++i) { write(key.otherPrimeInfos()[i].primeFactor); write(key.otherPrimeInfos()[i].factorCRTExponent); write(key.otherPrimeInfos()[i].factorCRTCoefficient); } } void write(const CryptoKey* key) { write(currentKeyFormatVersion); write(key->extractable()); CryptoKeyUsageBitmap usages = key->usagesBitmap(); write(countUsages(usages)); if (usages & CryptoKeyUsageEncrypt) write(CryptoKeyUsageTag::Encrypt); if (usages & CryptoKeyUsageDecrypt) write(CryptoKeyUsageTag::Decrypt); if (usages & CryptoKeyUsageSign) write(CryptoKeyUsageTag::Sign); if (usages & CryptoKeyUsageVerify) write(CryptoKeyUsageTag::Verify); if (usages & CryptoKeyUsageDeriveKey) write(CryptoKeyUsageTag::DeriveKey); if (usages & CryptoKeyUsageDeriveBits) write(CryptoKeyUsageTag::DeriveBits); if (usages & CryptoKeyUsageWrapKey) write(CryptoKeyUsageTag::WrapKey); if (usages & CryptoKeyUsageUnwrapKey) write(CryptoKeyUsageTag::UnwrapKey); switch (key->keyClass()) { case CryptoKeyClass::HMAC: write(CryptoKeyClassSubtag::HMAC); write(downcast(*key).key()); write(downcast(*key).hashAlgorithmIdentifier()); break; case CryptoKeyClass::AES: write(CryptoKeyClassSubtag::AES); write(key->algorithmIdentifier()); write(downcast(*key).key()); break; case CryptoKeyClass::EC: write(CryptoKeyClassSubtag::EC); write(key->algorithmIdentifier()); write(downcast(*key).namedCurveString()); switch (key->type()) { case CryptoKey::Type::Public: { write(CryptoKeyAsymmetricTypeSubtag::Public); auto result = downcast(*key).exportRaw(); ASSERT(!result.hasException()); write(result.releaseReturnValue()); break; } case CryptoKey::Type::Private: { write(CryptoKeyAsymmetricTypeSubtag::Private); // Use the standard complied method is not very efficient, but simple/reliable. auto result = downcast(*key).exportPkcs8(); ASSERT(!result.hasException()); write(result.releaseReturnValue()); break; } default: ASSERT_NOT_REACHED(); } break; case CryptoKeyClass::Raw: write(CryptoKeyClassSubtag::Raw); write(key->algorithmIdentifier()); write(downcast(*key).key()); break; case CryptoKeyClass::RSA: write(CryptoKeyClassSubtag::RSA); write(key->algorithmIdentifier()); CryptoAlgorithmIdentifier hash; bool isRestrictedToHash = downcast(*key).isRestrictedToHash(hash); write(isRestrictedToHash); if (isRestrictedToHash) write(hash); write(*downcast(*key).exportData()); break; } } #endif void write(const uint8_t* data, unsigned length) { m_buffer.append(data, length); } Vector& m_buffer; // Vector& m_blobHandles; ObjectPool m_objectPool; ObjectPool m_transferredMessagePorts; ObjectPool m_transferredArrayBuffers; // ObjectPool m_transferredImageBitmaps; // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) // ObjectPool m_transferredOffscreenCanvases; // #endif // #if ENABLE(WEB_RTC) // ObjectPool m_transferredRTCDataChannels; // #endif typedef HashMap, uint32_t, IdentifierRepHash> StringConstantPool; StringConstantPool m_constantPool; Identifier m_emptyIdentifier; SerializationContext m_context; ArrayBufferContentsArray& m_sharedBuffers; #if ENABLE(WEBASSEMBLY) WasmModuleArray& m_wasmModules; WasmMemoryHandleArray& m_wasmMemoryHandles; #endif }; SerializationReturnCode CloneSerializer::serialize(JSValue in) { VM& vm = m_lexicalGlobalObject->vm(); Vector indexStack; Vector lengthStack; Vector propertyStack; Vector inputObjectStack; Vector mapIteratorStack; Vector setIteratorStack; Vector mapIteratorValueStack; Vector stateStack; WalkerState lexicalGlobalObject = StateUnknown; JSValue inValue = in; auto scope = DECLARE_THROW_SCOPE(vm); while (1) { switch (lexicalGlobalObject) { arrayStartState: case ArrayStartState: { ASSERT(isArray(vm, inValue)); if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSArray* inArray = asArray(inValue); unsigned length = inArray->length(); if (!startArray(inArray)) break; inputObjectStack.append(inArray); indexStack.append(0); lengthStack.append(length); } arrayStartVisitMember: FALLTHROUGH; case ArrayStartVisitMember: { JSObject* array = inputObjectStack.last(); uint32_t index = indexStack.last(); if (index == lengthStack.last()) { indexStack.removeLast(); lengthStack.removeLast(); propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); array->getOwnNonIndexPropertyNames(m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; if (propertyStack.last().size()) { write(NonIndexPropertiesTag); indexStack.append(0); goto objectStartVisitMember; } propertyStack.removeLast(); endObject(); inputObjectStack.removeLast(); break; } inValue = array->getDirectIndex(m_lexicalGlobalObject, index); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; if (!inValue) { indexStack.last()++; goto arrayStartVisitMember; } write(index); auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; if (dumpIfTerminal(inValue, terminalCode)) { if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) return terminalCode; indexStack.last()++; goto arrayStartVisitMember; } stateStack.append(ArrayEndVisitMember); goto stateUnknown; } case ArrayEndVisitMember: { indexStack.last()++; goto arrayStartVisitMember; } objectStartState: case ObjectStartState: { ASSERT(inValue.isObject()); if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSObject* inObject = asObject(inValue); if (!startObject(inObject)) break; // At this point, all supported objects other than Object // objects have been handled. If we reach this point and // the input is not an Object object then we should throw // a DataCloneError. if (inObject->classInfo(vm) != JSFinalObject::info()) return SerializationReturnCode::DataCloneError; inputObjectStack.append(inObject); indexStack.append(0); propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); inObject->methodTable(vm)->getOwnPropertyNames(inObject, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; } objectStartVisitMember: FALLTHROUGH; case ObjectStartVisitMember: { JSObject* object = inputObjectStack.last(); uint32_t index = indexStack.last(); PropertyNameArray& properties = propertyStack.last(); if (index == properties.size()) { endObject(); inputObjectStack.removeLast(); indexStack.removeLast(); propertyStack.removeLast(); break; } inValue = getProperty(vm, object, properties[index]); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; if (!inValue) { // Property was removed during serialisation indexStack.last()++; goto objectStartVisitMember; } write(properties[index]); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; if (!dumpIfTerminal(inValue, terminalCode)) { stateStack.append(ObjectEndVisitMember); goto stateUnknown; } if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) return terminalCode; FALLTHROUGH; } case ObjectEndVisitMember: { if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; indexStack.last()++; goto objectStartVisitMember; } mapStartState : { ASSERT(inValue.isObject()); if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSMap* inMap = jsCast(inValue); if (!startMap(inMap)) break; JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); m_gcBuffer.appendWithCrashOnOverflow(inMap); m_gcBuffer.appendWithCrashOnOverflow(iterator); mapIteratorStack.append(iterator); inputObjectStack.append(inMap); goto mapDataStartVisitEntry; } mapDataStartVisitEntry: case MapDataStartVisitEntry: { JSMapIterator* iterator = mapIteratorStack.last(); JSValue key, value; if (!iterator->nextKeyValue(m_lexicalGlobalObject, key, value)) { mapIteratorStack.removeLast(); JSObject* object = inputObjectStack.last(); ASSERT(jsDynamicCast(vm, object)); propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); object->methodTable(vm)->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; write(NonMapPropertiesTag); indexStack.append(0); goto objectStartVisitMember; } inValue = key; m_gcBuffer.appendWithCrashOnOverflow(value); mapIteratorValueStack.append(value); stateStack.append(MapDataEndVisitKey); goto stateUnknown; } case MapDataEndVisitKey: { inValue = mapIteratorValueStack.last(); mapIteratorValueStack.removeLast(); stateStack.append(MapDataEndVisitValue); goto stateUnknown; } case MapDataEndVisitValue: { goto mapDataStartVisitEntry; } setStartState : { ASSERT(inValue.isObject()); if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSSet* inSet = jsCast(inValue); if (!startSet(inSet)) break; JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); m_gcBuffer.appendWithCrashOnOverflow(inSet); m_gcBuffer.appendWithCrashOnOverflow(iterator); setIteratorStack.append(iterator); inputObjectStack.append(inSet); goto setDataStartVisitEntry; } setDataStartVisitEntry: case SetDataStartVisitEntry: { JSSetIterator* iterator = setIteratorStack.last(); JSValue key; if (!iterator->next(m_lexicalGlobalObject, key)) { setIteratorStack.removeLast(); JSObject* object = inputObjectStack.last(); ASSERT(jsDynamicCast(vm, object)); propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); object->methodTable(vm)->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; write(NonSetPropertiesTag); indexStack.append(0); goto objectStartVisitMember; } inValue = key; stateStack.append(SetDataEndVisitKey); goto stateUnknown; } case SetDataEndVisitKey: { goto setDataStartVisitEntry; } stateUnknown: case StateUnknown: { auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; if (dumpIfTerminal(inValue, terminalCode)) { if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) return terminalCode; break; } if (isArray(vm, inValue)) goto arrayStartState; if (isMap(vm, inValue)) goto mapStartState; if (isSet(vm, inValue)) goto setStartState; goto objectStartState; } } if (stateStack.isEmpty()) break; lexicalGlobalObject = stateStack.last(); stateStack.removeLast(); } if (m_failed) return SerializationReturnCode::UnspecifiedError; return SerializationReturnCode::SuccessfullyCompleted; } class CloneDeserializer : CloneBase { public: static String deserializeString(const Vector& buffer) { if (buffer.isEmpty()) return String(); const uint8_t* ptr = buffer.begin(); const uint8_t* end = buffer.end(); uint32_t version; if (!readLittleEndian(ptr, end, version) || version > CurrentVersion) return String(); uint8_t tag; if (!readLittleEndian(ptr, end, tag) || tag != StringTag) return String(); uint32_t length; if (!readLittleEndian(ptr, end, length)) return String(); bool is8Bit = length & StringDataIs8BitFlag; length &= ~StringDataIs8BitFlag; String str; if (!readString(ptr, end, str, length, is8Bit)) return String(); return str; } static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, /*Vector>&& backingStores*/ #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases #endif #if ENABLE(WEB_RTC) , Vector>&& detachedRTCDataChannels #endif , ArrayBufferContentsArray* arrayBufferContentsArray, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers #if ENABLE(WEBASSEMBLY) , WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles #endif ) { if (!buffer.size()) return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError); CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers, WTFMove(backingStores) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , WTFMove(detachedOffscreenCanvases) #endif #if ENABLE(WEB_RTC) , WTFMove(detachedRTCDataChannels) #endif #if ENABLE(WEBASSEMBLY) , wasmModules, wasmMemoryHandles #endif ); if (!deserializer.isValid()) return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); return deserializer.deserialize(); } private: struct CachedString { CachedString(const String& string) : m_string(string) { } JSValue jsString(JSGlobalObject* lexicalGlobalObject) { if (!m_jsString) m_jsString = JSC::jsString(lexicalGlobalObject->vm(), m_string); return m_jsString; } const String& string() { return m_string; } String takeString() { return WTFMove(m_string); } private: String m_string; JSValue m_jsString; }; struct CachedStringRef { CachedStringRef() : m_base(0) , m_index(0) { } CachedStringRef(Vector* base, size_t index) : m_base(base) , m_index(index) { } CachedString* operator->() { ASSERT(m_base); return &m_base->at(m_index); } private: Vector* m_base; size_t m_index; }; CloneDeserializer( JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, /*Vector>&& backingStores,*/ const Vector& buffer #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases = {} #endif #if ENABLE(WEB_RTC) , Vector>&& detachedRTCDataChannels = {} #endif #if ENABLE(WEBASSEMBLY) , WasmModuleArray* wasmModules = nullptr, WasmMemoryHandleArray* wasmMemoryHandles = nullptr #endif ) : CloneBase(lexicalGlobalObject) , m_globalObject(globalObject) , m_isDOMGlobalObject(globalObject->inherits(globalObject->vm())) , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits(globalObject->vm())) , m_ptr(buffer.data()) , m_end(buffer.data() + buffer.size()) , m_version(0xFFFFFFFF) , m_messagePorts(messagePorts) , m_arrayBufferContents(arrayBufferContents) , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) , m_backingStores(WTFMove(backingStores)) , m_imageBitmaps(m_backingStores.size()) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) #endif #if ENABLE(WEB_RTC) , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) , m_rtcDataChannels(m_detachedRTCDataChannels.size()) #endif #if ENABLE(WEBASSEMBLY) , m_wasmModules(wasmModules) , m_wasmMemoryHandles(wasmMemoryHandles) #endif { if (!read(m_version)) m_version = 0xFFFFFFFF; } CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers, /*Vector>&& backingStores*/ #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases #endif #if ENABLE(WEB_RTC) , Vector>&& detachedRTCDataChannels #endif #if ENABLE(WEBASSEMBLY) , WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles #endif ) : CloneBase(lexicalGlobalObject) , m_globalObject(globalObject) , m_isDOMGlobalObject(globalObject->inherits(globalObject->vm())) , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits(globalObject->vm())) , m_ptr(buffer.data()) , m_end(buffer.data() + buffer.size()) , m_version(0xFFFFFFFF) , m_messagePorts(messagePorts) , m_arrayBufferContents(arrayBufferContents) , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) // , m_blobURLs(blobURLs) // , m_blobFilePaths(blobFilePaths) , m_sharedBuffers(sharedBuffers) // , m_backingStores(WTFMove(backingStores)) // , m_imageBitmaps(m_backingStores.size()) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) #endif #if ENABLE(WEB_RTC) , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) , m_rtcDataChannels(m_detachedRTCDataChannels.size()) #endif #if ENABLE(WEBASSEMBLY) , m_wasmModules(wasmModules) , m_wasmMemoryHandles(wasmMemoryHandles) #endif { if (!read(m_version)) m_version = 0xFFFFFFFF; } DeserializationResult deserialize(); bool isValid() const { return m_version <= CurrentVersion; } template bool readLittleEndian(T& value) { if (m_failed || !readLittleEndian(m_ptr, m_end, value)) { fail(); return false; } return true; } #if ASSUME_LITTLE_ENDIAN template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) { if (ptr > end - sizeof(value)) return false; if (sizeof(T) == 1) value = *ptr++; else { value = *reinterpret_cast(ptr); ptr += sizeof(T); } return true; } #else template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) { if (ptr > end - sizeof(value)) return false; if (sizeof(T) == 1) value = *ptr++; else { value = 0; for (unsigned i = 0; i < sizeof(T); i++) value += ((T)*ptr++) << (i * 8); } return true; } #endif bool read(uint32_t& i) { return readLittleEndian(i); } bool read(int32_t& i) { return readLittleEndian(*reinterpret_cast(&i)); } bool read(uint16_t& i) { return readLittleEndian(i); } bool read(uint8_t& i) { return readLittleEndian(i); } bool read(double& d) { union { double d; uint64_t i64; } u; if (!readLittleEndian(u.i64)) return false; d = u.d; return true; } bool read(uint64_t& i) { return readLittleEndian(i); } bool readStringIndex(uint32_t& i) { return readConstantPoolIndex(m_constantPool, i); } template bool readConstantPoolIndex(const T& constantPool, uint32_t& i) { if (constantPool.size() <= 0xFF) { uint8_t i8; if (!read(i8)) return false; i = i8; return true; } if (constantPool.size() <= 0xFFFF) { uint16_t i16; if (!read(i16)) return false; i = i16; return true; } return read(i); } static bool readString(const uint8_t*& ptr, const uint8_t* end, String& str, unsigned length, bool is8Bit) { if (length >= std::numeric_limits::max() / sizeof(UChar)) return false; if (is8Bit) { if ((end - ptr) < static_cast(length)) return false; str = String { ptr, length }; ptr += length; return true; } unsigned size = length * sizeof(UChar); if ((end - ptr) < static_cast(size)) return false; #if ASSUME_LITTLE_ENDIAN str = String(reinterpret_cast(ptr), length); ptr += length * sizeof(UChar); #else UChar* characters; str = String::createUninitialized(length, characters); for (unsigned i = 0; i < length; ++i) { uint16_t c; readLittleEndian(ptr, end, c); characters[i] = c; } #endif return true; } bool readStringData(CachedStringRef& cachedString) { bool scratch; return readStringData(cachedString, scratch); } bool readStringData(CachedStringRef& cachedString, bool& wasTerminator) { if (m_failed) return false; uint32_t length = 0; if (!read(length)) return false; if (length == TerminatorTag) { wasTerminator = true; return false; } if (length == StringPoolTag) { unsigned index = 0; if (!readStringIndex(index)) { fail(); return false; } if (index >= m_constantPool.size()) { fail(); return false; } cachedString = CachedStringRef(&m_constantPool, index); return true; } bool is8Bit = length & StringDataIs8BitFlag; length &= ~StringDataIs8BitFlag; String str; if (!readString(m_ptr, m_end, str, length, is8Bit)) { fail(); return false; } m_constantPool.append(str); cachedString = CachedStringRef(&m_constantPool, m_constantPool.size() - 1); return true; } SerializationTag readTag() { if (m_ptr >= m_end) return ErrorTag; return static_cast(*m_ptr++); } bool readArrayBufferViewSubtag(ArrayBufferViewSubtag& tag) { if (m_ptr >= m_end) return false; tag = static_cast(*m_ptr++); return true; } void putProperty(JSObject* object, unsigned index, JSValue value) { object->putDirectIndex(m_lexicalGlobalObject, index, value); } void putProperty(JSObject* object, const Identifier& property, JSValue value) { object->putDirectMayBeIndex(m_lexicalGlobalObject, property, value); } // bool readFile(RefPtr& file) // { // CachedStringRef path; // if (!readStringData(path)) // return false; // CachedStringRef url; // if (!readStringData(url)) // return false; // CachedStringRef type; // if (!readStringData(type)) // return false; // CachedStringRef name; // if (!readStringData(name)) // return false; // std::optional optionalLastModified; // if (m_version > 6) { // double lastModified; // if (!read(lastModified)) // return false; // if (lastModified >= 0) // optionalLastModified = lastModified; // } // // If the blob URL for this file has an associated blob file path, prefer that one over the "built-in" path. // String filePath = blobFilePathForBlobURL(url->string()); // if (filePath.isEmpty()) // filePath = path->string(); // if (!m_canCreateDOMObject) // return true; // file = File::deserialize(executionContext(m_lexicalGlobalObject), filePath, URL { url->string() }, type->string(), name->string(), optionalLastModified); // return true; // } template bool readArrayBufferImpl(RefPtr& arrayBuffer) { LengthType length; if (!read(length)) return false; if (m_ptr + length > m_end) return false; arrayBuffer = ArrayBuffer::tryCreate(m_ptr, length); if (!arrayBuffer) return false; m_ptr += length; return true; } bool readArrayBuffer(RefPtr& arrayBuffer) { if (m_version < 10) return readArrayBufferImpl(arrayBuffer); return readArrayBufferImpl(arrayBuffer); } template bool readArrayBufferViewImpl(VM& vm, JSValue& arrayBufferView) { ArrayBufferViewSubtag arrayBufferViewSubtag; if (!readArrayBufferViewSubtag(arrayBufferViewSubtag)) return false; LengthType byteOffset; if (!read(byteOffset)) return false; LengthType byteLength; if (!read(byteLength)) return false; JSObject* arrayBufferObj = asObject(readTerminal()); if (!arrayBufferObj || !arrayBufferObj->inherits(vm)) return false; unsigned elementSize = typedArrayElementSize(arrayBufferViewSubtag); if (!elementSize) return false; LengthType length = byteLength / elementSize; if (length * elementSize != byteLength) return false; RefPtr arrayBuffer = toPossiblySharedArrayBuffer(vm, arrayBufferObj); switch (arrayBufferViewSubtag) { case DataViewTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, DataView::create(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Int8ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Int8Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Uint8ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint8Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Uint8ClampedArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint8ClampedArray::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Int16ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Int16Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Uint16ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint16Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Int32ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Int32Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Uint32ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint32Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Float32ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Float32Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case Float64ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Float64Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case BigInt64ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, BigInt64Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; case BigUint64ArrayTag: arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, BigUint64Array::tryCreate(WTFMove(arrayBuffer), byteOffset, length).get()); return true; default: return false; } } bool readArrayBufferView(VM& vm, JSValue& arrayBufferView) { if (m_version < 10) return readArrayBufferViewImpl(vm, arrayBufferView); return readArrayBufferViewImpl(vm, arrayBufferView); } bool read(Vector& result) { ASSERT(result.isEmpty()); uint32_t size; if (!read(size)) return false; if (m_ptr + size > m_end) return false; result.append(m_ptr, size); m_ptr += size; return true; } // bool read(PredefinedColorSpace& result) // { // uint8_t tag; // if (!read(tag)) // return false; // switch (static_cast(tag)) { // case PredefinedColorSpaceTag::SRGB: // result = PredefinedColorSpace::SRGB; // return true; // #if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) // case PredefinedColorSpaceTag::DisplayP3: // result = PredefinedColorSpace::DisplayP3; // return true; // #endif // default: // return false; // } // } // bool read(DestinationColorSpaceTag& tag) // { // if (m_ptr >= m_end) // return false; // tag = static_cast(*m_ptr++); // return true; // } #if PLATFORM(COCOA) bool read(RetainPtr& data) { uint32_t dataLength; if (!read(dataLength) || static_cast(m_end - m_ptr) < dataLength) return false; data = adoptCF(CFDataCreateWithBytesNoCopy(nullptr, m_ptr, dataLength, kCFAllocatorNull)); if (!data) return false; m_ptr += dataLength; return true; } #endif bool read(DestinationColorSpace& destinationColorSpace) { DestinationColorSpaceTag tag; if (!read(tag)) return false; switch (tag) { case DestinationColorSpaceSRGBTag: destinationColorSpace = DestinationColorSpace::SRGB(); return true; #if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) case DestinationColorSpaceLinearSRGBTag: destinationColorSpace = DestinationColorSpace::LinearSRGB(); return true; #endif #if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) case DestinationColorSpaceDisplayP3Tag: destinationColorSpace = DestinationColorSpace::DisplayP3(); return true; #endif #if PLATFORM(COCOA) case DestinationColorSpaceCGColorSpaceNameTag: { RetainPtr data; if (!read(data)) return false; auto name = adoptCF(CFStringCreateFromExternalRepresentation(nullptr, data.get(), kCFStringEncodingUTF8)); if (!name) return false; auto colorSpace = adoptCF(CGColorSpaceCreateWithName(name.get())); if (!colorSpace) return false; destinationColorSpace = DestinationColorSpace(colorSpace.get()); return true; } case DestinationColorSpaceCGColorSpacePropertyListTag: { RetainPtr data; if (!read(data)) return false; auto propertyList = adoptCF(CFPropertyListCreateWithData(nullptr, data.get(), kCFPropertyListImmutable, nullptr, nullptr)); if (!propertyList) return false; auto colorSpace = adoptCF(CGColorSpaceCreateWithPropertyList(propertyList.get())); if (!colorSpace) return false; destinationColorSpace = DestinationColorSpace(colorSpace.get()); return true; } #endif } ASSERT_NOT_REACHED(); return false; } #if ENABLE(WEB_CRYPTO) bool read(CryptoAlgorithmIdentifier& result) { uint8_t algorithmTag; if (!read(algorithmTag)) return false; if (algorithmTag > cryptoAlgorithmIdentifierTagMaximumValue) return false; switch (static_cast(algorithmTag)) { case CryptoAlgorithmIdentifierTag::RSAES_PKCS1_v1_5: result = CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5; break; case CryptoAlgorithmIdentifierTag::RSASSA_PKCS1_v1_5: result = CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5; break; case CryptoAlgorithmIdentifierTag::RSA_PSS: result = CryptoAlgorithmIdentifier::RSA_PSS; break; case CryptoAlgorithmIdentifierTag::RSA_OAEP: result = CryptoAlgorithmIdentifier::RSA_OAEP; break; case CryptoAlgorithmIdentifierTag::ECDSA: result = CryptoAlgorithmIdentifier::ECDSA; break; case CryptoAlgorithmIdentifierTag::ECDH: result = CryptoAlgorithmIdentifier::ECDH; break; case CryptoAlgorithmIdentifierTag::AES_CTR: result = CryptoAlgorithmIdentifier::AES_CTR; break; case CryptoAlgorithmIdentifierTag::AES_CBC: result = CryptoAlgorithmIdentifier::AES_CBC; break; case CryptoAlgorithmIdentifierTag::AES_GCM: result = CryptoAlgorithmIdentifier::AES_GCM; break; case CryptoAlgorithmIdentifierTag::AES_CFB: result = CryptoAlgorithmIdentifier::AES_CFB; break; case CryptoAlgorithmIdentifierTag::AES_KW: result = CryptoAlgorithmIdentifier::AES_KW; break; case CryptoAlgorithmIdentifierTag::HMAC: result = CryptoAlgorithmIdentifier::HMAC; break; case CryptoAlgorithmIdentifierTag::SHA_1: result = CryptoAlgorithmIdentifier::SHA_1; break; case CryptoAlgorithmIdentifierTag::SHA_224: result = CryptoAlgorithmIdentifier::SHA_224; break; case CryptoAlgorithmIdentifierTag::SHA_256: result = CryptoAlgorithmIdentifier::SHA_256; break; case CryptoAlgorithmIdentifierTag::SHA_384: result = CryptoAlgorithmIdentifier::SHA_384; break; case CryptoAlgorithmIdentifierTag::SHA_512: result = CryptoAlgorithmIdentifier::SHA_512; break; case CryptoAlgorithmIdentifierTag::HKDF: result = CryptoAlgorithmIdentifier::HKDF; break; case CryptoAlgorithmIdentifierTag::PBKDF2: result = CryptoAlgorithmIdentifier::PBKDF2; break; } return true; } bool read(CryptoKeyClassSubtag& result) { uint8_t tag; if (!read(tag)) return false; if (tag > cryptoKeyClassSubtagMaximumValue) return false; result = static_cast(tag); return true; } bool read(CryptoKeyUsageTag& result) { uint8_t tag; if (!read(tag)) return false; if (tag > cryptoKeyUsageTagMaximumValue) return false; result = static_cast(tag); return true; } bool read(CryptoKeyAsymmetricTypeSubtag& result) { uint8_t tag; if (!read(tag)) return false; if (tag > cryptoKeyAsymmetricTypeSubtagMaximumValue) return false; result = static_cast(tag); return true; } bool readHMACKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) { Vector keyData; if (!read(keyData)) return false; CryptoAlgorithmIdentifier hash; if (!read(hash)) return false; result = CryptoKeyHMAC::importRaw(0, hash, WTFMove(keyData), extractable, usages); return true; } bool readAESKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) { CryptoAlgorithmIdentifier algorithm; if (!read(algorithm)) return false; if (!CryptoKeyAES::isValidAESAlgorithm(algorithm)) return false; Vector keyData; if (!read(keyData)) return false; result = CryptoKeyAES::importRaw(algorithm, WTFMove(keyData), extractable, usages); return true; } bool readRSAKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) { CryptoAlgorithmIdentifier algorithm; if (!read(algorithm)) return false; int32_t isRestrictedToHash; CryptoAlgorithmIdentifier hash = CryptoAlgorithmIdentifier::SHA_1; if (!read(isRestrictedToHash)) return false; if (isRestrictedToHash && !read(hash)) return false; CryptoKeyAsymmetricTypeSubtag type; if (!read(type)) return false; Vector modulus; if (!read(modulus)) return false; Vector exponent; if (!read(exponent)) return false; if (type == CryptoKeyAsymmetricTypeSubtag::Public) { auto keyData = CryptoKeyRSAComponents::createPublic(modulus, exponent); auto key = CryptoKeyRSA::create(algorithm, hash, isRestrictedToHash, *keyData, extractable, usages); result = WTFMove(key); return true; } Vector privateExponent; if (!read(privateExponent)) return false; uint32_t primeCount; if (!read(primeCount)) return false; if (!primeCount) { auto keyData = CryptoKeyRSAComponents::createPrivate(modulus, exponent, privateExponent); auto key = CryptoKeyRSA::create(algorithm, hash, isRestrictedToHash, *keyData, extractable, usages); result = WTFMove(key); return true; } if (primeCount < 2) return false; CryptoKeyRSAComponents::PrimeInfo firstPrimeInfo; CryptoKeyRSAComponents::PrimeInfo secondPrimeInfo; Vector otherPrimeInfos(primeCount - 2); if (!read(firstPrimeInfo.primeFactor)) return false; if (!read(firstPrimeInfo.factorCRTExponent)) return false; if (!read(secondPrimeInfo.primeFactor)) return false; if (!read(secondPrimeInfo.factorCRTExponent)) return false; if (!read(secondPrimeInfo.factorCRTCoefficient)) return false; for (unsigned i = 2; i < primeCount; ++i) { if (!read(otherPrimeInfos[i].primeFactor)) return false; if (!read(otherPrimeInfos[i].factorCRTExponent)) return false; if (!read(otherPrimeInfos[i].factorCRTCoefficient)) return false; } auto keyData = CryptoKeyRSAComponents::createPrivateWithAdditionalData(modulus, exponent, privateExponent, firstPrimeInfo, secondPrimeInfo, otherPrimeInfos); auto key = CryptoKeyRSA::create(algorithm, hash, isRestrictedToHash, *keyData, extractable, usages); result = WTFMove(key); return true; } bool readECKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) { CryptoAlgorithmIdentifier algorithm; if (!read(algorithm)) return false; if (!CryptoKeyEC::isValidECAlgorithm(algorithm)) return false; CachedStringRef curve; if (!readStringData(curve)) return false; CryptoKeyAsymmetricTypeSubtag type; if (!read(type)) return false; Vector keyData; if (!read(keyData)) return false; switch (type) { case CryptoKeyAsymmetricTypeSubtag::Public: result = CryptoKeyEC::importRaw(algorithm, curve->string(), WTFMove(keyData), extractable, usages); break; case CryptoKeyAsymmetricTypeSubtag::Private: result = CryptoKeyEC::importPkcs8(algorithm, curve->string(), WTFMove(keyData), extractable, usages); break; } return true; } bool readRawKey(CryptoKeyUsageBitmap usages, RefPtr& result) { CryptoAlgorithmIdentifier algorithm; if (!read(algorithm)) return false; Vector keyData; if (!read(keyData)) return false; result = CryptoKeyRaw::create(algorithm, WTFMove(keyData), usages); return true; } bool readCryptoKey(JSValue& cryptoKey) { uint32_t keyFormatVersion; if (!read(keyFormatVersion) || keyFormatVersion > currentKeyFormatVersion) return false; int32_t extractable; if (!read(extractable)) return false; uint32_t usagesCount; if (!read(usagesCount)) return false; CryptoKeyUsageBitmap usages = 0; for (uint32_t i = 0; i < usagesCount; ++i) { CryptoKeyUsageTag usage; if (!read(usage)) return false; switch (usage) { case CryptoKeyUsageTag::Encrypt: usages |= CryptoKeyUsageEncrypt; break; case CryptoKeyUsageTag::Decrypt: usages |= CryptoKeyUsageDecrypt; break; case CryptoKeyUsageTag::Sign: usages |= CryptoKeyUsageSign; break; case CryptoKeyUsageTag::Verify: usages |= CryptoKeyUsageVerify; break; case CryptoKeyUsageTag::DeriveKey: usages |= CryptoKeyUsageDeriveKey; break; case CryptoKeyUsageTag::DeriveBits: usages |= CryptoKeyUsageDeriveBits; break; case CryptoKeyUsageTag::WrapKey: usages |= CryptoKeyUsageWrapKey; break; case CryptoKeyUsageTag::UnwrapKey: usages |= CryptoKeyUsageUnwrapKey; break; } } CryptoKeyClassSubtag cryptoKeyClass; if (!read(cryptoKeyClass)) return false; RefPtr result; switch (cryptoKeyClass) { case CryptoKeyClassSubtag::HMAC: if (!readHMACKey(extractable, usages, result)) return false; break; case CryptoKeyClassSubtag::AES: if (!readAESKey(extractable, usages, result)) return false; break; case CryptoKeyClassSubtag::RSA: if (!readRSAKey(extractable, usages, result)) return false; break; case CryptoKeyClassSubtag::EC: if (!readECKey(extractable, usages, result)) return false; break; case CryptoKeyClassSubtag::Raw: if (!readRawKey(usages, result)) return false; break; } cryptoKey = getJSValue(result.get()); return true; } #endif template JSValue getJSValue(T&& nativeObj) { return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), std::forward(nativeObj)); } // template // JSValue readDOMPoint() // { // double x; // if (!read(x)) // return {}; // double y; // if (!read(y)) // return {}; // double z; // if (!read(z)) // return {}; // double w; // if (!read(w)) // return {}; // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, z, w)); // } // template // JSValue readDOMMatrix() // { // uint8_t is2D; // if (!read(is2D)) // return {}; // if (is2D) { // double m11; // if (!read(m11)) // return {}; // double m12; // if (!read(m12)) // return {}; // double m21; // if (!read(m21)) // return {}; // double m22; // if (!read(m22)) // return {}; // double m41; // if (!read(m41)) // return {}; // double m42; // if (!read(m42)) // return {}; // TransformationMatrix matrix(m11, m12, m21, m22, m41, m42); // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::Yes)); // } else { // double m11; // if (!read(m11)) // return {}; // double m12; // if (!read(m12)) // return {}; // double m13; // if (!read(m13)) // return {}; // double m14; // if (!read(m14)) // return {}; // double m21; // if (!read(m21)) // return {}; // double m22; // if (!read(m22)) // return {}; // double m23; // if (!read(m23)) // return {}; // double m24; // if (!read(m24)) // return {}; // double m31; // if (!read(m31)) // return {}; // double m32; // if (!read(m32)) // return {}; // double m33; // if (!read(m33)) // return {}; // double m34; // if (!read(m34)) // return {}; // double m41; // if (!read(m41)) // return {}; // double m42; // if (!read(m42)) // return {}; // double m43; // if (!read(m43)) // return {}; // double m44; // if (!read(m44)) // return {}; // TransformationMatrix matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::No)); // } // } // template // JSValue readDOMRect() // { // double x; // if (!read(x)) // return {}; // double y; // if (!read(y)) // return {}; // double width; // if (!read(width)) // return {}; // double height; // if (!read(height)) // return {}; // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, width, height)); // } // std::optional readDOMPointInit() // { // DOMPointInit point; // if (!read(point.x)) // return std::nullopt; // if (!read(point.y)) // return std::nullopt; // if (!read(point.z)) // return std::nullopt; // if (!read(point.w)) // return std::nullopt; // return point; // } // JSValue readDOMQuad() // { // auto p1 = readDOMPointInit(); // if (!p1) // return JSValue(); // auto p2 = readDOMPointInit(); // if (!p2) // return JSValue(); // auto p3 = readDOMPointInit(); // if (!p3) // return JSValue(); // auto p4 = readDOMPointInit(); // if (!p4) // return JSValue(); // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), DOMQuad::create(p1.value(), p2.value(), p3.value(), p4.value())); // } // JSValue readTransferredImageBitmap() // { // uint32_t index; // bool indexSuccessfullyRead = read(index); // if (!indexSuccessfullyRead || index >= m_backingStores.size()) { // fail(); // return JSValue(); // } // if (!m_imageBitmaps[index]) // m_imageBitmaps[index] = ImageBitmap::create(WTFMove(m_backingStores.at(index))); // auto bitmap = m_imageBitmaps[index].get(); // return getJSValue(bitmap); // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) JSValue readOffscreenCanvas() { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_detachedOffscreenCanvases.size()) { fail(); return JSValue(); } if (!m_offscreenCanvases[index]) m_offscreenCanvases[index] = OffscreenCanvas::create(*executionContext(m_lexicalGlobalObject), WTFMove(m_detachedOffscreenCanvases.at(index))); auto offscreenCanvas = m_offscreenCanvases[index].get(); return getJSValue(offscreenCanvas); } #endif #if ENABLE(WEB_RTC) JSValue readRTCCertificate() { double expires; if (!read(expires)) { fail(); return JSValue(); } CachedStringRef certificate; if (!readStringData(certificate)) { fail(); return JSValue(); } CachedStringRef origin; if (!readStringData(origin)) { fail(); return JSValue(); } CachedStringRef keyedMaterial; if (!readStringData(keyedMaterial)) { fail(); return JSValue(); } unsigned size = 0; if (!read(size)) return JSValue(); Vector fingerprints; fingerprints.reserveInitialCapacity(size); for (unsigned i = 0; i < size; i++) { CachedStringRef algorithm; if (!readStringData(algorithm)) return JSValue(); CachedStringRef value; if (!readStringData(value)) return JSValue(); fingerprints.uncheckedAppend(RTCCertificate::DtlsFingerprint { algorithm->string(), value->string() }); } if (!m_canCreateDOMObject) return constructEmptyObject(m_lexicalGlobalObject, m_globalObject->objectPrototype()); auto rtcCertificate = RTCCertificate::create(SecurityOrigin::createFromString(origin->string()), expires, WTFMove(fingerprints), certificate->takeString(), keyedMaterial->takeString()); return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), WTFMove(rtcCertificate)); } JSValue readRTCDataChannel() { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_detachedRTCDataChannels.size()) { fail(); return JSValue(); } if (!m_rtcDataChannels[index]) { auto detachedChannel = WTFMove(m_detachedRTCDataChannels.at(index)); m_rtcDataChannels[index] = RTCDataChannel::create(*executionContext(m_lexicalGlobalObject), detachedChannel->identifier, WTFMove(detachedChannel->label), WTFMove(detachedChannel->options), detachedChannel->state); } return getJSValue(m_rtcDataChannels[index].get()); } #endif JSValue readImageBitmap() { uint8_t serializationState; int32_t logicalWidth; int32_t logicalHeight; double resolutionScale; auto colorSpace = DestinationColorSpace::SRGB(); RefPtr arrayBuffer; if (!read(serializationState) || !read(logicalWidth) || !read(logicalHeight) || !read(resolutionScale) || (m_version > 8 && !read(colorSpace)) || !readArrayBufferImpl(arrayBuffer)) { fail(); return JSValue(); } auto logicalSize = IntSize(logicalWidth, logicalHeight); auto imageDataSize = logicalSize; imageDataSize.scale(resolutionScale); auto buffer = ImageBitmap::createImageBuffer(*executionContext(m_lexicalGlobalObject), logicalSize, RenderingMode::Unaccelerated, colorSpace, resolutionScale); if (!buffer) { fail(); return JSValue(); } PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, colorSpace }; auto pixelBuffer = PixelBuffer::tryCreate(format, imageDataSize, arrayBuffer.releaseNonNull()); if (!pixelBuffer) { fail(); return JSValue(); } buffer->putPixelBuffer(WTFMove(*pixelBuffer), { IntPoint::zero(), logicalSize }); auto bitmap = ImageBitmap::create(ImageBitmapBacking(WTFMove(buffer), OptionSet::fromRaw(serializationState))); return getJSValue(bitmap); } JSValue readDOMException() { CachedStringRef message; if (!readStringData(message)) return JSValue(); CachedStringRef name; if (!readStringData(name)) return JSValue(); auto exception = DOMException::create(message->string(), name->string()); return getJSValue(exception); } JSValue readBigInt() { uint8_t sign = 0; if (!read(sign)) return JSValue(); uint32_t lengthInUint64 = 0; if (!read(lengthInUint64)) return JSValue(); if (!lengthInUint64) { #if USE(BIGINT32) return jsBigInt32(0); #else JSBigInt* bigInt = JSBigInt::tryCreateZero(m_lexicalGlobalObject->vm()); if (UNLIKELY(!bigInt)) { fail(); return JSValue(); } m_gcBuffer.appendWithCrashOnOverflow(bigInt); return bigInt; #endif } #if USE(BIGINT32) static_assert(sizeof(JSBigInt::Digit) == sizeof(uint64_t)); if (lengthInUint64 == 1) { uint64_t digit64 = 0; if (!read(digit64)) return JSValue(); if (sign) { if (digit64 <= static_cast(-static_cast(INT32_MIN))) return jsBigInt32(static_cast(-static_cast(digit64))); } else { if (digit64 <= INT32_MAX) return jsBigInt32(static_cast(digit64)); } ASSERT(digit64 != 0); JSBigInt* bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), 1); if (!bigInt) { fail(); return JSValue(); } bigInt->setDigit(0, digit64); bigInt->setSign(sign); bigInt = bigInt->tryRightTrim(m_lexicalGlobalObject->vm()); if (!bigInt) { fail(); return JSValue(); } m_gcBuffer.appendWithCrashOnOverflow(bigInt); return tryConvertToBigInt32(bigInt); } #endif JSBigInt* bigInt = nullptr; if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) { bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), lengthInUint64); if (!bigInt) { fail(); return JSValue(); } for (uint32_t index = 0; index < lengthInUint64; ++index) { uint64_t digit64 = 0; if (!read(digit64)) return JSValue(); bigInt->setDigit(index, digit64); } } else { ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t)); bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), lengthInUint64 * 2); if (!bigInt) { fail(); return JSValue(); } for (uint32_t index = 0; index < lengthInUint64; ++index) { uint64_t digit64 = 0; if (!read(digit64)) return JSValue(); bigInt->setDigit(index * 2, static_cast(digit64)); bigInt->setDigit(index * 2 + 1, static_cast(digit64 >> 32)); } } bigInt->setSign(sign); bigInt = bigInt->tryRightTrim(m_lexicalGlobalObject->vm()); if (!bigInt) { fail(); return JSValue(); } m_gcBuffer.appendWithCrashOnOverflow(bigInt); return tryConvertToBigInt32(bigInt); } JSValue readTerminal() { SerializationTag tag = readTag(); switch (tag) { case UndefinedTag: return jsUndefined(); case NullTag: return jsNull(); case IntTag: { int32_t i; if (!read(i)) return JSValue(); return jsNumber(i); } case ZeroTag: return jsNumber(0); case OneTag: return jsNumber(1); case FalseTag: return jsBoolean(false); case TrueTag: return jsBoolean(true); case FalseObjectTag: { BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure()); obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(false)); m_gcBuffer.appendWithCrashOnOverflow(obj); return obj; } case TrueObjectTag: { BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure()); obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(true)); m_gcBuffer.appendWithCrashOnOverflow(obj); return obj; } case DoubleTag: { double d; if (!read(d)) return JSValue(); return jsNumber(d); } case BigIntTag: return readBigInt(); case NumberObjectTag: { double d; if (!read(d)) return JSValue(); NumberObject* obj = constructNumber(m_globalObject, jsNumber(d)); m_gcBuffer.appendWithCrashOnOverflow(obj); return obj; } case BigIntObjectTag: { JSValue bigInt = readBigInt(); if (!bigInt) return JSValue(); ASSERT(bigInt.isBigInt()); BigIntObject* obj = BigIntObject::create(m_lexicalGlobalObject->vm(), m_globalObject, bigInt); m_gcBuffer.appendWithCrashOnOverflow(obj); return obj; } case DateTag: { double d; if (!read(d)) return JSValue(); return DateInstance::create(m_lexicalGlobalObject->vm(), m_globalObject->dateStructure(), d); } case FileTag: { RefPtr file; if (!readFile(file)) return JSValue(); if (!m_canCreateDOMObject) return jsNull(); return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), file.get()); } case FileListTag: { unsigned length = 0; if (!read(length)) return JSValue(); ASSERT(m_globalObject->inherits(m_globalObject->vm())); Vector> files; for (unsigned i = 0; i < length; i++) { RefPtr file; if (!readFile(file)) return JSValue(); if (m_canCreateDOMObject) files.append(file.releaseNonNull()); } if (!m_canCreateDOMObject) return jsNull(); return getJSValue(FileList::create(WTFMove(files)).get()); } case ImageDataTag: { uint32_t width; if (!read(width)) return JSValue(); uint32_t height; if (!read(height)) return JSValue(); uint32_t length; if (!read(length)) return JSValue(); if (static_cast(m_end - m_ptr) < length) { fail(); return JSValue(); } auto bufferStart = m_ptr; m_ptr += length; auto resultColorSpace = PredefinedColorSpace::SRGB; if (m_version > 7) { if (!read(resultColorSpace)) return JSValue(); } if (length && (IntSize(width, height).area() * 4) != length) { fail(); return JSValue(); } if (!m_isDOMGlobalObject) return jsNull(); auto result = ImageData::createUninitialized(width, height, resultColorSpace); if (result.hasException()) { fail(); return JSValue(); } if (length) memcpy(result.returnValue()->data().data(), bufferStart, length); else result.returnValue()->data().zeroFill(); return getJSValue(result.releaseReturnValue()); } case BlobTag: { CachedStringRef url; if (!readStringData(url)) return JSValue(); CachedStringRef type; if (!readStringData(type)) return JSValue(); uint64_t size = 0; if (!read(size)) return JSValue(); if (!m_canCreateDOMObject) return jsNull(); return getJSValue(Blob::deserialize(executionContext(m_lexicalGlobalObject), URL { url->string() }, type->string(), size, blobFilePathForBlobURL(url->string())).get()); } case StringTag: { CachedStringRef cachedString; if (!readStringData(cachedString)) return JSValue(); return cachedString->jsString(m_lexicalGlobalObject); } case EmptyStringTag: return jsEmptyString(m_lexicalGlobalObject->vm()); case StringObjectTag: { CachedStringRef cachedString; if (!readStringData(cachedString)) return JSValue(); StringObject* obj = constructString(m_lexicalGlobalObject->vm(), m_globalObject, cachedString->jsString(m_lexicalGlobalObject)); m_gcBuffer.appendWithCrashOnOverflow(obj); return obj; } case EmptyStringObjectTag: { VM& vm = m_lexicalGlobalObject->vm(); StringObject* obj = constructString(vm, m_globalObject, jsEmptyString(vm)); m_gcBuffer.appendWithCrashOnOverflow(obj); return obj; } case RegExpTag: { CachedStringRef pattern; if (!readStringData(pattern)) return JSValue(); CachedStringRef flags; if (!readStringData(flags)) return JSValue(); auto reFlags = Yarr::parseFlags(flags->string()); ASSERT(reFlags.has_value()); VM& vm = m_lexicalGlobalObject->vm(); RegExp* regExp = RegExp::create(vm, pattern->string(), reFlags.value()); return RegExpObject::create(vm, m_globalObject->regExpStructure(), regExp); } case ObjectReferenceTag: { unsigned index = 0; if (!readConstantPoolIndex(m_gcBuffer, index)) { fail(); return JSValue(); } return m_gcBuffer.at(index); } case MessagePortReferenceTag: { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { fail(); return JSValue(); } return getJSValue(m_messagePorts[index].get()); } #if ENABLE(WEBASSEMBLY) case WasmModuleTag: { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || !m_wasmModules || index >= m_wasmModules->size()) { fail(); return JSValue(); } auto scope = DECLARE_THROW_SCOPE(m_lexicalGlobalObject->vm()); JSValue result = JSC::JSWebAssemblyModule::createStub(m_lexicalGlobalObject->vm(), m_lexicalGlobalObject, m_globalObject->webAssemblyModuleStructure(), m_wasmModules->at(index)); // Since we are cloning a JSWebAssemblyModule, it's impossible for that // module to not have been a valid module. Therefore, createStub should // not throw. scope.releaseAssertNoException(); m_gcBuffer.appendWithCrashOnOverflow(result); return result; } case WasmMemoryTag: { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || !m_wasmMemoryHandles || index >= m_wasmMemoryHandles->size()) { fail(); return JSValue(); } RefPtr handle = m_wasmMemoryHandles->at(index); if (!handle) { fail(); return JSValue(); } auto& vm = m_lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSWebAssemblyMemory* result = JSC::JSWebAssemblyMemory::tryCreate(m_lexicalGlobalObject, vm, m_globalObject->webAssemblyMemoryStructure()); // Since we are cloning a JSWebAssemblyMemory, it's impossible for that // module to not have been a valid module. Therefore, createStub should // not throw. scope.releaseAssertNoException(); Ref memory = Wasm::Memory::create( handle.releaseNonNull(), [&vm](Wasm::Memory::NotifyPressure) { vm.heap.collectAsync(CollectionScope::Full); }, [&vm](Wasm::Memory::SyncTryToReclaim) { vm.heap.collectSync(CollectionScope::Full); }, [&vm, result](Wasm::Memory::GrowSuccess, Wasm::PageCount oldPageCount, Wasm::PageCount newPageCount) { result->growSuccessCallback(vm, oldPageCount, newPageCount); }); result->adopt(WTFMove(memory)); m_gcBuffer.appendWithCrashOnOverflow(result); return result; } #endif case ArrayBufferTag: { RefPtr arrayBuffer; if (!readArrayBuffer(arrayBuffer)) { fail(); return JSValue(); } Structure* structure = m_globalObject->arrayBufferStructure(arrayBuffer->sharingMode()); // A crazy RuntimeFlags mismatch could mean that we are not equipped to handle shared // array buffers while the sender is. In that case, we would see a null structure here. if (!structure) { fail(); return JSValue(); } JSValue result = JSArrayBuffer::create(m_lexicalGlobalObject->vm(), structure, WTFMove(arrayBuffer)); m_gcBuffer.appendWithCrashOnOverflow(result); return result; } case ArrayBufferTransferTag: { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_arrayBuffers.size()) { fail(); return JSValue(); } if (!m_arrayBuffers[index]) m_arrayBuffers[index] = ArrayBuffer::create(WTFMove(m_arrayBufferContents->at(index))); return getJSValue(m_arrayBuffers[index].get()); } case SharedArrayBufferTag: { uint32_t index = UINT_MAX; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || !m_sharedBuffers || index >= m_sharedBuffers->size()) { fail(); return JSValue(); } RELEASE_ASSERT(m_sharedBuffers->at(index)); auto buffer = ArrayBuffer::create(WTFMove(m_sharedBuffers->at(index))); JSValue result = getJSValue(buffer.get()); m_gcBuffer.appendWithCrashOnOverflow(result); return result; } case ArrayBufferViewTag: { JSValue arrayBufferView; if (!readArrayBufferView(m_lexicalGlobalObject->vm(), arrayBufferView)) { fail(); return JSValue(); } m_gcBuffer.appendWithCrashOnOverflow(arrayBufferView); return arrayBufferView; } #if ENABLE(WEB_CRYPTO) case CryptoKeyTag: { Vector wrappedKey; if (!read(wrappedKey)) { fail(); return JSValue(); } Vector serializedKey; if (!unwrapCryptoKey(m_lexicalGlobalObject, wrappedKey, serializedKey)) { fail(); return JSValue(); } JSValue cryptoKey; Vector> dummyMessagePorts; CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, {}, serializedKey); if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) { fail(); return JSValue(); } m_gcBuffer.appendWithCrashOnOverflow(cryptoKey); return cryptoKey; } #endif // case DOMPointReadOnlyTag: // return readDOMPoint(); // case DOMPointTag: // return readDOMPoint(); // case DOMRectReadOnlyTag: // return readDOMRect(); // case DOMRectTag: // return readDOMRect(); // case DOMMatrixReadOnlyTag: // return readDOMMatrix(); // case DOMMatrixTag: // return readDOMMatrix(); // case DOMQuadTag: // return readDOMQuad(); // case ImageBitmapTransferTag: // return readTransferredImageBitmap(); #if ENABLE(WEB_RTC) case RTCCertificateTag: return readRTCCertificate(); #endif // case ImageBitmapTag: // return readImageBitmap(); #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) case OffscreenCanvasTransferTag: return readOffscreenCanvas(); #endif #if ENABLE(WEB_RTC) case RTCDataChannelTransferTag: return readRTCDataChannel(); #endif case DOMExceptionTag: return readDOMException(); default: m_ptr--; // Push the tag back return JSValue(); } } template bool consumeCollectionDataTerminationIfPossible() { if (readTag() == Tag) return true; m_ptr--; return false; } JSGlobalObject* m_globalObject; bool m_isDOMGlobalObject; bool m_canCreateDOMObject; const uint8_t* m_ptr; const uint8_t* m_end; unsigned m_version; Vector m_constantPool; const Vector>& m_messagePorts; ArrayBufferContentsArray* m_arrayBufferContents; Vector> m_arrayBuffers; // Vector m_blobURLs; // Vector m_blobFilePaths; ArrayBufferContentsArray* m_sharedBuffers; // Vector> m_backingStores; // Vector> m_imageBitmaps; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> m_detachedOffscreenCanvases; Vector> m_offscreenCanvases; #endif #if ENABLE(WEB_RTC) Vector> m_detachedRTCDataChannels; Vector> m_rtcDataChannels; #endif #if ENABLE(WEBASSEMBLY) WasmModuleArray* m_wasmModules; WasmMemoryHandleArray* m_wasmMemoryHandles; #endif // String blobFilePathForBlobURL(const String& blobURL) // { // size_t i = 0; // for (; i < m_blobURLs.size(); ++i) { // if (m_blobURLs[i] == blobURL) // break; // } // return i < m_blobURLs.size() ? m_blobFilePaths[i] : String(); // } }; DeserializationResult CloneDeserializer::deserialize() { VM& vm = m_lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); Vector indexStack; Vector propertyNameStack; Vector outputObjectStack; Vector mapKeyStack; Vector mapStack; Vector setStack; Vector stateStack; WalkerState lexicalGlobalObject = StateUnknown; JSValue outValue; while (1) { switch (lexicalGlobalObject) { arrayStartState: case ArrayStartState: { uint32_t length; if (!read(length)) { fail(); goto error; } JSArray* outArray = constructEmptyArray(m_globalObject, static_cast(nullptr), length); if (UNLIKELY(scope.exception())) goto error; m_gcBuffer.appendWithCrashOnOverflow(outArray); outputObjectStack.append(outArray); } arrayStartVisitMember: FALLTHROUGH; case ArrayStartVisitMember: { uint32_t index; if (!read(index)) { fail(); goto error; } if (index == TerminatorTag) { JSObject* outArray = outputObjectStack.last(); outValue = outArray; outputObjectStack.removeLast(); break; } else if (index == NonIndexPropertiesTag) { goto objectStartVisitMember; } if (JSValue terminal = readTerminal()) { putProperty(outputObjectStack.last(), index, terminal); goto arrayStartVisitMember; } if (m_failed) goto error; indexStack.append(index); stateStack.append(ArrayEndVisitMember); goto stateUnknown; } case ArrayEndVisitMember: { JSObject* outArray = outputObjectStack.last(); putProperty(outArray, indexStack.last(), outValue); indexStack.removeLast(); goto arrayStartVisitMember; } objectStartState: case ObjectStartState: { if (outputObjectStack.size() > maximumFilterRecursion) return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); JSObject* outObject = constructEmptyObject(m_lexicalGlobalObject, m_globalObject->objectPrototype()); m_gcBuffer.appendWithCrashOnOverflow(outObject); outputObjectStack.append(outObject); } objectStartVisitMember: FALLTHROUGH; case ObjectStartVisitMember: { CachedStringRef cachedString; bool wasTerminator = false; if (!readStringData(cachedString, wasTerminator)) { if (!wasTerminator) goto error; JSObject* outObject = outputObjectStack.last(); outValue = outObject; outputObjectStack.removeLast(); break; } if (JSValue terminal = readTerminal()) { putProperty(outputObjectStack.last(), Identifier::fromString(vm, cachedString->string()), terminal); goto objectStartVisitMember; } stateStack.append(ObjectEndVisitMember); propertyNameStack.append(Identifier::fromString(vm, cachedString->string())); goto stateUnknown; } case ObjectEndVisitMember: { putProperty(outputObjectStack.last(), propertyNameStack.last(), outValue); propertyNameStack.removeLast(); goto objectStartVisitMember; } mapObjectStartState : { if (outputObjectStack.size() > maximumFilterRecursion) return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); JSMap* map = JSMap::create(m_lexicalGlobalObject, m_lexicalGlobalObject->vm(), m_globalObject->mapStructure()); if (UNLIKELY(scope.exception())) goto error; m_gcBuffer.appendWithCrashOnOverflow(map); outputObjectStack.append(map); mapStack.append(map); goto mapDataStartVisitEntry; } mapDataStartVisitEntry: case MapDataStartVisitEntry: { if (consumeCollectionDataTerminationIfPossible()) { mapStack.removeLast(); goto objectStartVisitMember; } stateStack.append(MapDataEndVisitKey); goto stateUnknown; } case MapDataEndVisitKey: { mapKeyStack.append(outValue); stateStack.append(MapDataEndVisitValue); goto stateUnknown; } case MapDataEndVisitValue: { mapStack.last()->set(m_lexicalGlobalObject, mapKeyStack.last(), outValue); mapKeyStack.removeLast(); goto mapDataStartVisitEntry; } setObjectStartState : { if (outputObjectStack.size() > maximumFilterRecursion) return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); JSSet* set = JSSet::create(m_lexicalGlobalObject, m_lexicalGlobalObject->vm(), m_globalObject->setStructure()); if (UNLIKELY(scope.exception())) goto error; m_gcBuffer.appendWithCrashOnOverflow(set); outputObjectStack.append(set); setStack.append(set); goto setDataStartVisitEntry; } setDataStartVisitEntry: case SetDataStartVisitEntry: { if (consumeCollectionDataTerminationIfPossible()) { setStack.removeLast(); goto objectStartVisitMember; } stateStack.append(SetDataEndVisitKey); goto stateUnknown; } case SetDataEndVisitKey: { JSSet* set = setStack.last(); set->add(m_lexicalGlobalObject, outValue); goto setDataStartVisitEntry; } stateUnknown: case StateUnknown: if (JSValue terminal = readTerminal()) { outValue = terminal; break; } SerializationTag tag = readTag(); if (tag == ArrayTag) goto arrayStartState; if (tag == ObjectTag) goto objectStartState; if (tag == MapObjectTag) goto mapObjectStartState; if (tag == SetObjectTag) goto setObjectStartState; goto error; } if (stateStack.isEmpty()) break; lexicalGlobalObject = stateStack.last(); stateStack.removeLast(); } ASSERT(outValue); ASSERT(!m_failed); return std::make_pair(outValue, SerializationReturnCode::SuccessfullyCompleted); error: fail(); return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); } SerializedScriptValue::~SerializedScriptValue() = default; SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::unique_ptr&& arrayBufferContentsArray #if ENABLE(WEB_RTC) , Vector>&& detachedRTCDataChannels #endif ) : m_data(WTFMove(buffer)) , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) #if ENABLE(WEB_RTC) , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) #endif { m_memoryCost = computeMemoryCost(); } SerializedScriptValue::SerializedScriptValue(Vector&& buffer, /* const Vector& blobHandles, */ std::unique_ptr arrayBufferContentsArray, std::unique_ptr sharedBufferContentsArray, Vector>&& backingStores #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases #endif #if ENABLE(WEB_RTC) , Vector>&& detachedRTCDataChannels #endif #if ENABLE(WEBASSEMBLY) , std::unique_ptr wasmModulesArray, std::unique_ptr wasmMemoryHandlesArray #endif ) : m_data(WTFMove(buffer)) , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) , m_sharedBufferContentsArray(WTFMove(sharedBufferContentsArray)) , m_backingStores(WTFMove(backingStores)) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) #endif #if ENABLE(WEB_RTC) , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) #endif #if ENABLE(WEBASSEMBLY) , m_wasmModulesArray(WTFMove(wasmModulesArray)) , m_wasmMemoryHandlesArray(WTFMove(wasmMemoryHandlesArray)) #endif // , m_blobHandles(blobHandles) { m_memoryCost = computeMemoryCost(); } size_t SerializedScriptValue::computeMemoryCost() const { size_t cost = m_data.size(); if (m_arrayBufferContentsArray) { for (auto& content : *m_arrayBufferContentsArray) cost += content.sizeInBytes(); } if (m_sharedBufferContentsArray) { for (auto& content : *m_sharedBufferContentsArray) cost += content.sizeInBytes(); } for (auto& backingStore : m_backingStores) { if (auto buffer = backingStore ? backingStore->buffer() : nullptr) cost += buffer->memoryCost(); } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) for (auto& canvas : m_detachedOffscreenCanvases) { if (canvas) cost += canvas->memoryCost(); } #endif #if ENABLE(WEB_RTC) for (auto& channel : m_detachedRTCDataChannels) { if (channel) cost += channel->memoryCost(); } #endif #if ENABLE(WEBASSEMBLY) // We are not supporting WebAssembly Module memory estimation yet. if (m_wasmMemoryHandlesArray) { for (auto& content : *m_wasmMemoryHandlesArray) cost += content->size(); } #endif // for (auto& handle : m_blobHandles) // cost += handle.url().string().sizeInBytes(); return cost; } static ExceptionOr> transferArrayBuffers(VM& vm, const Vector>& arrayBuffers) { if (arrayBuffers.isEmpty()) return nullptr; auto contents = makeUnique(arrayBuffers.size()); HashSet visited; for (size_t arrayBufferIndex = 0; arrayBufferIndex < arrayBuffers.size(); arrayBufferIndex++) { if (visited.contains(arrayBuffers[arrayBufferIndex].get())) continue; visited.add(arrayBuffers[arrayBufferIndex].get()); bool result = arrayBuffers[arrayBufferIndex]->transferTo(vm, contents->at(arrayBufferIndex)); if (!result) return Exception { TypeError }; } return contents; } static void maybeThrowExceptionIfSerializationFailed(JSGlobalObject& lexicalGlobalObject, SerializationReturnCode code) { auto& vm = lexicalGlobalObject.vm(); auto scope = DECLARE_THROW_SCOPE(vm); switch (code) { case SerializationReturnCode::SuccessfullyCompleted: break; case SerializationReturnCode::StackOverflowError: throwException(&lexicalGlobalObject, scope, createStackOverflowError(&lexicalGlobalObject)); break; case SerializationReturnCode::ValidationError: throwTypeError(&lexicalGlobalObject, scope, "Unable to deserialize data."_s); break; case SerializationReturnCode::DataCloneError: throwDataCloneError(lexicalGlobalObject, scope); break; case SerializationReturnCode::ExistingExceptionError: case SerializationReturnCode::UnspecifiedError: break; case SerializationReturnCode::InterruptedExecutionError: ASSERT_NOT_REACHED(); } } static Exception exceptionForSerializationFailure(SerializationReturnCode code) { ASSERT(code != SerializationReturnCode::SuccessfullyCompleted); switch (code) { case SerializationReturnCode::StackOverflowError: return Exception { StackOverflowError }; case SerializationReturnCode::ValidationError: return Exception { TypeError }; case SerializationReturnCode::DataCloneError: return Exception { DataCloneError }; case SerializationReturnCode::ExistingExceptionError: return Exception { ExistingExceptionError }; case SerializationReturnCode::UnspecifiedError: return Exception { TypeError }; case SerializationReturnCode::SuccessfullyCompleted: case SerializationReturnCode::InterruptedExecutionError: ASSERT_NOT_REACHED(); return Exception { TypeError }; } ASSERT_NOT_REACHED(); return Exception { TypeError }; } static bool containsDuplicates(const Vector>& imageBitmaps) { HashSet visited; for (auto& imageBitmap : imageBitmaps) { if (!visited.add(imageBitmap.get())) return true; } return false; } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) static bool canOffscreenCanvasesDetach(const Vector>& offscreenCanvases) { HashSet visited; for (auto& offscreenCanvas : offscreenCanvases) { if (!offscreenCanvas->canDetach()) return false; // Check the return value of add, we should not encounter duplicates. if (!visited.add(offscreenCanvas.get())) return false; } return true; } #endif #if ENABLE(WEB_RTC) static bool canDetachRTCDataChannels(const Vector>& channels) { HashSet visited; for (auto& channel : channels) { if (!channel->canDetach()) return false; // Check the return value of add, we should not encounter duplicates. if (!visited.add(channel.ptr())) return false; } return true; } #endif RefPtr SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationErrorMode throwExceptions, SerializationContext serializationContext) { Vector> dummyPorts; auto result = create(globalObject, value, {}, dummyPorts, throwExceptions, serializationContext); if (result.hasException()) return nullptr; return result.releaseReturnValue(); } ExceptionOr> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationContext serializationContext) { return create(globalObject, value, WTFMove(transferList), messagePorts, SerializationErrorMode::NonThrowing, serializationContext); } ExceptionOr> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationErrorMode throwExceptions, SerializationContext context) { VM& vm = lexicalGlobalObject.vm(); Vector> arrayBuffers; Vector> imageBitmaps; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> offscreenCanvases; #endif #if ENABLE(WEB_RTC) Vector> dataChannels; #endif HashSet uniqueTransferables; for (auto& transferable : transferList) { if (!uniqueTransferables.add(transferable.get()).isNewEntry) return Exception { DataCloneError, "Duplicate transferable for structured clone"_s }; if (auto arrayBuffer = toPossiblySharedArrayBuffer(vm, transferable.get())) { if (arrayBuffer->isDetached() || arrayBuffer->isShared()) return Exception { DataCloneError }; if (arrayBuffer->isLocked()) { auto scope = DECLARE_THROW_SCOPE(vm); throwVMTypeError(&lexicalGlobalObject, scope, errorMesasgeForTransfer(arrayBuffer)); return Exception { ExistingExceptionError }; } arrayBuffers.append(WTFMove(arrayBuffer)); continue; } if (auto port = JSMessagePort::toWrapped(vm, transferable.get())) { // FIXME: This should check if the port is detached as per https://html.spec.whatwg.org/multipage/infrastructure.html#istransferable. messagePorts.append(WTFMove(port)); continue; } if (auto imageBitmap = JSImageBitmap::toWrapped(vm, transferable.get())) { if (imageBitmap->isDetached()) return Exception { DataCloneError }; imageBitmaps.append(WTFMove(imageBitmap)); continue; } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) if (auto offscreenCanvas = JSOffscreenCanvas::toWrapped(vm, transferable.get())) { offscreenCanvases.append(WTFMove(offscreenCanvas)); continue; } #endif #if ENABLE(WEB_RTC) if (auto channel = JSRTCDataChannel::toWrapped(vm, transferable.get())) { dataChannels.append(*channel); continue; } #endif return Exception { DataCloneError }; } if (containsDuplicates(imageBitmaps)) return Exception { DataCloneError }; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) if (!canOffscreenCanvasesDetach(offscreenCanvases)) return Exception { InvalidStateError }; #endif #if ENABLE(WEB_RTC) if (!canDetachRTCDataChannels(dataChannels)) return Exception { DataCloneError }; #endif Vector buffer; // Vector blobHandles; #if ENABLE(WEBASSEMBLY) WasmModuleArray wasmModules; WasmMemoryHandleArray wasmMemoryHandles; #endif std::unique_ptr sharedBuffers = makeUnique(); auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, messagePorts, arrayBuffers, imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, #endif #if ENABLE(WEB_RTC) dataChannels, #endif #if ENABLE(WEBASSEMBLY) wasmModules, wasmMemoryHandles, #endif /*blobHandles,*/ buffer, context, *sharedBuffers); if (throwExceptions == SerializationErrorMode::Throwing) maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, code); if (code != SerializationReturnCode::SuccessfullyCompleted) return exceptionForSerializationFailure(code); auto arrayBufferContentsArray = transferArrayBuffers(vm, arrayBuffers); if (arrayBufferContentsArray.hasException()) return arrayBufferContentsArray.releaseException(); auto backingStores = ImageBitmap::detachBitmaps(WTFMove(imageBitmaps)); #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> detachedCanvases; for (auto offscreenCanvas : offscreenCanvases) detachedCanvases.append(offscreenCanvas->detach()); #endif #if ENABLE(WEB_RTC) Vector> detachedRTCDataChannels; for (auto& channel : dataChannels) detachedRTCDataChannels.append(channel->detach()); #endif return adoptRef(*new SerializedScriptValue(WTFMove(buffer), /*blobHandles,*/ arrayBufferContentsArray.releaseReturnValue(), context == SerializationContext::WorkerPostMessage ? WTFMove(sharedBuffers) : nullptr, WTFMove(backingStores) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , WTFMove(detachedCanvases) #endif #if ENABLE(WEB_RTC) , WTFMove(detachedRTCDataChannels) #endif #if ENABLE(WEBASSEMBLY) , makeUnique(wasmModules), context == SerializationContext::WorkerPostMessage ? makeUnique(wasmMemoryHandles) : nullptr #endif )); } RefPtr SerializedScriptValue::create(StringView string) { Vector buffer; if (!CloneSerializer::serialize(string, buffer)) return nullptr; return adoptRef(*new SerializedScriptValue(WTFMove(buffer))); } RefPtr SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception) { JSGlobalObject* lexicalGlobalObject = toJS(originContext); VM& vm = lexicalGlobalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); JSValue value = toJS(lexicalGlobalObject, apiValue); auto serializedValue = SerializedScriptValue::create(*lexicalGlobalObject, value); if (UNLIKELY(scope.exception())) { if (exception) *exception = toRef(lexicalGlobalObject, scope.exception()->value()); scope.clearException(); return nullptr; } ASSERT(serializedValue); return serializedValue; } String SerializedScriptValue::toString() const { return CloneDeserializer::deserializeString(m_data); } JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions) { return deserialize(lexicalGlobalObject, globalObject, {}, throwExceptions); } JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, SerializationErrorMode throwExceptions) { Vector dummyBlobs; Vector dummyPaths; return deserialize(lexicalGlobalObject, globalObject, messagePorts, dummyBlobs, dummyPaths, throwExceptions); } JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode throwExceptions) { DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , WTFMove(m_detachedOffscreenCanvases) #endif #if ENABLE(WEB_RTC) , WTFMove(m_detachedRTCDataChannels) #endif , m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() #if ENABLE(WEBASSEMBLY) , m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() #endif ); if (throwExceptions == SerializationErrorMode::Throwing) maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second); return result.first ? result.first : jsNull(); } JSValueRef SerializedScriptValue::deserialize(JSContextRef destinationContext, JSValueRef* exception) { JSGlobalObject* lexicalGlobalObject = toJS(destinationContext); VM& vm = lexicalGlobalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); JSValue value = deserialize(*lexicalGlobalObject, lexicalGlobalObject); if (UNLIKELY(scope.exception())) { if (exception) *exception = toRef(lexicalGlobalObject, scope.exception()->value()); scope.clearException(); return nullptr; } ASSERT(value); return toRef(lexicalGlobalObject, value); } Ref SerializedScriptValue::nullValue() { return adoptRef(*new SerializedScriptValue(Vector())); } uint32_t SerializedScriptValue::wireFormatVersion() { return CurrentVersion; } // Vector SerializedScriptValue::blobURLs() const // { // return m_blobHandles.map([](auto& handle) { // return handle.url().string(); // }); // } // void SerializedScriptValue::writeBlobsToDiskForIndexedDB(CompletionHandler&& completionHandler) // { // ASSERT(isMainThread()); // ASSERT(hasBlobURLs()); // blobRegistry().writeBlobsToTemporaryFilesForIndexedDB(blobURLs(), [completionHandler = WTFMove(completionHandler), this, protectedThis = Ref { *this }](auto&& blobFilePaths) mutable { // ASSERT(isMainThread()); // if (blobFilePaths.isEmpty()) { // // We should have successfully written blobs to temporary files. // // If we failed, then we can't successfully store this record. // completionHandler({}); // return; // } // ASSERT(m_blobHandles.size() == blobFilePaths.size()); // completionHandler({ *this, blobURLs(), blobFilePaths }); // }); // } // IDBValue SerializedScriptValue::writeBlobsToDiskForIndexedDBSynchronously() // { // ASSERT(!isMainThread()); // BinarySemaphore semaphore; // IDBValue value; // callOnMainThread([this, &semaphore, &value] { // writeBlobsToDiskForIndexedDB([&semaphore, &value](IDBValue&& result) { // ASSERT(isMainThread()); // value.setAsIsolatedCopy(result); // semaphore.signal(); // }); // }); // semaphore.wait(); // return value; // } } // namespace WebCore