Skip to content

Commit 5fefdf7

Browse files
feat(sdk): Add Hermes Debug Info flag to React Native Context (#3290)
1 parent e5c9b8b commit 5fefdf7

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add Hermes Debug Info flag to React Native Context ([#3290](https://github.com/getsentry/sentry-react-native/pull/3290))
8+
- This flag equals `true` when Hermes Bundle contains Debug Info (Hermes Source Map was not emitted)
9+
310
## 5.9.2
411

512
### Fixes

src/js/integrations/reactnativeinfo.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface ReactNativeContext extends Context {
1818
hermes_version?: string;
1919
react_native_version: string;
2020
component_stack?: string;
21+
hermes_debug_info?: boolean;
2122
}
2223

2324
/** Loads React Native context at runtime */
@@ -50,8 +51,9 @@ export class ReactNativeInfo implements Integration {
5051
reactNativeContext.js_engine = 'hermes';
5152
const hermesVersion = getHermesVersion();
5253
if (hermesVersion) {
53-
reactNativeContext.hermes_version = getHermesVersion();
54+
reactNativeContext.hermes_version = hermesVersion;
5455
}
56+
reactNativeContext.hermes_debug_info = !isEventWithHermesBytecodeFrames(event);
5557
} else if (reactNativeError?.jsEngine) {
5658
reactNativeContext.js_engine = reactNativeError.jsEngine;
5759
}
@@ -76,3 +78,32 @@ export class ReactNativeInfo implements Integration {
7678
});
7779
}
7880
}
81+
82+
/**
83+
* Guess if the event contains frames with Hermes bytecode
84+
* (thus Hermes bundle doesn't contain debug info)
85+
* based on the event exception/threads frames.
86+
*
87+
* This function can be relied on only if Hermes is enabled!
88+
*
89+
* Hermes bytecode position is always line 1 and column 0-based number.
90+
* If Hermes bundle has debug info, the bytecode frames pos are calculated
91+
* back to the plain bundle source code positions and line will be > 1.
92+
*
93+
* Line 1 contains start time var, it's safe to assume it won't crash.
94+
* The above only applies when Hermes is enabled.
95+
*
96+
* Javascript/Hermes bytecode frames have platform === undefined.
97+
* Native (Java, ObjC, C++) frames have platform === 'android'/'ios'/'native'.
98+
*/
99+
function isEventWithHermesBytecodeFrames(event: Event): boolean {
100+
for (const value of event.exception?.values || event.threads?.values || []) {
101+
for (const frame of value.stacktrace?.frames || []) {
102+
// platform === undefined we assume it's javascript (only native frames use the platform attribute)
103+
if (frame.platform === undefined && frame.lineno === 1) {
104+
return true;
105+
}
106+
}
107+
}
108+
return false;
109+
}

test/integrations/reactnativeinfo.test.ts

+85
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ describe('React Native Info', () => {
4949
turbo_module: false,
5050
fabric: false,
5151
js_engine: 'hermes',
52+
hermes_debug_info: true,
5253
react_native_version: '1000.0.0-test',
5354
expo: false,
5455
},
@@ -148,6 +149,90 @@ describe('React Native Info', () => {
148149
test: 'context',
149150
});
150151
});
152+
153+
it('add hermes_debug_info to react_native_context based on exception frames (hermes bytecode frames present -> no debug info)', async () => {
154+
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
155+
156+
const mockedEvent: Event = {
157+
exception: {
158+
values: [
159+
{
160+
stacktrace: {
161+
frames: [
162+
{
163+
platform: 'java',
164+
lineno: 2,
165+
},
166+
{
167+
lineno: 1,
168+
},
169+
],
170+
},
171+
},
172+
],
173+
},
174+
};
175+
const actualEvent = await executeIntegrationFor(mockedEvent, {});
176+
177+
expectMocksToBeCalledOnce();
178+
expect(actualEvent?.contexts?.react_native_context?.hermes_debug_info).toEqual(false);
179+
});
180+
181+
it('does not hermes_debug_info to react_native_context based on threads frames (hermes bytecode frames present -> no debug info)', async () => {
182+
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
183+
184+
const mockedEvent: Event = {
185+
threads: {
186+
values: [
187+
{
188+
stacktrace: {
189+
frames: [
190+
{
191+
platform: 'java',
192+
lineno: 2,
193+
},
194+
{
195+
lineno: 1,
196+
},
197+
],
198+
},
199+
},
200+
],
201+
},
202+
};
203+
const actualEvent = await executeIntegrationFor(mockedEvent, {});
204+
205+
expectMocksToBeCalledOnce();
206+
expect(actualEvent?.contexts?.react_native_context?.hermes_debug_info).toEqual(false);
207+
});
208+
209+
it('adds hermes_debug_info to react_native_context (no hermes bytecode frames found -> debug info present)', async () => {
210+
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
211+
212+
const mockedEvent: Event = {
213+
threads: {
214+
values: [
215+
{
216+
stacktrace: {
217+
frames: [
218+
{
219+
platform: 'java',
220+
lineno: 2,
221+
},
222+
{
223+
lineno: 2,
224+
},
225+
],
226+
},
227+
},
228+
],
229+
},
230+
};
231+
const actualEvent = await executeIntegrationFor(mockedEvent, {});
232+
233+
expectMocksToBeCalledOnce();
234+
expect(actualEvent?.contexts?.react_native_context?.hermes_debug_info).toEqual(true);
235+
});
151236
});
152237

153238
function expectMocksToBeCalledOnce() {

0 commit comments

Comments
 (0)