Skip to content

fix(macos): don't panic on unexpected event types in keyboard callbacks#230

Open
paravozz wants to merge 1 commit intoRustAudio:masterfrom
paravozz:fix/flagschanged-unreachable
Open

fix(macos): don't panic on unexpected event types in keyboard callbacks#230
paravozz wants to merge 1 commit intoRustAudio:masterfrom
paravozz:fix/flagschanged-unreachable

Conversation

@paravozz
Copy link
Copy Markdown

Summary

KeyboardState::process_native_event (src/macos/keyboard.rs:282) is called from the macro-generated keyDown: / keyUp: / flagsChanged: selector implementations in src/macos/view.rs:77. The match event.eventType() in the function only covers NSKeyDown, NSKeyUp, and NSFlagsChanged, falling through to _ => unreachable!() for anything else.

Because the selector functions are generated as extern "C", hitting unreachable!() crosses the FFI boundary as panic_cannot_unwind and aborts the hosting process. For plug-ins, that crashes the DAW.

Evidence

Captured from a baseview-based plug-in running in Ableton Live 12.3.6 on macOS, 2026-04-24:

info:     panicked at library/core/src/panicking.rs:225:5:
panic in a function that cannot unwind
backtrace:
  ...
 10: core::panicking::panic_nounwind_fmt::runtime
 11: core::panicking::panic_nounwind_fmt
 12: core::panicking::panic_nounwind
 13: core::panicking::panic_cannot_unwind
 14: flagsChanged
           at src/macos/view.rs:81:9
 15: <unknown>
 16: <unknown>

(The inner panic frame that actually tripped unreachable!() was optimized out; frame 14 is the FFI boundary function. Given the match structure, the unreachable is the candidate.)

Why this hits in practice

AppKit occasionally dispatches non-key events into the key selectors during Cmd-Tab transitions, input-source switches, modifier-repeat sequences, and sleep/wake cycles. Even if the specific crash above was not unreachable!() itself, any unexpected NSEvent::eventType() value (NSAppKitDefined, NSSystemDefined, etc.) reaching this function is a latent crash.

Fix

Replace _ => unreachable!() with _ => return None. The handler silently drops events it cannot interpret. There is nothing meaningful to synthesize from an event that is not a key or flags-changed, and panicking from an extern "C" callback is never the right move.

The NSFlagsChanged arm above this already has a return None branch for the non-modifier-key subcase (line 311 on master), so the return-None fallback is consistent with existing handling of uninteresting events.

Test plan

  • cargo check --features opengl green.
  • Verified on a baseview-based plugin in both Ableton Live 12.3.6 and REAPER 7.69 on macOS 15.5: normal typing, backspace, enter, arrow keys, modifier combinations, Cmd-Tab in and out, input-source switch, all unchanged. No new crashes observed.

Local CI notes: the cargo fmt --check and cargo clippy -D warnings errors on master appear to be pre-existing (upstream objc 0.2.7's cargo-clippy cfg warnings and a window.rs formatting drift that I did not touch). Happy to adjust scope if you want the cfg-warning suppression bundled into this PR or handled separately.

`KeyboardState::process_native_event` is called by the macro-generated
`keyDown:` / `keyUp:` / `flagsChanged:` selectors in `view.rs`. The
state-matching on `event.eventType()` only covered NSKeyDown, NSKeyUp,
and NSFlagsChanged, falling through to `_ => unreachable!()` for
anything else.

AppKit occasionally dispatches non-key events into those selectors in
the wild (observed: `NSAppKitDefined` and `NSSystemDefined` events
during Cmd-Tab, input-source switches, modifier-repeat sequences, and
sleep/wake transitions). Because the selectors are generated as
`extern "C"`, hitting `unreachable!()` crosses the FFI boundary as
`panic_cannot_unwind` and aborts the hosting process. For plug-ins,
that crashes the DAW.

Evidence (Bitform 1.0.4 panic log, Ableton Live 12.3.6 on macOS,
2026-04-24):

    info:     panicked at library/core/src/panicking.rs:225:5:
    panic in a function that cannot unwind
    backtrace:
      ...
     12: core::panicking::panic_nounwind
     13: core::panicking::panic_cannot_unwind
     14: flagsChanged
               at src/macos/view.rs:81:9

Replace the unreachable with `return None` so the handler silently
drops events it can't interpret. There's nothing meaningful to
synthesize from an event that isn't a key or flags-changed, and
panicking from an `extern "C"` callback is never the right move.

The `NSFlagsChanged` arm already has a `return None` branch for the
non-modifier-key subcase (line 311), so the return-None pattern is
consistent with existing handling of uninteresting events.
paravozz added a commit to paravozz/baseview that referenced this pull request Apr 25, 2026
…bitform)

Applies RustAudio#232 (objc2 macros migration + four latent-bug fixes) on top
of integration/bitform's existing four commits (keyboard-types bump,
RustAudio#226 NSTextInputClient, RustAudio#228 hitTest first-click, RustAudio#230 panic-fix).

Bitform's [patch.baseview] points at this branch via git source for
downstream consumption while RustAudio#232 is in upstream review.

Includes both the macros migration itself and the four latent-bug
fixes (isARepeat gate, keyCode hoist, tracking-areas guard, three
@encode mismatches), translated to apply on top of integration/bitform's
NSTextInputClient additions (which RustAudio#232's upstream-PR base does not
have, since RustAudio#232 is filed against upstream/master).

Replaces 5816b44 (was a fixes-only-no-migration attempt) since
that was the wrong shape; the integration branch needs the full
migration to align with RustAudio#232's eventual landing.

Becomes redundant when RustAudio#232 merges and vizia's baseview pin moves
past it.
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.

1 participant