Skip to content

fix: prevent native crash when a transformer worklet throws#14

Merged
janicduplessis merged 1 commit into
mainfrom
@janic/fix-throwing-transformer-crash
Jul 1, 2026
Merged

fix: prevent native crash when a transformer worklet throws#14
janicduplessis merged 1 commit into
mainfrom
@janic/fix-throwing-transformer-crash

Conversation

@janicduplessis

Copy link
Copy Markdown
Contributor

Description

A transformer worklet that throws kills the app natively: the error propagates uncaught from RunTransformer into the iOS textInputDidChange delegate / Android JNI TextWatcher and aborts with SIGABRT on the first keystroke. In release builds worklets' runSync calls the function raw (source); in debug the call guard returns undefined and the following asObject() throws instead. This is easy to hit from user code — any transformer returning an out-of-bounds selection makes validateSelection throw by design.

Solution

Never let a transformer error cross the native boundary, and surface it instead of swallowing it. The worklet call and result extraction are wrapped in try/catch; caught errors are logged (LogBox via the worklets call guard in debug, console.error on the UI runtime in release) and RunTransformer returns std::nullopt, which both platforms already treat as "no transform" — the input keeps the raw text.

The example app has a new "Throwing Transformer" card that exercises the path.

Test plan

Verified on-device with the new example card (every keystroke throws):

  • iOS debug: before the fix, one keystroke → SIGABRT (crash report stack goes through RunTransformertextInputDidChange). After: app stays alive, LogBox shows the error, input keeps the raw text.
  • iOS Release (no call guard, so this hits the new JSError catch directly): typed 5 characters; app survived and the syslog shows five [rntti] Transformer threw an error: … entries with the value growing 1→5.
  • Android debug (Pixel 6, JNI path) — verified post-fix only, the pre-fix crash repro was iOS-only and Android shares the same C++: app survives typing, logcat shows the errors, LogBox displays the worklet stack.
  • Happy path: phone/username/credit-card formatters still transform correctly on both platforms.

RunTransformer called the transformer through runSync and used the result
unconditionally. In release builds runSync is a raw jsi Function::call, so
an error thrown by the worklet propagated as jsi::JSError through the
UIKit text input delegate / Android TextWatcher and aborted the app. In
debug builds the worklets call guard reports the error to LogBox and
returns undefined, so the following asObject call threw with the same
result.

Catch jsi errors around the transformer invocation and result extraction,
log them to the UI runtime console, and fall back to leaving the input
untransformed. Add a throwing transformer card to the example app to
exercise the path.
@janicduplessis janicduplessis marked this pull request as ready for review July 1, 2026 23:15
@janicduplessis janicduplessis merged commit bbc9527 into main Jul 1, 2026
5 checks passed
@janicduplessis janicduplessis deleted the @janic/fix-throwing-transformer-crash branch July 1, 2026 23:15
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