WIP: Turbo Modules crash time context #6227
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-351packages/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
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
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
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
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
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
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
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'`.