Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Fixes

- Fix duplicate error reporting on iOS with New Architecture ([#5532](https://github.com/getsentry/sentry-react-native/pull/5532))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we specify if this was fixed on a specific version of React Native onwards?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this behavior is part of New Architecture's error handling, so the fix applies to all RN versions with New Architecture enabled (0.69+), not just a specific version

- Fix for missing `replay_id` from metrics ([#5483](https://github.com/getsentry/sentry-react-native/pull/5483))
- Skip span ID check when standalone mode is enabled ([#5493](https://github.com/getsentry/sentry-react-native/pull/5493))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,80 @@ - (void)testIgnoreErrorsRegexAndStringBothWork
XCTAssertNotNil(result3, @"Event with non-matching error should not be dropped");
}

- (void)testBeforeSendFiltersOutUnhandledJSException
{
RNSentry *rnSentry = [[RNSentry alloc] init];
NSError *error = nil;
NSMutableDictionary *mockedOptions = [@{
@"dsn" : @"https://abc@def.ingest.sentry.io/1234567",
} mutableCopy];
mockedOptions = [rnSentry prepareOptions:mockedOptions];
SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions
isSessionReplayEnabled:NO
error:&error];
XCTAssertNotNil(options);
XCTAssertNil(error);

SentryEvent *event = [[SentryEvent alloc] init];
SentryException *exception = [SentryException alloc];
exception.type = @"Unhandled JS Exception";
exception.value = @"Error: Test error";
event.exceptions = @[ exception ];
SentryEvent *result = options.beforeSend(event);
XCTAssertNil(result, @"Event with Unhandled JS Exception should be dropped");
}

- (void)testBeforeSendFiltersOutJSErrorCppException
{
RNSentry *rnSentry = [[RNSentry alloc] init];
NSError *error = nil;
NSMutableDictionary *mockedOptions = [@{
@"dsn" : @"https://abc@def.ingest.sentry.io/1234567",
} mutableCopy];
mockedOptions = [rnSentry prepareOptions:mockedOptions];
SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions
isSessionReplayEnabled:NO
error:&error];
XCTAssertNotNil(options);
XCTAssertNil(error);

// Test C++ exception with ExceptionsManager.reportException in value (actual format from New
// Architecture) The exception type is "C++ Exception" and the value contains the mangled name
// and error message
SentryEvent *event1 = [[SentryEvent alloc] init];
SentryException *exception1 = [SentryException alloc];
exception1.type = @"C++ Exception";
exception1.value = @"N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an "
@"exception: Unhandled JS Exception: Error: Test error";
event1.exceptions = @[ exception1 ];
SentryEvent *result1 = options.beforeSend(event1);
XCTAssertNil(
result1, @"Event with ExceptionsManager.reportException in value should be dropped");

// Test exception value containing ExceptionsManager.reportException (alternative format)
SentryEvent *event2 = [[SentryEvent alloc] init];
SentryException *exception2 = [SentryException alloc];
exception2.type = @"SomeOtherException";
exception2.value = @"ExceptionsManager.reportException raised an exception: Unhandled JS "
@"Exception: Error: Test";
event2.exceptions = @[ exception2 ];
SentryEvent *result2 = options.beforeSend(event2);
XCTAssertNil(
result2, @"Event with ExceptionsManager.reportException in value should be dropped");

// Test that legitimate C++ exceptions without ExceptionsManager.reportException are not
// filtered
SentryEvent *event3 = [[SentryEvent alloc] init];
SentryException *exception3 = [SentryException alloc];
exception3.type = @"C++ Exception";
exception3.value = @"std::runtime_error: Some other C++ error occurred";
event3.exceptions = @[ exception3 ];
SentryEvent *result3 = options.beforeSend(event3);
XCTAssertNotNil(result3,
@"Legitimate C++ exception without ExceptionsManager.reportException should not be "
@"dropped");
}

- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDefault
{
NSError *error = nil;
Expand Down
13 changes: 13 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ - (NSMutableDictionary *)prepareOptions:(NSDictionary *)options
return nil;
}

// With New Architecture, React Native wraps JS errors in C++ exceptions.
// These exceptions are caught by the native crash handler and should be filtered out
// since the JS error is already reported by the JS error handler.
// The key indicator is "ExceptionsManager.reportException" in the exception value,
// which is React Native's mechanism for reporting JS errors to the native layer.
for (SentryException *exception in event.exceptions) {
if (nil != exception.value &&
[exception.value rangeOfString:@"ExceptionsManager.reportException"].location
!= NSNotFound) {
return nil;
}
}

// Regex and Str are set when one of them has value so we only need to check one of them.
if (self->_ignoreErrorPatternsStr || self->_ignoreErrorPatternsRegex) {
for (SentryException *exception in event.exceptions) {
Expand Down
Loading