Skip to content

[🐞 BUG] Repeatable crash in TypingService.PasteboardRestore queue (EXC_BREAKPOINT brk #1) β€” affects both insertion modes via fallbackΒ #319

@domci

Description

@domci

Summary

FluidVoice 1.5.13 crashes deterministically with EXC_BREAKPOINT (brk #1, Swift fatal-error trap) on the TypingService.PasteboardRestore dispatch queue. I've reproduced it 5 times across 3 sessions, all identical signatures.

The same crash hits both Clipboard Paste and Clipboard Free Insert modes, because the keystroke path falls back to insertTextViaClipboard at Sources/Fluid/Services/TypingService.swift:368 when CGEvent + Accessibility both fail β€” so toggling the setting is not a workaround.

Environment

  • FluidVoice 1.5.13 (build 10), installed from official .dmg
  • macOS 26.3.1 (25D771280a)
  • Mac14,7 (Apple Silicon)

Crash signature (5/5 reports identical)

exception : EXC_BREAKPOINT (SIGTRAP)
ESR       : (Breakpoint) brk 1
queue     : TypingService.PasteboardRestore

Faulting frames (binary is stripped, so only offsets resolve):

0x100124b78 (in FluidVoice) + 1576    ← PC, brk #1
0x100124390 (in FluidVoice) + 64
0x100105bb8 (in FluidVoice) + 28
_dispatch_call_block_and_release
_dispatch_lane_serial_drain
...

+1576 places the trap deep inside a substantial function. By elimination via code audit (below), that function is almost certainly waitForFocusedTextVerification.

Audit β€” what runs on pasteboardRestoreQueue

The closure at TypingService.swift:553 only calls:

  • waitForFocusedTextVerification
  • NSPasteboard accessors (cannot brk #1)
  • restorePasteboardSnapshot (no force-unwraps, no subscripts)
  • log

brk #1 candidates inside waitForFocusedTextVerification (lines ~937–1000):

Line(s) Code Risk
973, 991 before.location + expectedLength High β€” overflows if AX returns NSNotFound (= Int.max) for selected-range location
974, 992 abs(after.location - expectedCaretLocation) High β€” same root cause

No force-unwraps, as! casts, or array subscripts on this path. The integer-overflow hypothesis is the only viable explanation in the file.

Why Clipboard Free Insert doesn't avoid it

TypingService.swift:368 β€” when CGEvent + Accessibility both fail (common against stubborn Electron / GPU-terminal / web targets), the pipeline falls through to insertTextViaClipboard, which dispatches onto pasteboardRestoreQueue and re-enters the same trap.

// Fallback: Use clipboard-based insertion (more reliable)
self.log("[TypingService] CGEvent failed, trying clipboard fallback")
if self.insertTextViaClipboard(text) { ... }   // ← re-enters the buggy queue

Suggested fix

Guard the arithmetic in waitForFocusedTextVerification against NSNotFound/overflow before using before.location:

// In both the `appScriptSelectedRange` and `selectedRange` branches:
guard before.location != NSNotFound,
      after.location  != NSNotFound,
      after.length == 0
else { /* skip this signal, keep polling */ }

let (expectedCaretLocation, addOverflow) =
    before.location.addingReportingOverflow(expectedLength)
guard !addOverflow else { continue }

let (rawDelta, subOverflow) =
    after.location.subtractingReportingOverflow(expectedCaretLocation)
guard !subOverflow else { continue }
let caretDelta = abs(rawDelta)

A defense-in-depth alternative is to sanitize at the source in getSelectedTextRange β€” return nil if range.location == NSNotFound or range.location < 0. That also protects any future callers.

What I'd need to be 100% sure of the line

The shipped binary is fully stripped (nm shows imported symbols only), and no dSYM is published with the v1.5.13 release. The atos output above is the best I can recover from outside.

Could you either:

  1. Attach Fluid-oss-1.5.13.dSYM.zip to the v1.5.13 GitHub release, or
  2. Paste the output of:
    atos -arch arm64 -o /path/to/FluidVoice.app/Contents/MacOS/FluidVoice -l <load-addr> 0x101000b78
    
    from a build that has symbols.

That pins the exact line, and I'll send a PR.

Crash report

Available on request β€” .ips files contain hardware identifiers I'd rather not paste publicly. Happy to share via a private channel.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions