mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
fix: resolve GC segfault in JSYogaNode lifecycle management
- Add missing cellLock() in JSYogaNode::visitOutputConstraints to prevent concurrent GC reads of WriteBarriers during mutation - Clear all WriteBarriers (m_children, m_measureFunc, m_dirtiedFunc, m_baselineFunc, m_config) in free() and freeRecursive() - Empty m_children JSArray in removeAllChildren() to prevent stale child references
This commit is contained in:
@@ -141,6 +141,14 @@ template<typename Visitor>
|
||||
void JSYogaNode::visitOutputConstraints(JSC::JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
auto* thisObject = jsCast<JSYogaNode*>(cell);
|
||||
|
||||
// Lock for concurrent GC thread safety - the mutator thread may be modifying
|
||||
// WriteBarriers (m_children, m_measureFunc, etc.) concurrently via insertChild,
|
||||
// removeChild, setMeasureFunc, free(), etc. Without this lock, the GC thread
|
||||
// can read a torn/partially-written pointer from a WriteBarrier, leading to
|
||||
// a segfault in validateCell when it tries to decode a corrupted StructureID.
|
||||
WTF::Locker locker { thisObject->cellLock() };
|
||||
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitOutputConstraints(thisObject, visitor);
|
||||
|
||||
|
||||
@@ -864,6 +864,15 @@ JSC_DEFINE_HOST_FUNCTION(jsYogaNodeProtoFuncFree, (JSC::JSGlobalObject * globalO
|
||||
// Lifecycle managed by RefCounted
|
||||
}
|
||||
|
||||
// Clear all WriteBarriers so the GC doesn't try to visit stale pointers.
|
||||
// Without this, a freed node's visitAdditionalChildren could attempt to
|
||||
// visit objects that are no longer valid.
|
||||
thisObject->m_children.clear();
|
||||
thisObject->m_measureFunc.clear();
|
||||
thisObject->m_dirtiedFunc.clear();
|
||||
thisObject->m_baselineFunc.clear();
|
||||
thisObject->m_config.clear();
|
||||
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
@@ -3100,6 +3109,15 @@ JSC_DEFINE_HOST_FUNCTION(jsYogaNodeProtoFuncRemoveAllChildren, (JSC::JSGlobalObj
|
||||
CHECK_YOGA_NODE_FREED(thisObject);
|
||||
|
||||
YGNodeRemoveAllChildren(thisObject->impl().yogaNode());
|
||||
|
||||
// Clear the children array to release strong references, matching the Yoga tree state.
|
||||
// Without this, the m_children JSArray retains stale references to removed children.
|
||||
if (thisObject->m_children) {
|
||||
JSC::JSArray* childrenArray = jsCast<JSC::JSArray*>(thisObject->m_children.get());
|
||||
if (childrenArray)
|
||||
childrenArray->setLength(globalObject, 0);
|
||||
}
|
||||
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
@@ -3160,10 +3178,14 @@ JSC_DEFINE_HOST_FUNCTION(jsYogaNodeProtoFuncFreeRecursive, (JSC::JSGlobalObject
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the JS wrapper for this node
|
||||
// Clear the JS wrapper's WriteBarriers so the GC doesn't visit stale pointers
|
||||
JSYogaNode* jsNode = JSYogaNode::fromYGNode(currentNode);
|
||||
if (jsNode) {
|
||||
// Lifecycle managed by RefCounted
|
||||
jsNode->m_children.clear();
|
||||
jsNode->m_measureFunc.clear();
|
||||
jsNode->m_dirtiedFunc.clear();
|
||||
jsNode->m_baselineFunc.clear();
|
||||
jsNode->m_config.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user