diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d38ade7..14ee3759e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Changelog ========= +## TBD + +### Enhancements + +* Capture basic report diagnostics in the file path in case of crash report + content corruption + [#327](https://github.com/bugsnag/bugsnag-cocoa/pull/327) + ## 5.17.3 (2018-12-19) ### Bug Fixes diff --git a/OSX/Bugsnag.xcodeproj/project.pbxproj b/OSX/Bugsnag.xcodeproj/project.pbxproj index eb63a4c0f..875a33720 100644 --- a/OSX/Bugsnag.xcodeproj/project.pbxproj +++ b/OSX/Bugsnag.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8A12006C221C50F40008C9C3 /* BSGFilepathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A12006B221C50F40008C9C3 /* BSGFilepathTests.m */; }; 8A2C8FAC1C6BC1F700846019 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8FA11C6BC1F700846019 /* Bugsnag.framework */; }; 8A2C8FCD1C6BC2C800846019 /* Bugsnag.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A2C8FBB1C6BC2C800846019 /* Bugsnag.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8A2C8FCE1C6BC2C800846019 /* Bugsnag.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FBC1C6BC2C800846019 /* Bugsnag.m */; }; @@ -188,6 +189,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8A12006B221C50F40008C9C3 /* BSGFilepathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSGFilepathTests.m; sourceTree = ""; }; 8A2C8FA11C6BC1F700846019 /* Bugsnag.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bugsnag.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A2C8FA61C6BC1F700846019 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8A2C8FAB1C6BC1F700846019 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -469,6 +471,7 @@ 8A2C8FAF1C6BC1F700846019 /* Tests */ = { isa = PBXGroup; children = ( + 8A12006B221C50F40008C9C3 /* BSGFilepathTests.m */, E7CE78861FD94E40001D07E0 /* KSCrash */, E791482D1FD82B0C003EFEBF /* BugsnagSessionTest.m */, E791482B1FD82B0C003EFEBF /* BugsnagSessionTrackerTest.m */, @@ -933,6 +936,7 @@ 8ACF0F752018136200173809 /* BugsnagCrashReportTests.m in Sources */, E7CE78CC1FD94E77001D07E0 /* KSSystemInfo_Tests.m in Sources */, E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */, + 8A12006C221C50F40008C9C3 /* BSGFilepathTests.m in Sources */, E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */, E7CE78D61FD94E9E001D07E0 /* FileBasedTestCase.m in Sources */, E7CE78D51FD94E93001D07E0 /* XCTestCase+KSCrash.m in Sources */, diff --git a/Source/BugsnagApiClient.h b/Source/BugsnagApiClient.h index 1c37a3054..d1cca8eab 100644 --- a/Source/BugsnagApiClient.h +++ b/Source/BugsnagApiClient.h @@ -7,7 +7,7 @@ @class BugsnagConfiguration; -typedef void (^RequestCompletion)(id data, BOOL success, NSError *error); +typedef void (^RequestCompletion)(NSUInteger reportCount, BOOL success, NSError *error); @interface BugsnagApiClient : NSObject @@ -21,11 +21,11 @@ typedef void (^RequestCompletion)(id data, BOOL success, NSError *error); - (NSOperation *)deliveryOperation; -- (void)sendData:(id)data - withPayload:(NSDictionary *)payload - toURL:(NSURL *)url - headers:(NSDictionary *)headers - onCompletion:(RequestCompletion)onCompletion; +- (void)sendItems:(NSUInteger)count + withPayload:(NSDictionary *)payload + toURL:(NSURL *)url + headers:(NSDictionary *)headers + onCompletion:(RequestCompletion)onCompletion; @property(readonly) NSOperationQueue *sendQueue; @property(readonly) BugsnagConfiguration *config; diff --git a/Source/BugsnagApiClient.m b/Source/BugsnagApiClient.m index 8550286c2..2463844f4 100644 --- a/Source/BugsnagApiClient.m +++ b/Source/BugsnagApiClient.m @@ -49,11 +49,11 @@ - (NSOperation *)deliveryOperation { #pragma mark - Delivery -- (void)sendData:(id)data - withPayload:(NSDictionary *)payload - toURL:(NSURL *)url - headers:(NSDictionary *)headers - onCompletion:(RequestCompletion)onCompletion { +- (void)sendItems:(NSUInteger)count + withPayload:(NSDictionary *)payload + toURL:(NSURL *)url + headers:(NSDictionary *)headers + onCompletion:(RequestCompletion)onCompletion { @try { NSError *error = nil; @@ -64,7 +64,7 @@ - (void)sendData:(id)data if (jsonData == nil) { if (onCompletion) { - onCompletion(data, NO, error); + onCompletion(0, NO, error); } return; } @@ -79,7 +79,7 @@ - (void)sendData:(id)data NSURLResponse *_Nullable response, NSError *_Nullable requestErr) { if (onCompletion) { - onCompletion(data, requestErr == nil, requestErr); + onCompletion(count, requestErr == nil, requestErr); } }]; [task resume]; @@ -92,13 +92,13 @@ - (void)sendData:(id)data returningResponse:&response error:&error]; if (onCompletion) { - onCompletion(data, error == nil, error); + onCompletion(count, error == nil, error); } #pragma clang diagnostic pop } } @catch (NSException *exception) { if (onCompletion) { - onCompletion(data, NO, + onCompletion(count, NO, [NSError errorWithDomain:exception.reason code:420 userInfo:@{BSGKeyException: exception}]); diff --git a/Source/BugsnagCrashReport.h b/Source/BugsnagCrashReport.h index 6d82b7a55..027a9fde1 100644 --- a/Source/BugsnagCrashReport.h +++ b/Source/BugsnagCrashReport.h @@ -39,6 +39,18 @@ NSString *_Nonnull BSGFormatSeverity(BSGSeverity severity); @interface BugsnagCrashReport : NSObject +/** + * Create a new crash report from a JSON crash report generated by + * BugsnagCrashSentry + * + * @param report a BugsnagCrashSentry JSON report + * @param metadata additional report info encoded as a string + * + * @return a Bugsnag crash report + */ +- (instancetype _Nonnull)initWithKSReport:(NSDictionary *_Nonnull)report + fileMetadata:(NSString *_Nonnull)metadata; + /** * Create a new crash report from a JSON crash report generated by * BugsnagCrashSentry @@ -193,6 +205,10 @@ __deprecated_msg("Use toJson: instead."); */ @property(readwrite, copy, nullable) NSDictionary *appState; +/** + * If YES, a complete report was not able to be obtained at generation time + */ +@property (readonly, nonatomic, getter=isIncomplete) BOOL incomplete; /** * Returns the enhanced error message for the thread, or nil if none exists. diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 00eb36769..87c9aa5d7 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -70,7 +70,8 @@ } NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, - NSString *errorType) { + NSString *errorType, + NSString *fallbackValue) { NSString *errorClass; if ([errorType isEqualToString:BSGKeyCppException]) { @@ -86,7 +87,7 @@ } if (!errorClass) { // use a default value - errorClass = @"Exception"; + errorClass = fallbackValue.length > 0 ? fallbackValue : @"Exception"; } return errorClass; } @@ -184,6 +185,13 @@ + (instancetype)errorDataFromThreads:(NSArray *)threads; - (instancetype)initWithClass:(NSString *_Nonnull)errorClass message:(NSString *_Nonnull)errorMessage NS_DESIGNATED_INITIALIZER; @end +@interface FallbackReportData : NSObject +@property (nonatomic, strong) NSString *errorClass; +@property (nonatomic, getter=isUnhandled) BOOL unhandled; +@property (nonatomic) BSGSeverity severity; +- (instancetype)initWithMetadata:(NSString *)metadata; +@end + @interface BugsnagCrashReport () /** @@ -212,25 +220,33 @@ @interface BugsnagCrashReport () @property(nonatomic, readwrite, copy, nullable) NSDictionary *customException; @property(nonatomic) BugsnagSession *session; +@property (nonatomic, readwrite, getter=isIncomplete) BOOL incomplete; @end @implementation BugsnagCrashReport - (instancetype)initWithKSReport:(NSDictionary *)report { + return [self initWithKSReport:report fileMetadata:@""]; +} + +- (instancetype)initWithKSReport:(NSDictionary *)report + fileMetadata:(NSString *)metadata { if (self = [super init]) { + FallbackReportData *fallback = [[FallbackReportData alloc] initWithMetadata:metadata]; _notifyReleaseStages = [report valueForKeyPath:@"user.config.notifyReleaseStages"]; _releaseStage = BSGParseReleaseStage(report); _error = [report valueForKeyPath:@"crash.error"]; + _incomplete = report.count == 0; _errorType = _error[BSGKeyType]; _threads = [report valueForKeyPath:@"crash.threads"]; RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:_threads]; if (data) { - _errorClass = data.errorClass; + _errorClass = data.errorClass ?: fallback.errorClass; _errorMessage = data.errorMessage; } else { - _errorClass = BSGParseErrorClass(_error, _errorType); + _errorClass = BSGParseErrorClass(_error, _errorType, fallback.errorClass); _errorMessage = BSGParseErrorMessage(report, _error, _errorType); } _binaryImages = report[@"binary_images"]; @@ -259,7 +275,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { // only makes sense to use serialised value for handled exceptions _depth = [[report valueForKeyPath:@"user.state.crash.depth"] unsignedIntegerValue]; - } else { // the event was unhandled. + } else if (_errorType != nil) { // the event was unhandled. BOOL isSignal = [BSGKeySignal isEqualToString:_errorType]; SeverityReasonType severityReason = isSignal ? Signal : UnhandledException; @@ -268,6 +284,11 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { severity:BSGSeverityError attrValue:_errorClass]; _depth = 0; + } else { // Incomplete report + SeverityReasonType severityReason = [fallback isUnhandled] ? UnhandledException : HandledError; + _handledState = [BugsnagHandledState handledStateWithSeverityReason:severityReason + severity:fallback.severity + attrValue:nil]; } _severity = _handledState.currentSeverity; @@ -478,7 +499,11 @@ - (NSDictionary *)toJson { BSGDictSetSafeObject(event, BSGFormatSeverity(self.severity), BSGKeySeverity); BSGDictSetSafeObject(event, [self breadcrumbs], BSGKeyBreadcrumbs); BSGDictSetSafeObject(event, metaData, BSGKeyMetaData); - + + if ([self isIncomplete]) { + BSGDictSetSafeObject(event, @YES, BSGKeyIncomplete); + } + NSDictionary *device = [self.device bsg_mergedInto:self.deviceState]; BSGDictSetSafeObject(event, device, BSGKeyDevice); @@ -620,6 +645,42 @@ - (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)th @end +@implementation FallbackReportData + +- (instancetype)initWithMetadata:(NSString *)metadata { + if (self = [super init]) { + NSString *separator = @"-"; + NSString *location = metadata; + NSRange range = [location rangeOfString:separator options:NSBackwardsSearch]; + if (range.location != NSNotFound) { + _errorClass = [location substringFromIndex:range.location + 1]; + location = [location substringToIndex:range.location]; + } + range = [location rangeOfString:separator options:NSBackwardsSearch]; + if (range.location != NSNotFound) { + NSString *value = [location substringFromIndex:range.location + 1]; + _unhandled = ![value isEqualToString:@"h"]; + location = [location substringToIndex:range.location + 1]; + } else { + _unhandled = YES; + } + range = [location rangeOfString:separator options:NSBackwardsSearch]; + if (range.location != NSNotFound) { + NSString *value = [location substringFromIndex:range.location]; + if ([value isEqualToString:@"w"]) { + _severity = BSGSeverityWarning; + } else if ([value isEqualToString:@"i"]) { + _severity = BSGSeverityInfo; + } else { + _severity = BSGSeverityError; + } + } + } + return self; +} + +@end + @implementation RegisterErrorData + (instancetype)errorDataFromThreads:(NSArray *)threads { for (NSDictionary *thread in threads) { diff --git a/Source/BugsnagErrorReportApiClient.m b/Source/BugsnagErrorReportApiClient.m index 0bf3b80b8..18b2d1cef 100644 --- a/Source/BugsnagErrorReportApiClient.m +++ b/Source/BugsnagErrorReportApiClient.m @@ -30,11 +30,11 @@ - (void)main { @autoreleasepool { @try { [[BSG_KSCrash sharedInstance] - sendAllReportsWithCompletion:^(NSArray *filteredReports, + sendAllReportsWithCompletion:^(NSUInteger sentReportCount, BOOL completed, NSError *error) { if (error) { bsg_log_warn(@"Failed to send reports: %@", error); - } else if (filteredReports.count > 0) { + } else if (sentReportCount > 0) { bsg_log_info(@"Reports sent."); } }]; diff --git a/Source/BugsnagFileStore.h b/Source/BugsnagFileStore.h index 48a10510f..fad1e36aa 100644 --- a/Source/BugsnagFileStore.h +++ b/Source/BugsnagFileStore.h @@ -40,6 +40,12 @@ */ - (NSArray *)allFiles; +/** Get a list of all files by filename. + * + * @return A collection of file contents indexed by filename. + */ +- (NSDictionary *)allFilesByName; + /** Delete a file. * * @param fileId The file ID. diff --git a/Source/BugsnagFileStore.m b/Source/BugsnagFileStore.m index 5fadd7a0e..1eee97744 100644 --- a/Source/BugsnagFileStore.m +++ b/Source/BugsnagFileStore.m @@ -128,13 +128,17 @@ - (NSUInteger)fileCount { } - (NSArray *)allFiles { + return [[self allFilesByName] allValues]; +} + +- (NSDictionary *)allFilesByName { NSArray *fileIds = [self fileIds]; - NSMutableArray *files = - [NSMutableArray arrayWithCapacity:[fileIds count]]; + NSMutableDictionary *files = + [NSMutableDictionary dictionaryWithCapacity:[fileIds count]]; for (NSString *fileId in fileIds) { NSDictionary *fileContents = [self fileWithId:fileId]; if (fileContents != nil) { - [files addObject:fileContents]; + [files setObject:fileContents forKey:fileId]; } } diff --git a/Source/BugsnagKeys.h b/Source/BugsnagKeys.h index 54a89693f..aabd15608 100644 --- a/Source/BugsnagKeys.h +++ b/Source/BugsnagKeys.h @@ -19,6 +19,7 @@ static NSString *const BSGKeyName = @"name"; static NSString *const BSGKeyTimestamp = @"timestamp"; static NSString *const BSGKeyType = @"type"; static NSString *const BSGKeyMetaData = @"metaData"; +static NSString *const BSGKeyIncomplete = @"incomplete"; static NSString *const BSGKeyId = @"id"; static NSString *const BSGKeyUser = @"user"; static NSString *const BSGKeyEmail = @"email"; diff --git a/Source/BugsnagSessionTrackingApiClient.m b/Source/BugsnagSessionTrackingApiClient.m index b140ecb7d..e63719e7e 100644 --- a/Source/BugsnagSessionTrackingApiClient.m +++ b/Source/BugsnagSessionTrackingApiClient.m @@ -47,11 +47,11 @@ - (void)deliverSessionsInStore:(BugsnagSessionFileStore *)store { @"Bugsnag-API-Key": apiKey, @"Bugsnag-Sent-At": [BSG_RFC3339DateTool stringFromDate:[NSDate new]] }; - [self sendData:payload - withPayload:[payload toJson] - toURL:sessionURL - headers:HTTPHeaders - onCompletion:^(id data, BOOL success, NSError *error) { + [self sendItems:sessions.count + withPayload:[payload toJson] + toURL:sessionURL + headers:HTTPHeaders + onCompletion:^(NSUInteger sentCount, BOOL success, NSError *error) { if (success && error == nil) { bsg_log_info(@"Sent %lu sessions to Bugsnag", (unsigned long) sessionCount); diff --git a/Source/BugsnagSink.m b/Source/BugsnagSink.m index 0fc2517a1..f81920713 100644 --- a/Source/BugsnagSink.m +++ b/Source/BugsnagSink.m @@ -56,15 +56,18 @@ - (instancetype)initWithApiClient:(BugsnagErrorReportApiClient *)apiClient { // - the report-specific `notifyReleaseStages` property is unset and the global // `notifyReleaseStages` property // and it contains the current stage -- (void)filterReports:(NSArray *)reports +- (void)filterReports:(NSDictionary *)reports onCompletion:(BSG_KSCrashReportFilterCompletion)onCompletion { NSMutableArray *bugsnagReports = [NSMutableArray new]; BugsnagConfiguration *configuration = [Bugsnag configuration]; - for (NSDictionary *report in reports) { - BugsnagCrashReport *bugsnagReport = [[BugsnagCrashReport alloc] initWithKSReport:report]; - BOOL incompleteReport = (![@"standard" isEqualToString:[report valueForKeyPath:@"report.type"]] || - [[report objectForKey:@"incomplete"] boolValue]); + for (NSString *fileKey in reports) { + NSDictionary *report = reports[fileKey]; + BugsnagCrashReport *bugsnagReport = [[BugsnagCrashReport alloc] initWithKSReport:report + fileMetadata:fileKey]; + BOOL incompleteReport = ([bugsnagReport isIncomplete] + || ![@"standard" isEqualToString:[report valueForKeyPath:@"report.type"]] + || [[report objectForKey:@"incomplete"] boolValue]); if (incompleteReport) { // append app/device data as this is unlikely to change between sessions NSDictionary *sysInfo = [BSG_KSSystemInfo systemInfo]; @@ -109,7 +112,7 @@ - (void)filterReports:(NSArray *)reports if (bugsnagReports.count == 0) { if (onCompletion) { - onCompletion(bugsnagReports, YES, nil); + onCompletion(bugsnagReports.count, YES, nil); } return; } @@ -120,7 +123,7 @@ - (void)filterReports:(NSArray *)reports #pragma clang diagnostic ignored "-Wdeprecated-declarations" for (BugsnagBeforeNotifyHook hook in configuration.beforeNotifyHooks) { if (reportData) { - reportData = hook(reports, reportData); + reportData = hook(bugsnagReports, reportData); } else { break; } @@ -129,16 +132,16 @@ - (void)filterReports:(NSArray *)reports if (reportData == nil) { if (onCompletion) { - onCompletion(@[], YES, nil); + onCompletion(0, YES, nil); } return; } - [self.apiClient sendData:bugsnagReports - withPayload:reportData - toURL:configuration.notifyURL - headers:[configuration errorApiHeaders] - onCompletion:onCompletion]; + [self.apiClient sendItems:bugsnagReports.count + withPayload:reportData + toURL:configuration.notifyURL + headers:[configuration errorApiHeaders] + onCompletion:onCompletion]; } diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m index deb835d17..39e977e65 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m @@ -320,12 +320,12 @@ - (void)sendAllReportsWithCompletion: (BSG_KSCrashReportFilterCompletion)onCompletion { [self.crashReportStore pruneFilesLeaving:self.maxStoredReports]; - NSArray *reports = [self allReports]; + NSDictionary *reports = [self allReportsByFilename]; BSG_KSLOG_INFO(@"Sending %d crash reports", [reports count]); [self sendReports:reports - onCompletion:^(NSArray *filteredReports, BOOL completed, + onCompletion:^(NSUInteger sentReportCount, BOOL completed, NSError *error) { BSG_KSLOG_DEBUG(@"Process finished with completion: %d", completed); if (error != nil) { @@ -336,7 +336,7 @@ - (void)sendAllReportsWithCompletion: self.deleteBehaviorAfterSendAll == BSG_KSCDeleteAlways) { [self deleteAllReports]; } - bsg_kscrash_i_callCompletion(onCompletion, filteredReports, + bsg_kscrash_i_callCompletion(onCompletion, sentReportCount, completed, error); }]; } @@ -357,6 +357,7 @@ - (void)reportUserException:(NSString *)name const char *cName = [name cStringUsingEncoding:NSUTF8StringEncoding]; const char *cReason = [reason cStringUsingEncoding:NSUTF8StringEncoding]; bsg_kscrash_reportUserException(cName, cReason, + [handledState[@"severity"] UTF8String], [self encodeAsJSONString:handledState], [self encodeAsJSONString:overrides], [self encodeAsJSONString:metadata], @@ -404,16 +405,16 @@ - (NSString *)crashReportsPath { return self.crashReportStore.path; } -- (void)sendReports:(NSArray *)reports +- (void)sendReports:(NSDictionary *)reports onCompletion:(BSG_KSCrashReportFilterCompletion)onCompletion { if ([reports count] == 0) { - bsg_kscrash_i_callCompletion(onCompletion, reports, YES, nil); + bsg_kscrash_i_callCompletion(onCompletion, 0, YES, nil); return; } if (self.sink == nil) { bsg_kscrash_i_callCompletion( - onCompletion, reports, NO, + onCompletion, 0, NO, [NSError bsg_errorWithDomain:[[self class] description] code:0 description:@"No sink set. Crash reports not sent."]); @@ -421,9 +422,9 @@ - (void)sendReports:(NSArray *)reports } [self.sink filterReports:reports - onCompletion:^(NSArray *filteredReports, BOOL completed, + onCompletion:^(NSUInteger sentReportCount, BOOL completed, NSError *error) { - bsg_kscrash_i_callCompletion(onCompletion, filteredReports, + bsg_kscrash_i_callCompletion(onCompletion, sentReportCount, completed, error); }]; } @@ -432,6 +433,10 @@ - (NSArray *)allReports { return [self.crashReportStore allFiles]; } +- (NSDictionary *)allReportsByFilename { + return [self.crashReportStore allFilesByName]; +} + - (BOOL)redirectConsoleLogsToFile:(NSString *)fullPath overwrite:(BOOL)overwrite { if (bsg_kslog_setLogFilename([fullPath UTF8String], overwrite)) { diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashAdvanced.h b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashAdvanced.h index 82692373e..24682d038 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashAdvanced.h +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashAdvanced.h @@ -85,6 +85,10 @@ typedef enum { */ - (NSArray *)allReports; +/** Get all reports as dictionaries, indexed by file name. + */ +- (NSDictionary *)allReportsByFilename; + #pragma mark - Configuration - /** Init BSG_KSCrash instance with custom report files directory path. */ @@ -156,7 +160,7 @@ typedef enum { * @param reports The reports to send. * @param onCompletion Called when sending is complete (nil = ignore). */ -- (void)sendReports:(NSArray *)reports +- (void)sendReports:(NSDictionary *)reports onCompletion:(BSG_KSCrashReportFilterCompletion)onCompletion; @end diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c index 1ebc3a93b..0165b7de5 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c @@ -63,11 +63,53 @@ static char *bsg_g_stateFilePath; // ============================================================================ #pragma mark - Utility - // ============================================================================ +static const int bsg_filepath_len = 512; +static const int bsg_error_class_filepath_len = 21; +static const char bsg_filepath_context_sep = '-'; static inline BSG_KSCrash_Context *crashContext(void) { return &bsg_g_crashReportContext; } +int bsg_create_filepath(char *base, char filepath[bsg_filepath_len], char severity, char error_class[bsg_error_class_filepath_len]) { + int length; + for (length = 0; length < bsg_filepath_len; length++) { + if (base[length] == '\0') { + break; + } + filepath[length] = base[length]; + } + if (length > 5) // Remove initial .json from path + length -= 5; + + // append contextual info + BSG_KSCrash_Context *context = crashContext(); + filepath[length++] = bsg_filepath_context_sep; + filepath[length++] = severity; + filepath[length++] = bsg_filepath_context_sep; + // 'h' for handled vs 'u'nhandled + filepath[length++] = context->crash.crashType == BSG_KSCrashTypeUserReported ? 'h' : 'u'; + filepath[length++] = bsg_filepath_context_sep; + for (int i = 0; error_class != NULL && i < bsg_error_class_filepath_len; i++) { + char c = error_class[i]; + if (c == '\0') + break; + else if (c == 47 || c > 126 || c <= 0) + // disallow '/' and characters outside of the ascii range + continue; + filepath[length++] = c; + } + // add suffix + filepath[length++] = '.'; + filepath[length++] = 'j'; + filepath[length++] = 's'; + filepath[length++] = 'o'; + filepath[length++] = 'n'; + filepath[length++] = '\0'; + + return length; +} + // ============================================================================ #pragma mark - Callbacks - // ============================================================================ @@ -78,7 +120,7 @@ static inline BSG_KSCrash_Context *crashContext(void) { * * This function gets passed as a callback to a crash handler. */ -void bsg_kscrash_i_onCrash(void) { +void bsg_kscrash_i_onCrash(char severity, char *errorClass) { BSG_KSLOG_DEBUG("Updating application state to note crash."); bsg_kscrashstate_notifyAppCrash(); @@ -92,8 +134,9 @@ void bsg_kscrash_i_onCrash(void) { bsg_kscrashreport_writeMinimalReport(context, bsg_g_recrashReportFilePath); } else { - bsg_kscrashreport_writeStandardReport(context, - bsg_g_crashReportFilePath); + char filepath[bsg_filepath_len]; + bsg_create_filepath(bsg_g_crashReportFilePath, filepath, severity, errorClass); + bsg_kscrashreport_writeStandardReport(context, filepath); } } @@ -242,6 +285,7 @@ void bsg_kscrash_setCrashNotifyCallback( } void bsg_kscrash_reportUserException(const char *name, const char *reason, + const char *severity, const char *handledState, const char *overrides, const char *metadata, @@ -249,7 +293,7 @@ void bsg_kscrash_reportUserException(const char *name, const char *reason, const char *config, int discardDepth, bool terminateProgram) { - bsg_kscrashsentry_reportUserException(name, reason, handledState, overrides, + bsg_kscrashsentry_reportUserException(name, reason, severity, handledState, overrides, metadata, appState, config, discardDepth, terminateProgram); } diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.h b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.h index 9df924549..2aeb81cc8 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.h +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.h @@ -187,6 +187,7 @@ void bsg_kscrash_setCrashNotifyCallback( * Terminate the program instead. */ void bsg_kscrash_reportUserException(const char *name, const char *reason, + const char *severity, const char *handledState, const char *overrides, const char *metadata, diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.c b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.c index a2cc2d98c..50b0bd1cd 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.c +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.c @@ -96,7 +96,7 @@ static bool bsg_g_threads_are_running = true; BSG_KSCrashType bsg_kscrashsentry_installWithContext(BSG_KSCrash_SentryContext *context, BSG_KSCrashType crashTypes, - void (*onCrash)(void)) { + void (*onCrash)(char, char *)) { if (bsg_ksmachisBeingTraced()) { if (context->reportWhenDebuggerIsAttached) { BSG_KSLOG_WARN("KSCrash: App is running in a debugger. Crash " @@ -206,7 +206,7 @@ void bsg_kscrashsentry_resumeThreads(void) { } void bsg_kscrashsentry_clearContext(BSG_KSCrash_SentryContext *context) { - void (*onCrash)(void) = context->onCrash; + void (*onCrash)(char, char *) = context->onCrash; bool threadTracingEnabled = context->threadTracingEnabled; bool reportWhenDebuggerIsAttached = context->reportWhenDebuggerIsAttached; bool suspendThreadsForUserReported = context->suspendThreadsForUserReported; diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.h b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.h index 1e8e73311..b6d51008e 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.h +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry.h @@ -51,7 +51,7 @@ typedef struct BSG_KSCrash_SentryContext { // Caller defined values. Caller must fill these out prior to installation. /** Called by the crash handler when a crash is detected. */ - void (*onCrash)(void); + void (*onCrash)(char, char[21]); /** If true, will suspend threads for user reported exceptions. */ bool suspendThreadsForUserReported; @@ -162,7 +162,7 @@ typedef struct BSG_KSCrash_SentryContext { BSG_KSCrashType bsg_kscrashsentry_installWithContext(BSG_KSCrash_SentryContext *context, BSG_KSCrashType crashTypes, - void (*onCrash)(void)); + void (*onCrash)(char, char *)); /** Uninstall crash sentry. * diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_CPPException.mm b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_CPPException.mm index f7de5f039..1456c83d2 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_CPPException.mm +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_CPPException.mm @@ -179,7 +179,9 @@ static void CPPExceptionTerminate(void) { bsg_g_context->crashReason = description; BSG_KSLOG_DEBUG(@"Calling main crash handler."); - bsg_g_context->onCrash(); + char errorClass[21]; + strncpy(errorClass, bsg_g_context->CPPException.name, sizeof(errorClass)); + bsg_g_context->onCrash('e', errorClass); BSG_KSLOG_DEBUG( @"Crash handling complete. Restoring original handlers."); diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Deadlock.m b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Deadlock.m index cb7e5b0bf..29706dd53 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Deadlock.m +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Deadlock.m @@ -114,7 +114,7 @@ - (void)handleDeadlock { bsg_g_context->registersAreValid = false; BSG_KSLOG_DEBUG(@"Calling main crash handler."); - bsg_g_context->onCrash(); + bsg_g_context->onCrash('e', ""); BSG_KSLOG_DEBUG(@"Crash handling complete. Restoring original handlers."); bsg_kscrashsentry_uninstall(BSG_KSCrashTypeAll); diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_MachException.c b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_MachException.c index 146ee01d6..5ce551ba9 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_MachException.c +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_MachException.c @@ -285,7 +285,9 @@ void *ksmachexc_i_handleExceptions(void *const userData) { bsg_g_context->mach.subcode = exceptionMessage.code[1]; BSG_KSLOG_DEBUG("Calling main crash handler."); - bsg_g_context->onCrash(); + char errorClass[21]; + strncpy(errorClass, bsg_ksmachexceptionName(bsg_g_context->mach.type), sizeof(errorClass)); + bsg_g_context->onCrash('e', errorClass); BSG_KSLOG_DEBUG( "Crash handling complete. Restoring original handlers."); diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_NSException.m b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_NSException.m index b72084eb4..10a46fb0c 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_NSException.m +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_NSException.m @@ -128,7 +128,9 @@ void bsg_recordException(NSException *exception) { bsg_g_context->stackTraceLength = (int)numFrames; BSG_KSLOG_DEBUG(@"Calling main crash handler."); - bsg_g_context->onCrash(); + char errorClass[21]; + strncpy(errorClass, bsg_g_context->NSException.name, sizeof(errorClass)); + bsg_g_context->onCrash('e', errorClass); } } diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Signal.c b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Signal.c index 51bf8a2ea..1cc26f8eb 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Signal.c +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_Signal.c @@ -107,7 +107,18 @@ void bsg_kssighndl_i_handleSignal(int sigNum, siginfo_t *signalInfo, bsg_g_context->signal.signalInfo = signalInfo; BSG_KSLOG_DEBUG("Calling main crash handler."); - bsg_g_context->onCrash(); + char errorClass[21]; + const char *sigName = bsg_kssignal_signalName(sigNum); + if (sigName != NULL) { + for (int i = 0; i < sizeof(errorClass); i++) { + char c = sigName[i]; + if (c == '\0') { + break; + } + errorClass[i] = c; + } + } + bsg_g_context->onCrash('e', errorClass); BSG_KSLOG_DEBUG( "Crash handling complete. Restoring original handlers."); diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.c b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.c index fddd69917..aa247955c 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.c +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.c @@ -48,6 +48,7 @@ void bsg_kscrashsentry_uninstallUserExceptionHandler(void) { } void bsg_kscrashsentry_reportUserException(const char *name, const char *reason, + const char *severity, const char *handledState, const char *overrides, const char *metadata, @@ -92,7 +93,9 @@ void bsg_kscrashsentry_reportUserException(const char *name, const char *reason, bsg_g_context->userException.state = appState; BSG_KSLOG_DEBUG("Calling main crash handler."); - bsg_g_context->onCrash(); + char errorClass[21]; + strncpy(errorClass, bsg_g_context->userException.name, sizeof(errorClass)); + bsg_g_context->onCrash(strlen(severity) > 0 ? severity[0] : 'w', errorClass); if (terminateProgram) { bsg_kscrashsentry_uninstall(BSG_KSCrashTypeAll); diff --git a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.h b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.h index 103f33823..f68f583e9 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.h +++ b/Source/KSCrash/Source/KSCrash/Recording/Sentry/BSG_KSCrashSentry_User.h @@ -67,6 +67,7 @@ void bsg_kscrashsentry_uninstallUserExceptionHandler(void); * Terminate the program instead. */ void bsg_kscrashsentry_reportUserException(const char *name, const char *reason, + const char *severity, const char *handledState, const char *overrides, const char *metadata, diff --git a/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.h b/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.h index d212ea4c7..bc0471720 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.h +++ b/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.h @@ -29,10 +29,10 @@ /** Conditionally call a completion method if it's not nil. * * @param onCompletion The completion block. If nil, this function does nothing. - * @param filteredReports The parameter to send as "filteredReports". + * @param sentReportCount The number of reports successfully handled. * @param completed The parameter to send as "completed". * @param error The parameter to send as "error". */ void bsg_kscrash_i_callCompletion( - BSG_KSCrashReportFilterCompletion onCompletion, NSArray *filteredReports, + BSG_KSCrashReportFilterCompletion onCompletion, NSUInteger sentReportCount, BOOL completed, NSError *error); diff --git a/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.m b/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.m index 7a7f2027d..181bdbb02 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.m +++ b/Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSCrashCallCompletion.m @@ -27,9 +27,9 @@ #import "BSG_KSCrashCallCompletion.h" void bsg_kscrash_i_callCompletion( - BSG_KSCrashReportFilterCompletion onCompletion, NSArray *filteredReports, + BSG_KSCrashReportFilterCompletion onCompletion, NSUInteger reportCount, BOOL completed, NSError *error) { if (onCompletion) { - onCompletion(filteredReports, completed, error); + onCompletion(reportCount, completed, error); } } diff --git a/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilter.h b/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilter.h index f09806c51..7f73fa20c 100644 --- a/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilter.h +++ b/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilter.h @@ -37,7 +37,7 @@ * @param reports The reports to process. * @param onCompletion Block to call when processing is complete. */ -- (void)filterReports:(NSArray *)reports +- (void)filterReports:(NSDictionary *)reports onCompletion:(BSG_KSCrashReportFilterCompletion)onCompletion; @end diff --git a/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilterCompletion.h b/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilterCompletion.h index 9462707dd..19177ad85 100644 --- a/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilterCompletion.h +++ b/Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilterCompletion.h @@ -28,13 +28,12 @@ /** Callback for filter operations. * - * @param filteredReports The filtered reports (may be incomplete if "completed" - * is false). + * @param sentReportCount The number of reports successfully sent. * @param completed True if filtering completed. * Can be false due to a non-erroneous condition (such as a * user cancelling the operation). * @param error Non-nil if an error occurred. */ -typedef void (^BSG_KSCrashReportFilterCompletion)(NSArray *filteredReports, +typedef void (^BSG_KSCrashReportFilterCompletion)(NSUInteger sentReportCount, BOOL completed, NSError *error); diff --git a/Tests/BSGFilepathTests.m b/Tests/BSGFilepathTests.m new file mode 100644 index 000000000..31416eec1 --- /dev/null +++ b/Tests/BSGFilepathTests.m @@ -0,0 +1,64 @@ +// +// BSGFilepathTests.m +// Tests +// +// Created by Delisa on 2/19/19. +// Copyright © 2019 Bugsnag. All rights reserved. +// + +#import +#include + + +int bsg_create_filepath(char *base, char filepath[512], char severity, char error_class[21]); + +@interface BSGFilepathTests : XCTestCase + +@end + +@implementation BSGFilepathTests + +- (void)testEncodeCharacters { + char *base = "/path/to/it/imagine this is a UUID.json"; + char filepath[512]; + bsg_create_filepath(base, filepath, 'w', "😃 HappyError"); + XCTAssertEqual(0, strcmp(filepath, "/path/to/it/imagine this is a UUID-w-u- HappyError.json")); +} + +- (void)testTruncateUnicodeCharacters { + char *base = "/path/to/it/imagine this is a UUID.json"; + char filepath[512]; + // The char limit is not on a character boundary + bsg_create_filepath(base, filepath, 'e', "AnExtremelyLongLong😃"); + XCTAssertEqual(0, strcmp(filepath, "/path/to/it/imagine this is a UUID-e-u-AnExtremelyLongLong.json")); +} + +- (void)testNullErrorClass { + char *base = "/path/to/it/imagine this is a UUID.json"; + char filepath[512]; + bsg_create_filepath(base, filepath, 'e', NULL); + XCTAssertEqual(0, strcmp(filepath, "/path/to/it/imagine this is a UUID-e-u-.json")); +} + +- (void)testEmptyErrorClass { + char *base = "/path/to/it/imagine this is a UUID.json"; + char filepath[512]; + bsg_create_filepath(base, filepath, 'e', ""); + XCTAssertEqual(0, strcmp(filepath, "/path/to/it/imagine this is a UUID-e-u-.json")); +} + +- (void)testEmptyErrorClassFromUnicode { + char *base = "/path/to/it/imagine this is a UUID.json"; + char filepath[512]; + bsg_create_filepath(base, filepath, 'e', "🀦🀨🁺😃"); + XCTAssertEqual(0, strcmp(filepath, "/path/to/it/imagine this is a UUID-e-u-.json")); +} + +- (void)testErrorClassLength { + char *base = "imagine this is a UUID.json"; + char filepath[512]; + bsg_create_filepath(base, filepath, 'i', "AnExtremelyLongLongErrorNameOmg"); + XCTAssertEqual(0, strcmp(filepath, "imagine this is a UUID-i-u-AnExtremelyLongLongEr.json")); +} + +@end diff --git a/Tests/BugsnagCrashReportTests.m b/Tests/BugsnagCrashReportTests.m index 54cfebbbd..b69fb9bd4 100644 --- a/Tests/BugsnagCrashReportTests.m +++ b/Tests/BugsnagCrashReportTests.m @@ -14,6 +14,7 @@ #import "BugsnagHandledState.h" #import "BugsnagSession.h" + @interface BugsnagCrashReportTests : XCTestCase @property BugsnagCrashReport *report; @end @@ -178,6 +179,45 @@ - (void)testDefaultErrorMessageNil { payload[@"exceptions"][0][@"message"]); } +- (void)testIncomplete { + XCTAssertTrue([[[BugsnagCrashReport alloc] initWithKSReport:@{}] isIncomplete]); + XCTAssertFalse([[[BugsnagCrashReport alloc] initWithKSReport:@{@"foo": @"bar"}] isIncomplete]); +} + +- (void)testFallbackValues { + BugsnagCrashReport *report = + [[BugsnagCrashReport alloc] initWithKSReport:@{} fileMetadata:@"w-h-SomeErr thing"]; + XCTAssertTrue([report isIncomplete]); + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"SomeErr thing", payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"warning", payload[@"severity"]); + XCTAssertEqualObjects(@NO, payload[@"unhandled"]); +} + +- (void)testUnneededFallbackValues { + BugsnagHandledState *state = [BugsnagHandledState handledStateWithSeverityReason:UserCallbackSetSeverity + severity:BSGSeverityInfo + attrValue:nil]; + NSDictionary *dict = @{@"user.handledState": [state toJson]}; + BugsnagCrashReport *report = + [[BugsnagCrashReport alloc] initWithKSReport:dict fileMetadata:@"w-h-SomeErr thing"]; + XCTAssertFalse([report isIncomplete]); + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"SomeErr thing", payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"info", payload[@"severity"]); + XCTAssertEqualObjects(@NO, payload[@"unhandled"]); +} + +- (void)testUnhandledFallbackValues { + BugsnagCrashReport *report = + [[BugsnagCrashReport alloc] initWithKSReport:@{} fileMetadata:@"foofoo-e-u-SomeErr thing"]; + XCTAssertTrue([report isIncomplete]); + NSDictionary *payload = [report toJson]; + XCTAssertEqualObjects(@"SomeErr thing", payload[@"exceptions"][0][@"errorClass"]); + XCTAssertEqualObjects(@"error", payload[@"severity"]); + XCTAssertEqualObjects(@YES, payload[@"unhandled"]); +} + - (void)testDefaultErrorMessageNilForEmptyThreads { BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:@{ @"threads" : @[] @@ -533,5 +573,4 @@ - (void)testAppVersionOverride { XCTAssertEqualObjects(@"1.2.3", dictionary[@"app"][@"version"]); } - @end diff --git a/Tests/KSCrash/KSCrashSentry_Tests.m b/Tests/KSCrash/KSCrashSentry_Tests.m index 6c0f1829a..1b165b79d 100755 --- a/Tests/KSCrash/KSCrashSentry_Tests.m +++ b/Tests/KSCrash/KSCrashSentry_Tests.m @@ -30,7 +30,7 @@ #import "BSG_KSCrashSentry.h" #import "BSG_KSCrashSentry_Private.h" -static void onCrash(void) +static void onCrash(char severity, char *errorClass) { // Do nothing } diff --git a/features/crashprobe.feature b/features/crashprobe.feature index d119c9612..d8b054959 100644 --- a/features/crashprobe.feature +++ b/features/crashprobe.feature @@ -218,3 +218,15 @@ Scenario: Access a non-object as an object And the exception "message" equals "Attempted to dereference garbage pointer 0x10." And the exception "errorClass" equals "EXC_BAD_ACCESS" And the "method" of stack frame 0 equals "objc_msgSend" + +Scenario: Crash report file corruption + When I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" + And I crash the app using "AccessNonObjectScenario" + And I corrupt all reports on disk + And I relaunch the app + Then I should receive a request + And the request is a valid for the error reporting API + And the exception "errorClass" equals "EXC_BAD_ACCESS" + And the event "unhandled" is true + And the event "incomplete" is true + And the event "severity" equals "error" diff --git a/features/steps/ios_steps.rb b/features/steps/ios_steps.rb index 9766630f3..e71eda56f 100644 --- a/features/steps/ios_steps.rb +++ b/features/steps/ios_steps.rb @@ -43,6 +43,15 @@ assert_not_equal(value1, value2) end +When("I corrupt all reports on disk") do + app_path = `xcrun simctl get_app_container booted com.bugsnag.iOSTestApp`.chomp + app_path.gsub!(/(.*Containers).*/, '\1') + files = Dir.glob("#{app_path}/**/KSCrashReports/iOSTestApp/*.json") + files.each do |path| + File.open(path, 'w') {|file| file.truncate(0) } + end +end + Then("each event in the payload for request {int} matches one of:") do |request_index, table| events = read_key_path(find_request(request_index)[:body], "events") table.hashes.each do |values| diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index d8cc3ea44..e9d60fcb2 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8A12006A221C36420008C9C3 /* BSGFilepathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A120069221C36420008C9C3 /* BSGFilepathTests.m */; }; 8A2C8F231C6BBD2300846019 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8F181C6BBD2300846019 /* Bugsnag.framework */; }; 8A2C8F4F1C6BBE3C00846019 /* Bugsnag.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A2C8F3D1C6BBE3C00846019 /* Bugsnag.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8A2C8F501C6BBE3C00846019 /* Bugsnag.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8F3E1C6BBE3C00846019 /* Bugsnag.m */; }; @@ -410,6 +411,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8A120069221C36420008C9C3 /* BSGFilepathTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BSGFilepathTests.m; path = ../../Tests/BSGFilepathTests.m; sourceTree = ""; }; 8A2C8F181C6BBD2300846019 /* Bugsnag.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bugsnag.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A2C8F1D1C6BBD2300846019 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 8A2C8F221C6BBD2300846019 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -732,6 +734,7 @@ F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */, F42954B7D892334E7551F0F3 /* RegisterErrorDataTest.m */, F429551527EAE3AFE1F605FE /* BugsnagThreadTest.m */, + 8A120069221C36420008C9C3 /* BSGFilepathTests.m */, ); name = Tests; path = BugsnagTests; @@ -1244,6 +1247,7 @@ E78C1EF11FCC2F1700B976D3 /* BugsnagSessionTrackerTest.m in Sources */, F4295995C3259BF7D9730BC4 /* BugsnagKSCrashSysInfoParserTest.m in Sources */, F4295F017754324FD52CCE46 /* RegisterErrorDataTest.m in Sources */, + 8A12006A221C36420008C9C3 /* BSGFilepathTests.m in Sources */, F42952D83435C02F8D891C40 /* BugsnagThreadTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/tvOS/Bugsnag.xcodeproj/project.pbxproj b/tvOS/Bugsnag.xcodeproj/project.pbxproj index 5f1f9aeb8..e1d3c867b 100644 --- a/tvOS/Bugsnag.xcodeproj/project.pbxproj +++ b/tvOS/Bugsnag.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8A12006E221C51550008C9C3 /* BSGFilepathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A12006D221C51550008C9C3 /* BSGFilepathTests.m */; }; 8A48EF291EAA824100B70024 /* BugsnagLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A48EF281EAA824100B70024 /* BugsnagLogger.h */; }; 8A627CD51EC3B69300F7C04E /* BSGSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A627CD31EC3B69300F7C04E /* BSGSerialization.h */; }; 8A627CD61EC3B69300F7C04E /* BSGSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A627CD41EC3B69300F7C04E /* BSGSerialization.m */; }; @@ -187,6 +188,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8A12006D221C51550008C9C3 /* BSGFilepathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BSGFilepathTests.m; path = ../../Tests/BSGFilepathTests.m; sourceTree = ""; }; 8A48EF281EAA824100B70024 /* BugsnagLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagLogger.h; path = ../Source/BugsnagLogger.h; sourceTree = ""; }; 8A627CD31EC3B69300F7C04E /* BSGSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BSGSerialization.h; path = ../Source/BSGSerialization.h; sourceTree = ""; }; 8A627CD41EC3B69300F7C04E /* BSGSerialization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BSGSerialization.m; path = ../Source/BSGSerialization.m; sourceTree = ""; }; @@ -465,6 +467,7 @@ isa = PBXGroup; children = ( E7CE78D71FD94EF1001D07E0 /* KSCrash */, + 8A12006D221C51550008C9C3 /* BSGFilepathTests.m */, E791488C1FD82E77003EFEBF /* BugsnagSessionTest.m */, E791488A1FD82E77003EFEBF /* BugsnagSessionTrackerTest.m */, E791488B1FD82E77003EFEBF /* BugsnagSessionTrackingPayloadTest.m */, @@ -941,6 +944,7 @@ E7CE79051FD94F1B001D07E0 /* KSDynamicLinker_Tests.m in Sources */, E7CE78F61FD94F1B001D07E0 /* FileBasedTestCase.m in Sources */, E7CE79031FD94F1B001D07E0 /* KSCrashReportConverter_Tests.m in Sources */, + 8A12006E221C51550008C9C3 /* BSGFilepathTests.m in Sources */, E79148911FD82E77003EFEBF /* BugsnagUserTest.m in Sources */, E791488E1FD82E77003EFEBF /* BugsnagSessionTrackerTest.m in Sources */, E79148901FD82E77003EFEBF /* BugsnagSessionTest.m in Sources */,