From 670a7682c597b33d484e6341cf422a6aa2be9a8b Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 23 Feb 2021 14:52:21 +0000 Subject: [PATCH] [PLAT-6075] Parse Swift 5.4 fatal error messages (#1010) --- Bugsnag/Payload/BugsnagError.m | 40 ++++++++++++++---------- CHANGELOG.md | 7 +++++ Tests/BugsnagErrorTest.m | 56 ++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 18 deletions(-) diff --git a/Bugsnag/Payload/BugsnagError.m b/Bugsnag/Payload/BugsnagError.m index e08ed8e41..b4f96f886 100644 --- a/Bugsnag/Payload/BugsnagError.m +++ b/Bugsnag/Payload/BugsnagError.m @@ -127,17 +127,25 @@ + (BugsnagError *)errorFromJson:(NSDictionary *)json { } - (void)updateWithCrashInfoMessage:(NSString *)crashInfoMessage { - @try { - // Messages that match this pattern should override the errorClass (and errorMessage if there is enough information.) - NSString *pattern = @"^(Assertion failed|Fatal error|Precondition failed): ((.+): )?file .+, line \\d+\n$"; - NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil]; - NSArray *matches = [regex matchesInString:crashInfoMessage options:0 range:NSMakeRange(0, crashInfoMessage.length)]; + NSArray *patterns = @[ + // From Swift 2.2: https://github.com/apple/swift/blob/swift-2.2-RELEASE/stdlib/public/stubs/Assert.cpp#L24-L39 + @"^(assertion failed|fatal error|precondition failed): ((.+): )?file .+, line \\d+\n$", + // From Swift 4.1: https://github.com/apple/swift/commit/d03a575279cf5c523779ef68f8d7903f09ba901e + @"^(Assertion failed|Fatal error|Precondition failed): ((.+): )?file .+, line \\d+\n$", + // From Swift 5.4: https://github.com/apple/swift/commit/1a051719e3b1b7c37a856684dd037d482fef8e59 + @"^.+:\\d+: (Assertion failed|Fatal error|Precondition failed)(: (.+))?\n$", + ]; + + for (NSString *pattern in patterns) { + NSArray *matches = nil; + @try { + NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:nil]; + matches = [regex matchesInString:crashInfoMessage options:0 range:NSMakeRange(0, crashInfoMessage.length)]; + } @catch (NSException *exception) { + bsg_log_err(@"Exception thrown while parsing crash info message: %@", exception); + } if (matches.count != 1 || matches[0].numberOfRanges != 4) { - if (!self.errorMessage.length) { - // It's better to fall back to the raw string than have an empty errorMessage. - self.errorMessage = crashInfoMessage; - } - return; + continue; } NSRange errorClassRange = [matches[0] rangeAtIndex:1]; if (errorClassRange.location != NSNotFound) { @@ -147,12 +155,12 @@ - (void)updateWithCrashInfoMessage:(NSString *)crashInfoMessage { if (errorMessageRange.location != NSNotFound) { self.errorMessage = [crashInfoMessage substringWithRange:errorMessageRange]; } - } @catch (NSException *exception) { - bsg_log_err(@"Exception thrown while parsing crash info message: %@", exception); - if (!self.errorMessage.length) { - // It's better to fall back to the raw string than have an empty errorMessage. - self.errorMessage = crashInfoMessage; - } + return; //!OCLint + } + + if (!self.errorMessage.length) { + // It's better to fall back to the raw string than have an empty errorMessage. + self.errorMessage = crashInfoMessage; } } diff --git a/CHANGELOG.md b/CHANGELOG.md index f64a53a58..c8c56ee7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Bug fixes + +* Fix Swift 5.4 fatal error message parsing. + [1010](https://github.com/bugsnag/bugsnag-cocoa/pull/1010) + ## 6.6.3 (2021-02-17) ### Bug fixes diff --git a/Tests/BugsnagErrorTest.m b/Tests/BugsnagErrorTest.m index ac727bb75..996c37fbe 100644 --- a/Tests/BugsnagErrorTest.m +++ b/Tests/BugsnagErrorTest.m @@ -168,9 +168,9 @@ - (void)testUpdateWithCrashInfoMessage { error.errorClass = nil; error.errorMessage = nil; - [error updateWithCrashInfoMessage:@"Fatal error: This should NEVER happen: file bugsnag_example/AnotherClass.swift, line 24\n"]; + [error updateWithCrashInfoMessage:@"Fatal error: A suffusion of yellow: file calc.swift, line 5\n"]; XCTAssertEqualObjects(error.errorClass, @"Fatal error"); - XCTAssertEqualObjects(error.errorMessage, @"This should NEVER happen"); + XCTAssertEqualObjects(error.errorMessage, @"A suffusion of yellow"); error.errorClass = nil; error.errorMessage = nil; @@ -253,4 +253,56 @@ - (void)testUpdateWithCrashInfoMessage { XCTAssertEqualObjects(error.errorMessage, @"Expected message",); } +- (void)testUpdateWithCrashInfoMessage_Swift54 { + BugsnagError *error = [[BugsnagError alloc] initWithErrorClass:@"" errorMessage:@"" errorType:BSGErrorTypeCocoa stacktrace:nil]; + + // Swift fatal errors with a message. + // The errorClass and errorMessage should be overwritten with values extracted from the crash info message. + + error.errorClass = nil; + error.errorMessage = nil; + [error updateWithCrashInfoMessage:@"bugsnag_example/AnotherClass.swift:24: Assertion failed: This should NEVER happen\n"]; + XCTAssertEqualObjects(error.errorClass, @"Assertion failed"); + XCTAssertEqualObjects(error.errorMessage, @"This should NEVER happen"); + + error.errorClass = nil; + error.errorMessage = nil; + [error updateWithCrashInfoMessage:@"calc.swift:5: Fatal error: A suffusion of yellow\n"]; + XCTAssertEqualObjects(error.errorClass, @"Fatal error"); + XCTAssertEqualObjects(error.errorMessage, @"A suffusion of yellow"); + + error.errorClass = nil; + error.errorMessage = nil; + [error updateWithCrashInfoMessage:@"bugsnag_example/AnotherClass.swift:24: Precondition failed: : strange formatting 😱::\n"]; + XCTAssertEqualObjects(error.errorClass, @"Precondition failed"); + XCTAssertEqualObjects(error.errorMessage, @" : strange formatting 😱::"); + + // Swift fatal errors without a message. + // The errorClass should be overwritten but the errorMessage left as-is. + + error.errorClass = nil; + error.errorMessage = nil; + [error updateWithCrashInfoMessage:@"bugsnag_example/AnotherClass.swift:24: Assertion failed\n"]; + XCTAssertEqualObjects(error.errorClass, @"Assertion failed"); + XCTAssertEqualObjects(error.errorMessage, nil); + + error.errorClass = nil; + error.errorMessage = @"Expected message"; + [error updateWithCrashInfoMessage:@"bugsnag_example/AnotherClass.swift:24: Assertion failed\n"]; + XCTAssertEqualObjects(error.errorClass, @"Assertion failed"); + XCTAssertEqualObjects(error.errorMessage, @"Expected message"); + + error.errorClass = nil; + error.errorMessage = @"Expected message"; + [error updateWithCrashInfoMessage:@"bugsnag_example/AnotherClass.swift:24: Fatal error\n"]; + XCTAssertEqualObjects(error.errorClass, @"Fatal error"); + XCTAssertEqualObjects(error.errorMessage, @"Expected message"); + + error.errorClass = nil; + error.errorMessage = @"Expected message"; + [error updateWithCrashInfoMessage:@"bugsnag_example/AnotherClass.swift:24: Precondition failed\n"]; + XCTAssertEqualObjects(error.errorClass, @"Precondition failed"); + XCTAssertEqualObjects(error.errorMessage, @"Expected message"); +} + @end