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

Create structured BugsnagThread class #532

Merged
merged 3 commits into from
Apr 9, 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Bugsnag Notifiers on other platforms.

## Enhancements

* Create structured `BugsnagThread` class
[#532](https://github.com/bugsnag/bugsnag-cocoa/pull/532)

* Convert `event.device` from `NSDictionary` to a structured class
[#526](https://github.com/bugsnag/bugsnag-cocoa/pull/526)

Expand Down
10 changes: 9 additions & 1 deletion OSX/Bugsnag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
E7433AD31F4F64F400C082D1 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8FF31C6BC3AE00846019 /* libc++.tbd */; };
E7529F8E243C8EBF006B4932 /* RegisterErrorData.h in Headers */ = {isa = PBXBuildFile; fileRef = E7529F8C243C8EBF006B4932 /* RegisterErrorData.h */; };
E7529F8F243C8EBF006B4932 /* RegisterErrorData.m in Sources */ = {isa = PBXBuildFile; fileRef = E7529F8D243C8EBF006B4932 /* RegisterErrorData.m */; };
E7529FA0243CAE35006B4932 /* BugsnagThreadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7529F9F243CAE34006B4932 /* BugsnagThreadTest.m */; };
E7529FA2243CAE3F006B4932 /* BugsnagThreadSerializationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7529FA1243CAE3F006B4932 /* BugsnagThreadSerializationTest.m */; };
E762E9F91F73F7F300E82B43 /* BugsnagHandledStateTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E762E9F71F73F7E900E82B43 /* BugsnagHandledStateTest.m */; };
E762E9FC1F73F80200E82B43 /* BugsnagHandledState.h in Headers */ = {isa = PBXBuildFile; fileRef = E762E9FA1F73F80200E82B43 /* BugsnagHandledState.h */; };
E762E9FD1F73F80200E82B43 /* BugsnagHandledState.m in Sources */ = {isa = PBXBuildFile; fileRef = E762E9FB1F73F80200E82B43 /* BugsnagHandledState.m */; };
Expand Down Expand Up @@ -279,6 +281,8 @@
E72AE1F8241A4E7500ED8972 /* BugsnagPluginClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagPluginClient.h; path = ../Source/BugsnagPluginClient.h; sourceTree = "<group>"; };
E7529F8C243C8EBF006B4932 /* RegisterErrorData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RegisterErrorData.h; path = ../Source/RegisterErrorData.h; sourceTree = "<group>"; };
E7529F8D243C8EBF006B4932 /* RegisterErrorData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegisterErrorData.m; path = ../Source/RegisterErrorData.m; sourceTree = "<group>"; };
E7529F9F243CAE34006B4932 /* BugsnagThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagThreadTest.m; sourceTree = "<group>"; };
E7529FA1243CAE3F006B4932 /* BugsnagThreadSerializationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagThreadSerializationTest.m; path = ../iOS/BugsnagTests/BugsnagThreadSerializationTest.m; sourceTree = "<group>"; };
E762E9F71F73F7E900E82B43 /* BugsnagHandledStateTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagHandledStateTest.m; path = ../Tests/BugsnagHandledStateTest.m; sourceTree = SOURCE_ROOT; };
E762E9FA1F73F80200E82B43 /* BugsnagHandledState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagHandledState.h; path = ../Source/BugsnagHandledState.h; sourceTree = SOURCE_ROOT; };
E762E9FB1F73F80200E82B43 /* BugsnagHandledState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagHandledState.m; path = ../Source/BugsnagHandledState.m; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -548,9 +552,11 @@
E791482E1FD82B0C003EFEBF /* BugsnagUserTest.m */,
E7D2E675243B8FB6005A3041 /* BugsnagStacktraceTest.m */,
E722105D243B6A0E0083CF15 /* BugsnagStackframeTest.m */,
E791482E1FD82B0C003EFEBF /* BugsnagUserTest.m */,
00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */,
00F9393A23FD2D9B008C7073 /* BugsnagTestsDummyClass.m */,
E7529FA1243CAE3F006B4932 /* BugsnagThreadSerializationTest.m */,
E7529F9F243CAE34006B4932 /* BugsnagThreadTest.m */,
E791482E1FD82B0C003EFEBF /* BugsnagUserTest.m */,
8A2C8FE41C6BC38200846019 /* report.json */,
8A2C8FB21C6BC1F700846019 /* TestsInfo.plist */,
E7CE78861FD94E40001D07E0 /* KSCrash */,
Expand Down Expand Up @@ -1135,6 +1141,7 @@
E790C42324324528006FFB26 /* BugsnagClientMirrorTest.m in Sources */,
E7CE78D01FD94E77001D07E0 /* RFC3339DateTool_Tests.m in Sources */,
E79148621FD82BB7003EFEBF /* BugsnagSessionTrackingPayloadTest.m in Sources */,
E7529FA2243CAE3F006B4932 /* BugsnagThreadSerializationTest.m in Sources */,
E79148631FD82BB7003EFEBF /* BugsnagUserTest.m in Sources */,
8A2C8FEA1C6BC38900846019 /* BugsnagBreadcrumbsTest.m in Sources */,
E7CE78BB1FD94E77001D07E0 /* KSCrashReportConverter_Tests.m in Sources */,
Expand All @@ -1152,6 +1159,7 @@
E762E9F91F73F7F300E82B43 /* BugsnagHandledStateTest.m in Sources */,
E7CE78C51FD94E77001D07E0 /* KSLogger_Tests.m in Sources */,
E7CE78C11FD94E77001D07E0 /* KSCrashState_Tests.m in Sources */,
E7529FA0243CAE35006B4932 /* BugsnagThreadTest.m in Sources */,
E7AB4B9E2423E184004F015A /* BugsnagOnBreadcrumbTest.m in Sources */,
E7CE78C31FD94E77001D07E0 /* KSFileUtils_Tests.m in Sources */,
E7CE78BC1FD94E77001D07E0 /* KSCrashReportStore_Tests.m in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions Source/BugsnagEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@class BugsnagAppWithState;
@class BugsnagDeviceWithState;
@class BugsnagMetadata;
@class BugsnagThread;

typedef NS_ENUM(NSUInteger, BSGSeverity) {
BSGSeverityError,
Expand Down Expand Up @@ -132,5 +133,10 @@ initWithErrorName:(NSString *_Nonnull)name
*/
@property(readonly) BOOL unhandled;

/**
* Thread traces for the error that occurred, if collection was enabled.
*/
@property(readonly, nonnull) NSMutableArray<BugsnagThread *> *threads;

@end

111 changes: 43 additions & 68 deletions Source/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import "BugsnagDeviceWithState.h"
#import "BugsnagClient.h"
#import "BugsnagStacktrace.h"
#import "BugsnagThread.h"
#import "RegisterErrorData.h"

static NSString *const DEFAULT_EXCEPTION_TYPE = @"cocoa";
Expand Down Expand Up @@ -72,6 +73,25 @@ + (BugsnagStackframe *)frameFromDict:(NSDictionary *)dict
withImages:(NSArray *)binaryImages;
@end

@interface BugsnagThread ()
@property BugsnagStacktrace *trace;
- (NSDictionary *)toDictionary;

- (instancetype)initWithThread:(NSDictionary *)thread
binaryImages:(NSArray *)binaryImages;

+ (NSMutableArray<BugsnagThread *> *)threadsFromArray:(NSArray *)threads
binaryImages:(NSArray *)binaryImages
depth:(NSUInteger)depth
errorType:(NSString *)errorType;

+ (NSMutableArray *)serializeThreads:(NSArray<BugsnagThread *> *)threads;
@end

@interface BugsnagStacktrace ()
- (NSArray *)toArray;
@end

// MARK: - KSCrashReport parsing

NSString *_Nonnull BSGParseErrorClass(NSDictionary *error,
Expand Down Expand Up @@ -216,14 +236,6 @@ @interface BugsnagEvent ()
* A unique hash identifying this device for the application or vendor
*/
@property(nonatomic, readonly, copy, nullable) NSString *deviceAppHash;
/**
* Binary images used to identify application symbols
*/
@property(nonatomic, readonly, copy, nullable) NSArray *binaryImages;
/**
* Thread information captured at the time of the error
*/
@property(nonatomic, readonly, copy, nullable) NSArray *threads;
/**
* User-provided exception metadata
*/
Expand Down Expand Up @@ -300,6 +312,9 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
_releaseStage = [report valueForKeyPath:@"user.state.oom.app.releaseStage"];
_handledState = [BugsnagHandledState handledStateWithSeverityReason:LikelyOutOfMemory];
_deviceAppHash = [report valueForKeyPath:@"user.state.oom.device.id"];

// no threads or metadata captured for OOMs
_threads = [NSMutableArray new];
self.metadata = [BugsnagMetadata new];

NSDictionary *sessionData = [report valueForKeyPath:@"user.state.oom.session"];
Expand All @@ -313,16 +328,17 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
} else {
_enabledReleaseStages = BSGLoadConfigValue(report, BSGKeyEnabledReleaseStages);
_releaseStage = BSGParseReleaseStage(report);
_threads = [report valueForKeyPath:@"crash.threads"];
RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:_threads];
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);
}
_binaryImages = report[@"binary_images"];
_breadcrumbs = BSGParseBreadcrumbs(report);
_deviceAppHash = [report valueForKeyPath:@"system.device_app_hash"];

Expand Down Expand Up @@ -370,6 +386,12 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
if (report[@"user"][@"id"]) {
_session = [[BugsnagSession alloc] initWithDictionary:report[@"user"]];
}

// generate threads last, relies on depth/errorType properties being calculated first
_threads = [BugsnagThread threadsFromArray:threadDict
binaryImages:binaryImages
depth:self.depth
errorType:self.errorType];
}
}
return self;
Expand Down Expand Up @@ -404,6 +426,7 @@ - (instancetype _Nonnull)initWithErrorName:(NSString *_Nonnull)name
_handledState = handledState;
_severity = handledState.currentSeverity;
_session = session;
_threads = [NSMutableArray new];
}
return self;
}
Expand Down Expand Up @@ -539,18 +562,23 @@ - (NSDictionary *)toJson {

if (self.customException) {
BSGDictSetSafeObject(event, @[ self.customException ], BSGKeyExceptions);
BSGDictSetSafeObject(event, [self serializeThreadsWithException:nil],
BSGKeyThreads);
} 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);

BSGDictSetSafeObject(
event, [self serializeThreadsWithException:exception], BSGKeyThreads);
// set the stacktrace for the exception from the threads
for (BugsnagThread *thread in self.threads) {
if (thread.errorReportingThread) {
BSGDictSetSafeObject(exception, [thread.trace toArray], BSGKeyStacktrace);
}
}
}

BSGDictSetSafeObject(event, [BugsnagThread serializeThreads:self.threads], BSGKeyThreads);

// Build Event
BSGDictSetSafeObject(event, BSGFormatSeverity(self.severity), BSGKeySeverity);
BSGDictSetSafeObject(event, [self serializeBreadcrumbs], BSGKeyBreadcrumbs);
Expand Down Expand Up @@ -614,59 +642,6 @@ - (NSDictionary *)generateSessionDict {
return sessionJson;
}

// Build all stacktraces for threads and the error
- (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception {
NSMutableArray *bugsnagThreads = [NSMutableArray array];

for (NSDictionary *thread in self.threads) {
NSArray *backtrace = thread[@"backtrace"][@"contents"];
BOOL stackOverflow = [thread[@"stack"][@"overflow"] boolValue];
BOOL isReportingThread = [thread[@"crashed"] boolValue];

if (isReportingThread) {
NSUInteger seen = 0;
NSMutableArray *stacktrace = [NSMutableArray array];

for (NSDictionary *frame in backtrace) {
NSMutableDictionary *mutableFrame = [frame mutableCopy];
if (seen++ >= [self depth]) {
// Mark the frame so we know where it came from
if (seen == 1 && !stackOverflow) {
BSGDictSetSafeObject(mutableFrame, @YES, BSGKeyIsPC);
}
if (seen == 2 && !stackOverflow &&
[@[ BSGKeySignal, BSGKeyMach ]
containsObject:[self errorType]]) {
BSGDictSetSafeObject(mutableFrame, @YES, BSGKeyIsLR);
}
BSGArrayInsertIfNotNil(stacktrace, mutableFrame);
}
}
BugsnagStacktrace *trace = [[BugsnagStacktrace alloc] initWithTrace:stacktrace binaryImages:self.binaryImages];
BSGDictSetSafeObject(exception, [trace toArray], BSGKeyStacktrace);
}
[self serialiseThread:bugsnagThreads thread:thread backtrace:backtrace reportingThread:isReportingThread];
}
return bugsnagThreads;
}

- (void)serialiseThread:(NSMutableArray *)bugsnagThreads
thread:(NSDictionary *)thread
backtrace:(NSArray *)backtrace
reportingThread:(BOOL)isReportingThread {
BugsnagStacktrace *stacktrace = [[BugsnagStacktrace alloc] initWithTrace:backtrace binaryImages:self.binaryImages];
NSMutableDictionary *threadDict = [NSMutableDictionary dictionary];
BSGDictSetSafeObject(threadDict, thread[@"index"], BSGKeyId);
BSGDictSetSafeObject(threadDict, [stacktrace toArray], BSGKeyStacktrace);
BSGDictSetSafeObject(threadDict, DEFAULT_EXCEPTION_TYPE, BSGKeyType);

if (isReportingThread) {
BSGDictSetSafeObject(threadDict, @YES, @"errorReportingThread");
}

BSGArrayAddSafeObject(bugsnagThreads, threadDict);
}

- (BOOL)unhandled {
return self.handledState.unhandled;
}
Expand Down
35 changes: 32 additions & 3 deletions Source/BugsnagThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,42 @@

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, BSGThreadType) {
BSGThreadTypeCocoa = 0,
BSGThreadTypeReactNativeJs = 1 << 1
};

@class BugsnagStackframe;

/**
* A representation of thread information recorded as part of a BugsnagEvent.
*/
@interface BugsnagThread : NSObject

@end
/**
* A unique ID which identifies this thread
*/
@property(nullable) NSString *id;

NS_ASSUME_NONNULL_END
/**
* The name which identifies this thread
*/
@property(nullable) NSString *name;

/**
* Whether this thread was the thread that triggered the captured error
*/
@property BOOL errorReportingThread;

/**
* Sets a representation of this thread's stacktrace
*/
@property(readonly, nonnull) NSArray<BugsnagStackframe *> *stacktrace;

/**
* Determines the type of thread based on the originating platform
* (intended for internal use only)
*/
@property BSGThreadType type;

@end
Loading