Repository navigation
Commit 9474ead
authored
Turbo Modules crash time context (#6227)
* Turbo Modules crash time context
* fixes
* fix(turbomodule): address review feedback
Addresses five review comments on the TurboModule crash-time context PR
(#6227):
- Pin the Scope on each tracker frame at push time. `popTurboModuleCall`
and `relabelTurboModuleCallKind` now always operate against the scope
captured at push, so an async call that spans a scope switch
(`withScope`, isolation-scope swaps) clears the right scope instead of
leaking stale `turbo_module` context onto the original. `pop` no
longer takes a `scope` argument — it was a footgun.
- Replace `setTag(key, undefined)` on clear with an empty-string
sentinel. The native `setTag(key: string, value: string)` TurboModule
spec requires a string; `undefined` would either be rejected or
silently dropped at the bridge. `setContext(key, null)` remains the
canonical "no active call" signal; the empty tags exist only to scrub
stale `name`/`method` from the previous call.
- Defer `wrappedModules.add(module)` until after at least one method
has been successfully wrapped. Previously, a JSI HostObject whose
methods materialise lazily was permanently locked out after the
first (empty) discovery pass — subsequent calls short-circuited even
though the module had since gained methods.
- Strengthen the sealed-module test to spy on `pushTurboModuleCall`
and assert exactly one push per real call. The previous assertion
only checked the post-call stack was empty, which is also true under
double-wrapping (each wrapper pushes and pops symmetrically).
- Move the changelog entry from the already-released `## 8.13.0`
section into `## Unreleased`.
* fix(turbomodule): address remaining PR review feedback
- Switch logger import to '@sentry/core' to match the convention used by
the rest of the codebase (per @antonis review).
- Guard the property read in wrapTurboModule's wrap loop. Some JSI
HostObject proxies under the New Architecture expose methods as
accessors, and a throwing getter would otherwise propagate out and
abort wrapping of all remaining methods on the module. A throwing
getter is now treated like a non-wrappable property and skipped.
- Fix popTurboModuleCall scope handling when scopes interleave on the
stack. Previously we only inspected the new top frame, so popping a
call from a stack like [A@s1, B@s2, C@s1] would clear s1's
turbo_module context even though A is still active on it. We now
walk the stack for the newest remaining frame on popped.scope and
re-sync onto it, only clearing the scope when no frames remain.
Adds tests for the throwing-getter path and for both the
preserve-on-interleave and clear-when-empty popTurboModuleCall cases.
* fix(turbomodule): prevent scope-sync recursion and harden wrapper edges
Addresses three review comments on #6227:
- Skip all scope-sync methods on RNSentry (`setContext`, `setTag`,
`setExtra`, `setUser`, `addBreadcrumb`, `clearBreadcrumbs`,
`setAttribute`, `setAttributes`, `removeAttribute`) in addition to
`addListener` / `removeListeners`. Without this the tracker recurses
infinitely on every TurboModule call: `pushTurboModuleCall` ->
`scope.setContext('turbo_module', ...)` -> `NATIVE.setContext` ->
`RNSentry.setContext` (wrapped) -> `pushTurboModuleCall` -> ... .
- Wrap the `.then` read in `isThenable` in try/catch. A user-defined
throwing `then` getter on a method's return value would otherwise
escape the wrapper and leak the in-flight tracker frame.
- Warn when methods are discovered but none could be wrapped (e.g.
`Object.freeze`d legacy `NativeModules.RNSentry` on the old
architecture, or read-only accessors). Previously this was a silent
no-op with no obvious cause for the missing crash context.
Tests added: scope-sync methods are not wrapped on RNSentry, throwing
`then` getter doesn't leak a frame, frozen module triggers the warning.
* fix(turbomodule): keep native context in sync after cross-scope pop
Addresses two unresolved review comments on #6227:
- Native SDKs (sentry-cocoa, sentry-java) share a single scope, but JS
has many Scope objects (global, isolation, withScope, ...). When a
cross-scope pop runs `clearScope` on the popped scope, the scope-sync
hook overwrites the native `turbo_module` context with null \u2014 even if
the global stack top still lives on a different JS scope with an
in-flight wrapped call. A crash following such a pop would land in
native without the active TurboModule attribution.
Fix: after updating the popped scope, re-sync the global stack top via
its own scope so the native scope ends up holding the correct active
call context. Dedup the write when the popped scope's new top *is* the
global top.
- Strengthen the `crash`-is-wrapped assertion in the recursion-guard
test. `expect(fakeModule.crash).not.toBe(jest.fn())` was trivially
true (every `jest.fn()` call returns a new reference), so it would
have passed even if the integration didn't wrap `crash` at all.
Capture the original mock before `setupOnce` and check the wrapper
replaced it (and lacks the `_isMockFunction` marker).
* fix(turbomodule): never let instrumentation break user TurboModule calls
Addresses the unresolved Warden finding on #6227.
Previously, the wrapper called `pushTurboModuleCall` *outside* its
`try` block. `pushTurboModuleCall` synchronously writes to the pinned
scope, and `scopeSync.ts`'s hooked `setContext` calls into
`NATIVE.setContext`, which throws `_NativeClientError` when the
RNSentry module isn't loaded (and any other native bridge error along
that path). A throw there would abort the wrapper before
`originalFn.apply` was reached \u2014 silently dropping the user's real
TurboModule call (including critical ones like `captureEnvelope`) and
surfacing an unexpected error to the caller. It also left the just-
pushed frame on the tracker stack with no matching pop, leaking the
`turbo_module` context.
Two-layer fix:
- `pushTurboModuleCall` is now atomic. If `syncToScope` throws, the
stack push is rolled back so the tracker stack stays consistent.
- The wrapper isolates every tracker interaction (push, pop, relabel)
in its own try/catch with a `logger.warn` on failure. The user's
call is always executed and its result/exception is always returned
to the caller. The worst case is loss of the `turbo_module`
attribution for that call \u2014 never a broken user invocation.
Tests added: atomic-push rollback on `syncToScope` throw; wrapper still
calls the original method (and returns its result) when push or pop
throws.
* Lint fixes
* chore(api-report): regenerate with workspace TypeScript 5.9.3
The committed API report had drifted from the format the workspace's
pinned TypeScript 5.9.3 produces \u2014 earlier regenerations had resolved
`tsc` via the system PATH (TS 4.9.5), which emits `number | undefined`
where 5.9.3 emits `number`, single-quoted vs double-quoted string
literals, and a different inlined form for `startIdleNavigationSpan`'s
options. CI's `api-report:check` (which always uses the workspace's TS)
caught the discrepancy.
Regenerate against the correct TS version so CI passes. The actual
public API surface is unchanged \u2014 only the textual representation in
the report file.1 parent 7a89652 commit 9474ead
12 files changed
Lines changed: 1244 additions & 0 deletions
File tree
- packages/core
- etc
- src/js
- integrations
- turbomodule
- test
- integrations
- turbomodule
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
357 | 360 | | |
358 | 361 | | |
359 | 362 | | |
| |||
378 | 381 | | |
379 | 382 | | |
380 | 383 | | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
381 | 387 | | |
382 | 388 | | |
383 | 389 | | |
| |||
530 | 536 | | |
531 | 537 | | |
532 | 538 | | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
533 | 542 | | |
534 | 543 | | |
535 | 544 | | |
536 | 545 | | |
537 | 546 | | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
538 | 555 | | |
539 | 556 | | |
540 | 557 | | |
| |||
808 | 825 | | |
809 | 826 | | |
810 | 827 | | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
811 | 849 | | |
812 | 850 | | |
813 | 851 | | |
| |||
852 | 890 | | |
853 | 891 | | |
854 | 892 | | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
855 | 898 | | |
856 | 899 | | |
857 | 900 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
157 | 157 | | |
158 | 158 | | |
159 | 159 | | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| |||
174 | 175 | | |
175 | 176 | | |
176 | 177 | | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
177 | 183 | | |
178 | 184 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
| 30 | + | |
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
0 commit comments