Skip to content

Turbo Modules crash time context

f43a4f5
Select commit
Loading
Failed to load commit list.
Open

WIP: Turbo Modules crash time context #6227

Turbo Modules crash time context
f43a4f5
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden: find-bugs completed May 28, 2026 in 22m 50s

5 issues

find-bugs: Found 5 issues (3 medium, 2 low)

Medium

Double-tracking when `defineProperty` fails on a sealed proxy - `packages/core/src/js/turbomodule/wrapTurboModule.ts:74-88`

When Object.defineProperty throws (e.g. on a sealed proxy), WRAPPED_FLAG is never stamped on the module. A subsequent call to wrapTurboModule with the same module will bypass the if (maybeWrapped[WRAPPED_FLAG]) guard, re-wrap every method a second time, and push two tracker frames per invocation instead of one — corrupting the call stack and scope context. The comment in the catch block describes this exact outcome but incorrectly labels it "a no-op".

Also found at:

  • packages/core/src/js/turbomodule/index.ts:8
`Object.keys` in `wrapTurboModule` silently skips prototype methods on JSI TurboModule proxies - `packages/core/test/integrations/turboModuleContext.test.ts:21-25`

wrapTurboModule uses Object.keys(target) which only enumerates own enumerable properties; if a real JSI-backed TurboModule proxy exposes methods via its prototype chain (as some RN HostObject implementations do), nothing gets wrapped and the integration silently provides no crash context.

Also found at:

  • packages/core/src/js/integrations/default.ts:45
Async TurboModule calls always reported as `kind: 'sync'` in crash context - `packages/core/test/turbomodule/wrapTurboModule.test.ts:59-79`

The JSDoc for wrapTurboModule promises async calls are tracked as kind: 'async', and the inline comment says the frame is "relabeled for the scope on completion", but the .then() callbacks only call popTurboModuleCall — the kind field is never updated from 'sync'. Every TurboModule call in crash reports will show kind: 'sync'.

Also found at:

  • packages/core/etc/sentry-react-native.api.md:349-351
  • packages/core/src/js/integrations/turboModuleContext.ts:43

Low

Unsettled async TurboModule promise leaves stale frame on tracker stack - `packages/core/etc/sentry-react-native.api.md:349-351`

In wrapTurboModule.ts, async methods are only popped from the tracker via .then/rejection handlers on the returned Promise. If a wrapped TurboModule method returns a Promise that never settles (e.g. a native method that drops its resolver, fire-and-forget patterns, or post-crash JS continuation), the corresponding frame remains on stack indefinitely. In turboModuleTracker.ts::popTurboModuleCall, subsequent pops always restore scope to stack[stack.length - 1], so the orphaned frame becomes the perpetual newTop for any unrelated later pop, causing contexts.turbo_module and turbo_module.* tags to be misattributed to the abandoned call. clearScope is only reached when stack.length === 0, which never happens once a frame is leaked. There is no max stack size, TTL, or eviction to recover from this.

Test comment says 'Inner' finishes first but the code pops the outer call - `packages/core/test/turbomodule/turboModuleTracker.test.ts:82`

The comment on line 82 reads "Inner async finishes first — pop the outer one" but first was pushed before second (making it the outer/bottom frame), and popTurboModuleCall(first, scope) pops that outer frame — the opposite of what the comment claims. This will mislead anyone adding related tests or debugging the out-of-order path.


⏱ 21m 18s · 1.7M in / 171.3k out · $5.43

Annotations

Check warning on line 88 in packages/core/src/js/turbomodule/wrapTurboModule.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

Double-tracking when `defineProperty` fails on a sealed proxy

When `Object.defineProperty` throws (e.g. on a sealed proxy), `WRAPPED_FLAG` is never stamped on the module. A subsequent call to `wrapTurboModule` with the same module will bypass the `if (maybeWrapped[WRAPPED_FLAG])` guard, re-wrap every method a second time, and push **two** tracker frames per invocation instead of one — corrupting the call stack and scope context. The comment in the catch block describes this exact outcome but incorrectly labels it "a no-op".

Check warning on line 8 in packages/core/src/js/turbomodule/index.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

[HF7-EU3] Double-tracking when `defineProperty` fails on a sealed proxy (additional location)

When `Object.defineProperty` throws (e.g. on a sealed proxy), `WRAPPED_FLAG` is never stamped on the module. A subsequent call to `wrapTurboModule` with the same module will bypass the `if (maybeWrapped[WRAPPED_FLAG])` guard, re-wrap every method a second time, and push **two** tracker frames per invocation instead of one — corrupting the call stack and scope context. The comment in the catch block describes this exact outcome but incorrectly labels it "a no-op".

Check warning on line 25 in packages/core/test/integrations/turboModuleContext.test.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

`Object.keys` in `wrapTurboModule` silently skips prototype methods on JSI TurboModule proxies

`wrapTurboModule` uses `Object.keys(target)` which only enumerates own enumerable properties; if a real JSI-backed TurboModule proxy exposes methods via its prototype chain (as some RN HostObject implementations do), nothing gets wrapped and the integration silently provides no crash context.

Check warning on line 45 in packages/core/src/js/integrations/default.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

[B3Z-ZZF] `Object.keys` in `wrapTurboModule` silently skips prototype methods on JSI TurboModule proxies (additional location)

`wrapTurboModule` uses `Object.keys(target)` which only enumerates own enumerable properties; if a real JSI-backed TurboModule proxy exposes methods via its prototype chain (as some RN HostObject implementations do), nothing gets wrapped and the integration silently provides no crash context.

Check warning on line 79 in packages/core/test/turbomodule/wrapTurboModule.test.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

Async TurboModule calls always reported as `kind: 'sync'` in crash context

The JSDoc for `wrapTurboModule` promises async calls are tracked as `kind: 'async'`, and the inline comment says the frame is "relabeled for the scope on completion", but the `.then()` callbacks only call `popTurboModuleCall` — the `kind` field is never updated from `'sync'`. Every TurboModule call in crash reports will show `kind: 'sync'`.

Check warning on line 351 in packages/core/etc/sentry-react-native.api.md

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

[PCL-LYA] Async TurboModule calls always reported as `kind: 'sync'` in crash context (additional location)

The JSDoc for `wrapTurboModule` promises async calls are tracked as `kind: 'async'`, and the inline comment says the frame is "relabeled for the scope on completion", but the `.then()` callbacks only call `popTurboModuleCall` — the `kind` field is never updated from `'sync'`. Every TurboModule call in crash reports will show `kind: 'sync'`.

Check warning on line 43 in packages/core/src/js/integrations/turboModuleContext.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

[PCL-LYA] Async TurboModule calls always reported as `kind: 'sync'` in crash context (additional location)

The JSDoc for `wrapTurboModule` promises async calls are tracked as `kind: 'async'`, and the inline comment says the frame is "relabeled for the scope on completion", but the `.then()` callbacks only call `popTurboModuleCall` — the `kind` field is never updated from `'sync'`. Every TurboModule call in crash reports will show `kind: 'sync'`.