Skip to content

WIP: Turbo Modules crash time context#6227

Open
alwx wants to merge 4 commits into
mainfrom
alwx/improvement/turbo-modules-chash-time-context
Open

WIP: Turbo Modules crash time context#6227
alwx wants to merge 4 commits into
mainfrom
alwx/improvement/turbo-modules-chash-time-context

Conversation

@alwx
Copy link
Copy Markdown
Contributor

@alwx alwx commented May 28, 2026

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

💡 Motivation and Context

💚 How did you test it?

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

@alwx alwx self-assigned this May 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • WIP: Turbo Modules crash time context by alwx in #6227
  • feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch by alwx in #6221
  • chore(deps): bump jwt in /samples/react-native by antonis in #6251
  • feat(profiling): Add measurements to Android profiling by antonis in #6250
  • chore(deps): update CLI to v3.5.0 by github-actions in #6248
  • chore(deps): bump jwt from 2.9.3 to 2.10.3 in /samples/react-native-macos by dependabot in #6247
  • chore(deps): bump jwt from 2.10.2 to 2.10.3 in /performance-tests by dependabot in #6246
  • feat(android): Warn when Gradle resolves unsupported sentry-android version by antonis in #6238
  • chore(deps): update JavaScript SDK to v10.56.0 by github-actions in #6249
  • chore(ci): Pin all GitHub Actions to full commit SHAs by antonis in #6243
  • fix(tracing): Enable fetch instrumentation when expo/fetch is active by antonis in #6226
  • fix: Bump tmp to 0.2.7 to resolve path traversal vulnerability by antonis in #6233
  • feat(logs): Add enableAutoConsoleLogs option to opt out of console capture by alwx in #6235
  • chore(deps): update JavaScript SDK to v10.55.0 by github-actions in #6222
  • chore(deps): update Sentry Android Gradle Plugin to v6.9.0 by github-actions in #6230
  • refactor(android): Convert sentry.gradle to Kotlin DSL (sentry.gradle.kts) by antonis in #6119

🤖 This preview updates automatically when you update the PR.

@alwx alwx changed the title Turbo Modules crash time context WIP: Turbo Modules crash time context May 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request

Generated by 🚫 dangerJS against 4f68d95

Comment thread packages/core/src/js/turbomodule/wrapTurboModule.ts
Comment thread packages/core/src/js/turbomodule/wrapTurboModule.ts Outdated
Comment thread packages/core/test/integrations/turboModuleContext.test.ts
Comment thread packages/core/test/turbomodule/wrapTurboModule.test.ts
@alwx alwx marked this pull request as ready for review June 3, 2026 13:09
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1d8f659. Configure here.

Comment thread packages/core/src/js/turbomodule/wrapTurboModule.ts
Comment thread packages/core/src/js/turbomodule/wrapTurboModule.ts
Comment thread packages/core/src/js/turbomodule/turboModuleTracker.ts Outdated
Comment thread packages/core/etc/sentry-react-native.api.md
Comment thread packages/core/src/js/turbomodule/wrapTurboModule.ts
Comment thread CHANGELOG.md Outdated
- Add `disableAutoUpload` option to Expo plugin to disable source map and debug symbol uploads ([#6195](https://github.com/getsentry/sentry-react-native/pull/6195))
- Expose `pauseAppHangTracking` and `resumeAppHangTracking` APIs on iOS ([#6192](https://github.com/getsentry/sentry-react-native/pull/6192))
- Better route and dynamic param extraction for Expo Router ([#6197](https://github.com/getsentry/sentry-react-native/pull/6197))
- Attach the active TurboModule method to native crash reports as `contexts.turbo_module` + `turbo_module.name` / `turbo_module.method` tags ([#6227](https://github.com/getsentry/sentry-react-native/pull/6227))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This need to move in the unreleased section

@@ -0,0 +1,135 @@
import { logger } from '@sentry/react';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to import from core like in other occurences

Suggested change
import { logger } from '@sentry/react';
import { logger } from '@sentry/core';

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`.
Copy link
Copy Markdown
Contributor

@antonis antonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your work on this Alex 🙇
Overall looks good. Left a few comments along with the agent feedback.
Let's also update the PR description and title for future reference.

spotlightIntegration,
stallTrackingIntegration,
timeToDisplayIntegration,
turboModuleContextIntegration,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

popTurboModuleCall clears scope that still has active calls when scopes interleave

In popTurboModuleCall (turboModuleTracker.ts), after removing a frame, the code looks only at the new stack top: if its scope differs from the popped frame's scope it calls clearScope(popped.scope). It never checks whether deeper frames still hold that same scope. Whenever scopes interleave on the stack (e.g. [A@scope1, B@scope2] LIFO-popping B, or [A@scope1, B@scope1, C@scope2] out-of-order-popping B), an active lower call on popped.scope silently loses its turbo_module context/tags until that call itself finishes and re-syncs. This degrades the crash-time context the integration is designed to provide.

Evidence
  • popTurboModuleCall in packages/core/src/js/turbomodule/turboModuleTracker.ts lines ~140-160 checks only stack[stack.length - 1] against popped.scope.
  • If the new top is on a different scope, it calls clearScope(popped.scope) unconditionally via setContext(CONTEXT_KEY, null) and resets the tag pair — even if a deeper frame in stack still holds that scope.
  • pushTurboModuleCall pins scope = args.scope ?? getCurrentScope(), so interleaving scopes on the stack is an explicitly supported case (the comment on InternalCall.scope calls this out).
  • popTurboModuleCall never scans stack for other frames with popped.scope, and tests in turboModuleTracker.test.ts do not cover multi-scope interleaving — only single-scope nesting.
Also found at 3 additional locations
  • packages/core/src/js/turbomodule/turboModuleTracker.ts:152-160
  • packages/core/etc/sentry-react-native.api.md:357-359
  • packages/core/etc/sentry-react-native.api.md:892

Identified by Warden code-review · AEP-X5X

for (const key of methodNames) {
if (skip.has(key)) {
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unguarded getter access in wrapping loop can throw unexpectedly

Reading target[key] without a try/catch can invoke getter-defined properties that throw, causing wrapTurboModule to propagate an unexpected exception and abort wrapping of all remaining methods — the try/catch only guards the assignment on line 97, not this read.

Evidence
  • collectMethodNames uses Object.getOwnPropertyNames, which includes getter-defined property names.
  • On line 60, target[key] performs a plain property access — if key refers to a getter, it is invoked here.
  • The only try/catch in the loop (lines 96–101) wraps target[key] = wrapper (the write), not this read.
  • An exception thrown by the getter would propagate out of wrapTurboModule, breaking module initialization for all remaining keys.
  • No test covers a getter-bearing TurboModule; JSI HostObject proxies under the New Architecture can expose properties as accessors.

Suggested fix: Wrap the property read in a try/catch so a throwing getter is treated like a non-wrappable property.

Suggested change
}
let original: unknown;
try {
original = target[key];
} catch {
// Getter threw — skip this property silently, same as a sealed write.
continue;
}

Identified by Warden code-review · P95-YE7

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.

2 participants