diff --git a/CHANGELOG.md b/CHANGELOG.md index e6186ca4f1..f21ad01bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes +- Preserve Hermes internal bytecode frames as non-application frames when rewriting Expo stack traces ([#6848](https://github.com/getsentry/sentry-react-native/pull/6848)) - Fix stale `turbo_module.*` tags on Android native crashes ([#6823](https://github.com/getsentry/sentry-react-native/pull/6823)) - Forward image attachments to Spotlight instead of stripping them from the envelope ([#6818](https://github.com/getsentry/sentry-react-native/pull/6818)) - Don't crash the iOS app when Sentry is initialized with an invalid DSN ([#6825](https://github.com/getsentry/sentry-react-native/pull/6825)) diff --git a/packages/core/src/js/integrations/rewriteframes.ts b/packages/core/src/js/integrations/rewriteframes.ts index 22bdb475ce..5d81062815 100644 --- a/packages/core/src/js/integrations/rewriteframes.ts +++ b/packages/core/src/js/integrations/rewriteframes.ts @@ -46,6 +46,13 @@ export function createReactNativeRewriteFrames(): Integration { frame.colno += 1; } + // Hermes runtime bytecode is separate from the Expo application bundle. + if (frame.filename === 'InternalBytecode.js' || frame.filename === '/InternalBytecode.js') { + frame.in_app = false; + frame.filename = 'app:///InternalBytecode.js'; + return frame; + } + // Expo adds hash to the end of bundle names if (isExpo() && Platform.OS === 'android') { frame.filename = ANDROID_DEFAULT_BUNDLE_NAME; @@ -58,10 +65,6 @@ export function createReactNativeRewriteFrames(): Integration { } const appPrefix = 'app://'; - // https://github.com/getsentry/sentry-react-native/issues/3348 - if (frame.filename === '/InternalBytecode.js') { - frame.in_app = false; - } // We always want to have a triple slash frame.filename = frame.filename.indexOf('/') === 0 ? `${appPrefix}${frame.filename}` : `${appPrefix}/${frame.filename}`; diff --git a/packages/core/test/integrations/rewriteframes.test.ts b/packages/core/test/integrations/rewriteframes.test.ts index 6bc9825328..7c0a2aab5f 100644 --- a/packages/core/test/integrations/rewriteframes.test.ts +++ b/packages/core/test/integrations/rewriteframes.test.ts @@ -865,6 +865,64 @@ describe('RewriteFrames', () => { }); }); + describe.each([ + { platform: 'ios' as const, expo: true, bundle: 'app:///main.jsbundle' }, + { platform: 'android' as const, expo: true, bundle: 'app:///index.android.bundle' }, + { platform: 'ios' as const, expo: false, bundle: 'app:///entry.hbc' }, + { platform: 'android' as const, expo: false, bundle: 'app:///entry.hbc' }, + ])('Hermes on $platform with Expo=$expo', ({ platform, expo, bundle }) => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it.each(['InternalBytecode.js', '/InternalBytecode.js', '/build/hermes/lib/InternalBytecode/InternalBytecode.js'])( + 'keeps %s frames separate from the application bundle', + async filename => { + mockFunction(isExpo).mockReturnValue(expo); + mockFunction(isHermesEnabled).mockReturnValue(true); + jest.replaceProperty(Platform, 'OS', platform); + + const error = { + message: 'Network request failed', + name: 'Error', + stack: + 'Error: Network request failed\n' + + ' at fetchToken (address at /updates/entry.hbc:1:4344175)\n' + + ` at tryCallOne (address at ${filename}:1:1180)\n` + + ` at anonymous (address at ${filename}:1:1874)\n` + + ' at apply (native)', + }; + + const exception = await exceptionFromError(error); + + expect(exception?.stacktrace?.frames).toEqual([ + { filename: 'native', function: 'apply', in_app: true }, + { + filename: 'app:///InternalBytecode.js', + function: 'anonymous', + lineno: 1, + colno: 1875, + in_app: false, + }, + { + filename: 'app:///InternalBytecode.js', + function: 'tryCallOne', + lineno: 1, + colno: 1181, + in_app: false, + }, + { + filename: bundle, + function: 'fetchToken', + lineno: 1, + colno: 4344176, + in_app: true, + }, + ]); + }, + ); + }); + it('InternalBytecode should be flaged as not InApp', async () => { mockFunction(isHermesEnabled).mockReturnValue(true);