From 85402f0e82ea830f0019951b83f99d9ef5868543 Mon Sep 17 00:00:00 2001 From: Paul Zabelin Date: Tue, 23 Jan 2018 11:41:14 -0800 Subject: [PATCH 01/10] fix crash while reporting user exception update expected cpu arch --- Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m index 9c49ef6e9..f9b3ae93a 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m @@ -177,8 +177,7 @@ - (BSG_CPUFamily)cpuFamily:(NSDictionary *)report { [cpuArch rangeOfString:@"86"].location == 2) { return BSG_CPUFamilyX86; } - if ([cpuArch rangeOfString:@"x86_64" options:NSCaseInsensitiveSearch] - .location != NSNotFound) { + if ([@[@"x86_64", @"x86"] containsObject:cpuArch]) { return BSG_CPUFamilyX86_64; } return BSG_CPUFamilyUnknown; From 4bdf2919f9771fc5c1fcb791a1f8b50e16ff8985 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 12:37:39 -0800 Subject: [PATCH 02/10] Handle case where register name is nil --- .../Source/KSCrash/Recording/BSG_KSCrashDoctor.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m index f9b3ae93a..423f712bb 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m @@ -366,10 +366,13 @@ - (BSG_KSCrashDoctorFunctionCall *)lastFunctionCall:(NSDictionary *)report { BSG_CPUFamily family = [self cpuFamily:report]; NSDictionary *registers = [self basicRegistersFromThreadReport:crashedThread]; - NSArray *regNames = @[[self registerNameForFamily:family paramIndex:0], - [self registerNameForFamily:family paramIndex:1], - [self registerNameForFamily:family paramIndex:2], - [self registerNameForFamily:family paramIndex:3]]; + NSMutableArray *regNames = [NSMutableArray arrayWithCapacity:4]; + for (int paramIndex = 0; paramIndex <= 3; paramIndex++) { + NSString *regName = [self registerNameForFamily:family paramIndex:paramIndex]; + if (regName.length > 0) { + [regNames addObject:regName]; + } + } NSMutableArray *params = [NSMutableArray arrayWithCapacity:4]; for (NSString *regName in regNames) { BSG_KSCrashDoctorParam *param = [[BSG_KSCrashDoctorParam alloc] init]; From cea348bd9b3e1747a655c98a5f76f8a23eb18bab Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 12:37:57 -0800 Subject: [PATCH 03/10] Add register names for arm64 The notable register names on arm64 is x{0-3} --- .../Source/KSCrash/Recording/BSG_KSCrashDoctor.m | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m index 423f712bb..75bd33e91 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashDoctor.m @@ -15,6 +15,7 @@ typedef enum { BSG_CPUFamilyUnknown, BSG_CPUFamilyArm, + BSG_CPUFamilyArm64, BSG_CPUFamilyX86, BSG_CPUFamilyX86_64 } BSG_CPUFamily; @@ -170,6 +171,9 @@ - (NSDictionary *)errorReport:(NSDictionary *)report { - (BSG_CPUFamily)cpuFamily:(NSDictionary *)report { NSDictionary *system = [self systemReport:report]; NSString *cpuArch = system[@BSG_KSSystemField_CPUArch]; + if ([cpuArch isEqualToString:@"arm64"]) { + return BSG_CPUFamilyArm64; + } if ([cpuArch rangeOfString:@"arm"].location == 0) { return BSG_CPUFamilyArm; } @@ -198,6 +202,18 @@ - (NSString *)registerNameForFamily:(BSG_CPUFamily)family return @"r3"; } } + case BSG_CPUFamilyArm64: { + switch (index) { + case 0: + return @"x0"; + case 1: + return @"x1"; + case 2: + return @"x2"; + case 3: + return @"x3"; + } + } case BSG_CPUFamilyX86: { switch (index) { case 0: From add71b3d91b672d5cb8db540f189f1529bdcc817 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 13:04:23 -0800 Subject: [PATCH 04/10] Make reserved word checks case-insensitive https://github.com/apple/swift/commit/d03a575279cf5c523779ef68f8d7903f09ba901e --- Source/BugsnagCrashReport.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 34af5632a..3aedf51b4 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -638,8 +638,8 @@ - (NSString *)enhancedErrorMessageForThread:(NSDictionary *)thread { * For assert, "assertion failed" will be in one of the registers. */ - (BOOL)isReservedWord:(NSString *)contentValue { - return [@"assertion failed" isEqualToString:contentValue] - || [@"fatal error" isEqualToString:contentValue]; + return [@"assertion failed" caseInsensitiveCompare:contentValue] == NSOrderedSame + || [@"fatal error" caseInsensitiveCompare:contentValue] == NSOrderedSame; } @end From 752cd2f65ba2cea921c87c457a332c83cf270f86 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 14:00:09 -0800 Subject: [PATCH 05/10] Populate error class from register data --- Source/BugsnagCrashReport.m | 81 ++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 3aedf51b4..3561e5d25 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -177,6 +177,12 @@ @interface NSDictionary (BSGKSMerge) - (NSDictionary *)BSG_mergedInto:(NSDictionary *)dest; @end +@interface RegisterErrorData : NSObject +@property (nonatomic, strong) NSString *errorClass; +@property (nonatomic, strong) NSString *errorMessage; +- (instancetype)initWithClass:(NSString *_Nonnull)errorClass message:(NSString *_Nonnull)errorMessage NS_DESIGNATED_INITIALIZER; +@end + @interface BugsnagCrashReport () /** @@ -542,10 +548,10 @@ - (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception { BOOL isCrashedThread = [thread[@"crashed"] boolValue]; if (isCrashedThread) { - NSString *errMsg = [self enhancedErrorMessageForThread:thread]; - - if (errMsg) { // use enhanced error message (currently swift assertions) - BSGDictInsertIfNotNil(exception, errMsg, BSGKeyMessage); + RegisterErrorData *errorData = [self enhancedErrorContentForThread:thread]; + if (errorData) { + BSGDictInsertIfNotNil(exception, errorData.errorMessage, BSGKeyMessage); + BSGDictInsertIfNotNil(exception, errorData.errorClass, BSGKeyErrorClass); } NSUInteger seen = 0; @@ -595,36 +601,43 @@ - (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception { /** * Returns the enhanced error message for the thread, or nil if none exists. + */ +- (NSString *)enhancedErrorMessageForThread:(NSDictionary *)thread { + return [self enhancedErrorContentForThread:thread].errorMessage; +} + +/** + * Inspect notable addresses to find the message and type from assertion failures * * This relies very heavily on heuristics rather than any documented APIs. */ -- (NSString *)enhancedErrorMessageForThread:(NSDictionary *)thread { +- (RegisterErrorData *_Nullable)enhancedErrorContentForThread:(NSDictionary *)thread { NSDictionary *notableAddresses = thread[@"notable_addresses"]; - NSMutableArray *msgBuffer = [NSMutableArray new]; - BOOL hasReservedWord = NO; - - if (notableAddresses) { - for (NSString *key in notableAddresses) { - if (![key hasPrefix:@"stack"]) { // skip stack frames, only use register values - NSDictionary *data = notableAddresses[key]; - NSString *contentValue = data[@"value"]; - - hasReservedWord = hasReservedWord || [self isReservedWord:contentValue]; - - // must be a string that isn't a reserved word and isn't a filepath - if ([@"string" isEqualToString:data[BSGKeyType]] - && ![self isReservedWord:contentValue] - && !([[contentValue componentsSeparatedByString:@"/"] count] > 2)) { - - [msgBuffer addObject:contentValue]; - } - } + NSMutableArray *interestingValues = [NSMutableArray new]; + NSString *reservedWord = nil; + + for (NSString *key in notableAddresses) { + if ([key hasPrefix:@"stack"]) { // skip stack frames, only use register values + continue; + } + NSDictionary *data = notableAddresses[key]; + NSString *contentValue = data[@"value"]; + + if ([self isReservedWord:contentValue]) { + reservedWord = contentValue; + } else if ([@"string" isEqualToString:data[BSGKeyType]] + && !([[contentValue componentsSeparatedByString:@"/"] count] > 2)) { + // must be a string that isn't a reserved word and isn't a filepath + [interestingValues addObject:contentValue]; } - [msgBuffer sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; } + + [interestingValues sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; - if (hasReservedWord && [msgBuffer count] > 0) { // needs to have a reserved word used + a message - return [msgBuffer componentsJoinedByString:@" | "]; + if (reservedWord.length > 0 && [interestingValues count] > 0) { // needs to have a reserved word used + a message + NSString *message = [interestingValues componentsJoinedByString:@" | "]; + return [[RegisterErrorData alloc] initWithClass:reservedWord + message:message]; } else { return nil; } @@ -643,3 +656,17 @@ - (BOOL)isReservedWord:(NSString *)contentValue { } @end + +@implementation RegisterErrorData +- (instancetype)init { + return [self initWithClass:@"Unknown" message:@""]; +} + +- (instancetype)initWithClass:(NSString *_Nonnull)errorClass message:(NSString *_Nonnull)errorMessage { + if (self = [super init]) { + _errorClass = errorClass; + _errorMessage = errorMessage; + } + return self; +} +@end From 088db49a7049d5bff6eeb75bfdbcdb589ee77e21 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 14:30:13 -0800 Subject: [PATCH 06/10] Deprecate enhancedErrorMessageForThread Its internally unused and not needed externally --- Source/BugsnagCrashReport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/BugsnagCrashReport.h b/Source/BugsnagCrashReport.h index 495961c63..6d82b7a55 100644 --- a/Source/BugsnagCrashReport.h +++ b/Source/BugsnagCrashReport.h @@ -197,6 +197,6 @@ __deprecated_msg("Use toJson: instead."); /** * Returns the enhanced error message for the thread, or nil if none exists. */ -- (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)thread; +- (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)thread __deprecated; @end From 1bec97610df1816afe6f569cef5224a9afa7fcbd Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 16:34:52 -0800 Subject: [PATCH 07/10] Process thread error message during initialization Makes customized error class/message available during callbacks --- Source/BugsnagCrashReport.m | 88 ++++--- Tests/BugsnagCrashReportTests.m | 404 ++++++++++++++++++++++++-------- 2 files changed, 353 insertions(+), 139 deletions(-) diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 3561e5d25..253666791 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -180,6 +180,7 @@ - (NSDictionary *)BSG_mergedInto:(NSDictionary *)dest; @interface RegisterErrorData : NSObject @property (nonatomic, strong) NSString *errorClass; @property (nonatomic, strong) NSString *errorMessage; ++ (instancetype)errorDataFromThreads:(NSArray *)threads; - (instancetype)initWithClass:(NSString *_Nonnull)errorClass message:(NSString *_Nonnull)errorMessage NS_DESIGNATED_INITIALIZER; @end @@ -223,10 +224,16 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { _error = [report valueForKeyPath:@"crash.error"]; _errorType = _error[BSGKeyType]; - _errorClass = BSGParseErrorClass(_error, _errorType); - _errorMessage = BSGParseErrorMessage(report, _error, _errorType); - _binaryImages = report[@"binary_images"]; _threads = [report valueForKeyPath:@"crash.threads"]; + RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:_threads]; + if (data) { + _errorClass = data.errorClass; + _errorMessage = data.errorMessage; + } else { + _errorClass = BSGParseErrorClass(_error, _errorType); + _errorMessage = BSGParseErrorMessage(report, _error, _errorType); + } + _binaryImages = report[@"binary_images"]; _breadcrumbs = BSGParseBreadcrumbs(report); _severity = BSGParseSeverity( [report valueForKeyPath:@"user.state.crash.severity"]); @@ -548,12 +555,6 @@ - (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception { BOOL isCrashedThread = [thread[@"crashed"] boolValue]; if (isCrashedThread) { - RegisterErrorData *errorData = [self enhancedErrorContentForThread:thread]; - if (errorData) { - BSGDictInsertIfNotNil(exception, errorData.errorMessage, BSGKeyMessage); - BSGDictInsertIfNotNil(exception, errorData.errorClass, BSGKeyErrorClass); - } - NSUInteger seen = 0; NSMutableArray *stacktrace = [NSMutableArray array]; @@ -599,48 +600,45 @@ - (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception { return bugsnagThreads; } -/** - * Returns the enhanced error message for the thread, or nil if none exists. - */ -- (NSString *)enhancedErrorMessageForThread:(NSDictionary *)thread { - return [self enhancedErrorContentForThread:thread].errorMessage; +- (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)thread { + return [self errorMessage]; } -/** - * Inspect notable addresses to find the message and type from assertion failures - * - * This relies very heavily on heuristics rather than any documented APIs. - */ -- (RegisterErrorData *_Nullable)enhancedErrorContentForThread:(NSDictionary *)thread { - NSDictionary *notableAddresses = thread[@"notable_addresses"]; - NSMutableArray *interestingValues = [NSMutableArray new]; - NSString *reservedWord = nil; +@end - for (NSString *key in notableAddresses) { - if ([key hasPrefix:@"stack"]) { // skip stack frames, only use register values +@implementation RegisterErrorData ++ (instancetype)errorDataFromThreads:(NSArray *)threads { + for (NSDictionary *thread in threads) { + if (![thread[@"crashed"] boolValue]) { continue; } - NSDictionary *data = notableAddresses[key]; - NSString *contentValue = data[@"value"]; - - if ([self isReservedWord:contentValue]) { - reservedWord = contentValue; - } else if ([@"string" isEqualToString:data[BSGKeyType]] - && !([[contentValue componentsSeparatedByString:@"/"] count] > 2)) { - // must be a string that isn't a reserved word and isn't a filepath - [interestingValues addObject:contentValue]; + NSDictionary *notableAddresses = thread[@"notable_addresses"]; + NSMutableArray *interestingValues = [NSMutableArray new]; + NSString *reservedWord = nil; + + for (NSString *key in notableAddresses) { + if ([key hasPrefix:@"stack"]) { // skip stack frames, only use register values + continue; + } + NSDictionary *data = notableAddresses[key]; + NSString *contentValue = data[@"value"]; + + if ([self isReservedWord:contentValue]) { + reservedWord = contentValue; + } else if ([@"string" isEqualToString:data[BSGKeyType]] + && !([[contentValue componentsSeparatedByString:@"/"] count] > 2)) { + // must be a string that isn't a reserved word and isn't a filepath + [interestingValues addObject:contentValue]; + } } - } - [interestingValues sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; - - if (reservedWord.length > 0 && [interestingValues count] > 0) { // needs to have a reserved word used + a message + [interestingValues sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + NSString *message = [interestingValues componentsJoinedByString:@" | "]; return [[RegisterErrorData alloc] initWithClass:reservedWord message:message]; - } else { - return nil; } + return nil; } /** @@ -650,19 +648,19 @@ - (RegisterErrorData *_Nullable)enhancedErrorContentForThread:(NSDictionary *)th * * For assert, "assertion failed" will be in one of the registers. */ -- (BOOL)isReservedWord:(NSString *)contentValue { ++ (BOOL)isReservedWord:(NSString *)contentValue { return [@"assertion failed" caseInsensitiveCompare:contentValue] == NSOrderedSame || [@"fatal error" caseInsensitiveCompare:contentValue] == NSOrderedSame; } -@end - -@implementation RegisterErrorData - (instancetype)init { return [self initWithClass:@"Unknown" message:@""]; } -- (instancetype)initWithClass:(NSString *_Nonnull)errorClass message:(NSString *_Nonnull)errorMessage { +- (instancetype)initWithClass:(NSString *)errorClass message:(NSString *)errorMessage { + if (errorClass.length == 0) { + return nil; + } if (self = [super init]) { _errorClass = errorClass; _errorMessage = errorMessage; diff --git a/Tests/BugsnagCrashReportTests.m b/Tests/BugsnagCrashReportTests.m index daa12f93b..c08b09b8e 100644 --- a/Tests/BugsnagCrashReportTests.m +++ b/Tests/BugsnagCrashReportTests.m @@ -9,10 +9,10 @@ #import #import +#import "BSG_RFC3339DateTool.h" #import "Bugsnag.h" #import "BugsnagHandledState.h" #import "BugsnagSession.h" -#import "BSG_RFC3339DateTool.h" @interface BugsnagCrashReportTests : XCTestCase @property BugsnagCrashReport *report; @@ -28,9 +28,9 @@ - (void)setUp { encoding:NSUTF8StringEncoding error:nil]; NSDictionary *dictionary = [NSJSONSerialization - JSONObjectWithData:[contents dataUsingEncoding:NSUTF8StringEncoding] - options:0 - error:nil]; + JSONObjectWithData:[contents dataUsingEncoding:NSUTF8StringEncoding] + options:0 + error:nil]; self.report = [[BugsnagCrashReport alloc] initWithKSReport:dictionary]; } @@ -102,14 +102,14 @@ - (void)testNotifyReleaseStagesSendsFromConfig { config.notifyReleaseStages = @[ @"foo" ]; config.releaseStage = @"foo"; BugsnagHandledState *state = - [BugsnagHandledState handledStateWithSeverityReason:HandledException]; + [BugsnagHandledState handledStateWithSeverityReason:HandledException]; BugsnagCrashReport *report = - [[BugsnagCrashReport alloc] initWithErrorName:@"Bad error" - errorMessage:@"it was so bad" - configuration:config - metaData:@{} - handledState:state - session:nil]; + [[BugsnagCrashReport alloc] initWithErrorName:@"Bad error" + errorMessage:@"it was so bad" + configuration:config + metaData:@{} + handledState:state + session:nil]; XCTAssertTrue([report shouldBeSent]); } @@ -117,16 +117,16 @@ - (void)testNotifyReleaseStagesSkipsSendFromConfig { BugsnagConfiguration *config = [BugsnagConfiguration new]; config.notifyReleaseStages = @[ @"foo", @"bar" ]; config.releaseStage = @"not foo or bar"; - + BugsnagHandledState *state = - [BugsnagHandledState handledStateWithSeverityReason:HandledException]; + [BugsnagHandledState handledStateWithSeverityReason:HandledException]; BugsnagCrashReport *report = - [[BugsnagCrashReport alloc] initWithErrorName:@"Bad error" - errorMessage:@"it was so bad" - configuration:config - metaData:@{} - handledState:state - session:nil]; + [[BugsnagCrashReport alloc] initWithErrorName:@"Bad error" + errorMessage:@"it was so bad" + configuration:config + metaData:@{} + handledState:state + session:nil]; XCTAssertFalse([report shouldBeSent]); } @@ -134,7 +134,7 @@ - (void)testSessionJson { BugsnagConfiguration *config = [BugsnagConfiguration new]; BugsnagHandledState *state = - [BugsnagHandledState handledStateWithSeverityReason:HandledException]; + [BugsnagHandledState handledStateWithSeverityReason:HandledException]; NSDate *now = [NSDate date]; BugsnagSession *bugsnagSession = [[BugsnagSession alloc] initWithId:@"123" startDate:now @@ -142,21 +142,22 @@ - (void)testSessionJson { autoCaptured:NO]; bugsnagSession.handledCount = 2; bugsnagSession.unhandledCount = 1; - + BugsnagCrashReport *report = - [[BugsnagCrashReport alloc] initWithErrorName:@"Bad error" - errorMessage:@"it was so bad" - configuration:config - metaData:@{} - handledState:state - session:bugsnagSession]; + [[BugsnagCrashReport alloc] initWithErrorName:@"Bad error" + errorMessage:@"it was so bad" + configuration:config + metaData:@{} + handledState:state + session:bugsnagSession]; NSDictionary *json = [report toJson]; XCTAssertNotNil(json); NSDictionary *session = json[@"session"]; XCTAssertNotNil(session); XCTAssertEqualObjects(@"123", session[@"id"]); - XCTAssertEqualObjects([BSG_RFC3339DateTool stringFromDate:now], session[@"startedAt"]); + XCTAssertEqualObjects([BSG_RFC3339DateTool stringFromDate:now], + session[@"startedAt"]); NSDictionary *events = session[@"events"]; XCTAssertNotNil(events); @@ -164,73 +165,288 @@ - (void)testSessionJson { XCTAssertEqualObjects(@1, events[@"unhandled"]); } -- (void)testEnhancedErrorMessage { - BugsnagCrashReport *errorReport = [BugsnagCrashReport new]; - NSMutableDictionary *thread = [NSMutableDictionary new]; - - // nil for empty threads - XCTAssertNil([errorReport enhancedErrorMessageForThread:thread]); - NSMutableDictionary *addresses = [NSMutableDictionary new]; - - // nil for empty notable addresses - thread[@"notable_addresses"] = addresses; - XCTAssertNil([errorReport enhancedErrorMessageForThread:thread]); - - // nil for "fatal error" with no additional dict present - - for (NSString *reservedWord in @[@"fatal error", @"assertion failed"]) { - addresses[@"r14"] = @{ - @"address": @4511089532, - @"type": @"string", - @"value": reservedWord - }; - XCTAssertNil([errorReport enhancedErrorMessageForThread:thread]); +- (void)testDefaultErrorMessageNil { + BugsnagCrashReport *report = + [[BugsnagCrashReport alloc] initWithKSReport:@{}]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"Exception", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testDefaultErrorMessageNilForEmptyThreads { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"threads" : @[] + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"Exception", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageNilForEmptyNotableAddresses { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"threads" : @[ @{@"crashed" : @YES, @"notable_addresses" : @{}} ] + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"Exception", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageForFatalErrorWithoutAdditionalMessage { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"r14" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : @"fatal error" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"fatal error", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageForAssertionWithoutAdditionalMessage { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"r14" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : @"assertion failed" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"assertion failed", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageForAssertionError { + for (NSString *assertionName in @[ + @"assertion failed", @"Assertion failed", @"fatal error", + @"Fatal error" + ]) { + BugsnagCrashReport *report = + [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"x9" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"Something went wrong" + }, + @"r16" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : assertionName + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(assertionName, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"Something went wrong", + payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); } - - // returns msg for "fatal error" with additional dict present - addresses[@"r12"] = @{ - @"address": @4511086448, - @"type": @"string", - @"value": @"Whoops - fatalerror" - }; - XCTAssertEqualObjects(@"Whoops - fatalerror", [errorReport enhancedErrorMessageForThread:thread]); - - - // ignores additional dict if more than 2 "/" present - addresses[@"r24"] = @{ - @"address": @4511084983, - @"type": @"string", - @"value": @"/usr/include/lib/something.swift" - }; - XCTAssertEqualObjects(@"Whoops - fatalerror", [errorReport enhancedErrorMessageForThread:thread]); - - // ignores dict if not type string - addresses[@"r25"] = @{ - @"address": @4511084983, - @"type": @"long", - @"value": @"Swift is hard" - }; - XCTAssertEqualObjects(@"Whoops - fatalerror", [errorReport enhancedErrorMessageForThread:thread]); - - // sorts and concatenates multiple multiple messages - addresses[@"r26"] = @{ - @"address": @4511082095, - @"type": @"string", - @"value": @"Swift is hard" - }; - XCTAssertEqualObjects(@"Swift is hard | Whoops - fatalerror", [errorReport enhancedErrorMessageForThread:thread]); - - // ignores stack frames - addresses[@"stack523409"] = @{ - @"address": @4511080001, - @"type": @"string", - @"value": @"Not a register" - }; - XCTAssertEqualObjects(@"Swift is hard | Whoops - fatalerror", [errorReport enhancedErrorMessageForThread:thread]); - - // ignores values if no reserved word used - addresses[@"r14"] = nil; - XCTAssertNil([errorReport enhancedErrorMessageForThread:thread]); +} + +- (void)testEnhancedErrorMessageIgnoresFilePaths { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"x9" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"/usr/include/lib/something.swift" + }, + @"r16" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : @"fatal error" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"fatal error", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageIgnoresStackFrames { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"stack@2342387" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"some nonsense" + }, + @"r16" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : @"fatal error" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"fatal error", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageIgnoresNonStrings { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"x9" : @{ + @"address" : @4511086448, + @"type" : @"long", + @"value" : @"A message from beyond" + }, + @"r16" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : @"fatal error" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"fatal error", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageConcatenatesMultipleMessages { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"x9" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"A message from beyond" + }, + @"r14" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"Wo0o0o" + }, + @"r16" : @{ + @"address" : @4511089532, + @"type" : @"string", + @"value" : @"Fatal error" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"Fatal error", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"A message from beyond | Wo0o0o", + payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); +} + +- (void)testEnhancedErrorMessageIgnoresUnknownAssertionTypes { + BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ + @"crash" : @{ + @"threads" : @[ @{ + @"crashed" : @YES, + @"notable_addresses" : @{ + @"x9" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"A message from beyond" + }, + @"r14" : @{ + @"address" : @4511086448, + @"type" : @"string", + @"value" : @"Wo0o0o" + } + } + } ] + } + }]; + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"Exception", + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); + XCTAssertEqualObjects(report.errorClass, + payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(report.errorMessage, + payload[@"exceptions"][0][@"message"]); } @end From 5fd7e3f733f1ab7d7890c68cb62252b047f16435 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 17:07:43 -0800 Subject: [PATCH 08/10] Fix file references in test targets for tvOS, macOS --- OSX/Bugsnag.xcodeproj/project.pbxproj | 6 ++---- tvOS/Bugsnag.xcodeproj/project.pbxproj | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/OSX/Bugsnag.xcodeproj/project.pbxproj b/OSX/Bugsnag.xcodeproj/project.pbxproj index 7a5a72341..eb63a4c0f 100644 --- a/OSX/Bugsnag.xcodeproj/project.pbxproj +++ b/OSX/Bugsnag.xcodeproj/project.pbxproj @@ -25,7 +25,6 @@ 8A2C8FDD1C6BC2C800846019 /* BugsnagSink.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A2C8FCB1C6BC2C800846019 /* BugsnagSink.h */; }; 8A2C8FDE1C6BC2C800846019 /* BugsnagSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FCC1C6BC2C800846019 /* BugsnagSink.m */; }; 8A2C8FEA1C6BC38900846019 /* BugsnagBreadcrumbsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FE01C6BC38200846019 /* BugsnagBreadcrumbsTest.m */; }; - 8A2C8FEB1C6BC38900846019 /* BugsnagCrashReportTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FE11C6BC38200846019 /* BugsnagCrashReportTests.m */; }; 8A2C8FEC1C6BC38900846019 /* BugsnagSinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FE21C6BC38200846019 /* BugsnagSinkTests.m */; }; 8A2C8FEE1C6BC38900846019 /* report.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A2C8FE41C6BC38200846019 /* report.json */; }; 8A2C8FF01C6BC3A200846019 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8FEF1C6BC3A200846019 /* SystemConfiguration.framework */; }; @@ -34,6 +33,7 @@ 8A627CD91EC3B75200F7C04E /* BSGSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A627CD71EC3B75200F7C04E /* BSGSerialization.h */; }; 8A627CDA1EC3B75200F7C04E /* BSGSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A627CD81EC3B75200F7C04E /* BSGSerialization.m */; }; 8A87352C1C6D3B1600EDBD5B /* BSG_KSCrashReportWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A87352B1C6D3B1600EDBD5B /* BSG_KSCrashReportWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8ACF0F752018136200173809 /* BugsnagCrashReportTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FE11C6BC38200846019 /* BugsnagCrashReportTests.m */; }; 8AD9FA891E086351002859A7 /* BugsnagConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AD9FA851E0862DC002859A7 /* BugsnagConfigurationTests.m */; }; E72352C11F55924A00436528 /* BSGConnectivity.h in Headers */ = {isa = PBXBuildFile; fileRef = E72352BF1F55924A00436528 /* BSGConnectivity.h */; }; E72352C21F55924A00436528 /* BSGConnectivity.m in Sources */ = {isa = PBXBuildFile; fileRef = E72352C01F55924A00436528 /* BSGConnectivity.m */; }; @@ -930,13 +930,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8ACF0F752018136200173809 /* BugsnagCrashReportTests.m in Sources */, E7CE78CC1FD94E77001D07E0 /* KSSystemInfo_Tests.m in Sources */, E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */, E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */, E7CE78D61FD94E9E001D07E0 /* FileBasedTestCase.m in Sources */, E7CE78D51FD94E93001D07E0 /* XCTestCase+KSCrash.m in Sources */, E7CE78CD1FD94E77001D07E0 /* KSZombie_Tests.m in Sources */, - 8AD9FA881E08633F002859A7 /* BugsnagConfigurationSpec.m in Sources */, E7CE78CF1FD94E77001D07E0 /* NSError+SimpleConstructor_Tests.m in Sources */, E7CE78C81FD94E77001D07E0 /* KSSafeCollections_Tests.m in Sources */, E7CE78BF1FD94E77001D07E0 /* KSCrashSentry_Signal_Tests.m in Sources */, @@ -959,11 +959,9 @@ E7CE78BD1FD94E77001D07E0 /* KSCrashSentry_Deadlock_Tests.m in Sources */, E7CE78C51FD94E77001D07E0 /* KSLogger_Tests.m in Sources */, E7CE78C11FD94E77001D07E0 /* KSCrashState_Tests.m in Sources */, - 8A2C8FEB1C6BC38900846019 /* BugsnagCrashReportTests.m in Sources */, E7CE78C31FD94E77001D07E0 /* KSFileUtils_Tests.m in Sources */, E7CE78BC1FD94E77001D07E0 /* KSCrashReportStore_Tests.m in Sources */, E79148611FD82BB7003EFEBF /* BugsnagSessionTrackerTest.m in Sources */, - 8A2C8FEB1C6BC38900846019 /* BugsnagCrashReportTests.m in Sources */, E79148591FD82BAE003EFEBF /* BugsnagSessionTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/tvOS/Bugsnag.xcodeproj/project.pbxproj b/tvOS/Bugsnag.xcodeproj/project.pbxproj index 603eccfa3..5f1f9aeb8 100644 --- a/tvOS/Bugsnag.xcodeproj/project.pbxproj +++ b/tvOS/Bugsnag.xcodeproj/project.pbxproj @@ -13,8 +13,8 @@ 8AB151171D41356800C9B218 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A8D51241D41343500D33797 /* Bugsnag.framework */; }; 8AB151211D41361700C9B218 /* BugsnagBreadcrumbsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AB1511D1D41361700C9B218 /* BugsnagBreadcrumbsTest.m */; }; 8AB151221D41361700C9B218 /* BugsnagCrashReportTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AB1511E1D41361700C9B218 /* BugsnagCrashReportTests.m */; }; - 8AB151231D41361700C9B218 /* BugsnagSinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AB1511F1D41361700C9B218 /* BugsnagSinkTests.m */; }; 8AB151241D41361700C9B218 /* report.json in Resources */ = {isa = PBXBuildFile; fileRef = 8AB151201D41361700C9B218 /* report.json */; }; + 8ACF0F74201812AD00173809 /* BugsnagSinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AB1511F1D41361700C9B218 /* BugsnagSinkTests.m */; }; 8AD9A4F51D42EE87004E1CC5 /* Bugsnag.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AB151261D41366400C9B218 /* Bugsnag.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8AD9A4F61D42EE8E004E1CC5 /* BugsnagBreadcrumb.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AB151281D41366400C9B218 /* BugsnagBreadcrumb.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8AD9A4F71D42EE90004E1CC5 /* BugsnagCollections.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AB1512A1D41366400C9B218 /* BugsnagCollections.h */; }; @@ -927,6 +927,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8ACF0F74201812AD00173809 /* BugsnagSinkTests.m in Sources */, E7CE78F21FD94F1B001D07E0 /* KSMach_Tests.m in Sources */, E7CE79071FD94F1B001D07E0 /* KSSysCtl_Tests.m in Sources */, E7CE78F71FD94F1B001D07E0 /* KSCrashSentry_Signal_Tests.m in Sources */, @@ -938,11 +939,9 @@ E7CE78F31FD94F1B001D07E0 /* NSError+SimpleConstructor_Tests.m in Sources */, E7CE79001FD94F1B001D07E0 /* KSJSONCodec_Tests.m in Sources */, E7CE79051FD94F1B001D07E0 /* KSDynamicLinker_Tests.m in Sources */, - 8AB151231D41361700C9B218 /* BugsnagSinkTests.m in Sources */, E7CE78F61FD94F1B001D07E0 /* FileBasedTestCase.m in Sources */, E7CE79031FD94F1B001D07E0 /* KSCrashReportConverter_Tests.m in Sources */, E79148911FD82E77003EFEBF /* BugsnagUserTest.m in Sources */, - 8AB151231D41361700C9B218 /* BugsnagSinkTests.m in Sources */, E791488E1FD82E77003EFEBF /* BugsnagSessionTrackerTest.m in Sources */, E79148901FD82E77003EFEBF /* BugsnagSessionTest.m in Sources */, 8AB151211D41361700C9B218 /* BugsnagBreadcrumbsTest.m in Sources */, From 15906ac57dfd3a9506d1a6c86573c05b8285aa02 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 17:30:43 -0800 Subject: [PATCH 09/10] Avoid check before ensuring string value --- Source/BugsnagCrashReport.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 253666791..842adb0b1 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -621,12 +621,14 @@ + (instancetype)errorDataFromThreads:(NSArray *)threads { continue; } NSDictionary *data = notableAddresses[key]; + if (![@"string" isEqualToString:data[BSGKeyType]]) { + continue; + } NSString *contentValue = data[@"value"]; if ([self isReservedWord:contentValue]) { reservedWord = contentValue; - } else if ([@"string" isEqualToString:data[BSGKeyType]] - && !([[contentValue componentsSeparatedByString:@"/"] count] > 2)) { + } else if (!([[contentValue componentsSeparatedByString:@"/"] count] > 2)) { // must be a string that isn't a reserved word and isn't a filepath [interestingValues addObject:contentValue]; } From 9ab2c1600aa0cd38628ab16b43853556c6d4152d Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 23 Jan 2018 17:31:07 -0800 Subject: [PATCH 10/10] handle preconditionFailed --- Source/BugsnagCrashReport.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 842adb0b1..42e313aa7 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -652,7 +652,8 @@ + (instancetype)errorDataFromThreads:(NSArray *)threads { */ + (BOOL)isReservedWord:(NSString *)contentValue { return [@"assertion failed" caseInsensitiveCompare:contentValue] == NSOrderedSame - || [@"fatal error" caseInsensitiveCompare:contentValue] == NSOrderedSame; + || [@"fatal error" caseInsensitiveCompare:contentValue] == NSOrderedSame + || [@"precondition failed" caseInsensitiveCompare:contentValue] == NSOrderedSame; } - (instancetype)init {