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 fatal `NSInvalidArgumentException` crash in `RNSentryReplayBreadcrumbConverter` when a touch breadcrumb path contains a non-dictionary element ([#6346](https://github.com/getsentry/sentry-react-native/pull/6346))
- Apply `SENTRY_ENVIRONMENT`, `SENTRY_RELEASE` and `SENTRY_DIST` build-time overrides to the JS bundled options to match the native SDKs ([#6330](https://github.com/getsentry/sentry-react-native/pull/6330))
- Fix user `geo` being dropped from the native scope by forwarding it as a structured object instead of a JSON string ([#6309](https://github.com/getsentry/sentry-react-native/pull/6309))
- Remove unused `React/RCTTextView.h` import that broke iOS builds on React Native 0.87, where the header was removed as part of the legacy architecture cleanup ([#6322](https://github.com/getsentry/sentry-react-native/pull/6322))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,53 @@ final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
XCTAssertEqual(actual, nil)
}

// Reproduces https://github.com/getsentry/sentry-react-native/issues/6342
// A non-dictionary element in the path (e.g. an NSString) must not crash with
// `-[__NSCFString objectForKey:]: unrecognized selector sent to instance`.
func testTouchMessageReturnsNilOnNonDictionaryPathElement() throws {
let testPath: [Any?] = ["not-a-dictionary"]
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
XCTAssertEqual(actual, nil)
}

func testTouchMessageReturnsNilWhenAnyPathElementIsNotADictionary() throws {
let testPath: [Any?] = [
["name": "name1"],
"not-a-dictionary",
["name": "name3"]
]
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
XCTAssertEqual(actual, nil)
}

func testConvertTouchBreadcrumbWithNonDictionaryPathElementDoesNotCrash() {
let converter = RNSentryReplayBreadcrumbConverter()
let testBreadcrumb = Breadcrumb()
testBreadcrumb.timestamp = Date()
testBreadcrumb.level = .info
testBreadcrumb.type = "user"
testBreadcrumb.category = "touch"
testBreadcrumb.data = [
"path": ["not-a-dictionary"]
]
// Must not raise NSInvalidArgumentException.
_ = converter.convert(from: testBreadcrumb)
}

func testConvertTouchBreadcrumbWithNonArrayPathDoesNotCrash() {
let converter = RNSentryReplayBreadcrumbConverter()
let testBreadcrumb = Breadcrumb()
testBreadcrumb.timestamp = Date()
testBreadcrumb.level = .info
testBreadcrumb.type = "user"
testBreadcrumb.category = "touch"
testBreadcrumb.data = [
"path": "not-an-array"
]
// Must not raise (NSString does not respond to `count`/`objectAtIndex:`).
_ = converter.convert(from: testBreadcrumb)
}

func testTouchMessageReturnsMessageOnValidPathExample1() throws {
let testPath: [Any?] = [
["label": "label0"],
Expand Down
17 changes: 11 additions & 6 deletions packages/core/ios/RNSentryReplayBreadcrumbConverter.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ - (instancetype _Nonnull)init
return nil;
}

NSMutableArray *path = [breadcrumb.data valueForKey:@"path"];
NSString *message = [RNSentryReplayBreadcrumbConverter getTouchPathMessageFrom:path];
id maybePath = [breadcrumb.data valueForKey:@"path"];
if (![maybePath isKindOfClass:[NSArray class]]) {
return nil;
}

NSString *message = [RNSentryReplayBreadcrumbConverter getTouchPathMessageFrom:maybePath];

return [SentrySessionReplayHybridSDK createBreadcrumbwithTimestamp:breadcrumb.timestamp
category:@"ui.tap"
Expand Down Expand Up @@ -112,10 +116,11 @@ + (NSString *_Nullable)getTouchPathMessageFrom:(NSArray *_Nullable)path

NSMutableString *message = [[NSMutableString alloc] init];
for (NSInteger i = MIN(3, pathCount - 1); i >= 0; i--) {
NSDictionary *item = [path objectAtIndex:i];
if (item == nil) {
return nil; // There should be no nil (undefined) from JS, but to be safe we check it
// here
id item = [path objectAtIndex:i];
if (![item isKindOfClass:[NSDictionary class]]) {
return nil; // There should be no nil (undefined) or non-dictionary entry from JS, but
// to be safe we check it here. See
// https://github.com/getsentry/sentry-react-native/issues/6342
}

id name = [item objectForKey:@"name"];
Expand Down
Loading