Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unhandled override support for unhandled errors #921

Merged
merged 1 commit into from
Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions Bugsnag/Payload/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,25 @@ - (instancetype)initWithKSCrashData:(NSDictionary *)event {
NSDictionary *error = [event valueForKeyPath:@"crash.error"];
NSString *errorType = error[BSGKeyType];

// Always assume that a report coming from KSCrash is by default an unhandled error.
BOOL isUnhandled = YES;
BOOL isUnhandledOverridden = NO;
BOOL hasBecomeHandled = [event valueForKeyPath:@"user.unhandled"] != nil &&
[[event valueForKeyPath:@"user.unhandled"] boolValue] == false;
if (hasBecomeHandled) {
const int handledCountAdjust = 1;
isUnhandled = NO;
isUnhandledOverridden = YES;
NSMutableDictionary *user = [event[BSGKeyUser] mutableCopy];
user[@"unhandled"] = @(isUnhandled);
user[@"unhandledOverridden"] = @(isUnhandledOverridden);
user[@"unhandledCount"] = @([user[@"unhandledCount"] intValue] - handledCountAdjust);
user[@"handledCount"] = @([user[@"handledCount"] intValue] + handledCountAdjust);
NSMutableDictionary *eventCopy = [event mutableCopy];
eventCopy[BSGKeyUser] = user;
event = eventCopy;
}

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

Expand Down Expand Up @@ -386,13 +405,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 = isUnhandledOverridden;
}

NSMutableDictionary *userAtCrash = [self parseOnCrashData:event];
Expand Down Expand Up @@ -695,7 +716,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
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ - (void)testCorrectEventKeys {
@"severityReason",
@"threads",
@"unhandled",
@"unhandledOverridden",
@"user",
];
XCTAssertEqualObjects(actualKeys, eventKeys);
Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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"
Loading