Skip to content

Commit 071ae5c

Browse files
authored
feat: Add experimental flag enableUnhandledCPPExceptionsV2 on iOS (#4975)
* feat: Add experimental flag `enableUnhandledCPPExceptionsV2` on iOS * Adds changelog * Use an RNSentryExperimentalOptions Obj-C wrapper to access the property * Fixes lint issues
1 parent ed5d418 commit 071ae5c

File tree

7 files changed

+161
-0
lines changed

7 files changed

+161
-0
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@
88
99
## Unreleased
1010

11+
### Features
12+
13+
- Add experimental flag `enableUnhandledCPPExceptionsV2` on iOS ([#4975](https://github.com/getsentry/sentry-react-native/pull/4975))
14+
15+
```js
16+
import * as Sentry from '@sentry/react-native';
17+
18+
Sentry.init({
19+
_experiments: {
20+
enableUnhandledCPPExceptionsV2: true,
21+
},
22+
});
23+
```
24+
1125
### Dependencies
1226

1327
- Bump CLI from v2.46.0 to v2.47.0 ([#4979](https://github.com/getsentry/sentry-react-native/pull/4979))

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,82 @@ - (void)testCreateOptionsWithDictionarySpotlightZero
239239
XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight");
240240
}
241241

242+
- (void)testCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Enabled
243+
{
244+
RNSentry *rnSentry = [[RNSentry alloc] init];
245+
NSError *error = nil;
246+
247+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
248+
@"dsn" : @"https://[email protected]/123456",
249+
@"_experiments" : @ {
250+
@"enableUnhandledCPPExceptionsV2" : @YES,
251+
},
252+
};
253+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
254+
error:&error];
255+
256+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
257+
XCTAssertNil(error, @"Should not pass no error");
258+
259+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
260+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
261+
262+
BOOL enableUnhandledCPPExceptions =
263+
[[experimentalOptions valueForKey:@"enableUnhandledCPPExceptionsV2"] boolValue];
264+
XCTAssertTrue(
265+
enableUnhandledCPPExceptions, @"enableUnhandledCPPExceptionsV2 should be enabled");
266+
}
267+
268+
- (void)testCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Disabled
269+
{
270+
RNSentry *rnSentry = [[RNSentry alloc] init];
271+
NSError *error = nil;
272+
273+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
274+
@"dsn" : @"https://[email protected]/123456",
275+
@"_experiments" : @ {
276+
@"enableUnhandledCPPExceptionsV2" : @NO,
277+
},
278+
};
279+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
280+
error:&error];
281+
282+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
283+
XCTAssertNil(error, @"Should not pass no error");
284+
285+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
286+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
287+
288+
BOOL enableUnhandledCPPExceptions =
289+
[[experimentalOptions valueForKey:@"enableUnhandledCPPExceptionsV2"] boolValue];
290+
XCTAssertFalse(
291+
enableUnhandledCPPExceptions, @"enableUnhandledCPPExceptionsV2 should be disabled");
292+
}
293+
294+
- (void)testCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Default
295+
{
296+
RNSentry *rnSentry = [[RNSentry alloc] init];
297+
NSError *error = nil;
298+
299+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
300+
@"dsn" : @"https://[email protected]/123456",
301+
};
302+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
303+
error:&error];
304+
305+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
306+
XCTAssertNil(error, @"Should not pass no error");
307+
308+
// Test that when no _experiments are provided, the experimental option defaults to false
309+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
310+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
311+
312+
BOOL enableUnhandledCPPExceptions =
313+
[[experimentalOptions valueForKey:@"enableUnhandledCPPExceptionsV2"] boolValue];
314+
XCTAssertFalse(
315+
enableUnhandledCPPExceptions, @"enableUnhandledCPPExceptionsV2 should default to disabled");
316+
}
317+
242318
- (void)testPassesErrorOnWrongDsn
243319
{
244320
RNSentry *rnSentry = [[RNSentry alloc] init];

packages/core/ios/RNSentry.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
# import "RNSentryRNSScreen.h"
5050
#endif
5151

52+
#import "RNSentryExperimentalOptions.h"
5253
#import "RNSentryVersion.h"
5354

5455
@interface
@@ -225,6 +226,14 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
225226
// Failed requests can only be enabled in one SDK to avoid duplicates
226227
sentryOptions.enableCaptureFailedRequests = NO;
227228

229+
NSDictionary *experiments = options[@"_experiments"];
230+
if (experiments != nil && [experiments isKindOfClass:[NSDictionary class]]) {
231+
BOOL enableUnhandledCPPExceptions =
232+
[experiments[@"enableUnhandledCPPExceptionsV2"] boolValue];
233+
[RNSentryExperimentalOptions setEnableUnhandledCPPExceptionsV2:enableUnhandledCPPExceptions
234+
sentryOptions:sentryOptions];
235+
}
236+
228237
return sentryOptions;
229238
}
230239

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@class SentryOptions;
4+
5+
NS_ASSUME_NONNULL_BEGIN
6+
7+
@interface RNSentryExperimentalOptions : NSObject
8+
9+
/**
10+
* Sets the enableUnhandledCPPExceptionsV2 experimental option on SentryOptions
11+
* @param sentryOptions The SentryOptions instance to configure
12+
* @param enabled Whether to enable unhandled C++ exceptions V2
13+
*/
14+
+ (void)setEnableUnhandledCPPExceptionsV2:(BOOL)enabled
15+
sentryOptions:(SentryOptions *)sentryOptions;
16+
17+
/**
18+
* Gets the current value of enableUnhandledCPPExceptionsV2 experimental option
19+
* @param sentryOptions The SentryOptions instance to read from
20+
* @return The current value of enableUnhandledCPPExceptionsV2
21+
*/
22+
+ (BOOL)getEnableUnhandledCPPExceptionsV2:(SentryOptions *)sentryOptions;
23+
24+
@end
25+
26+
NS_ASSUME_NONNULL_END
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#import "RNSentryExperimentalOptions.h"
2+
@import Sentry;
3+
4+
@implementation RNSentryExperimentalOptions
5+
6+
+ (void)setEnableUnhandledCPPExceptionsV2:(BOOL)enabled sentryOptions:(SentryOptions *)sentryOptions
7+
{
8+
if (sentryOptions == nil) {
9+
return;
10+
}
11+
sentryOptions.experimental.enableUnhandledCPPExceptionsV2 = enabled;
12+
}
13+
14+
+ (BOOL)getEnableUnhandledCPPExceptionsV2:(SentryOptions *)sentryOptions
15+
{
16+
if (sentryOptions == nil) {
17+
return NO;
18+
}
19+
return sentryOptions.experimental.enableUnhandledCPPExceptionsV2;
20+
}
21+
22+
@end

packages/core/src/js/options.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,17 @@ export interface BaseReactNativeOptions {
253253
* This will be removed in the next major version.
254254
*/
255255
replaysOnErrorSampleRate?: number;
256+
257+
/**
258+
* Experiment: A more reliable way to report unhandled C++ exceptions in iOS.
259+
*
260+
* This approach hooks into all instances of the `__cxa_throw` function, which provides a more comprehensive and consistent exception handling across an app’s runtime, regardless of the number of C++ modules or how they’re linked. It helps in obtaining accurate stack traces.
261+
*
262+
* - Note: The mechanism of hooking into `__cxa_throw` could cause issues with symbolication on iOS due to caching of symbol references.
263+
*
264+
* @default false
265+
*/
266+
enableUnhandledCPPExceptionsV2?: boolean;
256267
};
257268

258269
/**

samples/react-native/src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ Sentry.init({
171171
// This should be disabled when manually initializing the native SDK
172172
// Note that options from JS are not passed to the native SDKs when initialized manually
173173
autoInitializeNativeSdk: true,
174+
_experiments: {
175+
enableUnhandledCPPExceptionsV2: true,
176+
},
174177
});
175178

176179
const Stack = isMobileOs

0 commit comments

Comments
 (0)