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:
parent
682a57d7db
commit
5bbdd87c29
2 changed files with 61 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue