diff --git a/LayoutTests/js/structuredClone/structured-clone-validation-with-big-int-expected.txt b/LayoutTests/js/structuredClone/structured-clone-validation-with-big-int-expected.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/LayoutTests/js/structuredClone/structured-clone-validation-with-big-int-expected.txt @@ -0,0 +1 @@ + diff --git a/LayoutTests/js/structuredClone/structured-clone-validation-with-big-int.html b/LayoutTests/js/structuredClone/structured-clone-validation-with-big-int.html new file mode 100644 index 0000000000000..edbc3fecc7ff3 --- /dev/null +++ b/LayoutTests/js/structuredClone/structured-clone-validation-with-big-int.html @@ -0,0 +1,42 @@ + + + + + + diff --git a/Source/JavaScriptCore/runtime/OptionsList.h b/Source/JavaScriptCore/runtime/OptionsList.h index 3b22571a99ff3..c0238d61ffcb1 100644 --- a/Source/JavaScriptCore/runtime/OptionsList.h +++ b/Source/JavaScriptCore/runtime/OptionsList.h @@ -237,6 +237,7 @@ bool canUseWebAssemblyFastMemory(); v(Unsigned, defaultB3OptLevel, 2, Normal, nullptr) \ v(Bool, b3AlwaysFailsBeforeCompile, false, Normal, nullptr) \ v(Bool, b3AlwaysFailsBeforeLink, false, Normal, nullptr) \ + v(Bool, validateSerializedValue, false, Normal, nullptr) /* tests CloneSerializer/Deserializer */ \ v(Bool, ftlCrashes, false, Normal, nullptr) /* fool-proof way of checking that you ended up in the FTL. ;-) */\ v(Bool, clobberAllRegsInFTLICSlowPath, ASSERT_ENABLED, Normal, nullptr) \ v(Bool, enableJITDebugAssertions, ASSERT_ENABLED, Normal, nullptr) \ diff --git a/Source/WebCore/bindings/js/SerializedScriptValue.cpp b/Source/WebCore/bindings/js/SerializedScriptValue.cpp index 35d713f86a7f9..33057f6518357 100644 --- a/Source/WebCore/bindings/js/SerializedScriptValue.cpp +++ b/Source/WebCore/bindings/js/SerializedScriptValue.cpp @@ -92,6 +92,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +132,13 @@ namespace WebCore { using namespace JSC; +namespace SerializationHelper { + +static constexpr bool verboseTrace = false; + +} // namespace SerializationHelper + + DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(SerializedScriptValue); static constexpr unsigned maximumFilterRecursion = 40000; @@ -248,6 +257,141 @@ enum ArrayBufferViewSubtag { BigUint64ArrayTag = 11, }; +static const char* name(SerializationTag tag) +{ + switch (tag) { + case ArrayTag: return "ArrayTag"; + case ObjectTag: return "ObjectTag"; + case UndefinedTag: return "UndefinedTag"; + case NullTag: return "NullTag"; + case IntTag: return "IntTag"; + case ZeroTag: return "ZeroTag"; + case OneTag: return "OneTag"; + case FalseTag: return "FalseTag"; + case TrueTag: return "TrueTag"; + case DoubleTag: return "DoubleTag"; + case DateTag: return "DateTag"; + case FileTag: return "FileTag"; + case FileListTag: return "FileListTag"; + case ImageDataTag: return "ImageDataTag"; + case BlobTag: return "BlobTag"; + case StringTag: return "StringTag"; + case EmptyStringTag: return "EmptyStringTag"; + case RegExpTag: return "RegExpTag"; + case ObjectReferenceTag: return "ObjectReferenceTag"; + case MessagePortReferenceTag: return "MessagePortReferenceTag"; + case ArrayBufferTag: return "ArrayBufferTag"; + case ArrayBufferViewTag: return "ArrayBufferViewTag"; + case ArrayBufferTransferTag: return "ArrayBufferTransferTag"; + case TrueObjectTag: return "TrueObjectTag"; + case FalseObjectTag: return "FalseObjectTag"; + case StringObjectTag: return "StringObjectTag"; + case EmptyStringObjectTag: return "EmptyStringObjectTag"; + case NumberObjectTag: return "NumberObjectTag"; + case SetObjectTag: return "SetObjectTag"; + case MapObjectTag: return "MapObjectTag"; + case NonMapPropertiesTag: return "NonMapPropertiesTag"; + case NonSetPropertiesTag: return "NonSetPropertiesTag"; +#if ENABLE(WEB_CRYPTO) + case CryptoKeyTag: return "CryptoKeyTag"; +#endif + case SharedArrayBufferTag: return "SharedArrayBufferTag"; +#if ENABLE(WEBASSEMBLY) + case WasmModuleTag: return "WasmModuleTag"; +#endif + case DOMPointReadOnlyTag: return "DOMPointReadOnlyTag"; + case DOMPointTag: return "DOMPointTag"; + case DOMRectReadOnlyTag: return "DOMRectReadOnlyTag"; + case DOMRectTag: return "DOMRectTag"; + case DOMMatrixReadOnlyTag: return "DOMMatrixReadOnlyTag"; + case DOMMatrixTag: return "DOMMatrixTag"; + case DOMQuadTag: return "DOMQuadTag"; + case ImageBitmapTransferTag: return "ImageBitmapTransferTag"; +#if ENABLE(WEB_RTC) + case RTCCertificateTag: return "RTCCertificateTag"; +#endif + case ImageBitmapTag: return "ImageBitmapTag"; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + case OffscreenCanvasTransferTag: return "OffscreenCanvasTransferTag"; +#endif + case BigIntTag: return "BigIntTag"; + case BigIntObjectTag: return "BigIntObjectTag"; +#if ENABLE(WEBASSEMBLY) + case WasmMemoryTag: return "WasmMemoryTag"; +#endif +#if ENABLE(WEB_RTC) + case RTCDataChannelTransferTag: return "RTCDataChannelTransferTag"; +#endif + case DOMExceptionTag: return "DOMExceptionTag"; +#if ENABLE(WEB_CODECS) + case WebCodecsEncodedVideoChunkTag: return "WebCodecsEncodedVideoChunkTag"; + case WebCodecsVideoFrameTag: return "WebCodecsVideoFrameTag"; +#endif + case ResizableArrayBufferTag: return "ResizableArrayBufferTag"; + case ErrorInstanceTag: return "ErrorInstanceTag"; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + case InMemoryOffscreenCanvasTag: return "InMemoryOffscreenCanvasTag"; +#endif + case InMemoryMessagePortTag: return "InMemoryMessagePortTag"; +#if ENABLE(WEB_CODECS) + case WebCodecsEncodedAudioChunkTag: return "WebCodecsEncodedAudioChunkTag"; + case WebCodecsAudioDataTag: return "WebCodecsAudioDataTag"; +#endif + case ErrorTag: return "ErrorTag"; + } +} + +} // namespace WebCore + +namespace WTF { + +void printInternal(PrintStream&, WebCore::SerializationTag); + +void printInternal(PrintStream& out, WebCore::SerializationTag tag) +{ + const char* tagName = WebCore::name(tag); + if (tagName[0] != '<') + out.print(tagName); + else + out.print("(tag), ">"); +} + +} // namespace WTF + +namespace WebCore { + +// This function is only used for a sanity check mechanism used in +// CloneSerializer::addToObjectPoolIfNotDupe() and CloneDeserializer::addToObjectPool(). +static constexpr bool canBeAddedToObjectPool(SerializationTag tag) +{ + // If you add a type to the allow ist (i.e. returns true) here, it means + // that both the serializer and deserializer will push objects of this + // type onto their m_objectPool. This is important because the order of + // the objects in the m_objectPool must match for both the serializer and + // deserializer. + switch (tag) { + case ArrayTag: + case ArrayBufferTag: + case ArrayBufferViewTag: + case BigIntObjectTag: + case EmptyStringObjectTag: + case FalseObjectTag: + case MapObjectTag: + case NumberObjectTag: + case ObjectTag: + case ResizableArrayBufferTag: + case SetObjectTag: + case SharedArrayBufferTag: + case StringObjectTag: + case TrueObjectTag: + return true; + default: + break; + } + return false; +} + + static bool isTypeExposedToGlobalObject(JSC::JSGlobalObject& globalObject, SerializationTag tag) { #if ENABLE(WEB_AUDIO) @@ -435,6 +579,12 @@ enum DestinationColorSpaceTag { #endif }; +#define SERIALIZE_TRACE(...) do { \ + if constexpr (SerializationHelper::verboseTrace) \ + dataLogLn("TRACE ", __VA_ARGS__, " @ ", __LINE__); \ + } while (false) + + #if ENABLE(WEBASSEMBLY) static String agentClusterIDFromGlobalObject(JSGlobalObject& globalObject) { @@ -751,9 +901,25 @@ class CloneBase { m_failed = true; } +#if ASSERT_ENABLED +public: + const Vector& objectPoolTags() const { return m_objectPoolTags; } + +protected: + void appendObjectPoolTag(SerializationTag tag) + { + m_objectPoolTags.append(tag); + } +#else + ALWAYS_INLINE void appendObjectPoolTag(SerializationTag) { } +#endif + JSGlobalObject* const m_lexicalGlobalObject; bool m_failed; - MarkedArgumentBuffer m_gcBuffer; + MarkedArgumentBuffer m_objectPool; +#if ASSERT_ENABLED + Vector m_objectPoolTags; +#endif }; #if ENABLE(WEB_CRYPTO) @@ -815,7 +981,12 @@ template <> bool writeLittleEndian(Vector& buffer, const uint8 return true; } -class CloneSerializer : CloneBase { +class CloneSerializer; +#if ASSERT_ENABLED +static void validateSerializedResult(CloneSerializer&, SerializationReturnCode, Vector& result, JSGlobalObject*, Vector>&, ArrayBufferContentsArray&, ArrayBufferContentsArray& sharedBuffers, Vector>&); +#endif + +class CloneSerializer : public CloneBase { WTF_FORBID_HEAP_ALLOCATION; public: static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, @@ -840,6 +1011,11 @@ class CloneSerializer : CloneBase { Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) { +#if ASSERT_ENABLED + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); +#endif + CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers, imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, @@ -860,7 +1036,12 @@ class CloneSerializer : CloneBase { wasmMemoryHandles, #endif blobHandles, out, context, sharedBuffers, forStorage); - return serializer.serialize(value); + auto code = serializer.serialize(value); +#if ASSERT_ENABLED + RETURN_IF_EXCEPTION(scope, code); + validateSerializedResult(serializer, code, out, lexicalGlobalObject, messagePorts, sharedBuffers, sharedBuffers, inMemoryMessagePorts); +#endif + return code; } static bool serialize(StringView string, Vector& out) @@ -879,8 +1060,15 @@ class CloneSerializer : CloneBase { return writeLittleEndian(out, string.characters16(), string.length()); } +#if ASSERT_ENABLED + bool didSeeComplexCases() const { return m_didSeeComplexCases; } + void setDidSeeComplexCases() { m_didSeeComplexCases = true; } +#else + ALWAYS_INLINE void setDidSeeComplexCases() { } +#endif + private: - typedef HashMap ObjectPool; + using ObjectPoolMap = HashMap; CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) @@ -937,7 +1125,7 @@ class CloneSerializer : CloneBase { } template - void fillTransferMap(const Vector& input, ObjectPool& result) + void fillTransferMap(const Vector& input, ObjectPoolMap& result) { if (input.isEmpty()) return; @@ -974,71 +1162,54 @@ class CloneSerializer : CloneBase { return object->inherits(); } - bool checkForDuplicate(JSObject* object) + template + bool writeObjectReferenceIfDupe(JSObject* object) { + static_assert(canBeAddedToObjectPool(tag1) + && (canBeAddedToObjectPool(tag2) || tag2 == ErrorTag) + && (canBeAddedToObjectPool(tag3) || tag3 == ErrorTag)); + // Record object for graph reconstruction - ObjectPool::const_iterator found = m_objectPool.find(object); + auto found = m_objectPoolMap.find(object); // Handle duplicate references - if (found != m_objectPool.end()) { + if (found != m_objectPoolMap.end()) { write(ObjectReferenceTag); - ASSERT(found->value < m_objectPool.size()); + ASSERT(found->value < m_objectPoolMap.size()); writeObjectIndex(found->value); - return true; + return true; // is dupe. } - - return false; + return false; // not dupe. } - void recordObject(JSObject* object) + template + bool addToObjectPool(JSObject* object) { - m_objectPool.add(object, m_objectPool.size()); - m_gcBuffer.appendWithCrashOnOverflow(object); - } + static_assert(canBeAddedToObjectPool(tag1) + && (canBeAddedToObjectPool(tag2) || tag2 == ErrorTag) + && (canBeAddedToObjectPool(tag3) || tag3 == ErrorTag)); - bool startObjectInternal(JSObject* object) - { - if (checkForDuplicate(object)) - return false; - recordObject(object); - return true; - } + m_objectPoolMap.add(object, m_objectPoolMap.size()); + m_objectPool.appendWithCrashOnOverflow(object); - bool startObject(JSObject* object) - { - if (!startObjectInternal(object)) - return false; - write(ObjectTag); - return true; - } + if constexpr (tag2 == ErrorTag) + appendObjectPoolTag(tag1); - bool startArray(JSArray* array) - { - if (!startObjectInternal(array)) - return false; - - unsigned length = array->length(); - write(ArrayTag); - write(length); - return true; + return true; // new object added. } - bool startSet(JSSet* set) + template + bool addToObjectPoolIfNotDupe(JSObject* object) { - if (!startObjectInternal(set)) - return false; + static_assert(canBeAddedToObjectPool(tag1) + && (canBeAddedToObjectPool(tag2) || tag2 == ErrorTag) + && (canBeAddedToObjectPool(tag3) || tag3 == ErrorTag)); - write(SetObjectTag); - return true; - } - - bool startMap(JSMap* map) - { - if (!startObjectInternal(map)) - return false; + if (writeObjectReferenceIfDupe(object)) + return false; // new object NOT added. It's a dupe. - write(MapObjectTag); - return true; + addToObjectPool(object); + return true; // new object added. } void endObject() @@ -1111,9 +1282,11 @@ class CloneSerializer : CloneBase { void dumpStringObject(const String& string) { - if (string.isEmpty()) + if (string.isEmpty()) { + appendObjectPoolTag(EmptyStringObjectTag); write(EmptyStringObjectTag); - else { + } else { + appendObjectPoolTag(StringObjectTag); write(StringObjectTag); write(string); } @@ -1216,8 +1389,12 @@ class CloneSerializer : CloneBase { write(BigInt64ArrayTag); else if (obj->inherits()) write(BigUint64ArrayTag); - else - return false; + else { + // We need to return true here because the client only checks for the error condition if + // the return value is true (same as all the error cases below). + code = SerializationReturnCode::DataCloneError; + return true; + } if (UNLIKELY(jsCast(obj)->isOutOfBounds())) { code = SerializationReturnCode::DataCloneError; @@ -1521,31 +1698,33 @@ class CloneSerializer : CloneBase { return true; } if (auto* booleanObject = jsDynamicCast(obj)) { - if (!startObjectInternal(booleanObject)) // handle duplicates + if (!addToObjectPoolIfNotDupe(booleanObject)) return true; - write(booleanObject->internalValue().toBoolean(m_lexicalGlobalObject) ? TrueObjectTag : FalseObjectTag); + auto tag = booleanObject->internalValue().toBoolean(m_lexicalGlobalObject) ? TrueObjectTag : FalseObjectTag; + write(tag); + appendObjectPoolTag(tag); return true; } if (auto* stringObject = jsDynamicCast(obj)) { - if (!startObjectInternal(stringObject)) // handle duplicates + if (!addToObjectPoolIfNotDupe(stringObject)) return true; String str = asString(stringObject->internalValue())->value(m_lexicalGlobalObject); dumpStringObject(str); return true; } if (auto* numberObject = jsDynamicCast(obj)) { - if (!startObjectInternal(numberObject)) // handle duplicates + if (!addToObjectPoolIfNotDupe(numberObject)) return true; write(NumberObjectTag); write(numberObject->internalValue().asNumber()); return true; } if (auto* bigIntObject = jsDynamicCast(obj)) { - if (!startObjectInternal(bigIntObject)) // handle duplicates + if (!addToObjectPoolIfNotDupe(bigIntObject)) return true; + write(BigIntObjectTag); JSValue bigIntValue = bigIntObject->internalValue(); ASSERT(bigIntValue.isBigInt()); - write(BigIntObjectTag); dumpBigIntData(bigIntValue); return true; } @@ -1683,7 +1862,7 @@ class CloneSerializer : CloneBase { write(index->value); return true; } - if (!startObjectInternal(obj)) // handle duplicates + if (!addToObjectPoolIfNotDupe(obj)) return true; if (arrayBuffer->isShared() && m_context == SerializationContext::WorkerPostMessage) { @@ -1695,6 +1874,7 @@ class CloneSerializer : CloneBase { uint32_t index = m_sharedBuffers.size(); ArrayBufferContents contents; if (arrayBuffer->shareWith(contents)) { + appendObjectPoolTag(SharedArrayBufferTag); write(SharedArrayBufferTag); m_sharedBuffers.append(WTFMove(contents)); write(index); @@ -1703,6 +1883,7 @@ class CloneSerializer : CloneBase { } if (arrayBuffer->isResizableOrGrowableShared()) { + appendObjectPoolTag(ResizableArrayBufferTag); write(ResizableArrayBufferTag); uint64_t byteLength = arrayBuffer->byteLength(); write(byteLength); @@ -1712,6 +1893,7 @@ class CloneSerializer : CloneBase { return true; } + appendObjectPoolTag(ArrayBufferTag); write(ArrayBufferTag); uint64_t byteLength = arrayBuffer->byteLength(); write(byteLength); @@ -1719,10 +1901,14 @@ class CloneSerializer : CloneBase { return true; } if (obj->inherits()) { - if (checkForDuplicate(obj)) + // Note: we can't just use addToObjectPoolIfNotDupe() here because the deserializer + // expects to deserialize the children before it deserializes the JSArrayBufferView. + // We need to make the serializer follow the same serialization order here by doing + // this dance with writeObjectReferenceIfDupe() and addToObjectPool(). + if (writeObjectReferenceIfDupe(obj)) return true; bool success = dumpArrayBufferView(obj, code); - recordObject(obj); + addToObjectPool(obj); return success; } #if ENABLE(WEB_CRYPTO) @@ -1894,6 +2080,7 @@ class CloneSerializer : CloneBase { void write(SerializationTag tag) { + SERIALIZE_TRACE("serialize ", tag); writeLittleEndian(m_buffer, static_cast(tag)); } @@ -1986,7 +2173,7 @@ class CloneSerializer : CloneBase { void writeObjectIndex(unsigned i) { - writeConstantPoolIndex(m_objectPool, i); + writeConstantPoolIndex(m_objectPoolMap, i); } template void writeConstantPoolIndex(const T& constantPool, unsigned i) @@ -2357,15 +2544,15 @@ class CloneSerializer : CloneBase { Vector& m_buffer; Vector& m_blobHandles; - ObjectPool m_objectPool; - ObjectPool m_transferredMessagePorts; - ObjectPool m_transferredArrayBuffers; - ObjectPool m_transferredImageBitmaps; + ObjectPoolMap m_objectPoolMap; + ObjectPoolMap m_transferredMessagePorts; + ObjectPoolMap m_transferredArrayBuffers; + ObjectPoolMap m_transferredImageBitmaps; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - ObjectPool m_transferredOffscreenCanvases; + ObjectPoolMap m_transferredOffscreenCanvases; #endif #if ENABLE(WEB_RTC) - ObjectPool m_transferredRTCDataChannels; + ObjectPoolMap m_transferredRTCDataChannels; #endif typedef HashMap, uint32_t, IdentifierRepHash> StringConstantPool; StringConstantPool m_constantPool; @@ -2389,6 +2576,11 @@ class CloneSerializer : CloneBase { Vector>& m_serializedAudioData; #endif SerializationForStorage m_forStorage; + + MarkedArgumentBuffer m_keepAliveBuffer; +#if ASSERT_ENABLED + bool m_didSeeComplexCases { false }; +#endif }; SerializationReturnCode CloneSerializer::serialize(JSValue in) @@ -2415,8 +2607,10 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) JSArray* inArray = asArray(inValue); unsigned length = inArray->length(); - if (!startArray(inArray)) + if (!addToObjectPoolIfNotDupe(inArray)) break; + write(ArrayTag); + write(length); inputObjectStack.append(inArray); indexStack.append(0); lengthStack.append(length); @@ -2475,8 +2669,9 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSObject* inObject = asObject(inValue); - if (!startObject(inObject)) + if (!addToObjectPoolIfNotDupe(inObject)) break; + write(ObjectTag); // 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 @@ -2538,11 +2733,11 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSMap* inMap = jsCast(inValue); - if (!startMap(inMap)) + if (!addToObjectPoolIfNotDupe(inMap)) break; + write(MapObjectTag); JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); - m_gcBuffer.appendWithCrashOnOverflow(inMap); - m_gcBuffer.appendWithCrashOnOverflow(iterator); + m_keepAliveBuffer.appendWithCrashOnOverflow(iterator); mapIteratorStack.append(iterator); inputObjectStack.append(inMap); goto mapDataStartVisitEntry; @@ -2564,7 +2759,7 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) goto objectStartVisitMember; } inValue = key; - m_gcBuffer.appendWithCrashOnOverflow(value); + m_keepAliveBuffer.appendWithCrashOnOverflow(value); mapIteratorValueStack.append(value); stateStack.append(MapDataEndVisitKey); goto stateUnknown; @@ -2584,11 +2779,11 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) if (inputObjectStack.size() > maximumFilterRecursion) return SerializationReturnCode::StackOverflowError; JSSet* inSet = jsCast(inValue); - if (!startSet(inSet)) + if (!addToObjectPoolIfNotDupe(inSet)) break; + write(SetObjectTag); JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); - m_gcBuffer.appendWithCrashOnOverflow(inSet); - m_gcBuffer.appendWithCrashOnOverflow(iterator); + m_keepAliveBuffer.appendWithCrashOnOverflow(iterator); setIteratorStack.append(iterator); inputObjectStack.append(inSet); goto setDataStartVisitEntry; @@ -2647,7 +2842,7 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) return SerializationReturnCode::SuccessfullyCompleted; } -class CloneDeserializer : CloneBase { +class CloneDeserializer : public CloneBase { WTF_FORBID_HEAP_ALLOCATION; public: enum class ShouldAtomize : bool { No, Yes }; @@ -2889,9 +3084,18 @@ class CloneDeserializer : CloneBase { bool isValid() const { return m_version <= CurrentVersion; } + template + inline void addToObjectPool(JSValue object) + { + static_assert(canBeAddedToObjectPool(tag)); + m_objectPool.appendWithCrashOnOverflow(object); + appendObjectPoolTag(tag); + } + template bool readLittleEndian(T& value) { if (m_failed || !readLittleEndian(m_ptr, m_end, value)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return false; } @@ -3086,6 +3290,7 @@ class CloneDeserializer : CloneBase { if (length == StringPoolTag) { auto index = readStringIndex(); if (!index || *index >= m_constantPool.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return false; } @@ -3096,6 +3301,7 @@ class CloneDeserializer : CloneBase { length &= ~StringDataIs8BitFlag; String str; if (!readString(m_ptr, m_end, str, length, is8Bit, shouldAtomize)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return false; } @@ -3106,9 +3312,13 @@ class CloneDeserializer : CloneBase { SerializationTag readTag() { - if (m_ptr >= m_end) + if (m_ptr >= m_end) { + SERIALIZE_TRACE("FAIL deserialize"); return ErrorTag; - return static_cast(*m_ptr++); + } + auto tag = static_cast(*m_ptr++); + SERIALIZE_TRACE("deserialize ", tag); + return tag; } bool readArrayBufferViewSubtag(ArrayBufferViewSubtag& tag) @@ -3956,6 +4166,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_backingStores.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -3975,6 +4186,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_detachedOffscreenCanvases.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -3991,6 +4203,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_inMemoryOffscreenCanvases.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4003,21 +4216,25 @@ class CloneDeserializer : CloneBase { { double expires; if (!read(expires)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } CachedStringRef certificate; if (!readStringData(certificate)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } CachedStringRef origin; if (!readStringData(origin)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } CachedStringRef keyedMaterial; if (!readStringData(keyedMaterial)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4049,6 +4266,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_detachedRTCDataChannels.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4068,6 +4286,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_serializedVideoChunks.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4082,6 +4301,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_serializedVideoFrames.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4096,6 +4316,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_serializedAudioChunks.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4110,6 +4331,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_serializedAudioData.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4131,6 +4353,7 @@ class CloneDeserializer : CloneBase { RefPtr arrayBuffer; if (!read(serializationState) || !read(logicalWidth) || !read(logicalHeight) || !read(resolutionScale) || (m_version > 8 && !read(colorSpace)) || !readArrayBufferImpl(arrayBuffer)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4141,6 +4364,7 @@ class CloneDeserializer : CloneBase { auto buffer = ImageBitmap::createImageBuffer(*executionContext(m_lexicalGlobalObject), logicalSize, RenderingMode::Unaccelerated, colorSpace, resolutionScale); if (!buffer) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4148,6 +4372,7 @@ class CloneDeserializer : CloneBase { PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, colorSpace }; auto pixelBuffer = ByteArrayPixelBuffer::tryCreate(format, imageDataSize, arrayBuffer.releaseNonNull()); if (!pixelBuffer) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4185,10 +4410,10 @@ class CloneDeserializer : CloneBase { #else JSBigInt* bigInt = JSBigInt::tryCreateZero(m_lexicalGlobalObject->vm()); if (UNLIKELY(!bigInt)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } - m_gcBuffer.appendWithCrashOnOverflow(bigInt); return bigInt; #endif } @@ -4209,6 +4434,7 @@ class CloneDeserializer : CloneBase { ASSERT(digit64 != 0); JSBigInt* bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), 1); if (!bigInt) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4216,10 +4442,10 @@ class CloneDeserializer : CloneBase { bigInt->setSign(sign); bigInt = bigInt->tryRightTrim(m_lexicalGlobalObject->vm()); if (!bigInt) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } - m_gcBuffer.appendWithCrashOnOverflow(bigInt); return tryConvertToBigInt32(bigInt); } #endif @@ -4227,6 +4453,7 @@ class CloneDeserializer : CloneBase { if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) { bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), numberOfUint64Elements); if (!bigInt) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4240,11 +4467,13 @@ class CloneDeserializer : CloneBase { ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t)); auto actualBigIntLength = WTF::checkedProduct(numberOfUint64Elements, 2); if (actualBigIntLength.hasOverflowed()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), actualBigIntLength.value()); if (!bigInt) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4259,10 +4488,10 @@ class CloneDeserializer : CloneBase { bigInt->setSign(sign); bigInt = bigInt->tryRightTrim(m_lexicalGlobalObject->vm()); if (!bigInt) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } - m_gcBuffer.appendWithCrashOnOverflow(bigInt); return tryConvertToBigInt32(bigInt); } @@ -4270,6 +4499,7 @@ class CloneDeserializer : CloneBase { { SerializationTag tag = readTag(); if (!isTypeExposedToGlobalObject(*m_globalObject, tag)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4295,13 +4525,13 @@ class CloneDeserializer : CloneBase { case FalseObjectTag: { BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure()); obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(false)); - m_gcBuffer.appendWithCrashOnOverflow(obj); + addToObjectPool(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); + addToObjectPool(obj); return obj; } case DoubleTag: { @@ -4317,7 +4547,7 @@ class CloneDeserializer : CloneBase { if (!read(d)) return JSValue(); NumberObject* obj = constructNumber(m_globalObject, jsNumber(d)); - m_gcBuffer.appendWithCrashOnOverflow(obj); + addToObjectPool(obj); return obj; } case BigIntObjectTag: { @@ -4326,7 +4556,7 @@ class CloneDeserializer : CloneBase { return JSValue(); ASSERT(bigInt.isBigInt()); BigIntObject* obj = BigIntObject::create(m_lexicalGlobalObject->vm(), m_globalObject, bigInt); - m_gcBuffer.appendWithCrashOnOverflow(obj); + addToObjectPool(obj); return obj; } case DateTag: { @@ -4367,6 +4597,7 @@ class CloneDeserializer : CloneBase { if (width == ImageDataPoolTag) { auto index = readImageDataIndex(); if (!index || *index >= m_imageDataPool.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4379,6 +4610,7 @@ class CloneDeserializer : CloneBase { if (!read(length)) return JSValue(); if (static_cast(m_end - m_ptr) < length) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4392,6 +4624,7 @@ class CloneDeserializer : CloneBase { } if (length && (IntSize(width, height).area() * 4) != length) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4401,6 +4634,7 @@ class CloneDeserializer : CloneBase { auto result = ImageData::createUninitialized(width, height, resultColorSpace); if (result.hasException()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4441,13 +4675,13 @@ class CloneDeserializer : CloneBase { if (!readStringData(cachedString)) return JSValue(); StringObject* obj = constructString(m_lexicalGlobalObject->vm(), m_globalObject, cachedString->jsString(m_lexicalGlobalObject)); - m_gcBuffer.appendWithCrashOnOverflow(obj); + addToObjectPool(obj); return obj; } case EmptyStringObjectTag: { VM& vm = m_lexicalGlobalObject->vm(); StringObject* obj = constructString(vm, m_globalObject, jsEmptyString(vm)); - m_gcBuffer.appendWithCrashOnOverflow(obj); + addToObjectPool(obj); return obj; } case RegExpTag: { @@ -4466,48 +4700,56 @@ class CloneDeserializer : CloneBase { case ErrorInstanceTag: { SerializableErrorType serializedErrorType; if (!read(serializedErrorType)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } String message; if (!readNullableString(message)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } uint32_t line; if (!read(line)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } uint32_t column; if (!read(column)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } String sourceURL; if (!readNullableString(sourceURL)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } String stackString; if (!readNullableString(stackString)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } return ErrorInstance::create(m_lexicalGlobalObject, WTFMove(message), toErrorType(serializedErrorType), line, column, WTFMove(sourceURL), WTFMove(stackString)); } case ObjectReferenceTag: { - auto index = readConstantPoolIndex(m_gcBuffer); + auto index = readConstantPoolIndex(m_objectPool); if (!index) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } - return m_gcBuffer.at(*index); + return m_objectPool.at(*index); } case MessagePortReferenceTag: { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4517,6 +4759,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_inMemoryMessagePorts.size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4529,6 +4772,7 @@ class CloneDeserializer : CloneBase { CachedStringRef agentClusterID; bool agentClusterIDSuccessfullyRead = readStringData(agentClusterID); if (!agentClusterIDSuccessfullyRead || agentClusterID->string() != agentClusterIDFromGlobalObject(*m_globalObject)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4536,6 +4780,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || !m_wasmModules || index >= m_wasmModules->size()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4545,7 +4790,6 @@ class CloneDeserializer : CloneBase { // module to not have been a valid module. Therefore, createStub should // not throw. scope.releaseAssertNoException(); - m_gcBuffer.appendWithCrashOnOverflow(result); return result; } case WasmMemoryTag: { @@ -4553,6 +4797,7 @@ class CloneDeserializer : CloneBase { CachedStringRef agentClusterID; bool agentClusterIDSuccessfullyRead = readStringData(agentClusterID); if (!agentClusterIDSuccessfullyRead || agentClusterID->string() != agentClusterIDFromGlobalObject(*m_globalObject)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4560,6 +4805,7 @@ class CloneDeserializer : CloneBase { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || !m_wasmMemoryHandles || index >= m_wasmMemoryHandles->size() || !JSC::Options::useSharedArrayBuffer()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4576,6 +4822,7 @@ class CloneDeserializer : CloneBase { auto handler = [&vm, result] (Wasm::Memory::GrowSuccess, PageCount oldPageCount, PageCount newPageCount) { result->growSuccessCallback(vm, oldPageCount, newPageCount); }; if (RefPtr contents = m_wasmMemoryHandles->at(index)) { if (!contents->memoryHandle()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4586,13 +4833,13 @@ class CloneDeserializer : CloneBase { } result->adopt(memory.releaseNonNull()); - m_gcBuffer.appendWithCrashOnOverflow(result); return result; } #endif case ArrayBufferTag: { RefPtr arrayBuffer; if (!readArrayBuffer(arrayBuffer)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4600,16 +4847,18 @@ class CloneDeserializer : CloneBase { // 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 (UNLIKELY(!structure)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } JSValue result = JSArrayBuffer::create(m_lexicalGlobalObject->vm(), structure, WTFMove(arrayBuffer)); - m_gcBuffer.appendWithCrashOnOverflow(result); + addToObjectPool(result); return result; } case ResizableArrayBufferTag: { RefPtr arrayBuffer; if (!readResizableNonSharedArrayBuffer(arrayBuffer)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4617,17 +4866,19 @@ class CloneDeserializer : CloneBase { // 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 (UNLIKELY(!structure)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } JSValue result = JSArrayBuffer::create(m_lexicalGlobalObject->vm(), structure, WTFMove(arrayBuffer)); - m_gcBuffer.appendWithCrashOnOverflow(result); + addToObjectPool(result); return result; } case ArrayBufferTransferTag: { uint32_t index; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || index >= m_arrayBuffers.size()) { + SERIALIZE_TRACE("FAIL deserialize ArrayBufferTransferTag: indexSuccessfullyRead ", indexSuccessfullyRead, " index ", index, " m_arrayBuffers.size() ", m_arrayBuffers.size()); fail(); return JSValue(); } @@ -4642,6 +4893,7 @@ class CloneDeserializer : CloneBase { uint32_t index = UINT_MAX; bool indexSuccessfullyRead = read(index); if (!indexSuccessfullyRead || !m_sharedBuffers || index >= m_sharedBuffers->size() || !JSC::Options::useSharedArrayBuffer()) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4651,27 +4903,30 @@ class CloneDeserializer : CloneBase { m_sharedBuffers->at(index).shareWith(arrayBufferContents); auto buffer = ArrayBuffer::create(WTFMove(arrayBufferContents)); JSValue result = getJSValue(buffer.get()); - m_gcBuffer.appendWithCrashOnOverflow(result); + addToObjectPool(result); return result; } case ArrayBufferViewTag: { JSValue arrayBufferView; if (!readArrayBufferView(m_lexicalGlobalObject->vm(), arrayBufferView)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } - m_gcBuffer.appendWithCrashOnOverflow(arrayBufferView); + addToObjectPool(arrayBufferView); return arrayBufferView; } #if ENABLE(WEB_CRYPTO) case CryptoKeyTag: { Vector wrappedKey; if (!read(wrappedKey)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } Vector serializedKey; if (!unwrapCryptoKey(m_lexicalGlobalObject, wrappedKey, serializedKey)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } @@ -4679,10 +4934,10 @@ class CloneDeserializer : CloneBase { Vector> dummyMessagePorts; CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, { }, serializedKey); if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); return JSValue(); } - m_gcBuffer.appendWithCrashOnOverflow(cryptoKey); return cryptoKey; } #endif @@ -4733,6 +4988,7 @@ class CloneDeserializer : CloneBase { return readDOMException(); default: + SERIALIZE_TRACE("push back ", tag); m_ptr--; // Push the tag back return JSValue(); } @@ -4798,6 +5054,10 @@ class CloneDeserializer : CloneBase { return i < m_blobURLs.size() ? m_blobFilePaths[i] : String(); } + +#if ASSERT_ENABLED + friend void validateSerializedResult(CloneSerializer&, SerializationReturnCode, Vector&, JSGlobalObject*, Vector>&, ArrayBufferContentsArray&, ArrayBufferContentsArray&, Vector>&); +#endif }; DeserializationResult CloneDeserializer::deserialize() @@ -4821,13 +5081,16 @@ DeserializationResult CloneDeserializer::deserialize() case ArrayStartState: { uint32_t length; if (!read(length)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); goto error; } JSArray* outArray = constructEmptyArray(m_globalObject, static_cast(nullptr), length); - if (UNLIKELY(scope.exception())) + if (UNLIKELY(scope.exception())) { + SERIALIZE_TRACE("FAIL deserialize"); goto error; - m_gcBuffer.appendWithCrashOnOverflow(outArray); + } + addToObjectPool(outArray); outputObjectStack.append(outArray); } arrayStartVisitMember: @@ -4835,6 +5098,7 @@ DeserializationResult CloneDeserializer::deserialize() case ArrayStartVisitMember: { uint32_t index; if (!read(index)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); goto error; } @@ -4843,6 +5107,7 @@ DeserializationResult CloneDeserializer::deserialize() if (index == TerminatorTag) { // We reached the end of the indexed properties section. if (!read(index)) { + SERIALIZE_TRACE("FAIL deserialize"); fail(); goto error; } @@ -4888,7 +5153,7 @@ DeserializationResult CloneDeserializer::deserialize() if (outputObjectStack.size() > maximumFilterRecursion) return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); JSObject* outObject = constructEmptyObject(m_lexicalGlobalObject, m_globalObject->objectPrototype()); - m_gcBuffer.appendWithCrashOnOverflow(outObject); + addToObjectPool(outObject); outputObjectStack.append(outObject); } objectStartVisitMember: @@ -4897,8 +5162,10 @@ DeserializationResult CloneDeserializer::deserialize() CachedStringRef cachedString; bool wasTerminator = false; if (!readStringData(cachedString, wasTerminator, ShouldAtomize::Yes)) { - if (!wasTerminator) + if (!wasTerminator) { + SERIALIZE_TRACE("FAIL deserialize"); goto error; + } JSObject* outObject = outputObjectStack.last(); outValue = outObject; @@ -4919,11 +5186,13 @@ DeserializationResult CloneDeserializer::deserialize() propertyNameStack.removeLast(); goto objectStartVisitMember; } - mapObjectStartState: { - if (outputObjectStack.size() > maximumFilterRecursion) + mapStartState: { + if (outputObjectStack.size() > maximumFilterRecursion) { + SERIALIZE_TRACE("FAIL deserialize"); return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); + } JSMap* map = JSMap::create(m_lexicalGlobalObject->vm(), m_globalObject->mapStructure()); - m_gcBuffer.appendWithCrashOnOverflow(map); + addToObjectPool(map); outputObjectStack.append(map); mapStack.append(map); goto mapDataStartVisitEntry; @@ -4948,11 +5217,13 @@ DeserializationResult CloneDeserializer::deserialize() goto mapDataStartVisitEntry; } - setObjectStartState: { - if (outputObjectStack.size() > maximumFilterRecursion) + setStartState: { + if (outputObjectStack.size() > maximumFilterRecursion) { + SERIALIZE_TRACE("FAIL deserialize"); return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); + } JSSet* set = JSSet::create(m_lexicalGlobalObject->vm(), m_globalObject->setStructure()); - m_gcBuffer.appendWithCrashOnOverflow(set); + addToObjectPool(set); outputObjectStack.append(set); setStack.append(set); goto setDataStartVisitEntry; @@ -4984,9 +5255,9 @@ DeserializationResult CloneDeserializer::deserialize() if (tag == ObjectTag) goto objectStartState; if (tag == MapObjectTag) - goto mapObjectStartState; + goto mapStartState; if (tag == SetObjectTag) - goto setObjectStartState; + goto setStartState; goto error; } if (stateStack.isEmpty()) @@ -5003,6 +5274,116 @@ DeserializationResult CloneDeserializer::deserialize() return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); } +#if ASSERT_ENABLED +void validateSerializedResult(CloneSerializer& serializer, SerializationReturnCode code, Vector& result, JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, ArrayBufferContentsArray& arrayBufferContentsArray, ArrayBufferContentsArray& sharedBuffers, Vector>& inMemoryMessagePorts) +{ + if (!JSC::Options::validateSerializedValue()) + return; + if (serializer.didSeeComplexCases()) + return; + if (code != SerializationReturnCode::SuccessfullyCompleted) + return; + + SERIALIZE_TRACE("validation start"); + + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + JSGlobalObject* globalObject = lexicalGlobalObject; + Vector blobURLs; + Vector blobFilePaths; + Vector> backingStores; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + Vector> detachedOffscreenCanvases; + Vector> inMemoryOffscreenCanvases; +#endif +#if ENABLE(WEB_RTC) + Vector> detachedRTCDataChannels; +#endif +#if ENABLE(WEBASSEMBLY) + WasmModuleArray* wasmModules = nullptr; + WasmMemoryHandleArray* wasmMemoryHandles = nullptr; +#endif +#if ENABLE(WEB_CODECS) + Vector> serializedVideoChunks; + Vector serializedVideoFrames; + Vector> serializedAudioChunks; + Vector serializedAudioData; +#endif + + CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, &arrayBufferContentsArray, result, blobURLs, blobFilePaths, &sharedBuffers, WTFMove(backingStores) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , WTFMove(detachedOffscreenCanvases) + , inMemoryOffscreenCanvases +#endif + , inMemoryMessagePorts +#if ENABLE(WEB_RTC) + , WTFMove(detachedRTCDataChannels) +#endif +#if ENABLE(WEBASSEMBLY) + , wasmModules + , wasmMemoryHandles +#endif +#if ENABLE(WEB_CODECS) + , WTFMove(serializedVideoChunks) + , WTFMove(serializedVideoFrames) + , WTFMove(serializedAudioChunks) + , WTFMove(serializedAudioData) +#endif + ); + RELEASE_ASSERT(deserializer.isValid()); + auto deserializationResult = deserializer.deserialize(); + if (scope.exception()) { + scope.clearException(); + SERIALIZE_TRACE("validation abort due to exception"); + return; + } + + if (deserializationResult.second != SerializationReturnCode::SuccessfullyCompleted) { + SERIALIZE_TRACE("validation abort due to deserialization failure"); + return; + } + + unsigned numChecks = 0; + unsigned numMismatches = 0; +#define VALIDATE_EQ(a, b, ...) do { \ + if ((a) != (b)) \ + numMismatches++; \ + } while (false) + + auto& serializerTags = serializer.objectPoolTags(); + auto& deserializerTags = deserializer.objectPoolTags(); + VALIDATE_EQ(serializerTags.size(), deserializerTags.size(), ""); + size_t commonMinSize = std::min(serializerTags.size(), deserializerTags.size()); + size_t maxSize = std::max(serializerTags.size(), deserializerTags.size()); + for (size_t i = 0; i < commonMinSize; ++i) + VALIDATE_EQ(serializerTags[i], deserializerTags[i], " at i ", i); + numChecks = 1 + maxSize; + if (commonMinSize != maxSize) + numMismatches += maxSize - commonMinSize; + + if (numMismatches) { + dataLogLn("\n\nERROR: serialization / deserialization mismatch:"); + dataLogLn("\n"); + dataLogLn(" # of serializerTags = ", serializerTags.size()); + dataLogLn(" # of deserializerTags = ", deserializerTags.size()); + dataLogLn(" FOUND ", numMismatches, " mismatches out of ", numChecks, " checks"); + + for (size_t i = 0; i < commonMinSize; ++i) + dataLogLn(" serializerTags[", i, "] (", serializerTags[i], ") deserializerTags[", i, "] (", deserializerTags[i], ")", serializerTags[i] == deserializerTags[i] ? "" : " DIFFERENT"); + for (size_t i = commonMinSize; i < serializerTags.size(); ++i) + dataLogLn(" serializerTags[", i, "] (", serializerTags[i], ") DIFFERENT"); + for (size_t i = commonMinSize; i < deserializerTags.size(); ++i) + dataLogLn(" deserializerTags[", i, "] (", deserializerTags[i], ") DIFFERENT"); + dataLogLn("\n\n"); + } +#undef VALIDATE_EQ + RELEASE_ASSERT(!numMismatches); + + SERIALIZE_TRACE("validation done"); +} +#endif // ASSERT_ENABLED + SerializedScriptValue::~SerializedScriptValue() = default; SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::unique_ptr&& arrayBufferContentsArray