From 1039f0c3235ffd9a6584657adb34db10c562e4af Mon Sep 17 00:00:00 2001 From: Mark Lam Date: Fri, 31 Mar 2023 10:49:49 -0700 Subject: [PATCH] Cherry-pick 2c49ff7b0481. rdar://problem/107369977 CloneDeserializer::deserialize() should store cell pointers in a MarkedVector. https://bugs.webkit.org/show_bug.cgi?id=254797 rdar://107369977 Reviewed by Justin Michaud. Previously, CloneDeserializer::deserialize() was storing pointers to newly created objects in a few Vectors. This is problematic because the GC is not aware of Vectors, and cannot scan them. In this patch, we refactor the MarkedArgumentBuffer class into a MarkedVector template class that offer 2 enhancements: 1. It can be configured to store specific types of cell pointer types. This avoids us having to constantly cast JSValues into these pointers. 2. It allows us to specify the type of OverflowHandler we want to use. In this case, we want to use CrashOnOverflow. The previous MarkedArgumentBuffer always assumes RecordOnOverflow. This allows us to avoid having to manually check for overflows, or have to use appendWithCrashOnOverflow. For our current needs, MarkedVector can be used as a drop in replacement for Vector. And we fix the CloneDeserializer::deserialize() issue by replacing the use of Vectors with MarkedVector instead. * Source/JavaScriptCore/heap/Heap.cpp: (JSC::Heap::addCoreConstraints): * Source/JavaScriptCore/heap/Heap.h: * Source/JavaScriptCore/heap/HeapInlines.h: * Source/JavaScriptCore/runtime/ArgList.cpp: (JSC::MarkedVectorBase::addMarkSet): (JSC::MarkedVectorBase::markLists): (JSC::MarkedVectorBase::slowEnsureCapacity): (JSC::MarkedVectorBase::expandCapacity): (JSC::MarkedVectorBase::slowAppend): (JSC::MarkedArgumentBufferBase::addMarkSet): Deleted. (JSC::MarkedArgumentBufferBase::markLists): Deleted. (JSC::MarkedArgumentBufferBase::slowEnsureCapacity): Deleted. (JSC::MarkedArgumentBufferBase::expandCapacity): Deleted. (JSC::MarkedArgumentBufferBase::slowAppend): Deleted. * Source/JavaScriptCore/runtime/ArgList.h: (JSC::MarkedVectorWithSize::MarkedVectorWithSize): (JSC::MarkedVectorWithSize::at const): (JSC::MarkedVectorWithSize::clear): (JSC::MarkedVectorWithSize::append): (JSC::MarkedVectorWithSize::appendWithCrashOnOverflow): (JSC::MarkedVectorWithSize::last const): (JSC::MarkedVectorWithSize::takeLast): (JSC::MarkedVectorWithSize::ensureCapacity): (JSC::MarkedVectorWithSize::hasOverflowed): (JSC::MarkedVectorWithSize::fill): (JSC::MarkedArgumentBufferWithSize::MarkedArgumentBufferWithSize): Deleted. * Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp: (WebCore::AudioWorkletProcessor::buildJSArguments): * Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h: * Source/WebCore/bindings/js/SerializedScriptValue.cpp: (WebCore::CloneDeserializer::deserialize): Canonical link: https://commits.webkit.org/259548.530@safari-7615-branch Identifier: 259548.395@safari-7615.1.26.11-branch --- Source/JavaScriptCore/heap/Heap.cpp | 4 +- Source/JavaScriptCore/heap/Heap.h | 8 +- Source/JavaScriptCore/heap/HeapInlines.h | 2 +- Source/JavaScriptCore/runtime/ArgList.cpp | 46 ++-- Source/JavaScriptCore/runtime/ArgList.h | 206 ++++++++++-------- .../webaudio/AudioWorkletProcessor.cpp | 4 +- .../Modules/webaudio/AudioWorkletProcessor.h | 7 +- .../bindings/js/SerializedScriptValue.cpp | 11 +- 8 files changed, 158 insertions(+), 130 deletions(-) diff --git a/Source/JavaScriptCore/heap/Heap.cpp b/Source/JavaScriptCore/heap/Heap.cpp index d773eb9e79d6..37bf0e94b266 100644 --- a/Source/JavaScriptCore/heap/Heap.cpp +++ b/Source/JavaScriptCore/heap/Heap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2022 Apple Inc. All rights reserved. + * Copyright (C) 2003-2023 Apple Inc. All rights reserved. * Copyright (C) 2007 Eric Seidel * * This library is free software; you can redistribute it and/or @@ -2854,7 +2854,7 @@ void Heap::addCoreConstraints() if (!m_markListSet.isEmpty()) { SetRootMarkReasonScope rootScope(visitor, RootMarkReason::ConservativeScan); - MarkedArgumentBufferBase::markLists(visitor, m_markListSet); + MarkedVectorBase::markLists(visitor, m_markListSet); } { diff --git a/Source/JavaScriptCore/heap/Heap.h b/Source/JavaScriptCore/heap/Heap.h index d6cb99c4e4b5..315d62e50b1d 100644 --- a/Source/JavaScriptCore/heap/Heap.h +++ b/Source/JavaScriptCore/heap/Heap.h @@ -1,7 +1,7 @@ /* * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) * Copyright (C) 2001 Peter Kelly (pmk@post.com) - * Copyright (C) 2003-2022 Apple Inc. All rights reserved. + * Copyright (C) 2003-2023 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -85,7 +85,7 @@ class MarkStackArray; class MarkStackMergingConstraint; class MarkedJSValueRefArray; class BlockDirectory; -class MarkedArgumentBufferBase; +class MarkedVectorBase; class MarkingConstraint; class MarkingConstraintSet; class MutatorScheduler; @@ -413,7 +413,7 @@ class Heap { JS_EXPORT_PRIVATE std::unique_ptr protectedObjectTypeCounts(); JS_EXPORT_PRIVATE std::unique_ptr objectTypeCounts(); - HashSet& markListSet(); + HashSet& markListSet(); void addMarkedJSValueRefArray(MarkedJSValueRefArray*); template void forEachProtectedCell(const Functor&); @@ -782,7 +782,7 @@ class Heap { size_t m_deprecatedExtraMemorySize { 0 }; ProtectCountSet m_protectedValues; - HashSet m_markListSet; + HashSet m_markListSet; SentinelLinkedList> m_markedJSValueRefArrays; std::unique_ptr m_machineThreads; diff --git a/Source/JavaScriptCore/heap/HeapInlines.h b/Source/JavaScriptCore/heap/HeapInlines.h index f91546bb62c4..8e33eaae4a4f 100644 --- a/Source/JavaScriptCore/heap/HeapInlines.h +++ b/Source/JavaScriptCore/heap/HeapInlines.h @@ -205,7 +205,7 @@ inline void Heap::decrementDeferralDepthAndGCIfNeeded() } } -inline HashSet& Heap::markListSet() +inline HashSet& Heap::markListSet() { return m_markListSet; } diff --git a/Source/JavaScriptCore/runtime/ArgList.cpp b/Source/JavaScriptCore/runtime/ArgList.cpp index f2815b80c8c7..a72dea74a56f 100644 --- a/Source/JavaScriptCore/runtime/ArgList.cpp +++ b/Source/JavaScriptCore/runtime/ArgList.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2021 Apple Inc. All rights reserved. + * Copyright (C) 2003-2023 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -27,7 +27,7 @@ using std::min; namespace JSC { -void MarkedArgumentBufferBase::addMarkSet(JSValue v) +void MarkedVectorBase::addMarkSet(JSValue v) { if (m_markSet) return; @@ -52,47 +52,47 @@ void ArgList::getSlice(int startIndex, ArgList& result) const } template -void MarkedArgumentBufferBase::markLists(Visitor& visitor, ListSet& markSet) +void MarkedVectorBase::markLists(Visitor& visitor, ListSet& markSet) { ListSet::iterator end = markSet.end(); for (ListSet::iterator it = markSet.begin(); it != end; ++it) { - MarkedArgumentBufferBase* list = *it; + MarkedVectorBase* list = *it; for (int i = 0; i < list->m_size; ++i) visitor.appendUnbarriered(JSValue::decode(list->slotFor(i))); } } -template void MarkedArgumentBufferBase::markLists(AbstractSlotVisitor&, ListSet&); -template void MarkedArgumentBufferBase::markLists(SlotVisitor&, ListSet&); +template void MarkedVectorBase::markLists(AbstractSlotVisitor&, ListSet&); +template void MarkedVectorBase::markLists(SlotVisitor&, ListSet&); -void MarkedArgumentBufferBase::slowEnsureCapacity(size_t requestedCapacity) +auto MarkedVectorBase::slowEnsureCapacity(size_t requestedCapacity) -> Status { setNeedsOverflowCheck(); auto checkedNewCapacity = CheckedInt32(requestedCapacity); if (UNLIKELY(checkedNewCapacity.hasOverflowed())) - return this->overflowed(); - expandCapacity(checkedNewCapacity); + return Status::Overflowed; + return expandCapacity(checkedNewCapacity); } -void MarkedArgumentBufferBase::expandCapacity() +auto MarkedVectorBase::expandCapacity() -> Status { setNeedsOverflowCheck(); auto checkedNewCapacity = CheckedInt32(m_capacity) * 2; if (UNLIKELY(checkedNewCapacity.hasOverflowed())) - return this->overflowed(); - expandCapacity(checkedNewCapacity); + return Status::Overflowed; + return expandCapacity(checkedNewCapacity); } -void MarkedArgumentBufferBase::expandCapacity(int newCapacity) +auto MarkedVectorBase::expandCapacity(int newCapacity) -> Status { setNeedsOverflowCheck(); ASSERT(m_capacity < newCapacity); auto checkedSize = CheckedSize(newCapacity) * sizeof(EncodedJSValue); if (UNLIKELY(checkedSize.hasOverflowed())) - return this->overflowed(); + return Status::Overflowed; EncodedJSValue* newBuffer = static_cast(Gigacage::tryMalloc(Gigacage::JSValue, checkedSize)); if (!newBuffer) - return this->overflowed(); + return Status::Overflowed; for (int i = 0; i < m_size; ++i) { newBuffer[i] = m_buffer[i]; addMarkSet(JSValue::decode(m_buffer[i])); @@ -103,21 +103,23 @@ void MarkedArgumentBufferBase::expandCapacity(int newCapacity) m_buffer = newBuffer; m_capacity = newCapacity; + return Status::Success; } -void MarkedArgumentBufferBase::slowAppend(JSValue v) +auto MarkedVectorBase::slowAppend(JSValue v) -> Status { ASSERT(m_size <= m_capacity); - if (m_size == m_capacity) - expandCapacity(); - if (UNLIKELY(Base::hasOverflowed())) { - ASSERT(m_needsOverflowCheck); - return; + if (m_size == m_capacity) { + auto status = expandCapacity(); + if (status == Status::Overflowed) { + ASSERT(m_needsOverflowCheck); + return status; + } } - slotFor(m_size) = JSValue::encode(v); ++m_size; addMarkSet(v); + return Status::Success; } } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/ArgList.h b/Source/JavaScriptCore/runtime/ArgList.h index 8ea9b0e308b8..07632263266b 100644 --- a/Source/JavaScriptCore/runtime/ArgList.h +++ b/Source/JavaScriptCore/runtime/ArgList.h @@ -28,20 +28,20 @@ namespace JSC { -class alignas(alignof(EncodedJSValue)) MarkedArgumentBufferBase : public RecordOverflow { - WTF_MAKE_NONCOPYABLE(MarkedArgumentBufferBase); - WTF_MAKE_NONMOVABLE(MarkedArgumentBufferBase); +class alignas(alignof(EncodedJSValue)) MarkedVectorBase { + WTF_MAKE_NONCOPYABLE(MarkedVectorBase); + WTF_MAKE_NONMOVABLE(MarkedVectorBase); WTF_FORBID_HEAP_ALLOCATION; friend class VM; friend class ArgList; +protected: + enum class Status { Success, Overflowed }; public: - using Base = RecordOverflow; - typedef HashSet ListSet; + typedef HashSet ListSet; - ~MarkedArgumentBufferBase() + ~MarkedVectorBase() { - ASSERT(!m_needsOverflowCheck); if (m_markSet) m_markSet->remove(this); @@ -52,92 +52,20 @@ class alignas(alignof(EncodedJSValue)) MarkedArgumentBufferBase : public RecordO size_t size() const { return m_size; } bool isEmpty() const { return !m_size; } - JSValue at(int i) const - { - if (i >= m_size) - return jsUndefined(); - - return JSValue::decode(slotFor(i)); - } - - void clear() - { - ASSERT(!m_needsOverflowCheck); - clearOverflow(); - m_size = 0; - } - - enum OverflowCheckAction { - CrashOnOverflow, - WillCheckLater - }; - template - void appendWithAction(JSValue v) - { - ASSERT(m_size <= m_capacity); - if (m_size == m_capacity || mallocBase()) { - slowAppend(v); - if (action == CrashOnOverflow) - RELEASE_ASSERT(!hasOverflowed()); - return; - } - - slotFor(m_size) = JSValue::encode(v); - ++m_size; - } - void append(JSValue v) { appendWithAction(v); } - void appendWithCrashOnOverflow(JSValue v) { appendWithAction(v); } - void removeLast() { ASSERT(m_size); m_size--; } - JSValue last() - { - ASSERT(m_size); - return JSValue::decode(slotFor(m_size - 1)); - } - - JSValue takeLast() - { - JSValue result = last(); - removeLast(); - return result; - } - template static void markLists(Visitor&, ListSet&); - void ensureCapacity(size_t requestedCapacity) - { - if (requestedCapacity > static_cast(m_capacity)) - slowEnsureCapacity(requestedCapacity); - } - - bool hasOverflowed() - { - clearNeedsOverflowCheck(); - return Base::hasOverflowed(); - } - void overflowCheckNotNeeded() { clearNeedsOverflowCheck(); } - template - void fill(size_t count, const Functor& func) - { - ASSERT(!m_size); - ensureCapacity(count); - if (Base::hasOverflowed()) - return; - m_size = count; - func(reinterpret_cast(&slotFor(0))); - } - protected: // Constructor for a read-write list, to which you may append values. // FIXME: Remove all clients of this API, then remove this API. - MarkedArgumentBufferBase(size_t capacity) + MarkedVectorBase(size_t capacity) : m_size(0) , m_capacity(capacity) , m_buffer(inlineBuffer()) @@ -147,17 +75,16 @@ class alignas(alignof(EncodedJSValue)) MarkedArgumentBufferBase : public RecordO EncodedJSValue* inlineBuffer() { - return bitwise_cast(bitwise_cast(this) + sizeof(MarkedArgumentBufferBase)); + return bitwise_cast(bitwise_cast(this) + sizeof(MarkedVectorBase)); } -private: - void expandCapacity(); - void expandCapacity(int newCapacity); - void slowEnsureCapacity(size_t requestedCapacity); + Status expandCapacity(); + Status expandCapacity(int newCapacity); + Status slowEnsureCapacity(size_t requestedCapacity); void addMarkSet(JSValue); - JS_EXPORT_PRIVATE void slowAppend(JSValue); + JS_EXPORT_PRIVATE Status slowAppend(JSValue); EncodedJSValue& slotFor(int item) const { @@ -172,11 +99,14 @@ class alignas(alignof(EncodedJSValue)) MarkedArgumentBufferBase : public RecordO } #if ASSERT_ENABLED - void setNeedsOverflowCheck() { m_needsOverflowCheck = true; } + void disableNeedsOverflowCheck() { m_overflowCheckEnabled = false; } + void setNeedsOverflowCheck() { m_needsOverflowCheck = m_overflowCheckEnabled; } void clearNeedsOverflowCheck() { m_needsOverflowCheck = false; } bool m_needsOverflowCheck { false }; + bool m_overflowCheckEnabled { true }; #else + void disableNeedsOverflowCheck() { } void setNeedsOverflowCheck() { } void clearNeedsOverflowCheck() { } #endif // ASSERT_ENABLED @@ -186,22 +116,114 @@ class alignas(alignof(EncodedJSValue)) MarkedArgumentBufferBase : public RecordO ListSet* m_markSet; }; -template -class MarkedArgumentBufferWithSize : public MarkedArgumentBufferBase { +template +class MarkedVector : public OverflowHandler, public MarkedVectorBase { public: static constexpr size_t inlineCapacity = passedInlineCapacity; - MarkedArgumentBufferWithSize() - : MarkedArgumentBufferBase(inlineCapacity) + MarkedVector() + : MarkedVectorBase(inlineCapacity) { ASSERT(inlineBuffer() == m_inlineBuffer); + if constexpr (std::is_same_v) { + // CrashOnOverflow handles overflows immediately. So, we do not + // need to check for it after. + disableNeedsOverflowCheck(); + } + } + + auto at(int i) const -> decltype(auto) + { + if constexpr (std::is_same_v) { + if (i >= m_size) + return jsUndefined(); + return JSValue::decode(slotFor(i)); + } else { + if (i >= m_size) + return static_cast(nullptr); + return jsCast(JSValue::decode(slotFor(i)).asCell()); + } + } + + void clear() + { + ASSERT(!m_needsOverflowCheck); + OverflowHandler::clearOverflow(); + m_size = 0; + } + + void append(T v) + { + ASSERT(m_size <= m_capacity); + if (m_size == m_capacity || mallocBase()) { + if (slowAppend(v) == Status::Overflowed) + this->overflowed(); + return; + } + + slotFor(m_size) = JSValue::encode(v); + ++m_size; + } + + void appendWithCrashOnOverflow(T v) + { + append(v); + if constexpr (!std::is_same::value) + RELEASE_ASSERT(!this->hasOverflowed()); + } + + auto last() const -> decltype(auto) + { + if constexpr (std::is_same_v) { + ASSERT(m_size); + return JSValue::decode(slotFor(m_size - 1)); + } else { + ASSERT(m_size); + return jsCast(JSValue::decode(slotFor(m_size - 1)).asCell()); + } + } + + JSValue takeLast() + { + JSValue result = last(); + removeLast(); + return result; + } + + void ensureCapacity(size_t requestedCapacity) + { + if (requestedCapacity > static_cast(m_capacity)) { + if (slowEnsureCapacity(requestedCapacity) == Status::Overflowed) + this->overflowed(); + } + } + + bool hasOverflowed() + { + clearNeedsOverflowCheck(); + return OverflowHandler::hasOverflowed(); + } + + template + void fill(size_t count, const Functor& func) + { + ASSERT(!m_size); + ensureCapacity(count); + if (OverflowHandler::hasOverflowed()) + return; + m_size = count; + func(reinterpret_cast(&slotFor(0))); } private: EncodedJSValue m_inlineBuffer[inlineCapacity] { }; }; -using MarkedArgumentBuffer = MarkedArgumentBufferWithSize<>; +template +class MarkedArgumentBufferWithSize : public MarkedVector { +}; + +using MarkedArgumentBuffer = MarkedVector; class ArgList { WTF_MAKE_FAST_ALLOCATED; diff --git a/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp b/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp index e41a46dd57de..2ab3abb48117 100644 --- a/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp +++ b/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Apple Inc. All rights reserved. + * Copyright (C) 2020-2023 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -220,7 +220,7 @@ AudioWorkletProcessor::AudioWorkletProcessor(AudioWorkletGlobalScope& globalScop ASSERT(!isMainThread()); } -void AudioWorkletProcessor::buildJSArguments(VM& vm, JSGlobalObject& globalObject, MarkedArgumentBufferBase& args, const Vector>& inputs, Vector>& outputs, const MemoryCompactLookupOnlyRobinHoodHashMap>& paramValuesMap) +void AudioWorkletProcessor::buildJSArguments(VM& vm, JSGlobalObject& globalObject, MarkedArgumentBuffer& args, const Vector>& inputs, Vector>& outputs, const MemoryCompactLookupOnlyRobinHoodHashMap>& paramValuesMap) { // For performance reasons, we cache the arrays passed to JS and reconstruct them only when the topology changes. if (!copyDataFromBusesToJSArray(globalObject, inputs, toJSArray(m_jsInputs))) diff --git a/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h b/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h index 746059067f87..40751a2e501b 100644 --- a/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h +++ b/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Apple Inc. All rights reserved. + * Copyright (C) 2020-2023 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,7 +41,8 @@ namespace JSC { class JSArray; -class MarkedArgumentBufferBase; +template class MarkedVector; +using MarkedArgumentBuffer = MarkedVector; } namespace WebCore { @@ -71,7 +72,7 @@ class AudioWorkletProcessor : public ScriptWrappable, public ThreadSafeRefCounte private: explicit AudioWorkletProcessor(AudioWorkletGlobalScope&, const AudioWorkletProcessorConstructionData&); - void buildJSArguments(JSC::VM&, JSC::JSGlobalObject&, JSC::MarkedArgumentBufferBase&, const Vector>& inputs, Vector>& outputs, const MemoryCompactLookupOnlyRobinHoodHashMap>& paramValuesMap); + void buildJSArguments(JSC::VM&, JSC::JSGlobalObject&, JSC::MarkedArgumentBuffer&, const Vector>& inputs, Vector>& outputs, const MemoryCompactLookupOnlyRobinHoodHashMap>& paramValuesMap); AudioWorkletGlobalScope& m_globalScope; String m_name; diff --git a/Source/WebCore/bindings/js/SerializedScriptValue.cpp b/Source/WebCore/bindings/js/SerializedScriptValue.cpp index ad135b5da8f8..a465d5a57a73 100644 --- a/Source/WebCore/bindings/js/SerializedScriptValue.cpp +++ b/Source/WebCore/bindings/js/SerializedScriptValue.cpp @@ -573,6 +573,7 @@ static const unsigned StringDataIs8BitFlag = 0x80000000; using DeserializationResult = std::pair; class CloneBase { + WTF_FORBID_HEAP_ALLOCATION; protected: CloneBase(JSGlobalObject* lexicalGlobalObject) : m_lexicalGlobalObject(lexicalGlobalObject) @@ -650,6 +651,7 @@ template <> bool writeLittleEndian(Vector& buffer, const uint8 } class CloneSerializer : CloneBase { + WTF_FORBID_HEAP_ALLOCATION; public: static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) @@ -2318,6 +2320,7 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) } class CloneDeserializer : CloneBase { + WTF_FORBID_HEAP_ALLOCATION; public: static String deserializeString(const Vector& buffer) { @@ -4285,10 +4288,10 @@ DeserializationResult CloneDeserializer::deserialize() Vector indexStack; Vector propertyNameStack; - Vector outputObjectStack; - Vector mapKeyStack; - Vector mapStack; - Vector setStack; + MarkedVector outputObjectStack; + MarkedVector mapKeyStack; + MarkedVector mapStack; + MarkedVector setStack; Vector stateStack; WalkerState lexicalGlobalObject = StateUnknown; JSValue outValue;