Skip to content

Commit e64a66f

Browse files
feat(rn): Add Native Linked Errors (RN Mixed stack traces) (#3201)
1 parent a36d5e1 commit e64a66f

File tree

18 files changed

+1138
-5
lines changed

18 files changed

+1138
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
### Features
66

7+
- Add support for React Native mixed stacktraces ([#3201](https://github.com/getsentry/sentry-react-native/pull/3201))
8+
9+
In the current `react-native@nightly` (`0.73.0-nightly-20230809-cb60e5c67`) JS errors from native modules can
10+
contain native JVM or Objective-C exception stack trace. Both JS and native stack trace
11+
are processed by default no configuration needed.
12+
713
- Add `tracePropagationTargets` option ([#3230](https://github.com/getsentry/sentry-react-native/pull/3230))
814

915
This release adds support for [distributed tracing](https://docs.sentry.io/platforms/react-native/usage/distributed-tracing/)

RNSentryCocoaTester/Podfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ platform :ios, '12.4'
55
target 'RNSentryCocoaTesterTests' do
66
use_react_native!()
77
pod 'RNSentry', :path => '../RNSentry.podspec'
8+
pod 'OCMock', '3.9.1'
89
end

RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
/* Begin PBXFileReference section */
1515
1482D5685A340AB93348A43D /* Pods-RNSentryCocoaTesterTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.release.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.release.xcconfig"; sourceTree = "<group>"; };
1616
3360898D29524164007C7730 /* RNSentryCocoaTesterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RNSentryCocoaTesterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
17+
338739072A7D7D2800950DDD /* RNSentry+initNativeSdk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentry+initNativeSdk.h"; sourceTree = "<group>"; };
1718
33F58ACF2977037D008F60EA /* RNSentry+initNativeSdk.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "RNSentry+initNativeSdk.mm"; sourceTree = "<group>"; };
1819
650CB718ACFBD05609BF2126 /* libPods-RNSentryCocoaTesterTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNSentryCocoaTesterTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
1920
E2321E7CFA55AB617247098E /* Pods-RNSentryCocoaTesterTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.debug.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.debug.xcconfig"; sourceTree = "<group>"; };
@@ -62,6 +63,7 @@
6263
isa = PBXGroup;
6364
children = (
6465
33F58ACF2977037D008F60EA /* RNSentry+initNativeSdk.mm */,
66+
338739072A7D7D2800950DDD /* RNSentry+initNativeSdk.h */,
6567
);
6668
path = RNSentryCocoaTesterTests;
6769
sourceTree = "<group>";
@@ -249,6 +251,7 @@
249251
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
250252
GCC_WARN_UNUSED_FUNCTION = YES;
251253
GCC_WARN_UNUSED_VARIABLE = YES;
254+
HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/Sentry/Sources/Sentry/include\"";
252255
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
253256
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
254257
MTL_FAST_MATH = YES;
@@ -301,6 +304,7 @@
301304
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
302305
GCC_WARN_UNUSED_FUNCTION = YES;
303306
GCC_WARN_UNUSED_VARIABLE = YES;
307+
HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/Sentry/Sources/Sentry/include\"";
304308
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
305309
MTL_ENABLE_DEBUG_INFO = NO;
306310
MTL_FAST_MATH = YES;
@@ -316,6 +320,49 @@
316320
CODE_SIGN_STYLE = Automatic;
317321
CURRENT_PROJECT_VERSION = 1;
318322
GENERATE_INFOPLIST_FILE = YES;
323+
HEADER_SEARCH_PATHS = (
324+
"$(inherited)",
325+
"\"${PODS_ROOT}/Headers/Public\"",
326+
"\"${PODS_ROOT}/Headers/Public/DoubleConversion\"",
327+
"\"${PODS_ROOT}/Headers/Public/FBLazyVector\"",
328+
"\"${PODS_ROOT}/Headers/Public/OCMock\"",
329+
"\"${PODS_ROOT}/Headers/Public/RCT-Folly\"",
330+
"\"${PODS_ROOT}/Headers/Public/RCTRequired\"",
331+
"\"${PODS_ROOT}/Headers/Public/RCTTypeSafety\"",
332+
"\"${PODS_ROOT}/Headers/Public/RNSentry\"",
333+
"\"${PODS_ROOT}/Headers/Public/React-Codegen\"",
334+
"\"${PODS_ROOT}/Headers/Public/React-Core\"",
335+
"\"${PODS_ROOT}/Headers/Public/React-NativeModulesApple\"",
336+
"\"${PODS_ROOT}/Headers/Public/React-RCTAnimation\"",
337+
"\"${PODS_ROOT}/Headers/Public/React-RCTAppDelegate\"",
338+
"\"${PODS_ROOT}/Headers/Public/React-RCTBlob\"",
339+
"\"${PODS_ROOT}/Headers/Public/React-RCTText\"",
340+
"\"${PODS_ROOT}/Headers/Public/React-callinvoker\"",
341+
"\"${PODS_ROOT}/Headers/Public/React-cxxreact\"",
342+
"\"${PODS_ROOT}/Headers/Public/React-debug\"",
343+
"\"${PODS_ROOT}/Headers/Public/React-hermes\"",
344+
"\"${PODS_ROOT}/Headers/Public/React-jsi\"",
345+
"\"${PODS_ROOT}/Headers/Public/React-jsiexecutor\"",
346+
"\"${PODS_ROOT}/Headers/Public/React-jsinspector\"",
347+
"\"${PODS_ROOT}/Headers/Public/React-logger\"",
348+
"\"${PODS_ROOT}/Headers/Public/React-perflogger\"",
349+
"\"${PODS_ROOT}/Headers/Public/React-runtimeexecutor\"",
350+
"\"${PODS_ROOT}/Headers/Public/React-runtimescheduler\"",
351+
"\"${PODS_ROOT}/Headers/Public/React-utils\"",
352+
"\"${PODS_ROOT}/Headers/Public/ReactCommon\"",
353+
"\"${PODS_ROOT}/Headers/Public/Sentry\"",
354+
"\"${PODS_ROOT}/Headers/Public/SocketRocket\"",
355+
"\"${PODS_ROOT}/Headers/Public/Yoga\"",
356+
"\"${PODS_ROOT}/Headers/Public/fmt\"",
357+
"\"${PODS_ROOT}/Headers/Public/glog\"",
358+
"\"${PODS_ROOT}/Headers/Public/hermes-engine\"",
359+
"\"${PODS_ROOT}/Headers/Public/libevent\"",
360+
"\"$(PODS_ROOT)/DoubleConversion\"",
361+
"\"$(PODS_ROOT)/boost\"",
362+
"\"$(PODS_ROOT)/Headers/Private/React-Core\"",
363+
"\"$(PODS_TARGET_SRCROOT)/include/\"",
364+
"\"${PODS_ROOT}/Sentry/Sources/Sentry/include\"",
365+
);
319366
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
320367
MARKETING_VERSION = 1.0;
321368
PRODUCT_BUNDLE_IDENTIFIER = io.sentry.RNSentryCocoaTesterTests;
@@ -335,6 +382,49 @@
335382
CODE_SIGN_STYLE = Automatic;
336383
CURRENT_PROJECT_VERSION = 1;
337384
GENERATE_INFOPLIST_FILE = YES;
385+
HEADER_SEARCH_PATHS = (
386+
"$(inherited)",
387+
"\"${PODS_ROOT}/Headers/Public\"",
388+
"\"${PODS_ROOT}/Headers/Public/DoubleConversion\"",
389+
"\"${PODS_ROOT}/Headers/Public/FBLazyVector\"",
390+
"\"${PODS_ROOT}/Headers/Public/OCMock\"",
391+
"\"${PODS_ROOT}/Headers/Public/RCT-Folly\"",
392+
"\"${PODS_ROOT}/Headers/Public/RCTRequired\"",
393+
"\"${PODS_ROOT}/Headers/Public/RCTTypeSafety\"",
394+
"\"${PODS_ROOT}/Headers/Public/RNSentry\"",
395+
"\"${PODS_ROOT}/Headers/Public/React-Codegen\"",
396+
"\"${PODS_ROOT}/Headers/Public/React-Core\"",
397+
"\"${PODS_ROOT}/Headers/Public/React-NativeModulesApple\"",
398+
"\"${PODS_ROOT}/Headers/Public/React-RCTAnimation\"",
399+
"\"${PODS_ROOT}/Headers/Public/React-RCTAppDelegate\"",
400+
"\"${PODS_ROOT}/Headers/Public/React-RCTBlob\"",
401+
"\"${PODS_ROOT}/Headers/Public/React-RCTText\"",
402+
"\"${PODS_ROOT}/Headers/Public/React-callinvoker\"",
403+
"\"${PODS_ROOT}/Headers/Public/React-cxxreact\"",
404+
"\"${PODS_ROOT}/Headers/Public/React-debug\"",
405+
"\"${PODS_ROOT}/Headers/Public/React-hermes\"",
406+
"\"${PODS_ROOT}/Headers/Public/React-jsi\"",
407+
"\"${PODS_ROOT}/Headers/Public/React-jsiexecutor\"",
408+
"\"${PODS_ROOT}/Headers/Public/React-jsinspector\"",
409+
"\"${PODS_ROOT}/Headers/Public/React-logger\"",
410+
"\"${PODS_ROOT}/Headers/Public/React-perflogger\"",
411+
"\"${PODS_ROOT}/Headers/Public/React-runtimeexecutor\"",
412+
"\"${PODS_ROOT}/Headers/Public/React-runtimescheduler\"",
413+
"\"${PODS_ROOT}/Headers/Public/React-utils\"",
414+
"\"${PODS_ROOT}/Headers/Public/ReactCommon\"",
415+
"\"${PODS_ROOT}/Headers/Public/Sentry\"",
416+
"\"${PODS_ROOT}/Headers/Public/SocketRocket\"",
417+
"\"${PODS_ROOT}/Headers/Public/Yoga\"",
418+
"\"${PODS_ROOT}/Headers/Public/fmt\"",
419+
"\"${PODS_ROOT}/Headers/Public/glog\"",
420+
"\"${PODS_ROOT}/Headers/Public/hermes-engine\"",
421+
"\"${PODS_ROOT}/Headers/Public/libevent\"",
422+
"\"$(PODS_ROOT)/DoubleConversion\"",
423+
"\"$(PODS_ROOT)/boost\"",
424+
"\"$(PODS_ROOT)/Headers/Private/React-Core\"",
425+
"\"$(PODS_TARGET_SRCROOT)/include/\"",
426+
"\"${PODS_ROOT}/Sentry/Sources/Sentry/include\"",
427+
);
338428
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
339429
MARKETING_VERSION = 1.0;
340430
PRODUCT_BUNDLE_IDENTIFIER = io.sentry.RNSentryCocoaTesterTests;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#import <Foundation/Foundation.h>
2+
#import <RNSentry/RNSentry.h>
3+
4+
@interface
5+
SentrySDK (PrivateTests)
6+
- (nullable SentryOptions *) options;
7+
@end
8+
9+
@interface SentryBinaryImageInfo : NSObject
10+
@property (nonatomic, strong) NSString *name;
11+
@property (nonatomic) uint64_t address;
12+
@property (nonatomic) uint64_t size;
13+
@end
14+
15+
@interface SentryBinaryImageCache : NSObject
16+
@property (nonatomic, readonly, class) SentryBinaryImageCache *shared;
17+
- (void)start;
18+
- (void)stop;
19+
- (nullable SentryBinaryImageInfo *)imageByAddress:(const uint64_t)address;
20+
@end
21+
22+
@interface SentryDependencyContainer : NSObject
23+
+ (instancetype)sharedInstance;
24+
@property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider;
25+
@end

RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentry+initNativeSdk.mm

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
#import "RNSentry+initNativeSdk.h"
2+
#import <OCMock/OCMock.h>
13
#import <UIKit/UIKit.h>
24
#import <XCTest/XCTest.h>
3-
#import <Sentry/SentryOptions.h>
4-
#import <Sentry/SentryEvent.h>
5-
#import "RNSentry.h"
5+
#import <RNSentry/RNSentry.h>
66

77
@interface RNSentryInitNativeSdkTests : XCTestCase
88

@@ -168,4 +168,123 @@ - (void)testEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten
168168
XCTAssertEqual(testEvent.tags[@"event.environment"], @"native");
169169
}
170170

171+
void (^expectRejecterNotCalled)(NSString*, NSString*, NSError*) = ^(NSString *code, NSString *message, NSError *error) {
172+
@throw [NSException exceptionWithName:@"Promise Rejector should not be called." reason:nil userInfo:nil];
173+
};
174+
175+
uint64_t MOCKED_SYMBOL_ADDRESS = 123;
176+
char const* MOCKED_SYMBOL_NAME = "symbolicatedname";
177+
178+
int sucessfulSymbolicate(const void *, Dl_info *info){
179+
info->dli_saddr = (void *) MOCKED_SYMBOL_ADDRESS;
180+
info->dli_sname = MOCKED_SYMBOL_NAME;
181+
return 1;
182+
}
183+
184+
- (void)prepareNativeFrameMocksWithLocalSymbolication: (BOOL) debug
185+
{
186+
SentryOptions* sentryOptions = [[SentryOptions alloc] init];
187+
sentryOptions.debug = debug; //no local symbolication
188+
189+
id sentrySDKMock = OCMClassMock([SentrySDK class]);
190+
OCMStub([(SentrySDK*) sentrySDKMock options]).andReturn(sentryOptions);
191+
192+
id sentryBinaryImageInfoMockOne = OCMClassMock([SentryBinaryImageInfo class]);
193+
OCMStub([(SentryBinaryImageInfo*) sentryBinaryImageInfoMockOne address]).andReturn([@112233 unsignedLongLongValue]);
194+
OCMStub([sentryBinaryImageInfoMockOne name]).andReturn(@"testnameone");
195+
196+
id sentryBinaryImageInfoMockTwo = OCMClassMock([SentryBinaryImageInfo class]);
197+
OCMStub([(SentryBinaryImageInfo*) sentryBinaryImageInfoMockTwo address]).andReturn([@112233 unsignedLongLongValue]);
198+
OCMStub([sentryBinaryImageInfoMockTwo name]).andReturn(@"testnametwo");
199+
200+
id sentryBinaryImageCacheMock = OCMClassMock([SentryBinaryImageCache class]);
201+
OCMStub(ClassMethod([sentryBinaryImageCacheMock shared])).andReturn(sentryBinaryImageCacheMock);
202+
OCMStub([sentryBinaryImageCacheMock imageByAddress:[@123 unsignedLongLongValue]]).andReturn(sentryBinaryImageInfoMockOne);
203+
OCMStub([sentryBinaryImageCacheMock imageByAddress:[@456 unsignedLongLongValue]]).andReturn(sentryBinaryImageInfoMockTwo);
204+
205+
NSDictionary* serializedDebugImage = @{
206+
@"uuid": @"mockuuid",
207+
@"debug_id": @"mockdebugid",
208+
@"type": @"macho",
209+
@"image_addr": @"0x000000000001b669",
210+
};
211+
id sentryDebugImageMock = OCMClassMock([SentryDebugMeta class]);
212+
OCMStub([sentryDebugImageMock serialize]).andReturn(serializedDebugImage);
213+
214+
id sentryDebugImageProviderMock = OCMClassMock([SentryDebugImageProvider class]);
215+
OCMStub([sentryDebugImageProviderMock getDebugImagesForAddresses:[NSSet setWithObject:@"0x000000000001b669"] isCrash:false]).andReturn(@[sentryDebugImageMock]);
216+
217+
id sentryDependencyContainerMock = OCMClassMock([SentryDependencyContainer class]);
218+
OCMStub(ClassMethod([sentryDependencyContainerMock sharedInstance])).andReturn(sentryDependencyContainerMock);
219+
OCMStub([sentryDependencyContainerMock debugImageProvider]).andReturn(sentryDebugImageProviderMock);
220+
}
221+
222+
- (void)testFetchNativeStackFramesByInstructionsServerSymbolication
223+
{
224+
[self prepareNativeFrameMocksWithLocalSymbolication:NO];
225+
RNSentry* rnSentry = [[RNSentry alloc] init];
226+
NSDictionary* actual = [rnSentry fetchNativeStackFramesBy: @[@123, @456]
227+
symbolicate: sucessfulSymbolicate];
228+
229+
NSDictionary* expected = @{
230+
@"debugMetaImages": @[
231+
@{
232+
@"uuid": @"mockuuid",
233+
@"debug_id": @"mockdebugid",
234+
@"type": @"macho",
235+
@"image_addr": @"0x000000000001b669",
236+
},
237+
],
238+
@"frames": @[
239+
@{
240+
@"package": @"testnameone",
241+
@"in_app": @NO,
242+
@"platform": @"cocoa",
243+
@"instruction_addr": @"0x000000000000007b", //123
244+
@"image_addr": @"0x000000000001b669", //112233
245+
},
246+
@{
247+
@"package": @"testnametwo",
248+
@"in_app": @NO,
249+
@"platform": @"cocoa",
250+
@"instruction_addr": @"0x00000000000001c8", //456
251+
@"image_addr": @"0x000000000001b669", //445566
252+
},
253+
],
254+
};
255+
XCTAssertTrue([actual isEqualToDictionary:expected]);
256+
}
257+
258+
- (void)testFetchNativeStackFramesByInstructionsOnDeviceSymbolication
259+
{
260+
[self prepareNativeFrameMocksWithLocalSymbolication:YES];
261+
RNSentry* rnSentry = [[RNSentry alloc] init];
262+
NSDictionary* actual = [rnSentry fetchNativeStackFramesBy: @[@123, @456]
263+
symbolicate: sucessfulSymbolicate];
264+
265+
NSDictionary* expected = @{
266+
@"frames": @[
267+
@{
268+
@"function": @"symbolicatedname",
269+
@"package": @"testnameone",
270+
@"in_app": @NO,
271+
@"platform": @"cocoa",
272+
@"symbol_addr": @"0x000000000000007b", //123
273+
@"instruction_addr": @"0x000000000000007b", //123
274+
@"image_addr": @"0x000000000001b669", //112233
275+
},
276+
@{
277+
@"function": @"symbolicatedname",
278+
@"package": @"testnametwo",
279+
@"in_app": @NO,
280+
@"platform": @"cocoa",
281+
@"symbol_addr": @"0x000000000000007b", //123
282+
@"instruction_addr": @"0x00000000000001c8", //456
283+
@"image_addr": @"0x000000000001b669", //445566
284+
},
285+
],
286+
};
287+
XCTAssertTrue([actual isEqualToDictionary:expected]);
288+
}
289+
171290
@end

android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,10 @@ public void fetchNativeSdkInfo(Promise promise) {
704704
}
705705
}
706706

707+
public void fetchNativePackageName(Promise promise) {
708+
promise.resolve(packageInfo.packageName);
709+
}
710+
707711
private void setEventOriginTag(SentryEvent event) {
708712
SdkVersion sdk = event.getSdk();
709713
if (sdk != null) {

android/src/newarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,14 @@ public WritableMap startProfiling() {
133133
public WritableMap stopProfiling() {
134134
return this.impl.stopProfiling();
135135
}
136+
137+
@Override
138+
public void fetchNativePackageName(Promise promise) {
139+
this.impl.fetchNativePackageName(promise);
140+
}
141+
142+
@Override
143+
public void fetchNativeStackFramesBy(Promise promise) {
144+
// Not used on Android
145+
}
136146
}

android/src/oldarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,14 @@ public WritableMap startProfiling() {
132132
public WritableMap stopProfiling() {
133133
return this.impl.stopProfiling();
134134
}
135+
136+
@ReactMethod
137+
public void fetchNativePackageName(Promise promise) {
138+
this.impl.fetchNativePackageName(promise);
139+
}
140+
141+
@ReactMethod
142+
public void fetchNativeStackFramesBy(Promise promise) {
143+
// Not used on Android
144+
}
135145
}

ios/RNSentry.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,31 @@
44
#import "RCTBridge.h"
55
#endif
66

7+
#import <dlfcn.h>
8+
9+
#import <Sentry/Sentry.h>
710
#import <Sentry/SentryOptions.h>
11+
#import <Sentry/SentryDebugImageProvider.h>
12+
13+
typedef int (*SymbolicateCallbackType)(const void *, Dl_info *);
14+
15+
@interface SentryDebugImageProvider ()
16+
- (NSArray<SentryDebugMeta *> * _Nonnull)getDebugImagesForAddresses:(NSSet<NSString *> * _Nonnull)addresses isCrash:(BOOL)isCrash;
17+
@end
18+
19+
@interface
20+
SentrySDK (Private)
21+
@property (nonatomic, nullable, readonly, class) SentryOptions *options;
22+
@end
823

924
@interface RNSentry : NSObject <RCTBridgeModule>
1025

1126
- (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options
1227
error:(NSError *_Nullable*_Nonnull)errorPointer;
1328

14-
- (void)setEventOriginTag:(SentryEvent *)event;
29+
- (void) setEventOriginTag: (SentryEvent*) event;
30+
31+
- (NSDictionary*_Nonnull) fetchNativeStackFramesBy: (NSArray<NSNumber*>*)instructionsAddr
32+
symbolicate: (SymbolicateCallbackType) symbolicate;
1533

1634
@end

0 commit comments

Comments
 (0)