- Revision
- 289513
- Author
- alanc...@apple.com
- Date
- 2022-02-09 17:27:00 -0800 (Wed, 09 Feb 2022)
Log Message
Cherry-pick r289383. rdar://problem/88366849
[WebCore] JSValueInWrappedObject is not correct for concurrent GC
https://bugs.webkit.org/show_bug.cgi?id=236277
rdar://88366849
Reviewed by Saam Barati.
JSValueInWrappedObject is broken for concurrent GC's marking. It is using std::variant<> to store Weak / JSValue,
which is not safe if concurrent GC reads it while changing that std::variant. This patch fixes several problems
in JSValueInWrappedObject.
1. We must not use std::variant here since concurrent access can happen. We have both JSValue and Weak, and change
Weak after fully initialize WeakImpl's content in Weak. To ensure that, we emit storeStoreBarrier before setting
Weak to the JSValueInWrappedObject's field.
2. Assignment operator & copy constructor are basically wrong for this class as we need a write-barrier to set a value
to the field. We remove them and make it explicit that we do not have write-barrier, which reveals that IDBRequest
has a semantic bug.
3. We also add clear() instead of assigning empty JSValueInWrappedObject. And we ensure that this new clear() works
well with concurrent GC threads: we clear the underlying WeakImpl* pointer to nullptr. But since WeakImpl* is kept
alive until GC clears weak-related things in its end phase, concurrent GC thread can access the old WeakImpl*.
* Modules/indexeddb/IDBCursor.cpp:
(WebCore::IDBCursor::setGetResult):
* Modules/indexeddb/IDBRequest.cpp:
(WebCore::IDBRequest::setResult):
(WebCore::IDBRequest::setResultToStructuredClone):
(WebCore::IDBRequest::setResultToUndefined):
(WebCore::IDBRequest::willIterateCursor):
(WebCore::IDBRequest::didOpenOrIterateCursor):
* Modules/paymentrequest/PaymentMethodChangeEvent.cpp:
* Modules/paymentrequest/PaymentResponse.cpp:
(WebCore::PaymentResponse::setDetailsFunction):
* Modules/webaudio/AudioBuffer.cpp:
(WebCore::AudioBuffer::getChannelData):
(WebCore::AudioBuffer::visitChannelWrappers):
* bindings/js/JSValueInWrappedObject.h:
(WebCore::JSValueInWrappedObject::JSValueInWrappedObject):
(WebCore::JSValueInWrappedObject::operator JSC::JSValue const):
(WebCore::JSValueInWrappedObject::visit const):
(WebCore::JSValueInWrappedObject::setWeakly):
(WebCore::JSValueInWrappedObject::set):
(WebCore::JSValueInWrappedObject::clear):
(WebCore::JSValueInWrappedObject::setWithoutBarrier):
(WebCore::cachedPropertyValue):
(WebCore::JSValueInWrappedObject::makeValue): Deleted.
(WebCore::JSValueInWrappedObject::operator=): Deleted.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@289383 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Modified Paths
Diff
Modified: branches/safari-613-branch/Source/WebCore/ChangeLog (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/ChangeLog 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/ChangeLog 2022-02-10 01:27:00 UTC (rev 289513)
@@ -1,3 +1,103 @@
+2022-02-09 Alan Coon <alanc...@apple.com>
+
+ Cherry-pick r289383. rdar://problem/88366849
+
+ [WebCore] JSValueInWrappedObject is not correct for concurrent GC
+ https://bugs.webkit.org/show_bug.cgi?id=236277
+ rdar://88366849
+
+ Reviewed by Saam Barati.
+
+ JSValueInWrappedObject is broken for concurrent GC's marking. It is using std::variant<> to store Weak / JSValue,
+ which is not safe if concurrent GC reads it while changing that std::variant. This patch fixes several problems
+ in JSValueInWrappedObject.
+
+ 1. We must not use std::variant here since concurrent access can happen. We have both JSValue and Weak, and change
+ Weak after fully initialize WeakImpl's content in Weak. To ensure that, we emit storeStoreBarrier before setting
+ Weak to the JSValueInWrappedObject's field.
+ 2. Assignment operator & copy constructor are basically wrong for this class as we need a write-barrier to set a value
+ to the field. We remove them and make it explicit that we do not have write-barrier, which reveals that IDBRequest
+ has a semantic bug.
+ 3. We also add clear() instead of assigning empty JSValueInWrappedObject. And we ensure that this new clear() works
+ well with concurrent GC threads: we clear the underlying WeakImpl* pointer to nullptr. But since WeakImpl* is kept
+ alive until GC clears weak-related things in its end phase, concurrent GC thread can access the old WeakImpl*.
+
+ * Modules/indexeddb/IDBCursor.cpp:
+ (WebCore::IDBCursor::setGetResult):
+ * Modules/indexeddb/IDBRequest.cpp:
+ (WebCore::IDBRequest::setResult):
+ (WebCore::IDBRequest::setResultToStructuredClone):
+ (WebCore::IDBRequest::setResultToUndefined):
+ (WebCore::IDBRequest::willIterateCursor):
+ (WebCore::IDBRequest::didOpenOrIterateCursor):
+ * Modules/paymentrequest/PaymentMethodChangeEvent.cpp:
+ * Modules/paymentrequest/PaymentResponse.cpp:
+ (WebCore::PaymentResponse::setDetailsFunction):
+ * Modules/webaudio/AudioBuffer.cpp:
+ (WebCore::AudioBuffer::getChannelData):
+ (WebCore::AudioBuffer::visitChannelWrappers):
+ * bindings/js/JSValueInWrappedObject.h:
+ (WebCore::JSValueInWrappedObject::JSValueInWrappedObject):
+ (WebCore::JSValueInWrappedObject::operator JSC::JSValue const):
+ (WebCore::JSValueInWrappedObject::visit const):
+ (WebCore::JSValueInWrappedObject::setWeakly):
+ (WebCore::JSValueInWrappedObject::set):
+ (WebCore::JSValueInWrappedObject::clear):
+ (WebCore::JSValueInWrappedObject::setWithoutBarrier):
+ (WebCore::cachedPropertyValue):
+ (WebCore::JSValueInWrappedObject::makeValue): Deleted.
+ (WebCore::JSValueInWrappedObject::operator=): Deleted.
+
+ git-svn-id: https://svn.webkit.org/repository/webkit/trunk@289383 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+ 2022-02-07 Yusuke Suzuki <ysuz...@apple.com>
+
+ [WebCore] JSValueInWrappedObject is not correct for concurrent GC
+ https://bugs.webkit.org/show_bug.cgi?id=236277
+ rdar://88366849
+
+ Reviewed by Saam Barati.
+
+ JSValueInWrappedObject is broken for concurrent GC's marking. It is using std::variant<> to store Weak / JSValue,
+ which is not safe if concurrent GC reads it while changing that std::variant. This patch fixes several problems
+ in JSValueInWrappedObject.
+
+ 1. We must not use std::variant here since concurrent access can happen. We have both JSValue and Weak, and change
+ Weak after fully initialize WeakImpl's content in Weak. To ensure that, we emit storeStoreBarrier before setting
+ Weak to the JSValueInWrappedObject's field.
+ 2. Assignment operator & copy constructor are basically wrong for this class as we need a write-barrier to set a value
+ to the field. We remove them and make it explicit that we do not have write-barrier, which reveals that IDBRequest
+ has a semantic bug.
+ 3. We also add clear() instead of assigning empty JSValueInWrappedObject. And we ensure that this new clear() works
+ well with concurrent GC threads: we clear the underlying WeakImpl* pointer to nullptr. But since WeakImpl* is kept
+ alive until GC clears weak-related things in its end phase, concurrent GC thread can access the old WeakImpl*.
+
+ * Modules/indexeddb/IDBCursor.cpp:
+ (WebCore::IDBCursor::setGetResult):
+ * Modules/indexeddb/IDBRequest.cpp:
+ (WebCore::IDBRequest::setResult):
+ (WebCore::IDBRequest::setResultToStructuredClone):
+ (WebCore::IDBRequest::setResultToUndefined):
+ (WebCore::IDBRequest::willIterateCursor):
+ (WebCore::IDBRequest::didOpenOrIterateCursor):
+ * Modules/paymentrequest/PaymentMethodChangeEvent.cpp:
+ * Modules/paymentrequest/PaymentResponse.cpp:
+ (WebCore::PaymentResponse::setDetailsFunction):
+ * Modules/webaudio/AudioBuffer.cpp:
+ (WebCore::AudioBuffer::getChannelData):
+ (WebCore::AudioBuffer::visitChannelWrappers):
+ * bindings/js/JSValueInWrappedObject.h:
+ (WebCore::JSValueInWrappedObject::JSValueInWrappedObject):
+ (WebCore::JSValueInWrappedObject::operator JSC::JSValue const):
+ (WebCore::JSValueInWrappedObject::visit const):
+ (WebCore::JSValueInWrappedObject::setWeakly):
+ (WebCore::JSValueInWrappedObject::set):
+ (WebCore::JSValueInWrappedObject::clear):
+ (WebCore::JSValueInWrappedObject::setWithoutBarrier):
+ (WebCore::cachedPropertyValue):
+ (WebCore::JSValueInWrappedObject::makeValue): Deleted.
+ (WebCore::JSValueInWrappedObject::operator=): Deleted.
+
2022-02-08 Russell Epstein <repst...@apple.com>
Cherry-pick r288435. rdar://problem/83668578
Modified: branches/safari-613-branch/Source/WebCore/Modules/indexeddb/IDBCursor.cpp (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/Modules/indexeddb/IDBCursor.cpp 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/Modules/indexeddb/IDBCursor.cpp 2022-02-10 01:27:00 UTC (rev 289513)
@@ -336,9 +336,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
- m_keyWrapper = { };
- m_primaryKeyWrapper = { };
- m_valueWrapper = { };
+ clearWrappers();
if (!getResult.isDefined()) {
m_keyData = { };
Modified: branches/safari-613-branch/Source/WebCore/Modules/indexeddb/IDBRequest.cpp (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/Modules/indexeddb/IDBRequest.cpp 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/Modules/indexeddb/IDBRequest.cpp 2022-02-10 01:27:00 UTC (rev 289513)
@@ -369,7 +369,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
m_result = keyData;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
void IDBRequest::setResult(const Vector<IDBKeyData>& keyDatas)
@@ -383,7 +383,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
m_result = keyDatas;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
void IDBRequest::setResult(const IDBGetAllResult& result)
@@ -397,7 +397,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
m_result = result;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
void IDBRequest::setResult(uint64_t number)
@@ -411,7 +411,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
m_result = number;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
void IDBRequest::setResultToStructuredClone(const IDBGetResult& result)
@@ -427,7 +427,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
m_result = result;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
void IDBRequest::setResultToUndefined()
@@ -441,7 +441,7 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
m_result = NullResultType::Undefined;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
IDBCursor* IDBRequest::resultCursor()
@@ -474,9 +474,14 @@
VM& vm = context->vm();
JSLockHolder lock(vm);
+ // FIXME: This code is wrong: let's consider that these fields' access are reordered in the concurrent GC thread.
+ // And we just scanned cleared m_resultWrapper and then, we missed scanning m_cursorWrapper with a new value.
+ // Then we could make both collected. Whenever changing JSValueInWrappedObject fields, we should emit a write barrier
+ // if we would like to keep them alive.
+ // https://bugs.webkit.org/show_bug.cgi?id=236278
if (m_resultWrapper)
- m_cursorWrapper = m_resultWrapper;
- m_resultWrapper = { };
+ m_cursorWrapper.setWithoutBarrier(m_resultWrapper);
+ m_resultWrapper.clear();
m_readyState = ReadyState::Pending;
m_domError = nullptr;
m_idbError = IDBError { };
@@ -495,11 +500,16 @@
JSLockHolder lock(vm);
m_result = NullResultType::Empty;
- m_resultWrapper = { };
+ m_resultWrapper.clear();
+ // FIXME: This code is wrong: let's consider that these fields' access are reordered in the concurrent GC thread.
+ // And we just scanned cleared m_resultWrapper and then, we missed scanning m_cursorWrapper with a new value.
+ // Then we could make both collected. Whenever changing JSValueInWrappedObject fields, we should emit a write barrier
+ // if we would like to keep them alive.
+ // https://bugs.webkit.org/show_bug.cgi?id=236278
if (resultData.type() == IDBResultType::IterateCursorSuccess || resultData.type() == IDBResultType::OpenCursorSuccess) {
if (m_pendingCursor->setGetResult(*this, resultData.getResult(), m_currentTransactionOperationID) && m_cursorWrapper)
- m_resultWrapper = m_cursorWrapper;
+ m_resultWrapper.setWithoutBarrier(m_cursorWrapper);
if (resultData.getResult().isDefined())
m_result = m_pendingCursor;
}
@@ -552,7 +562,7 @@
JSLockHolder lock(vm);
m_result = RefPtr<IDBDatabase> { WTFMove(database) };
- m_resultWrapper = { };
+ m_resultWrapper.clear();
}
void IDBRequest::clearWrappers()
Modified: branches/safari-613-branch/Source/WebCore/Modules/paymentrequest/PaymentMethodChangeEvent.cpp (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/Modules/paymentrequest/PaymentMethodChangeEvent.cpp 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/Modules/paymentrequest/PaymentMethodChangeEvent.cpp 2022-02-10 01:27:00 UTC (rev 289513)
@@ -42,7 +42,7 @@
PaymentMethodChangeEvent::PaymentMethodChangeEvent(const AtomString& type, Init&& eventInit)
: PaymentRequestUpdateEvent { type, eventInit }
, m_methodName { WTFMove(eventInit.methodName) }
- , m_methodDetails { JSValueInWrappedObject { eventInit.methodDetails.get() } }
+ , m_methodDetails { std::in_place_type_t<JSValueInWrappedObject>(), eventInit.methodDetails.get() }
{
}
@@ -49,7 +49,7 @@
PaymentMethodChangeEvent::PaymentMethodChangeEvent(const AtomString& type, const String& methodName, MethodDetailsFunction&& methodDetailsFunction)
: PaymentRequestUpdateEvent { type }
, m_methodName { methodName }
- , m_methodDetails { WTFMove(methodDetailsFunction) }
+ , m_methodDetails { std::in_place_type_t<MethodDetailsFunction>(), WTFMove(methodDetailsFunction) }
{
}
Modified: branches/safari-613-branch/Source/WebCore/Modules/paymentrequest/PaymentResponse.cpp (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/Modules/paymentrequest/PaymentResponse.cpp 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/Modules/paymentrequest/PaymentResponse.cpp 2022-02-10 01:27:00 UTC (rev 289513)
@@ -60,7 +60,7 @@
void PaymentResponse::setDetailsFunction(DetailsFunction&& detailsFunction)
{
m_detailsFunction = WTFMove(detailsFunction);
- m_cachedDetails = { };
+ m_cachedDetails.clear();
}
void PaymentResponse::complete(std::optional<PaymentComplete>&& result, DOMPromiseDeferred<void>&& promise)
Modified: branches/safari-613-branch/Source/WebCore/Modules/webaudio/AudioBuffer.cpp (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/Modules/webaudio/AudioBuffer.cpp 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/Modules/webaudio/AudioBuffer.cpp 2022-02-10 01:27:00 UTC (rev 289513)
@@ -159,7 +159,7 @@
if (globalObject.worldIsNormal()) {
if (!m_channelWrappers[channelIndex])
- m_channelWrappers[channelIndex] = { constructJSArray() };
+ m_channelWrappers[channelIndex].setWeakly(constructJSArray());
return static_cast<JSC::JSValue>(m_channelWrappers[channelIndex]);
}
return constructJSArray();
@@ -168,6 +168,8 @@
template<typename Visitor>
void AudioBuffer::visitChannelWrappers(Visitor& visitor)
{
+ // FIXME: AudioBuffer::releaseMemory can clear this buffer while visiting it from concurrent GC thread.
+ // https://bugs.webkit.org/show_bug.cgi?id=236279
for (auto& channelWrapper : m_channelWrappers)
channelWrapper.visit(visitor);
}
Modified: branches/safari-613-branch/Source/WebCore/bindings/js/JSValueInWrappedObject.h (289512 => 289513)
--- branches/safari-613-branch/Source/WebCore/bindings/js/JSValueInWrappedObject.h 2022-02-10 01:26:56 UTC (rev 289512)
+++ branches/safari-613-branch/Source/WebCore/bindings/js/JSValueInWrappedObject.h 2022-02-10 01:27:00 UTC (rev 289513)
@@ -34,57 +34,46 @@
namespace WebCore {
class JSValueInWrappedObject {
+ // It must not be copyable. Changing this will break concurrent GC.
+ WTF_MAKE_NONCOPYABLE(JSValueInWrappedObject);
public:
JSValueInWrappedObject(JSC::JSValue = { });
- JSValueInWrappedObject(const JSValueInWrappedObject&);
+
+ // FIXME: Remove them once AudioBuffer's m_channelWrappers bug is fixed.
+ // https://bugs.webkit.org/show_bug.cgi?id=236279
+ JSValueInWrappedObject(JSValueInWrappedObject&&) = default;
+ JSValueInWrappedObject& operator=(JSValueInWrappedObject&&) = default;
+
operator JSC::JSValue() const;
explicit operator bool() const;
- JSValueInWrappedObject& operator=(const JSValueInWrappedObject& other);
template<typename Visitor> void visit(Visitor&) const;
void clear();
+ void set(JSC::VM&, const JSC::JSCell* owner, JSC::JSValue);
+ void setWeakly(JSC::JSValue);
+
+ // FIXME: Remove this once IDBRequest semantic bug is fixed.
+ // https://bugs.webkit.org/show_bug.cgi?id=236278
+ void setWithoutBarrier(JSValueInWrappedObject&);
+
private:
- // Use a weak pointer here so that if this code or client code has a visiting mistake,
- // we get null rather than a dangling pointer to a deleted object.
- using Weak = JSC::Weak<JSC::JSCell>;
- // FIXME: Would storing a separate JSValue alongside a Weak be better than using a Variant?
- using Value = std::variant<JSC::JSValue, Weak>;
- static Value makeValue(JSC::JSValue);
- Value m_value;
+ // Keep in mind that all of these fields are accessed concurrently without lock from concurrent GC thread.
+ JSC::JSValue m_nonCell { };
+ JSC::Weak<JSC::JSCell> m_cell { };
};
JSC::JSValue cachedPropertyValue(JSC::JSGlobalObject&, const JSDOMObject& owner, JSValueInWrappedObject& cacheSlot, const Function<JSC::JSValue()>&);
-inline auto JSValueInWrappedObject::makeValue(JSC::JSValue value) -> Value
-{
- if (!value.isCell())
- return value;
- // FIXME: This is not quite right. It is possible that this value is being
- // stored in a wrapped object that does not yet have a wrapper. If garbage
- // collection occurs before the wrapped object gets a wrapper, it's possible
- // the value object could be collected, and this will become null. A future
- // version of this class should prevent the value from being collected in
- // that case. Unclear if this can actually happen in practice.
- return Weak { value.asCell() };
-}
-
inline JSValueInWrappedObject::JSValueInWrappedObject(JSC::JSValue value)
- : m_value(makeValue(JSC::JSValue(value)))
{
+ setWeakly(value);
}
-inline JSValueInWrappedObject::JSValueInWrappedObject(const JSValueInWrappedObject& value)
- : m_value(makeValue(value))
-{
-}
-
inline JSValueInWrappedObject::operator JSC::JSValue() const
{
- return WTF::switchOn(m_value, [] (JSC::JSValue value) {
- return value;
- }, [] (const Weak& value) -> JSC::JSValue {
- return value.get();
- });
+ if (m_nonCell)
+ return m_nonCell;
+ return m_cell.get();
}
inline JSValueInWrappedObject::operator bool() const
@@ -92,39 +81,54 @@
return JSC::JSValue { *this }.operator bool();
}
-inline JSValueInWrappedObject& JSValueInWrappedObject::operator=(const JSValueInWrappedObject& other)
-{
- m_value = makeValue(JSC::JSValue(other));
- return *this;
-}
-
template<typename Visitor>
inline void JSValueInWrappedObject::visit(Visitor& visitor) const
{
- return WTF::switchOn(m_value, [] (JSC::JSValue) {
- // Nothing to visit.
- }, [&visitor] (const Weak& value) {
- visitor.append(value);
- });
+ visitor.append(m_cell);
}
template void JSValueInWrappedObject::visit(JSC::AbstractSlotVisitor&) const;
template void JSValueInWrappedObject::visit(JSC::SlotVisitor&) const;
+inline void JSValueInWrappedObject::setWeakly(JSC::JSValue value)
+{
+ if (!value.isCell()) {
+ m_nonCell = value;
+ m_cell.clear();
+ return;
+ }
+ m_nonCell = { };
+ JSC::Weak weak { value.asCell() };
+ WTF::storeStoreFence();
+ m_cell = WTFMove(weak);
+}
+
+inline void JSValueInWrappedObject::set(JSC::VM& vm, const JSC::JSCell* owner, JSC::JSValue value)
+{
+ setWeakly(value);
+ vm.writeBarrier(owner, value);
+}
+
inline void JSValueInWrappedObject::clear()
{
- WTF::switchOn(m_value, [] (Weak& value) {
- value.clear();
- }, [] (auto&) { });
+ m_nonCell = { };
+ m_cell.clear();
}
+inline void JSValueInWrappedObject::setWithoutBarrier(JSValueInWrappedObject& other)
+{
+ JSC::Weak weak { other.m_cell.get() };
+ WTF::storeStoreFence(); // Ensure Weak is fully initialized for concurrent access.
+ m_nonCell = other.m_nonCell;
+ m_cell = WTFMove(weak);
+}
+
inline JSC::JSValue cachedPropertyValue(JSC::JSGlobalObject& lexicalGlobalObject, const JSDOMObject& owner, JSValueInWrappedObject& cachedValue, const Function<JSC::JSValue()>& function)
{
if (cachedValue && isWorldCompatible(lexicalGlobalObject, cachedValue))
return cachedValue;
auto value = function();
- cachedValue = cloneAcrossWorlds(lexicalGlobalObject, owner, value);
- lexicalGlobalObject.vm().writeBarrier(&owner, value);
+ cachedValue.set(lexicalGlobalObject.vm(), &owner, cloneAcrossWorlds(lexicalGlobalObject, owner, value));
ASSERT(isWorldCompatible(lexicalGlobalObject, cachedValue));
return cachedValue;
}