Skip to content

Commit

Permalink
Add unhandled override support for unhandled errors.
Browse files Browse the repository at this point in the history
  • Loading branch information
kstenerud committed Dec 3, 2020
1 parent 003d8d4 commit e6bc161
Show file tree
Hide file tree
Showing 21 changed files with 224 additions and 9 deletions.
32 changes: 30 additions & 2 deletions Bugsnag/Payload/BugsnagEvent.m
Expand Up @@ -339,6 +339,30 @@ - (instancetype)initWithKSCrashData:(NSDictionary *)event {
NSDictionary *error = [event valueForKeyPath:@"crash.error"];
NSString *errorType = error[BSGKeyType];

// Data coming from KSCrash will have either user.handledCount = 0 and user.unhandledCount > 0, or the opposite
BOOL wasUnhandled = [event valueForKeyPath:@"user.unhandledCount"] == nil ||
[[event valueForKeyPath:@"user.unhandledCount"] intValue] > 0;
BOOL wasUnhandledChanged = [event valueForKeyPath:@"user.unhandled"] != nil &&
[[event valueForKeyPath:@"user.unhandled"] boolValue] != wasUnhandled;
BOOL isUnhandled = wasUnhandled;

if (wasUnhandledChanged) {
isUnhandled = !wasUnhandled;
NSMutableDictionary *user = [event[BSGKeyUser] mutableCopy];
user[@"unhandled"] = @(isUnhandled);
user[@"unhandledOverridden"] = @YES;
if (wasUnhandled) {
user[@"unhandledCount"] = @0;
user[@"handledCount"] = @1;
} else {
user[@"unhandledCount"] = @1;
user[@"handledCount"] = @0;
}
NSMutableDictionary *eventCopy = [event mutableCopy];
eventCopy[BSGKeyUser] = user;
event = eventCopy;
}

id userMetadata = [event valueForKeyPath:@"user.metaData"];
BugsnagMetadata *metadata;

Expand Down Expand Up @@ -386,13 +410,15 @@ - (instancetype)initWithKSCrashData:(NSDictionary *)event {
BugsnagHandledState *handledState;
if (recordedState) {
handledState = [[BugsnagHandledState alloc] initWithDictionary:recordedState];
} else { // the event was unhandled.
} else { // the event was (probably) unhandled.
BOOL isSignal = [BSGKeySignal isEqualToString:errorType];
SeverityReasonType severityReason = isSignal ? Signal : UnhandledException;
handledState = [BugsnagHandledState
handledStateWithSeverityReason:severityReason
severity:BSGSeverityError
attrValue:errors[0].errorClass];
handledState.unhandled = isUnhandled;
handledState.unhandledOverridden = wasUnhandledChanged;
}

NSMutableDictionary *userAtCrash = [self parseOnCrashData:event];
Expand Down Expand Up @@ -695,7 +721,9 @@ - (NSDictionary *)toJson {


BSGDictSetSafeObject(event, @(self.handledState.unhandled), BSGKeyUnhandled);
BSGDictSetSafeObject(event, @(self.handledState.unhandledOverridden), BSGKeyUnhandledOverridden);
if (self.handledState.unhandledOverridden) {
BSGDictSetSafeObject(event, @(self.handledState.unhandledOverridden), BSGKeyUnhandledOverridden);
}

// serialize handled/unhandled into payload
NSMutableDictionary *severityReason = [NSMutableDictionary new];
Expand Down
1 change: 0 additions & 1 deletion Tests/BugsnagErrorReportSinkTests.m
Expand Up @@ -121,7 +121,6 @@ - (void)testCorrectEventKeys {
@"severityReason",
@"threads",
@"unhandled",
@"unhandledOverridden",
@"user",
];
XCTAssertEqualObjects(actualKeys, eventKeys);
Expand Down
Expand Up @@ -48,6 +48,7 @@
A1117E592535B29800014FDA /* OOMEnabledErrorTypesScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1117E582535B29800014FDA /* OOMEnabledErrorTypesScenario.swift */; };
A1117E5B2536036400014FDA /* OOMSessionScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1117E5A2536036400014FDA /* OOMSessionScenario.swift */; };
C4D0B5FF8E60C0835B86DFE9 /* Pods_iOSTestApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4994F05E0421A0B037DD2CC5 /* Pods_iOSTestApp.framework */; };
CBB787912578FC0C0071BDE4 /* MarkUnhandledHandledScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB787902578FC0C0071BDE4 /* MarkUnhandledHandledScenario.m */; };
CBE1C9242574F532004B8B5B /* OnErrorOverwriteUnhandledFalseScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1C9222574F532004B8B5B /* OnErrorOverwriteUnhandledFalseScenario.swift */; };
CBE1C9252574F532004B8B5B /* OnErrorOverwriteUnhandledTrueScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1C9232574F532004B8B5B /* OnErrorOverwriteUnhandledTrueScenario.swift */; };
E700EE48247D1158008CFFB6 /* UserEventOverrideScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = E700EE47247D1158008CFFB6 /* UserEventOverrideScenario.swift */; };
Expand Down Expand Up @@ -205,6 +206,8 @@
A1117E562535B22300014FDA /* OOMAutoDetectErrorsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMAutoDetectErrorsScenario.swift; sourceTree = "<group>"; };
A1117E582535B29800014FDA /* OOMEnabledErrorTypesScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMEnabledErrorTypesScenario.swift; sourceTree = "<group>"; };
A1117E5A2536036400014FDA /* OOMSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMSessionScenario.swift; sourceTree = "<group>"; };
CBB7878F2578FC0C0071BDE4 /* MarkUnhandledHandledScenario.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MarkUnhandledHandledScenario.h; sourceTree = "<group>"; };
CBB787902578FC0C0071BDE4 /* MarkUnhandledHandledScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MarkUnhandledHandledScenario.m; sourceTree = "<group>"; };
CBE1C9222574F532004B8B5B /* OnErrorOverwriteUnhandledFalseScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnErrorOverwriteUnhandledFalseScenario.swift; sourceTree = "<group>"; };
CBE1C9232574F532004B8B5B /* OnErrorOverwriteUnhandledTrueScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnErrorOverwriteUnhandledTrueScenario.swift; sourceTree = "<group>"; };
E700EE47247D1158008CFFB6 /* UserEventOverrideScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEventOverrideScenario.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -549,6 +552,8 @@
8AB1081823301FE600672818 /* ReleaseStageScenarios.swift */,
F4295ABA693D273D52AA9F6B /* Scenario.h */,
F42954E8B66F3FB7F5333CF7 /* Scenario.m */,
CBB7878F2578FC0C0071BDE4 /* MarkUnhandledHandledScenario.h */,
CBB787902578FC0C0071BDE4 /* MarkUnhandledHandledScenario.m */,
E7A324D4247E707D008B0052 /* Session Callbacks */,
F49695AC2445471400105DA9 /* Session stopping */,
F49695AB244546CA00105DA9 /* Session tracking */,
Expand Down Expand Up @@ -910,6 +915,7 @@
E7A324EA247E9DA5008B0052 /* BreadcrumbCallbackOverrideScenario.swift in Sources */,
F42955DB6D08642528917FAB /* CxxExceptionScenario.mm in Sources */,
8A3B5F2B240807EE00CE4A3A /* ModifyBreadcrumbInNotify.swift in Sources */,
CBB787912578FC0C0071BDE4 /* MarkUnhandledHandledScenario.m in Sources */,
8A32DB8222424E3000EDD92F /* NSExceptionShiftScenario.m in Sources */,
F42954B7318A02824C65C514 /* ObjCMsgSendScenario.m in Sources */,
E700EE5D247D322D008CFFB6 /* OnSendCallbackOrderScenario.swift in Sources */,
Expand Down
Expand Up @@ -126,6 +126,7 @@
01F47D30254B1B3100B184AD /* AutoContextNSExceptionScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F47CBF254B1B3000B184AD /* AutoContextNSExceptionScenario.swift */; };
01F47D31254B1B3100B184AD /* OverwriteLinkRegisterScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F47CC0254B1B3000B184AD /* OverwriteLinkRegisterScenario.m */; };
01F47D32254B1B3100B184AD /* ResumeSessionOOMScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F47CC1254B1B3000B184AD /* ResumeSessionOOMScenario.m */; };
CBB7878E2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB7878C2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.m */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -318,6 +319,8 @@
01F47CC1254B1B3000B184AD /* ResumeSessionOOMScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResumeSessionOOMScenario.m; sourceTree = "<group>"; };
01F47CC2254B1B3000B184AD /* SIGBUSScenario.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SIGBUSScenario.h; sourceTree = "<group>"; };
01F47CC3254B1B3100B184AD /* SIGFPEScenario.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SIGFPEScenario.h; sourceTree = "<group>"; };
CBB7878C2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MarkUnhandledHandledScenario.m; sourceTree = "<group>"; };
CBB7878D2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MarkUnhandledHandledScenario.h; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -486,6 +489,8 @@
01F47C90254B1B2F00B184AD /* UnhandledInternalNotifyScenario.swift */,
01F47C38254B1B2D00B184AD /* UnhandledMachExceptionScenario.h */,
01F47C4C254B1B2D00B184AD /* UnhandledMachExceptionScenario.m */,
CBB7878D2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.h */,
CBB7878C2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.m */,
01F47CB7254B1B3000B184AD /* UserDefaultInfoScenario.swift */,
01F47CB3254B1B3000B184AD /* UserDisabledScenario.swift */,
01F47C75254B1B2E00B184AD /* UserEmailScenario.swift */,
Expand Down Expand Up @@ -656,6 +661,7 @@
01F47CDC254B1B3100B184AD /* HandledErrorScenario.swift in Sources */,
01F47CFD254B1B3100B184AD /* OnCrashHandlerScenario.m in Sources */,
01F47D29254B1B3100B184AD /* DiscardSessionScenario.swift in Sources */,
CBB7878E2578FB3F0071BDE4 /* MarkUnhandledHandledScenario.m in Sources */,
01F47CCC254B1B3100B184AD /* OnSendCallbackOrderScenario.swift in Sources */,
01F47D04254B1B3100B184AD /* ModifyBreadcrumbScenario.swift in Sources */,
01F47CD3254B1B3100B184AD /* EnabledBreadcrumbTypesIsNilScenario.swift in Sources */,
Expand Down
6 changes: 5 additions & 1 deletion features/fixtures/shared/scenarios/AbortScenario.h
Expand Up @@ -25,11 +25,15 @@
*/

#import <Foundation/Foundation.h>
#import "Scenario.h"
#import "MarkUnhandledHandledScenario.h"

/**
* Call abort() to terminate the program.
*/
@interface AbortScenario: Scenario

@end

@interface AbortOverrideScenario: MarkUnhandledHandledScenario

@end
13 changes: 13 additions & 0 deletions features/fixtures/shared/scenarios/AbortScenario.m
Expand Up @@ -38,3 +38,16 @@ - (void)run {
}

@end

@implementation AbortOverrideScenario

- (void)startBugsnag {
self.config.autoTrackSessions = NO;
[super startBugsnag];
}

- (void)run {
abort();
}

@end
5 changes: 4 additions & 1 deletion features/fixtures/shared/scenarios/CxxExceptionScenario.h
Expand Up @@ -25,8 +25,11 @@
*/

#import <Foundation/Foundation.h>
#import "Scenario.h"
#import "MarkUnhandledHandledScenario.h"


@interface CxxExceptionScenario : Scenario
@end

@interface CxxExceptionOverrideScenario : MarkUnhandledHandledScenario
@end
17 changes: 17 additions & 0 deletions features/fixtures/shared/scenarios/CxxExceptionScenario.mm
Expand Up @@ -55,3 +55,20 @@ - (void)crash __attribute__((noreturn)) {
}

@end

@implementation CxxExceptionOverrideScenario

- (void)startBugsnag {
self.config.autoTrackSessions = NO;
[super startBugsnag];
}

- (void)run {
[self crash];
}

- (void)crash __attribute__((noreturn)) {
throw new kaboom_exception;
}

@end
17 changes: 17 additions & 0 deletions features/fixtures/shared/scenarios/MarkUnhandledHandledScenario.h
@@ -0,0 +1,17 @@
//
// MarkUnhandledHandledScenario.h
// iOSTestApp
//
// Created by Karl Stenerud on 03.12.20.
// Copyright © 2020 Bugsnag. All rights reserved.
//

#import "Scenario.h"

NS_ASSUME_NONNULL_BEGIN

@interface MarkUnhandledHandledScenario : Scenario

@end

NS_ASSUME_NONNULL_END
18 changes: 18 additions & 0 deletions features/fixtures/shared/scenarios/MarkUnhandledHandledScenario.m
@@ -0,0 +1,18 @@
//
// MarkUnhandledHandledScenario.m
// iOSTestApp
//
// Created by Karl Stenerud on 03.12.20.
// Copyright © 2020 Bugsnag. All rights reserved.
//

#import "MarkUnhandledHandledScenario.h"

@implementation MarkUnhandledHandledScenario

- (void)startBugsnag {
self.config.onCrashHandler = markErrorHandledCallback;
[super startBugsnag];
}

@end
6 changes: 4 additions & 2 deletions features/fixtures/shared/scenarios/ObjCExceptionScenario.h
Expand Up @@ -25,8 +25,10 @@
*/

#import <Foundation/Foundation.h>
#import "Scenario.h"

#import "MarkUnhandledHandledScenario.h"

@interface ObjCExceptionScenario : Scenario
@end

@interface ObjCExceptionOverrideScenario : MarkUnhandledHandledScenario
@end
14 changes: 14 additions & 0 deletions features/fixtures/shared/scenarios/ObjCExceptionScenario.m
Expand Up @@ -44,3 +44,17 @@ - (void)run __attribute__((noreturn)) {
}

@end

@implementation ObjCExceptionOverrideScenario

- (void)startBugsnag {
self.config.autoTrackSessions = NO;
[super startBugsnag];
}

- (void)run __attribute__((noreturn)) {
@throw [NSException exceptionWithName:NSGenericException reason:@"An uncaught exception! SCREAM."
userInfo:@{NSLocalizedDescriptionKey: @"I'm in your program, catching your exceptions!"}];
}

@end
2 changes: 2 additions & 0 deletions features/fixtures/shared/scenarios/Scenario.h
Expand Up @@ -6,6 +6,8 @@
#import <Foundation/Foundation.h>
#import <Bugsnag/Bugsnag.h>

void markErrorHandledCallback(const BSG_KSCrashReportWriter * _Nonnull writer);

@interface Scenario : NSObject

@property (strong, nonatomic, nonnull) BugsnagConfiguration *config;
Expand Down
4 changes: 4 additions & 0 deletions features/fixtures/shared/scenarios/Scenario.m
Expand Up @@ -6,6 +6,10 @@

#import "Scenario.h"

void markErrorHandledCallback(const BSG_KSCrashReportWriter *writer) {
writer->addBooleanElement(writer, "unhandled", false);
}

@implementation Scenario

+ (Scenario *)createScenarioNamed:(NSString *)className
Expand Down
Expand Up @@ -7,12 +7,16 @@
//

#import <Foundation/Foundation.h>
#import "Scenario.h"
#import "MarkUnhandledHandledScenario.h"

NS_ASSUME_NONNULL_BEGIN

@interface UnhandledMachExceptionScenario : Scenario

@end

@interface UnhandledMachExceptionOverrideScenario : MarkUnhandledHandledScenario

@end

NS_ASSUME_NONNULL_END
Expand Up @@ -21,3 +21,17 @@ - (void)run {
}

@end

@implementation UnhandledMachExceptionOverrideScenario

- (void)startBugsnag {
self.config.autoTrackSessions = NO;
[super startBugsnag];
}

- (void)run {
void (*ptr)(void) = (void *)0xDEADBEEF;
ptr();
}

@end
1 change: 0 additions & 1 deletion features/steps/ios_steps.rb
Expand Up @@ -252,7 +252,6 @@ def request_fields_are_equal(key, index_a, index_b)
And the event "severity" equals "error"
And the event "severityReason.type" equals "outOfMemory"
And the event "unhandled" is true
And the event "unhandledOverridden" is false
}
end

Expand Down
13 changes: 13 additions & 0 deletions features/unhandled_cpp_exception.feature
Expand Up @@ -14,3 +14,16 @@ Feature: Thrown C++ exceptions are captured by Bugsnag
And the event "severity" equals "error"
And the event "unhandled" is true
And the event "severityReason.type" equals "unhandledException"

Scenario: Throwing a C++ exception with unhandled override
When I run "CxxExceptionOverrideScenario" and relaunch the app
And I configure Bugsnag for "CxxExceptionOverrideScenario"
And I wait to receive a request
Then the request is valid for the error reporting API version "4.0" for the "iOS Bugsnag Notifier" notifier
And the exception "errorClass" equals "P16kaboom_exception"
And the exception "type" equals "cocoa"
And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 elements
And the event "severity" equals "error"
And the event "unhandled" is false
And the event "unhandledOverridden" is true
And the event "severityReason.type" equals "unhandledException"
19 changes: 19 additions & 0 deletions features/unhandled_mach_exception.feature
Expand Up @@ -20,3 +20,22 @@ Feature: Bugsnag captures an unhandled mach exception
And the event "severity" equals "error"
And the event "unhandled" is true
And the event "severityReason.type" equals "unhandledException"

Scenario: Trigger a mach exception with unhandled override
When I run "UnhandledMachExceptionOverrideScenario" and relaunch the app
And I configure Bugsnag for "UnhandledMachExceptionOverrideScenario"
And I wait to receive a request
Then the request is valid for the error reporting API version "4.0" for the "iOS Bugsnag Notifier" notifier
And the event "exceptions.0.errorClass" equals "EXC_BAD_ACCESS"
And the event "exceptions.0.message" equals "Attempted to dereference garbage pointer 0xdeadbeef."
And the event "metaData.error.address" equals 3735928559
And the event "metaData.error.type" equals "mach"
And the event "metaData.error.mach.code" equals "0x101"
And the event "metaData.error.mach.code_name" equals "EXC_ARM_DA_ALIGN"
And the event "metaData.error.mach.exception" equals 1
And the event "metaData.error.mach.exception_name" equals "EXC_BAD_ACCESS"
And the event "metaData.error.mach.subcode" equals "0xdeadbeef"
And the event "severity" equals "error"
And the event "unhandled" is false
And the event "unhandledOverridden" is true
And the event "severityReason.type" equals "unhandledException"
16 changes: 16 additions & 0 deletions features/unhandled_nsexception.feature
Expand Up @@ -17,3 +17,19 @@ Feature: Uncaught NSExceptions are captured by Bugsnag
And the event "severity" equals "error"
And the event "unhandled" is true
And the event "severityReason.type" equals "unhandledException"

Scenario: Throw a NSException with unhandled override
When I run "ObjCExceptionOverrideScenario" and relaunch the app
And I configure Bugsnag for "ObjCExceptionOverrideScenario"
And I wait to receive a request
Then the request is valid for the error reporting API version "4.0" for the "iOS Bugsnag Notifier" notifier
And the exception "message" equals "An uncaught exception! SCREAM."
And the exception "errorClass" equals "NSGenericException"
And the "method" of stack frame 0 equals "<redacted>"
And the "method" of stack frame 1 equals "objc_exception_throw"
And the "method" of stack frame 2 equals "-[ObjCExceptionOverrideScenario run]"
And the payload field "events.0.device.time" is a date
And the event "severity" equals "error"
And the event "unhandled" is false
And the event "unhandledOverridden" is true
And the event "severityReason.type" equals "unhandledException"

0 comments on commit e6bc161

Please sign in to comment.