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

[PLAT-4706] Fix thread sending behaviour #1077

Merged
merged 1 commit into from
Apr 21, 2021
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
13 changes: 5 additions & 8 deletions Bugsnag/Client/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
#import "BugsnagSession+Private.h"
#import "BugsnagSessionTracker+Private.h"
#import "BugsnagSessionTrackingApiClient.h"
#import "BugsnagStackframe+Private.h"
#import "BugsnagStateEvent.h"
#import "BugsnagSystemState.h"
#import "BugsnagThread+Private.h"
Expand Down Expand Up @@ -870,18 +871,14 @@ - (void)notify:(NSException *)exception

NSArray<NSNumber *> *callStack = exception.callStackReturnAddresses;
if (!callStack.count) {
// If the NSException was not raised by the Objective-C runtime, it will be missing a call stack.
// Use the current call stack instead.
callStack = BSGArraySubarrayFromIndex(NSThread.callStackReturnAddresses, depth);
}
BOOL recordAllThreads = self.configuration.sendThreads == BSGThreadSendPolicyAlways;
NSArray *threads = [BugsnagThread allThreads:recordAllThreads callStackReturnAddresses:callStack];
NSArray *threads = recordAllThreads ? [BugsnagThread allThreads:YES callStackReturnAddresses:callStack] : @[];

NSArray<BugsnagStackframe *> *stacktrace = nil;
for (BugsnagThread *thread in threads) {
if (thread.errorReportingThread) {
stacktrace = thread.stacktrace;
break;
}
}
NSArray<BugsnagStackframe *> *stacktrace = [BugsnagStackframe stackframesWithCallStackReturnAddresses:callStack];

BugsnagError *error = [[BugsnagError alloc] initWithErrorClass:exception.name ?: NSStringFromClass([exception class])
errorMessage:exception.reason ?: @""
Expand Down
2 changes: 1 addition & 1 deletion Bugsnag/Payload/BugsnagError+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface BugsnagError ()

- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(nullable BugsnagThread *)thread;
- (instancetype)initWithKSCrashReport:(NSDictionary *)event stacktrace:(NSArray<BugsnagStackframe *> *)stacktrace;

- (instancetype)initWithErrorClass:(NSString *)errorClass
errorMessage:(NSString *)errorMessage
Expand Down
8 changes: 2 additions & 6 deletions Bugsnag/Payload/BugsnagError.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ @implementation BugsnagError

@dynamic type;

- (instancetype)initWithErrorReportingThread:(BugsnagThread *)thread {
return [self initWithEvent:@{} errorReportingThread:thread];
}

- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(BugsnagThread *)thread {
- (instancetype)initWithKSCrashReport:(NSDictionary *)event stacktrace:(NSArray<BugsnagStackframe *> *)stacktrace {
if (self = [super init]) {
NSDictionary *error = [event valueForKeyPath:@"crash.error"];
NSString *errorType = error[BSGKeyType];
Expand All @@ -96,7 +92,7 @@ - (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(Bugsna
_typeString = BSGSerializeErrorType(BSGErrorTypeCocoa);

if (![[event valueForKeyPath:@"user.state.didOOM"] boolValue]) {
_stacktrace = thread.stacktrace;
_stacktrace = stacktrace;
}
}
return self;
Expand Down
15 changes: 9 additions & 6 deletions Bugsnag/Payload/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -310,20 +310,23 @@ - (instancetype)initWithKSCrashData:(NSDictionary *)event {
// generate threads/error info
NSArray *binaryImages = event[@"binary_images"];
NSArray *threadDict = [event valueForKeyPath:@"crash.threads"];
NSMutableArray<BugsnagThread *> *threads = [BugsnagThread threadsFromArray:threadDict
binaryImages:binaryImages
depth:depth
errorType:errorType];
NSArray<BugsnagThread *> *threads = [BugsnagThread threadsFromArray:threadDict binaryImages:binaryImages depth:depth errorType:errorType];

BugsnagThread *errorReportingThread;
BugsnagThread *errorReportingThread = nil;
for (BugsnagThread *thread in threads) {
if (thread.errorReportingThread) {
errorReportingThread = thread;
break;
}
}

NSArray<BugsnagError *> *errors = @[[[BugsnagError alloc] initWithEvent:event errorReportingThread:errorReportingThread]];
NSArray<BugsnagError *> *errors = @[[[BugsnagError alloc] initWithKSCrashReport:event stacktrace:errorReportingThread.stacktrace ?: @[]]];

// KSCrash captures only the offending thread when sendThreads = BSGThreadSendPolicyNever.
// The BugsnagEvent should not contain threads in this case, only the stacktrace.
if (threads.count == 1) {
threads = @[];
}

if (errorReportingThread.crashInfoMessage) {
[errors[0] updateWithCrashInfoMessage:(NSString * _Nonnull)errorReportingThread.crashInfoMessage];
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

## TBD

### Bug fixes

* `event.threads` will now be empty, rather than containing a single thread, if `sendThreads` dictates that threads should not be sent.
[#1077](https://github.com/bugsnag/bugsnag-cocoa/pull/1077)

## 6.9.0 (2021-04-21)

### Enhancements
Expand Down
6 changes: 3 additions & 3 deletions Tests/BugsnagErrorTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ - (void)setUp {

- (void)testErrorLoad {
BugsnagThread *thread = [self findErrorReportingThread:self.event];
BugsnagError *error = [[BugsnagError alloc] initWithEvent:self.event errorReportingThread:thread];
BugsnagError *error = [[BugsnagError alloc] initWithKSCrashReport:self.event stacktrace:thread.stacktrace];
XCTAssertEqualObjects(@"Foo Exception", error.errorClass);
XCTAssertEqualObjects(@"Foo overload", error.errorMessage);
XCTAssertEqual(BSGErrorTypeCocoa, error.type);
Expand All @@ -79,7 +79,7 @@ - (void)testErrorLoad {

- (void)testToDictionary {
BugsnagThread *thread = [self findErrorReportingThread:self.event];
BugsnagError *error = [[BugsnagError alloc] initWithEvent:self.event errorReportingThread:thread];
BugsnagError *error = [[BugsnagError alloc] initWithKSCrashReport:self.event stacktrace:thread.stacktrace];
NSDictionary *dict = [error toDictionary];
XCTAssertEqualObjects(@"Foo Exception", dict[@"errorClass"]);
XCTAssertEqualObjects(@"Foo overload", dict[@"message"]);
Expand Down Expand Up @@ -141,7 +141,7 @@ - (void)testErrorMessageParse {

- (void)testStacktraceOverride {
BugsnagThread *thread = [self findErrorReportingThread:self.event];
BugsnagError *error = [[BugsnagError alloc] initWithEvent:self.event errorReportingThread:thread];
BugsnagError *error = [[BugsnagError alloc] initWithKSCrashReport:self.event stacktrace:thread.stacktrace];
XCTAssertNotNil(error.stacktrace);
XCTAssertEqual(1, error.stacktrace.count);
error.stacktrace = @[];
Expand Down
9 changes: 4 additions & 5 deletions features/barebone_tests.feature
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ Feature: Barebone tests
And the event "severity" equals "warning"
And the event "severityReason.type" equals "handledException"
And the event "severityReason.unhandledOverridden" is true
And the event "threads.0.errorReportingThread" is true
And the event "threads.0.id" equals "0"
And the event "threads.0.name" equals "com.apple.main-thread"
And the event "threads.0.stacktrace.0.method" matches "BareboneTestHandledScenario"
And the event "unhandled" is true
And the event "user.email" equals "[email protected]"
And the event "user.id" equals "foobar"
Expand All @@ -79,7 +75,7 @@ Feature: Barebone tests
And the error payload field "events.0.device.freeMemory" is an integer
And the error payload field "events.0.device.model" matches the test device model
And the error payload field "events.0.device.totalMemory" is an integer
And the error payload field "events.0.threads" is an array with 1 elements
And the error payload field "events.0.threads" is an array with 0 elements
And the "method" of stack frame 0 matches "BareboneTestHandledScenario"

And I discard the oldest error
Expand Down Expand Up @@ -158,6 +154,8 @@ Feature: Barebone tests
And the error payload field "events.0.device.freeMemory" is an integer
And the error payload field "events.0.device.model" matches the test device model
And the error payload field "events.0.device.totalMemory" is an integer
And the error payload field "events.0.threads" is a non-empty array
And the error payload field "events.0.threads.1" is not null
And the "method" of stack frame 0 matches "(assertionFailure|<redacted>)"

@skip_macos
Expand Down Expand Up @@ -246,3 +244,4 @@ Feature: Barebone tests
And the error payload field "events.0.device.freeMemory" is null
And the error payload field "events.0.device.model" matches the test device model
And the error payload field "events.0.device.totalMemory" is an integer
And the error payload field "events.0.threads" is an array with 0 elements
20 changes: 7 additions & 13 deletions features/threads.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Feature: Handled Errors and Exceptions
Feature: Threads

Background:
Given I clear all persistent data
Expand All @@ -10,6 +10,8 @@ Feature: Handled Errors and Exceptions
And the event "unhandled" is false
And the error payload field "events" is an array with 1 elements
And the exception "message" equals "HandledErrorThreadSendAlwaysScenario"
And the error payload field "events.0.threads" is a non-empty array
And the error payload field "events.0.threads.1" is not null
And the thread information is valid for the event

Scenario: Threads are captured for unhandled errors by default
Expand All @@ -20,6 +22,8 @@ Feature: Handled Errors and Exceptions
And the event "unhandled" is true
And the error payload field "events" is an array with 1 elements
And the exception "message" equals "UnhandledErrorThreadSendAlwaysScenario"
And the error payload field "events.0.threads" is a non-empty array
And the error payload field "events.0.threads.1" is not null
And the thread information is valid for the event

Scenario: Threads are not captured for handled errors when sendThreads is set to unhandled_only
Expand All @@ -29,12 +33,7 @@ Feature: Handled Errors and Exceptions
And the event "unhandled" is false
And the error payload field "events" is an array with 1 elements
And the exception "errorClass" equals "HandledErrorThreadSendUnhandledOnlyScenario"
And the error payload field "events.0.threads" is an array with 1 elements
And the error payload field "events.0.threads.0.errorReportingThread" is true
And the error payload field "events.0.threads.0.id" is not null
And the error payload field "events.0.threads.0.name" equals "com.apple.main-thread"
And the error payload field "events.0.threads.0.type" equals "cocoa"
And the thread information is valid for the event
And the error payload field "events.0.threads" is an array with 0 elements

Scenario: Threads are not captured for unhandled errors when sendThreads is set to never
When I run "UnhandledErrorThreadSendNeverScenario" and relaunch the app
Expand All @@ -44,9 +43,4 @@ Feature: Handled Errors and Exceptions
And the event "unhandled" is true
And the error payload field "events" is an array with 1 elements
And the exception "message" equals "UnhandledErrorThreadSendNeverScenario"
And the error payload field "events.0.threads" is an array with 1 elements
And the error payload field "events.0.threads.0.errorReportingThread" is true
And the error payload field "events.0.threads.0.id" is not null
And the error payload field "events.0.threads.0.name" is null
And the error payload field "events.0.threads.0.type" equals "cocoa"
And the thread information is valid for the event
And the error payload field "events.0.threads" is an array with 0 elements