diff --git a/CHANGELOG.md b/CHANGELOG.md index 98edc5f78..2bfed710b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Bugsnag Notifiers on other platforms. ## Enhancements +* Create structured `BugsnagError` class + [#533](https://github.com/bugsnag/bugsnag-cocoa/pull/533) + * Create structured `BugsnagThread` class [#532](https://github.com/bugsnag/bugsnag-cocoa/pull/532) @@ -17,9 +20,6 @@ Bugsnag Notifiers on other platforms. * Create structured `BugsnagStackframe` class [#528](https://github.com/bugsnag/bugsnag-cocoa/pull/528) -* Create structured `BugsnagStackframe` class - [#528](https://github.com/bugsnag/bugsnag-cocoa/pull/528) - * Convert `event.app` from `NSDictionary` to a structured class [#520](https://github.com/bugsnag/bugsnag-cocoa/pull/520) diff --git a/OSX/Bugsnag.xcodeproj/project.pbxproj b/OSX/Bugsnag.xcodeproj/project.pbxproj index abf0b57e6..e4b549783 100644 --- a/OSX/Bugsnag.xcodeproj/project.pbxproj +++ b/OSX/Bugsnag.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ E72352C21F55924A00436528 /* BSGConnectivity.m in Sources */ = {isa = PBXBuildFile; fileRef = E72352C01F55924A00436528 /* BSGConnectivity.m */; }; E72AE1F9241A4E7500ED8972 /* BugsnagPluginClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E72AE1F7241A4E7500ED8972 /* BugsnagPluginClient.m */; }; E72AE1FA241A4E7500ED8972 /* BugsnagPluginClient.h in Headers */ = {isa = PBXBuildFile; fileRef = E72AE1F8241A4E7500ED8972 /* BugsnagPluginClient.h */; }; + E73D443C243E192F001686F5 /* BugsnagErrorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E73D443B243E192F001686F5 /* BugsnagErrorTest.m */; }; E7433AD21F4F64EF00C082D1 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8FF11C6BC3A800846019 /* libz.tbd */; }; E7433AD31F4F64F400C082D1 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8FF31C6BC3AE00846019 /* libc++.tbd */; }; E7529F8E243C8EBF006B4932 /* RegisterErrorData.h in Headers */ = {isa = PBXBuildFile; fileRef = E7529F8C243C8EBF006B4932 /* RegisterErrorData.h */; }; @@ -281,6 +282,7 @@ E72AE1F8241A4E7500ED8972 /* BugsnagPluginClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagPluginClient.h; path = ../Source/BugsnagPluginClient.h; sourceTree = ""; }; E7529F8C243C8EBF006B4932 /* RegisterErrorData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RegisterErrorData.h; path = ../Source/RegisterErrorData.h; sourceTree = ""; }; E7529F8D243C8EBF006B4932 /* RegisterErrorData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegisterErrorData.m; path = ../Source/RegisterErrorData.m; sourceTree = ""; }; + E73D443B243E192F001686F5 /* BugsnagErrorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagErrorTest.m; sourceTree = ""; }; E7529F9F243CAE34006B4932 /* BugsnagThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagThreadTest.m; sourceTree = ""; }; E7529FA1243CAE3F006B4932 /* BugsnagThreadSerializationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagThreadSerializationTest.m; path = ../iOS/BugsnagTests/BugsnagThreadSerializationTest.m; sourceTree = ""; }; E762E9F71F73F7E900E82B43 /* BugsnagHandledStateTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagHandledStateTest.m; path = ../Tests/BugsnagHandledStateTest.m; sourceTree = SOURCE_ROOT; }; @@ -540,6 +542,7 @@ 4B775FD222CBE02A004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m */, 8AD9FA851E0862DC002859A7 /* BugsnagConfigurationTests.m */, E7A9E56C2436365300D99F8A /* BugsnagDeviceTest.m */, + E73D443B243E192F001686F5 /* BugsnagErrorTest.m */, 00D7ACA223E984B300FBE4A7 /* BugsnagEventTests.m */, E762E9F71F73F7E900E82B43 /* BugsnagHandledStateTest.m */, E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */, @@ -1149,6 +1152,7 @@ E7CE78BF1FD94E77001D07E0 /* KSCrashSentry_Signal_Tests.m in Sources */, 8A530CC922FDD74300F0C108 /* KSCrashIdentifierTests.m in Sources */, 4B406C1822CAD96400464D1D /* BugsnagCollectionsBSGDictMergeTest.m in Sources */, + E73D443C243E192F001686F5 /* BugsnagErrorTest.m in Sources */, 00D7ACA423E9854C00FBE4A7 /* BugsnagEventTests.m in Sources */, E7CE78CB1FD94E77001D07E0 /* KSSysCtl_Tests.m in Sources */, E790C42324324528006FFB26 /* BugsnagClientMirrorTest.m in Sources */, diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 989c8cc8d..54041070e 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -889,8 +889,8 @@ - (void)notify:(NSException *)exception int depth = (int)(BSGNotifierStackFrameCount + event.depth); - NSString *eventErrorClass = event.errorClass ?: NSStringFromClass([NSException class]); - NSString *eventMessage = event.errorMessage ?: @""; + NSString *eventErrorClass = event.errors[0].errorClass ?: NSStringFromClass([NSException class]); + NSString *eventMessage = event.errors[0].errorMessage ?: @""; [self.crashSentry reportUserException:eventErrorClass reason:eventMessage diff --git a/Source/BugsnagConfiguration.h b/Source/BugsnagConfiguration.h index a2f93dc8c..39c5e2c4d 100644 --- a/Source/BugsnagConfiguration.h +++ b/Source/BugsnagConfiguration.h @@ -75,7 +75,7 @@ typedef BOOL (^BugsnagOnBreadcrumbBlock)(BugsnagBreadcrumb *_Nonnull breadcrumb) */ typedef void(^BugsnagOnSessionBlock)(NSMutableDictionary *_Nonnull sessionPayload); -typedef NS_OPTIONS(NSUInteger, BSGErrorType) { +typedef NS_OPTIONS(NSUInteger, BSGEnabledErrorType) { BSGErrorTypesNone NS_SWIFT_NAME(None) = 0, BSGErrorTypesOOMs NS_SWIFT_NAME(OOMs) = 1 << 0, BSGErrorTypesNSExceptions NS_SWIFT_NAME(NSExceptions) = 1 << 1, @@ -170,7 +170,7 @@ typedef NS_OPTIONS(NSUInteger, BSGErrorType) { * Passed down to KSCrash in BugsnagCrashSentry. * Defaults to all-true */ -@property BSGErrorType enabledErrorTypes; +@property BSGEnabledErrorType enabledErrorTypes; /** * Required declaration to suppress a superclass designated-initializer error diff --git a/Source/BugsnagCrashSentry.m b/Source/BugsnagCrashSentry.m index fc2f9f435..99295159a 100644 --- a/Source/BugsnagCrashSentry.m +++ b/Source/BugsnagCrashSentry.m @@ -37,7 +37,7 @@ - (void)install:(BugsnagConfiguration *)config // If Bugsnag is autodetecting errors then the types of event detected is configurable // (otherwise it's just the user reported events) if (config.autoDetectErrors) { - BSGErrorType errorTypes = [config enabledErrorTypes]; + BSGEnabledErrorType errorTypes = [config enabledErrorTypes]; // Translate the relevant BSGErrorTypes bitfield into the equivalent BSG_KSCrashType one crashTypes = crashTypes | [self mapKSToBSGCrashTypes:errorTypes]; } @@ -58,7 +58,7 @@ - (void)install:(BugsnagConfiguration *)config * @param bsgCrashMask The BSGErrorType bitfield * @returns A BSG_KSCrashType equivalent (with the above caveats) to the input */ -- (BSG_KSCrashType)mapKSToBSGCrashTypes:(BSGErrorType)bsgCrashMask +- (BSG_KSCrashType)mapKSToBSGCrashTypes:(BSGEnabledErrorType)bsgCrashMask { BSG_KSCrashType crashType; crashType = (bsgCrashMask & BSGErrorTypesNSExceptions ? BSG_KSCrashTypeNSException : 0) diff --git a/Source/BugsnagError.h b/Source/BugsnagError.h index a46167812..7b7caab2a 100644 --- a/Source/BugsnagError.h +++ b/Source/BugsnagError.h @@ -8,13 +8,37 @@ #import -NS_ASSUME_NONNULL_BEGIN +@class BugsnagStackframe; + +typedef NS_OPTIONS(NSUInteger, BSGErrorType) { + BSGErrorTypeCocoa, + BSGErrorTypeC, + BSGErrorTypeReactNativeJs +}; /** * An Error represents information extracted from an NSError, NSException, or other error source. */ @interface BugsnagError : NSObject -@end +/** + * The class of the error generating the report + */ +@property(nullable) NSString *errorClass; + +/** + * The message of or reason for the error generating the report + */ +@property(nullable) NSString *errorMessage; -NS_ASSUME_NONNULL_END +/** + * Sets a representation of this error's stacktrace + */ +@property(readonly, nonnull) NSArray *stacktrace; + +/** + * The type of the captured error + */ +@property BSGErrorType type; + +@end diff --git a/Source/BugsnagError.m b/Source/BugsnagError.m index 24be24f14..35d3d0a79 100644 --- a/Source/BugsnagError.m +++ b/Source/BugsnagError.m @@ -7,7 +7,112 @@ // #import "BugsnagError.h" +#import "BugsnagKeys.h" +#import "BugsnagStackframe.h" +#import "BugsnagStacktrace.h" +#import "BugsnagCollections.h" +#import "RegisterErrorData.h" +#import "BugsnagThread.h" + +NSString *_Nonnull BSGSerializeErrorType(BSGErrorType errorType) { + switch (errorType) { + case BSGErrorTypeCocoa: + return @"cocoa"; + case BSGErrorTypeC: + return @"c"; + case BSGErrorTypeReactNativeJs: + return @"reactnativejs"; + default: + return nil; + } +} + +NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, NSString *errorType) { + NSString *errorClass; + + if ([errorType isEqualToString:BSGKeyCppException]) { + errorClass = error[BSGKeyCppException][BSGKeyName]; + } else if ([errorType isEqualToString:BSGKeyMach]) { + errorClass = error[BSGKeyMach][BSGKeyExceptionName]; + } else if ([errorType isEqualToString:BSGKeySignal]) { + errorClass = error[BSGKeySignal][BSGKeyName]; + } else if ([errorType isEqualToString:@"nsexception"]) { + errorClass = error[@"nsexception"][BSGKeyName]; + } else if ([errorType isEqualToString:BSGKeyUser]) { + errorClass = error[@"user_reported"][BSGKeyName]; + } + + if (!errorClass) { // use a default value + errorClass = @"Exception"; + } + return errorClass; +} + +NSString *BSGParseErrorMessage(NSDictionary *report, NSDictionary *error, NSString *errorType) { + if ([errorType isEqualToString:BSGKeyMach] || error[BSGKeyReason] == nil) { + NSString *diagnosis = [report valueForKeyPath:@"crash.diagnosis"]; + if (diagnosis && ![diagnosis hasPrefix:@"No diagnosis"]) { + return [[diagnosis componentsSeparatedByString:@"\n"] firstObject]; + } + } + return error[BSGKeyReason] ?: @""; +} + +@interface BugsnagStackframe () +- (NSDictionary *)toDictionary; +@end + +@interface BugsnagStacktrace () +@property NSMutableArray *trace; +@end @implementation BugsnagError +- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(BugsnagThread *)thread { + if (self = [super init]) { + NSDictionary *error = [event valueForKeyPath:@"crash.error"]; + NSString *errorType = error[BSGKeyType]; + _errorClass = BSGParseErrorClass(error, errorType); + _errorMessage = BSGParseErrorMessage(event, error, errorType); + _type = BSGErrorTypeCocoa; + + if (![[event valueForKeyPath:@"user.state.didOOM"] boolValue]) { + NSArray *threadDict = [event valueForKeyPath:@"crash.threads"]; + RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:threadDict]; + if (data) { + _errorClass = data.errorClass; + _errorMessage = data.errorMessage; + } + _stacktrace = thread.stacktrace; + } + } + return self; +} + +- (NSDictionary *)findErrorReportingThread:(NSDictionary *)event { + NSArray *threads = [event valueForKeyPath:@"crash.threads"]; + + for (NSDictionary *thread in threads) { + if ([thread[@"crashed"] boolValue]) { + return thread; + } + } + return nil; +} + +- (NSDictionary *)toDictionary { + NSMutableDictionary *dict = [NSMutableDictionary new]; + BSGDictInsertIfNotNil(dict, self.errorClass, BSGKeyErrorClass); + BSGDictInsertIfNotNil(dict, self.errorMessage, BSGKeyMessage); + BSGDictInsertIfNotNil(dict, BSGSerializeErrorType(self.type), BSGKeyType); + + NSMutableArray *frames = [NSMutableArray new]; + for (BugsnagStackframe *frame in self.stacktrace) { + [frames addObject:[frame toDictionary]]; + } + + BSGDictSetSafeObject(dict, frames, BSGKeyStacktrace); + return dict; +} + @end diff --git a/Source/BugsnagEvent.h b/Source/BugsnagEvent.h index de60e7276..53286636e 100644 --- a/Source/BugsnagEvent.h +++ b/Source/BugsnagEvent.h @@ -17,6 +17,7 @@ @class BugsnagDeviceWithState; @class BugsnagMetadata; @class BugsnagThread; +@class BugsnagError; typedef NS_ENUM(NSUInteger, BSGSeverity) { BSGSeverityError, @@ -30,19 +31,6 @@ typedef NS_ENUM(NSUInteger, BSGSeverity) { // MARK: - Initialisation // ----------------------------------------------------------------------------- -/** - * 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 - __deprecated_msg("Use initWithKSReport: instead."); - /** * Create a new crash report from a JSON crash report generated by * BugsnagCrashSentry @@ -93,14 +81,14 @@ initWithErrorName:(NSString *_Nonnull)name * The severity of the error generating the report */ @property(readwrite) BSGSeverity severity; + /** - * The class of the error generating the report - */ -@property(readwrite, copy, nonnull) NSString *errorClass; -/** - * The message of or reason for the error generating the report + * Information extracted from the error that caused the event. The list contains + * at least one error that represents the root cause, with subsequent elements populated + * from the cause. */ -@property(readwrite, copy, nullable) NSString *errorMessage; +@property(readonly, nonnull) NSMutableArray *errors; + /** * Customized hash for grouping this report with other errors */ diff --git a/Source/BugsnagEvent.m b/Source/BugsnagEvent.m index d94c686d1..d50fa656d 100644 --- a/Source/BugsnagEvent.m +++ b/Source/BugsnagEvent.m @@ -18,18 +18,10 @@ #import "BugsnagHandledState.h" #import "BugsnagLogger.h" #import "BugsnagKeys.h" -#import "BugsnagBreadcrumb.h" #import "BugsnagSession.h" #import "Private.h" #import "BSG_RFC3339DateTool.h" -#import "BugsnagKeys.h" -#import "BugsnagDeviceWithState.h" -#import "BugsnagClient.h" #import "BugsnagStacktrace.h" -#import "BugsnagThread.h" -#import "RegisterErrorData.h" - -static NSString *const DEFAULT_EXCEPTION_TYPE = @"cocoa"; // MARK: - Accessing hidden methods/properties @@ -98,8 +90,6 @@ - (NSArray *)toArray; @end @interface BugsnagThread () -- (NSDictionary *)toDict; - - (instancetype)initWithThread:(NSDictionary *)thread binaryImages:(NSArray *)binaryImages; @@ -107,49 +97,20 @@ - (instancetype)initWithThread:(NSDictionary *)thread binaryImages:(NSArray *)binaryImages depth:(NSUInteger)depth errorType:(NSString *)errorType; - -+ (NSMutableArray *)serializeThreads:(NSMutableDictionary *)event - threads:(NSArray *)threads; @end @interface BugsnagStacktrace () - (NSArray *)toArray; @end -// MARK: - KSCrashReport parsing - -NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, - NSString *errorType) { - NSString *errorClass; - - if ([errorType isEqualToString:BSGKeyCppException]) { - errorClass = error[BSGKeyCppException][BSGKeyName]; - } else if ([errorType isEqualToString:BSGKeyMach]) { - errorClass = error[BSGKeyMach][BSGKeyExceptionName]; - } else if ([errorType isEqualToString:BSGKeySignal]) { - errorClass = error[BSGKeySignal][BSGKeyName]; - } else if ([errorType isEqualToString:@"nsexception"]) { - errorClass = error[@"nsexception"][BSGKeyName]; - } else if ([errorType isEqualToString:BSGKeyUser]) { - errorClass = error[@"user_reported"][BSGKeyName]; - } - - if (!errorClass) { // use a default value - errorClass = @"Exception"; - } - return errorClass; -} +@interface BugsnagError () +- (NSDictionary *)toDictionary; +- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(BugsnagThread *)thread; +@end -NSString *BSGParseErrorMessage(NSDictionary *report, NSDictionary *error, - NSString *errorType) { - if ([errorType isEqualToString:BSGKeyMach] || error[BSGKeyReason] == nil) { - NSString *diagnosis = [report valueForKeyPath:@"crash.diagnosis"]; - if (diagnosis && ![diagnosis hasPrefix:@"No diagnosis"]) { - return [[diagnosis componentsSeparatedByString:@"\n"] firstObject]; - } - } - return error[BSGKeyReason] ?: @""; -} +// MARK: - KSCrashReport parsing +NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, NSString *errorType); +NSString *BSGParseErrorMessage(NSDictionary *report, NSDictionary *error, NSString *errorType); id BSGLoadConfigValue(NSDictionary *report, NSString *valueName) { NSString *keypath = [NSString stringWithFormat:@"user.config.%@", valueName]; @@ -300,7 +261,7 @@ - (BOOL)shouldBeSent; @property (nonatomic, strong) BugsnagMetadata *metadata; /** - * Raw error data + * Raw error data added to metadata */ @property(readwrite, copy, nullable) NSDictionary *error; @@ -312,11 +273,6 @@ - (BOOL)shouldBeSent; @implementation BugsnagEvent -- (instancetype)initWithKSReport:(NSDictionary *)report - fileMetadata:(NSString *)metadata { - return [self initWithKSReport:report]; -} - - (instancetype)initWithKSReport:(NSDictionary *)report { if (report.count == 0) { return nil; // report is empty @@ -325,11 +281,11 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { if (self = [super init]) { BugsnagConfiguration *config = [Bugsnag configuration]; + _errors = [NSMutableArray new]; + _error = [report valueForKeyPath:@"crash.error"]; _errorType = _error[BSGKeyType]; if ([[report valueForKeyPath:@"user.state.didOOM"] boolValue]) { - _errorClass = BSGParseErrorClass(_error, _errorType); - _errorMessage = BSGParseErrorMessage(report, _error, _errorType); _breadcrumbs = BSGParseBreadcrumbs(report); _app = [BugsnagAppWithState appWithOomData:[report valueForKeyPath:@"user.state.oom.app"]]; _device = [BugsnagDeviceWithState deviceWithOomData:[report valueForKeyPath:@"user.state.oom.device"]]; @@ -339,6 +295,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { // no threads or metadata captured for OOMs _threads = [NSMutableArray new]; + [_errors addObject:[[BugsnagError alloc] initWithEvent:report errorReportingThread:nil]]; self.metadata = [BugsnagMetadata new]; NSDictionary *sessionData = [report valueForKeyPath:@"user.state.oom.session"]; @@ -352,17 +309,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { } else { _enabledReleaseStages = BSGLoadConfigValue(report, BSGKeyEnabledReleaseStages); _releaseStage = BSGParseReleaseStage(report); - NSArray *binaryImages = report[@"binary_images"]; - NSArray *threadDict = [report valueForKeyPath:@"crash.threads"]; - RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:threadDict]; - if (data) { - _errorClass = data.errorClass ; - _errorMessage = data.errorMessage; - } else { - _errorClass = BSGParseErrorClass(_error, _errorType); - _errorMessage = BSGParseErrorMessage(report, _error, _errorType); - } _breadcrumbs = BSGParseBreadcrumbs(report); _deviceAppHash = [report valueForKeyPath:@"system.device_app_hash"]; @@ -382,7 +329,6 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { _app = [BugsnagAppWithState appWithDictionary:report config:config]; _groupingHash = BSGParseGroupingHash(report); _overrides = [report valueForKeyPath:@"user.overrides"]; - _customException = BSGParseCustomException(report, [_errorClass copy], [_errorMessage copy]); NSDictionary *recordedState = [report valueForKeyPath:@"user.handledState"]; @@ -393,29 +339,41 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { // only makes sense to use serialised value for handled exceptions _depth = [[report valueForKeyPath:@"user.depth"] unsignedIntegerValue]; - } - - // the event was unhandled. - else { - BOOL isSignal = [BSGKeySignal isEqualToString:_errorType]; - SeverityReasonType severityReason = isSignal ? Signal : UnhandledException; - _handledState = [BugsnagHandledState - handledStateWithSeverityReason:severityReason - severity:BSGSeverityError - attrValue:_errorClass]; + } else { _depth = 0; } - _severity = _handledState.currentSeverity; if (report[@"user"][@"id"]) { _session = [[BugsnagSession alloc] initWithDictionary:report[@"user"]]; } - // generate threads last, relies on depth/errorType properties being calculated first + // generate threads/error info + NSArray *binaryImages = report[@"binary_images"]; + NSArray *threadDict = [report valueForKeyPath:@"crash.threads"]; _threads = [BugsnagThread threadsFromArray:threadDict binaryImages:binaryImages depth:self.depth errorType:self.errorType]; + BugsnagThread *errorReportingThread; + for (BugsnagThread *thread in _threads) { + if (thread.errorReportingThread) { + errorReportingThread = thread; + break; + } + } + + [_errors addObject:[[BugsnagError alloc] initWithEvent:report errorReportingThread:errorReportingThread]]; + _customException = BSGParseCustomException(report, [_errors[0].errorClass copy], [_errors[0].errorMessage copy]); + + if (!recordedState) { // the event was unhandled. + BOOL isSignal = [BSGKeySignal isEqualToString:_errorType]; + SeverityReasonType severityReason = isSignal ? Signal : UnhandledException; + _handledState = [BugsnagHandledState + handledStateWithSeverityReason:severityReason + severity:BSGSeverityError + attrValue:_errors[0].errorClass]; + } + _severity = _handledState.currentSeverity; } } return self; @@ -429,8 +387,13 @@ - (instancetype _Nonnull)initWithErrorName:(NSString *_Nonnull)name session:(BugsnagSession *_Nullable)session { if (self = [super init]) { - _errorClass = name; - _errorMessage = message; + _errors = [NSMutableArray new]; + BugsnagError *error = [BugsnagError new]; + error.errorClass = name; + error.errorMessage = message; + error.type = BSGErrorTypeCocoa; + [_errors addObject:error]; + _overrides = [NSDictionary new]; _device = [BugsnagDeviceWithState new]; self.metadata = [metadata deepCopy] ?: [BugsnagMetadata new]; @@ -587,22 +550,15 @@ - (NSDictionary *)toJson { if (self.customException) { BSGDictSetSafeObject(event, @[ self.customException ], BSGKeyExceptions); } else { - NSMutableDictionary *exception = [NSMutableDictionary dictionary]; - BSGDictSetSafeObject(exception, [self errorClass], BSGKeyErrorClass); - BSGDictInsertIfNotNil(exception, [self errorMessage], BSGKeyMessage); - BSGDictInsertIfNotNil(exception, DEFAULT_EXCEPTION_TYPE, BSGKeyType); - BSGDictSetSafeObject(event, @[ exception ], BSGKeyExceptions); - - // set the stacktrace for the exception from the threads - for (BugsnagThread *thread in self.threads) { - if (thread.errorReportingThread) { - BSGDictSetSafeObject(exception, [thread.trace toArray], BSGKeyStacktrace); - } + NSMutableArray *array = [NSMutableArray new]; + + for (BugsnagError *error in self.errors) { + BSGArrayAddSafeObject(array, [error toDictionary]); } + BSGDictSetSafeObject(event, array, BSGKeyExceptions); + BSGDictSetSafeObject(event, [BugsnagThread serializeThreads:self.threads], BSGKeyThreads); } - BSGDictSetSafeObject(event, [BugsnagThread serializeThreads:self.threads], BSGKeyThreads); - // Build Event BSGDictSetSafeObject(event, BSGFormatSeverity(self.severity), BSGKeySeverity); BSGDictSetSafeObject(event, [self serializeBreadcrumbs], BSGKeyBreadcrumbs); diff --git a/Tests/BugsnagClientTests.m b/Tests/BugsnagClientTests.m index 9efe106f8..3ff623c70 100644 --- a/Tests/BugsnagClientTests.m +++ b/Tests/BugsnagClientTests.m @@ -72,8 +72,8 @@ - (void)testAutomaticNotifyBreadcrumbData { XCTAssertEqualObjects(event.apiKey, DUMMY_APIKEY_32CHAR_1); // Grab the values that end up in the event for later comparison - eventErrorClass = [event errorClass]; - eventErrorMessage = [event errorMessage]; + eventErrorClass = event.errors[0].errorClass; + eventErrorMessage = event.errors[0].errorMessage; eventUnhandled = [event valueForKeyPath:@"handledState.unhandled"] ? YES : NO; eventSeverity = BSGFormatSeverity([event severity]); }]; diff --git a/Tests/BugsnagConfigurationTests.m b/Tests/BugsnagConfigurationTests.m index e7c133220..e273c9c9c 100644 --- a/Tests/BugsnagConfigurationTests.m +++ b/Tests/BugsnagConfigurationTests.m @@ -31,7 +31,7 @@ - (NSDictionary *_Nonnull)sessionApiHeaders; @end @interface BugsnagCrashSentry () -- (BSG_KSCrashType)mapKSToBSGCrashTypes:(BSGErrorType)bsgCrashMask; +- (BSG_KSCrashType)mapKSToBSGCrashTypes:(BSGEnabledErrorType)bsgCrashMask; @end // ============================================================================= @@ -674,7 +674,7 @@ -(void)testBSGErrorTypes { BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; // Test all are set by default - BSGErrorType enabledErrors = BSGErrorTypesNSExceptions + BSGEnabledErrorType enabledErrors = BSGErrorTypesNSExceptions | BSGErrorTypesSignals | BSGErrorTypesMach | BSGErrorTypesCPP; @@ -700,15 +700,15 @@ -(void)testCrashTypeMapping { | BSG_KSCrashTypeMachException | BSG_KSCrashTypeSignal | BSG_KSCrashTypeCPPException; - + XCTAssertEqual(crashTypes, [sentry mapKSToBSGCrashTypes:[config enabledErrorTypes]]); - + crashTypes = crashTypes | BSG_KSCrashTypeUserReported; XCTAssertNotEqual(crashTypes, [sentry mapKSToBSGCrashTypes:[config enabledErrorTypes]]); // Check partial sets - BSGErrorType partialErrors = BSGErrorTypesNSExceptions | BSGErrorTypesCPP; + BSGEnabledErrorType partialErrors = BSGErrorTypesNSExceptions | BSGErrorTypesCPP; crashTypes = BSG_KSCrashTypeNSException | BSG_KSCrashTypeCPPException; XCTAssertEqual((NSUInteger)crashTypes, [sentry mapKSToBSGCrashTypes:(NSUInteger)partialErrors]); diff --git a/Tests/BugsnagErrorTest.m b/Tests/BugsnagErrorTest.m new file mode 100644 index 000000000..3345d2b2b --- /dev/null +++ b/Tests/BugsnagErrorTest.m @@ -0,0 +1,183 @@ +// +// BugsnagErrorTest.m +// Tests +// +// Created by Jamie Lynch on 08/04/2020. +// Copyright © 2020 Bugsnag. All rights reserved. +// + +#import + +#import "BugsnagKeys.h" +#import "BugsnagError.h" +#import "BugsnagStackframe.h" +#import "BugsnagThread.h" + +NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, NSString *errorType); + +NSString *BSGParseErrorMessage(NSDictionary *report, NSDictionary *error, NSString *errorType); + +@interface BugsnagError () +- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(BugsnagThread *)thread; + +- (NSDictionary *)toDictionary; +@end + +@interface BugsnagThread () ++ (NSMutableArray *)threadsFromArray:(NSArray *)threads + binaryImages:(NSArray *)binaryImages + depth:(NSUInteger)depth + errorType:(NSString *)errorType; +@end + +@interface BugsnagErrorTest : XCTestCase +@property NSDictionary *event; +@end + +@implementation BugsnagErrorTest + +- (void)setUp { + self.event = [self generateEvent:@{}]; +} + +- (NSDictionary *)generateEvent:(NSDictionary *)notableAddresses { + NSDictionary *thread = @{ + @"current_thread": @YES, + @"crashed": @YES, + @"index": @4, + @"backtrace": @{ + @"skipped": @0, + @"contents": @[ + @{ + @"symbol_name": @"kscrashsentry_reportUserException", + @"symbol_addr": @4491038467, + @"instruction_addr": @4491038575, + @"object_name": @"CrashProbeiOS", + @"object_addr": @4490747904 + } + ] + }, + @"notable_addresses": notableAddresses + }; + NSDictionary *binaryImage = @{ + @"uuid": @"D0A41830-4FD2-3B02-A23B-0741AD4C7F52", + @"image_vmaddr": @4294967296, + @"image_addr": @4490747904, + @"image_size": @483328, + @"name": @"/Users/joesmith/foo", + }; + return @{ + @"crash": @{ + @"error": @{ + @"type": @"user", + @"user_reported": @{ + @"name": @"Foo Exception" + }, + @"reason": @"Foo overload" + }, + @"threads": @[thread], + }, + @"binary_images": @[binaryImage] + }; +} + +- (void)testErrorLoad { + BugsnagThread *thread = [self findErrorReportingThread:self.event]; + BugsnagError *error = [[BugsnagError alloc] initWithEvent:self.event errorReportingThread:thread]; + XCTAssertEqualObjects(@"Foo Exception", error.errorClass); + XCTAssertEqualObjects(@"Foo overload", error.errorMessage); + XCTAssertEqual(BSGErrorTypeCocoa, error.type); + + XCTAssertEqual(1, [error.stacktrace count]); + BugsnagStackframe *frame = error.stacktrace[0]; + XCTAssertEqualObjects(@"kscrashsentry_reportUserException", frame.method); + XCTAssertEqualObjects(@"CrashProbeiOS", frame.machoFile); + XCTAssertEqualObjects(@"D0A41830-4FD2-3B02-A23B-0741AD4C7F52", frame.machoUuid); +} + +- (void)testToDictionary { + BugsnagThread *thread = [self findErrorReportingThread:self.event]; + BugsnagError *error = [[BugsnagError alloc] initWithEvent:self.event errorReportingThread:thread]; + NSDictionary *dict = [error toDictionary]; + XCTAssertEqualObjects(@"Foo Exception", dict[@"errorClass"]); + XCTAssertEqualObjects(@"Foo overload", dict[@"message"]); + XCTAssertEqualObjects(@"cocoa", dict[@"type"]); + + XCTAssertEqual(1, [dict[@"stacktrace"] count]); + NSDictionary *frame = dict[@"stacktrace"][0]; + XCTAssertEqualObjects(@"kscrashsentry_reportUserException", frame[@"method"]); + XCTAssertEqualObjects(@"D0A41830-4FD2-3B02-A23B-0741AD4C7F52", frame[@"machoUUID"]); + XCTAssertEqualObjects(@"CrashProbeiOS", frame[@"machoFile"]); +} + +- (BugsnagThread *)findErrorReportingThread:(NSDictionary *)event { + NSArray *binaryImages = event[@"binary_images"]; + NSArray *threadDict = [event valueForKeyPath:@"crash.threads"]; + NSArray *threads = [BugsnagThread threadsFromArray:threadDict + binaryImages:binaryImages + depth:0 + errorType:@"user"]; + for (BugsnagThread *thread in threads) { + if (thread.errorReportingThread) { + return thread; + } + } + return nil; +} + +/** + * If notable addresses are in the event, the error message/class should be enhanced + * with these values + */ +- (void)testMessageEnhancement { + self.event = [self generateEvent:@{ + @"x9": @{ + @"address": @4511086448, + @"type": @"string", + @"value": @"Something went wrong" + }, + @"r16": @{ + @"address": @4511089532, + @"type": @"string", + @"value": @"Fatal error" + } + }]; + BugsnagError *error = [[BugsnagError alloc] initWithEvent:self.event errorReportingThread:nil]; + NSDictionary *dict = [error toDictionary]; + XCTAssertEqualObjects(@"Fatal error", dict[@"errorClass"]); + XCTAssertEqualObjects(@"Something went wrong", dict[@"message"]); +} + +- (void)testErrorClassParse { + XCTAssertEqualObjects(@"foo", BSGParseErrorClass(@{@"cpp_exception": @{@"name": @"foo"}}, @"cpp_exception")); + XCTAssertEqualObjects(@"bar", BSGParseErrorClass(@{@"mach": @{@"exception_name": @"bar"}}, @"mach")); + XCTAssertEqualObjects(@"wham", BSGParseErrorClass(@{@"signal": @{@"name": @"wham"}}, @"signal")); + XCTAssertEqualObjects(@"zed", BSGParseErrorClass(@{@"nsexception": @{@"name": @"zed"}}, @"nsexception")); + XCTAssertEqualObjects(@"ooh", BSGParseErrorClass(@{@"user_reported": @{@"name": @"ooh"}}, @"user")); + XCTAssertEqualObjects(@"Exception", BSGParseErrorClass(@{}, @"some-val")); +} + +- (void)testErrorMessageParse { + XCTAssertEqualObjects(@"", BSGParseErrorMessage(@{}, @{}, @"")); + XCTAssertEqualObjects(@"foo", BSGParseErrorMessage(@{}, @{@"reason": @"foo"}, @"")); + + XCTAssertEqualObjects(@"Exception", BSGParseErrorMessage(@{ + @"crash": @{ + @"diagnosis": @"Exception" + } + }, @{}, @"signal")); + + XCTAssertEqualObjects(@"Exceptional circumstance", BSGParseErrorMessage(@{ + @"crash": @{ + @"diagnosis": @"Exceptional circumstance\ntest" + } + }, @{}, @"mach")); + + XCTAssertEqualObjects(@"", BSGParseErrorMessage(@{ + @"crash": @{ + @"diagnosis": @"No diagnosis foo" + } + }, @{}, @"mach")); +} + +@end diff --git a/Tests/BugsnagEventTests.m b/Tests/BugsnagEventTests.m index 03b5af7c3..d121847a4 100644 --- a/Tests/BugsnagEventTests.m +++ b/Tests/BugsnagEventTests.m @@ -118,9 +118,9 @@ - (void)testDefaultErrorMessageNilForEmptyThreads { XCTAssertEqualObjects(@"Exception", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -132,9 +132,9 @@ - (void)testEnhancedErrorMessageNilForEmptyNotableAddresses { XCTAssertEqualObjects(@"Exception", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -157,9 +157,9 @@ - (void)testEnhancedErrorMessageForFatalErrorWithoutAdditionalMessage { XCTAssertEqualObjects(@"fatal error", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -182,9 +182,9 @@ - (void)testEnhancedErrorMessageForAssertionWithoutAdditionalMessage { XCTAssertEqualObjects(@"assertion failed", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -218,9 +218,9 @@ - (void)testEnhancedErrorMessageForAssertionError { payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"Something went wrong", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } } @@ -249,9 +249,9 @@ - (void)testEnhancedErrorMessageIgnoresFilePaths { XCTAssertEqualObjects(@"fatal error", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -279,9 +279,9 @@ - (void)testEnhancedErrorMessageIgnoresNonStrings { XCTAssertEqualObjects(@"fatal error", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -315,9 +315,9 @@ - (void)testEnhancedErrorMessageConcatenatesMultipleMessages { payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"A message from beyond | Wo0o0o", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } @@ -345,9 +345,9 @@ - (void)testEnhancedErrorMessageIgnoresUnknownAssertionTypes { XCTAssertEqualObjects(@"Exception", payload[@"exceptions"][0][@"errorClass"]); XCTAssertEqualObjects(@"", payload[@"exceptions"][0][@"message"]); - XCTAssertEqualObjects(event.errorClass, + XCTAssertEqualObjects(event.errors[0].errorClass, payload[@"exceptions"][0][@"errorClass"]); - XCTAssertEqualObjects(event.errorMessage, + XCTAssertEqualObjects(event.errors[0].errorMessage, payload[@"exceptions"][0][@"message"]); } diff --git a/Tests/BugsnagSinkTests.m b/Tests/BugsnagSinkTests.m index b1cb2fd58..f967f6a61 100644 --- a/Tests/BugsnagSinkTests.m +++ b/Tests/BugsnagSinkTests.m @@ -245,7 +245,7 @@ - (void)testExceptionStacktrace { NSArray *exceptions = [self.processedData[@"events"] firstObject][@"exceptions"]; NSArray *stacktrace = [exceptions firstObject][@"stacktrace"]; - XCTAssert([stacktrace count] != 0); + XCTAssertNotEqual(0, [stacktrace count]); XCTAssertNotNil(stacktrace); for (NSDictionary *frame in stacktrace) { XCTAssertNotNil([frame valueForKey:@"machoUUID"]); diff --git a/Tests/BugsnagTests.m b/Tests/BugsnagTests.m index ce4adc422..d0fc8b970 100644 --- a/Tests/BugsnagTests.m +++ b/Tests/BugsnagTests.m @@ -67,8 +67,8 @@ - (void)testBugsnagMetadataAddition { [Bugsnag notify:exception1 block:^(BugsnagEvent * _Nonnull event) { XCTAssertEqualObjects([event getMetadataFromSection:@"mySection1" withKey:@"aKey1"], @"aValue1"); - XCTAssertEqual([event errorClass], @"exception1"); - XCTAssertEqual([event errorMessage], @"reason1"); + XCTAssertEqual(event.errors[0].errorClass, @"exception1"); + XCTAssertEqual(event.errors[0].errorMessage, @"reason1"); XCTAssertNil([event getMetadataFromSection:@"mySection2"]); // Add some additional metadata once we're sure it's not already there @@ -78,8 +78,8 @@ - (void)testBugsnagMetadataAddition { [Bugsnag notify:exception2 block:^(BugsnagEvent * _Nonnull event) { XCTAssertEqualObjects([event getMetadataFromSection:@"mySection1" withKey:@"aKey1"], @"aValue1"); XCTAssertEqualObjects([event getMetadataFromSection:@"mySection2" withKey:@"aKey2"], @"aValue2"); - XCTAssertEqual([event errorClass], @"exception2"); - XCTAssertEqual([event errorMessage], @"reason2"); + XCTAssertEqual(event.errors[0].errorClass, @"exception2"); + XCTAssertEqual(event.errors[0].errorMessage, @"reason2"); }]; // Check nil value causes deletions diff --git a/UPGRADING.md b/UPGRADING.md index 07d15ff70..65e5d6d3d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -224,8 +224,8 @@ This is now BugsnagEvent. ``` `event.device` is now a structured class with properties for each value, rather than an `NSDictionary`. - `event.app` is now a structured class with properties for each value, rather than an `NSDictionary`. +`event.errors` is now an array containing a structured class with properties for each `BugsnagError` value. `event.threads` is now an array containing a structured class with properties for each `BugsnagThread` value. #### Renames diff --git a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift index 67272f87b..ae478640d 100644 --- a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift +++ b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift @@ -20,8 +20,9 @@ class HandledErrorOverrideScenario: Scenario { fileprivate func logError(_ error: Error) { Bugsnag.notifyError(error) { report in - report.errorMessage = "Foo" - report.errorClass = "Bar" + let error = report.errors[0] as! BugsnagError + error.errorMessage = "Foo" + error.errorClass = "Bar" let depth: Int = report.value(forKey: "depth") as! Int report.setValue(depth + 2, forKey: "depth") report.addMetadata(["items": [400,200]], section: "account") diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index 1a7369c06..35df4e434 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -280,6 +280,7 @@ E7397EED1F83CFC20034242A /* BugsnagClient.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8A2C8F4B1C6BBE3C00846019 /* BugsnagClient.h */; }; E7397EEE1F83CFC20034242A /* BugsnagSink.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8A2C8F4D1C6BBE3C00846019 /* BugsnagSink.h */; }; E7397EEF1F83CFC20034242A /* BugsnagHandledState.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E737DEA01F73AD7400BC7C80 /* BugsnagHandledState.h */; }; + E73D443A243E1912001686F5 /* BugsnagErrorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E73D4439243E1912001686F5 /* BugsnagErrorTest.m */; }; E7433AD01F4F64D400C082D1 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8F6F1C6BBE9F00846019 /* libc++.tbd */; }; E7433AD11F4F64D900C082D1 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8F6D1C6BBE9A00846019 /* libz.tbd */; }; E748DA781FD02A3F00B14909 /* BugsnagSessionFileStore.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = F42955025DBE1DCEFD928CAA /* BugsnagSessionFileStore.h */; }; @@ -656,6 +657,7 @@ E737DEA01F73AD7400BC7C80 /* BugsnagHandledState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagHandledState.h; path = ../Source/BugsnagHandledState.h; sourceTree = SOURCE_ROOT; }; E737DEA11F73AD7400BC7C80 /* BugsnagHandledState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagHandledState.m; path = ../Source/BugsnagHandledState.m; sourceTree = SOURCE_ROOT; }; E7397DC41F83BAC50034242A /* libBugsnagStatic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBugsnagStatic.a; sourceTree = BUILT_PRODUCTS_DIR; }; + E73D4439243E1912001686F5 /* BugsnagErrorTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagErrorTest.m; path = ../../Tests/BugsnagErrorTest.m; sourceTree = ""; }; E74D8E92243B3DF000F2A630 /* ORGANIZATION.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = ORGANIZATION.md; path = ../ORGANIZATION.md; sourceTree = ""; }; E7529F87243C8DA2006B4932 /* RegisterErrorData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RegisterErrorData.h; path = ../Source/RegisterErrorData.h; sourceTree = ""; }; E7529F88243C8DA2006B4932 /* RegisterErrorData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RegisterErrorData.m; path = ../Source/RegisterErrorData.m; sourceTree = ""; }; @@ -863,6 +865,7 @@ 4BE6C42522CAD61A0056305D /* BugsnagCollectionsBSGDictMergeTest.m */, 8A4E733E1DC13281001F7CC8 /* BugsnagConfigurationTests.m */, E7A9E56A2436363900D99F8A /* BugsnagDeviceTest.m */, + E73D4439243E1912001686F5 /* BugsnagErrorTest.m */, 8A2C8F8C1C6BBFDD00846019 /* BugsnagEventTests.m */, 4B47970922A9AE1F00FF9C2E /* BugsnagEventFromKSCrashReportTest.m */, E77316E11F73B46600A14F06 /* BugsnagHandledStateTest.m */, @@ -1502,6 +1505,7 @@ E790C49B2434C8C8006FFB26 /* BugsnagAppTest.m in Sources */, E7A9E56B2436363900D99F8A /* BugsnagDeviceTest.m in Sources */, E7D2E670243B8F8D005A3041 /* BugsnagStacktraceTest.m in Sources */, + E73D443A243E1912001686F5 /* BugsnagErrorTest.m in Sources */, E7B970311FD702DA00590C27 /* KSLogger_Tests.m in Sources */, E7529F9C243CAE02006B4932 /* BugsnagThreadSerializationTest.m in Sources */, 8AA661AD23D8C1F50031ECC8 /* BSGConnectivityTest.m in Sources */, diff --git a/tvOS/Bugsnag.xcodeproj/project.pbxproj b/tvOS/Bugsnag.xcodeproj/project.pbxproj index d762d14c6..05bced933 100644 --- a/tvOS/Bugsnag.xcodeproj/project.pbxproj +++ b/tvOS/Bugsnag.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ E72352BE1F55923700436528 /* BSGConnectivity.m in Sources */ = {isa = PBXBuildFile; fileRef = E72352BC1F55923700436528 /* BSGConnectivity.m */; }; E72AE1FD241A4ED600ED8972 /* BugsnagPluginClient.h in Headers */ = {isa = PBXBuildFile; fileRef = E72AE1FB241A4ED600ED8972 /* BugsnagPluginClient.h */; }; E72AE1FE241A4ED600ED8972 /* BugsnagPluginClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E72AE1FC241A4ED600ED8972 /* BugsnagPluginClient.m */; }; + E73D443E243E1945001686F5 /* BugsnagErrorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E73D443D243E1945001686F5 /* BugsnagErrorTest.m */; }; E742DB22243C8129002C4621 /* BugsnagStacktrace.h in Headers */ = {isa = PBXBuildFile; fileRef = E7D2E679243B8FD2005A3041 /* BugsnagStacktrace.h */; }; E742DB23243C812E002C4621 /* BugsnagStacktrace.m in Sources */ = {isa = PBXBuildFile; fileRef = E7D2E67A243B8FD2005A3041 /* BugsnagStacktrace.m */; }; E7433AD61F4F650C00C082D1 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = E7433AD51F4F650C00C082D1 /* libc++.tbd */; }; @@ -281,6 +282,7 @@ E72352BC1F55923700436528 /* BSGConnectivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BSGConnectivity.m; path = ../Source/BSGConnectivity.m; sourceTree = ""; }; E72AE1FB241A4ED600ED8972 /* BugsnagPluginClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagPluginClient.h; path = ../Source/BugsnagPluginClient.h; sourceTree = ""; }; E72AE1FC241A4ED600ED8972 /* BugsnagPluginClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagPluginClient.m; path = ../Source/BugsnagPluginClient.m; sourceTree = ""; }; + E73D443D243E1945001686F5 /* BugsnagErrorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagErrorTest.m; path = ../../Tests/BugsnagErrorTest.m; sourceTree = ""; }; E7433AD51F4F650C00C082D1 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; E7433AD71F4F651200C082D1 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; E7529F90243C8ED4006B4932 /* RegisterErrorData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegisterErrorData.m; path = ../Source/RegisterErrorData.m; sourceTree = ""; }; @@ -546,6 +548,7 @@ 4B406C1222CAD94100464D1D /* BugsnagCollectionsBSGDictSetSafeObjectTest.m */, 4B775FD022CBE01F004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m */, 8AD9FA8B1E0863A1002859A7 /* BugsnagConfigurationTests.m */, + E73D443D243E1945001686F5 /* BugsnagErrorTest.m */, 00D7ACA623E98A5D00FBE4A7 /* BugsnagEventTests.m */, 00D7ACA923E98A9A00FBE4A7 /* BugsnagEventFromKSCrashReportTest.m */, E762E9EE1F73F6CF00E82B43 /* BugsnagHandledStateTest.m */, @@ -1178,6 +1181,7 @@ E7CE78F91FD94F1B001D07E0 /* KSSystemInfo_Tests.m in Sources */, 4B3CD2BB22C5676800DBFF33 /* BSGOutOfMemoryWatchdogTests.m in Sources */, E7CE78F41FD94F1B001D07E0 /* KSFileUtils_Tests.m in Sources */, + E73D443E243E1945001686F5 /* BugsnagErrorTest.m in Sources */, 00F9393F23FD2DDB008C7073 /* BugsnagTestsDummyClass.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0;