Fix re-entrant exclusive-access crash in drag handle hit test (#771)

When sibling.hitTest() triggers a SwiftUI layout pass during the
drag handle's sibling walk, AppKit can call back into
windowDragHandleShouldCaptureHit before the outer invocation
finishes. This re-entry accesses SwiftUI view state that is already
held exclusively, causing a Swift runtime SIGABRT.

Add a module-level re-entrancy guard that bails out (returns false)
on nested calls to the sibling walk. Since hitTest is always called
on the main thread, a simple Bool flag is sufficient.

Crash was reproduced on macOS Sequoia 15.1.1 (24B91) in a UTM VM.
The crash stack: DraggableView.hitTest -> windowDragHandleShouldCaptureHit
-> sibling.hitTest -> SwiftUI body evaluation -> hitTest (re-entry)
-> exclusive-access violation -> SIGABRT.
This commit is contained in:
Lawrence Chen 2026-03-02 19:20:14 -08:00 committed by GitHub
parent 682a57d7db
commit 5bbdd87c29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 0 deletions

View file

@ -218,6 +218,12 @@ func windowDragHandleShouldTreatTopHitAsPassiveHost(_ view: NSView) -> Bool {
return false
}
/// Re-entrancy guard for the sibling hit-test walk. When `sibling.hitTest()`
/// triggers SwiftUI view-body evaluation, AppKit can call back into this
/// function before the outer invocation finishes, causing a Swift
/// exclusive-access violation (SIGABRT). Main-thread only, no lock needed.
private var _windowDragHandleIsResolvingSiblingHits = false
/// Returns whether the titlebar drag handle should capture a hit at `point`.
/// We only claim the hit when no sibling view already handles it, so interactive
/// controls layered in the titlebar (e.g. proxy folder icon) keep their gestures.
@ -295,6 +301,20 @@ func windowDragHandleShouldCaptureHit(
return true
}
// Bail out if we're already inside a sibling hit-test walk. This happens
// when sibling.hitTest() re-enters SwiftUI layout, which calls hitTest on
// this drag handle again. Proceeding would trigger an exclusive-access
// violation in the Swift runtime.
guard !_windowDragHandleIsResolvingSiblingHits else {
#if DEBUG
dlog("titlebar.dragHandle.hitTest capture=false reason=reentrant point=\(windowDragHandleFormatPoint(point))")
#endif
return false
}
_windowDragHandleIsResolvingSiblingHits = true
defer { _windowDragHandleIsResolvingSiblingHits = false }
let siblingSnapshot = Array(superview.subviews.reversed())
#if DEBUG