Skip to content

🐞 Fix stack overflow crash on window-move actions#1113

Merged
mrkai77 merged 1 commit into
mrkai77:developfrom
bit-saver:fix/window-move-frame-recursion
Jul 1, 2026
Merged

🐞 Fix stack overflow crash on window-move actions#1113
mrkai77 merged 1 commit into
mrkai77:developfrom
bit-saver:fix/window-move-frame-recursion

Conversation

@bit-saver

Copy link
Copy Markdown
Contributor

Summary

Window-move actions (moveUp / moveDown / moveLeft / moveRight) crash Loop with EXC_BAD_ACCESS (stack overflow) as soon as they're triggered — via a keybind, the radial-menu preview, or a loop://direction/Move… URL.

Root cause

WindowFrameResolver.calculateTargetFrame(...) resolves the willMove branch by reading context.getTargetFrame(). ResizeContext.getTargetFrame() lazily calls recomputeTargetFrame(), which calls WindowFrameResolver.getFrame(resizeContext: self) on the same context — re-entering the willMove branch and calling getTargetFrame() again. Since needsRecompute is only cleared at the end of recomputeTargetFrame(), the re-entrant call recomputes again → infinite mutual recursion → stack overflow.

Crash backtrace (abridged):

0  libswiftCore  swift_beginAccess
2  Loop  static WindowFrameResolver.getFrame(resizeContext:)
3  Loop  ResizeContext.recomputeTargetFrame()
4  Loop  static WindowFrameResolver.calculateTargetFrame(sidesToAdjust:context:)
5  Loop  static WindowFrameResolver.getFrame(resizeContext:)
…  (repeats until KERN_PROTECTION_FAILURE at the stack guard page)

Fix

  • WindowFrameResolver.swift — the willMove branch now reads lastAppliedFrame ?? cachedTargetFrame.raw (matching the grow/shrink branches) instead of getTargetFrame(). This removes the self-recursion at its source, and anchors moves to the window's actual position rather than a theoretical, possibly clamped-away target frame (also fixing move drift on size-constrained windows).
  • ResizeContext.swift (defensive) — recomputeTargetFrame() clears needsRecompute before resolving, so any future re-entrant getTargetFrame() returns the cached frame instead of recursing. Not strictly required after the resolver change; happy to drop it if you'd prefer a minimal one-file diff.

Testing

Before: open "loop://direction/MoveLeft" on a focused window → immediate EXC_BAD_ACCESS crash with the recursion backtrace above.

After: same trigger (MoveLeft + MoveRight) → resolver computes the frame, the window moves, action recorded, no crash:

[URLCommandHandler] Executing direction: MoveLeft
[URLCommandHandler] Found 14 eligible windows
[ResizeContext] Computed target frame … for action: WindowAction(direction: MoveLeft)
[WindowEngine] Resizing Window(…) to (4061.0, 867.0, 399.0, 455.0)

swiftformat --lint is clean; built with the Loop scheme (Release).

Window-move actions (moveUp/Down/Left/Right) crashed Loop with
EXC_BAD_ACCESS (stack overflow). In WindowFrameResolver.calculateTargetFrame,
the `willMove` branch read `context.getTargetFrame()`, whose lazy
recompute calls `WindowFrameResolver.getFrame` on the *same* ResizeContext,
which re-enters the willMove branch — infinite mutual recursion until the
stack guard page is hit.

Fix the willMove branch to read `lastAppliedFrame ?? cachedTargetFrame.raw`,
matching the grow/shrink branches. This removes the self-recursion at its
source and anchors moves to the window's actual position instead of a
theoretical (possibly clamped-away) target frame.

Also harden ResizeContext.recomputeTargetFrame to clear its `needsRecompute`
guard before resolving, so any future re-entrant getTargetFrame() returns the
cached frame instead of recursing.

@mrkai77 mrkai77 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to reproduce the issue locally, and this PR fixes it. Thanks :)

@mrkai77 mrkai77 merged commit 6e2b1d0 into mrkai77:develop Jul 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants