diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b7f4cad..e34ad8956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Changelog ## 5.16.3 (14 Aug 2018) +* Capture trace of error reporting thread and identify with boolean flag [#303](https://github.com/bugsnag/bugsnag-cocoa/pull/303) + ### Bug Fixes * Deregister notification observers and listeners before application termination [#301](https://github.com/bugsnag/bugsnag-cocoa/pull/301) diff --git a/Gemfile.lock b/Gemfile.lock index f3d9ad507..ecd230a91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,33 +1,34 @@ GIT remote: https://github.com/bugsnag/maze-runner - revision: f7123450d5a75b719911c6dd3baa0507e6062c2d + revision: a480183979a48aa2fc9f4891989a1237b757bb91 specs: bugsnag-maze-runner (1.0.0) cucumber (~> 3.1.0) cucumber-expressions (= 5.0.15) minitest (~> 5.0) rack (~> 2.0.0) + rake (~> 12.3.0) test-unit (~> 3.2.0) GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.6) + CFPropertyList (3.0.0) activesupport (4.2.10) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - atomos (0.1.2) + atomos (0.1.3) backports (3.11.3) builder (3.2.3) claide (1.0.2) - cocoapods (1.4.0) + cocoapods (1.5.3) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.4.0) + cocoapods-core (= 1.5.3) cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.1.3, < 2.0) + cocoapods-downloader (>= 1.2.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) @@ -37,21 +38,21 @@ GEM escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.6.4) + molinillo (~> 0.6.5) nap (~> 1.0) ruby-macho (~> 1.1) - xcodeproj (>= 1.5.4, < 2.0) - cocoapods-core (1.4.0) + xcodeproj (>= 1.5.7, < 2.0) + cocoapods-core (1.5.3) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.1.3) + cocoapods-downloader (1.2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) + cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.1.0) @@ -78,32 +79,33 @@ GEM fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - gherkin (5.0.0) + gherkin (5.1.0) i18n (0.9.5) concurrent-ruby (~> 1.0) minitest (5.11.3) - molinillo (0.6.4) + molinillo (0.6.6) multi_json (1.13.1) multi_test (0.1.2) - nanaimo (0.2.3) + nanaimo (0.2.6) nap (1.1.0) netrc (0.11.0) - power_assert (1.1.1) - rack (2.0.4) + power_assert (1.1.3) + rack (2.0.5) + rake (12.3.1) rouge (2.0.7) - ruby-macho (1.1.0) - test-unit (3.2.7) + ruby-macho (1.2.0) + test-unit (3.2.8) power_assert thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.5.6) - CFPropertyList (~> 2.3.3) - atomos (~> 0.1.2) + xcodeproj (1.6.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.3) - xcpretty (0.2.8) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) rouge (~> 2.0.7) PLATFORMS diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index d018a4e15..00eb36769 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -553,12 +553,13 @@ - (NSDictionary *)generateSessionDict { // Build all stacktraces for threads and the error - (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception { NSMutableArray *bugsnagThreads = [NSMutableArray array]; - for (NSDictionary *thread in [self threads]) { + + for (NSDictionary *thread in self.threads) { NSArray *backtrace = thread[@"backtrace"][@"contents"]; BOOL stackOverflow = [thread[@"stack"][@"overflow"] boolValue]; - BOOL isCrashedThread = [thread[@"crashed"] boolValue]; + BOOL isReportingThread = [thread[@"crashed"] boolValue]; - if (isCrashedThread) { + if (isReportingThread) { NSUInteger seen = 0; NSMutableArray *stacktrace = [NSMutableArray array]; @@ -579,29 +580,38 @@ - (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception { BSGFormatFrame(mutableFrame, [self binaryImages])); } } - BSGDictSetSafeObject(exception, stacktrace, BSGKeyStacktrace); - } else { - NSMutableArray *threadStack = [NSMutableArray array]; + } + [self serialiseThread:bugsnagThreads thread:thread backtrace:backtrace reportingThread:isReportingThread]; + } + return bugsnagThreads; +} - for (NSDictionary *frame in backtrace) { +- (void)serialiseThread:(NSMutableArray *)bugsnagThreads + thread:(NSDictionary *)thread + backtrace:(NSArray *)backtrace + reportingThread:(BOOL)isReportingThread { + NSMutableArray *threadStack = [NSMutableArray array]; + + for (NSDictionary *frame in backtrace) { BSGArrayInsertIfNotNil( threadStack, BSGFormatFrame(frame, [self binaryImages])); } - NSMutableDictionary *threadDict = [NSMutableDictionary dictionary]; - BSGDictSetSafeObject(threadDict, thread[@"index"], BSGKeyId); - BSGDictSetSafeObject(threadDict, threadStack, BSGKeyStacktrace); - BSGDictSetSafeObject(threadDict, DEFAULT_EXCEPTION_TYPE, BSGKeyType); - // only if this is enabled in BSG_KSCrash. - if (thread[BSGKeyName]) { - BSGDictSetSafeObject(threadDict, thread[BSGKeyName], BSGKeyName); - } + NSMutableDictionary *threadDict = [NSMutableDictionary dictionary]; + BSGDictSetSafeObject(threadDict, thread[@"index"], BSGKeyId); + BSGDictSetSafeObject(threadDict, threadStack, BSGKeyStacktrace); + BSGDictSetSafeObject(threadDict, DEFAULT_EXCEPTION_TYPE, BSGKeyType); - BSGArrayAddSafeObject(bugsnagThreads, threadDict); - } + // only if this is enabled in BSG_KSCrash. + if (thread[BSGKeyName]) { + BSGDictSetSafeObject(threadDict, thread[BSGKeyName], BSGKeyName); } - return bugsnagThreads; + if (isReportingThread) { + BSGDictSetSafeObject(threadDict, @YES, @"errorReportingThread"); + } + + BSGArrayAddSafeObject(bugsnagThreads, threadDict); } - (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)thread { diff --git a/Tests/BugsnagSinkTests.m b/Tests/BugsnagSinkTests.m index 55ee64075..fcc48659f 100644 --- a/Tests/BugsnagSinkTests.m +++ b/Tests/BugsnagSinkTests.m @@ -251,7 +251,7 @@ - (void)testExceptionStacktrace { - (void)testEventThreadCount { NSArray *threads = [self.processedData[@"events"] firstObject][@"threads"]; - XCTAssert(threads.count == 8); + XCTAssertTrue(threads.count == 9); } - (void)testEventDevice { diff --git a/examples/objective-c-ios/Bugsnag Test App/AppDelegate.m b/examples/objective-c-ios/Bugsnag Test App/AppDelegate.m index ee5283e97..cd658672c 100644 --- a/examples/objective-c-ios/Bugsnag Test App/AppDelegate.m +++ b/examples/objective-c-ios/Bugsnag Test App/AppDelegate.m @@ -27,7 +27,7 @@ - (void)startBugsnagWithConfiguration { } - (void)startBugsnagWithAPIKey { - [Bugsnag startBugsnagWithApiKey:@"6ef10e3707a961373e8592ae65d68ff1"]; + [Bugsnag startBugsnagWithApiKey:@"5d1ec8bd39a74caa1267142706a7fb20"]; [Bugsnag configuration].releaseStage = @"production"; [Bugsnag configuration].notifyReleaseStages = @[@"production"]; } diff --git a/examples/objective-c-ios/Podfile.lock b/examples/objective-c-ios/Podfile.lock index b4a6b6cb3..a40630d16 100644 --- a/examples/objective-c-ios/Podfile.lock +++ b/examples/objective-c-ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Bugsnag (5.16.0) + - Bugsnag (5.16.2) DEPENDENCIES: - Bugsnag (from `../..`) @@ -9,7 +9,7 @@ EXTERNAL SOURCES: :path: "../.." SPEC CHECKSUMS: - Bugsnag: 47bcc70b43e3c616ec35d30c2ca94497b957199f + Bugsnag: 4d84527ed289f9cda5d8291393918732372d50bd PODFILE CHECKSUM: 4c48f26cc704429f747c4af7a40e026b20fdc83e diff --git a/features/error_reporting_thread.feature b/features/error_reporting_thread.feature new file mode 100644 index 000000000..fd720e65f --- /dev/null +++ b/features/error_reporting_thread.feature @@ -0,0 +1,7 @@ +Feature: Error Reporting Thread + +Scenario: Only 1 thread is flagged as the error reporting thread + When I run "HandledErrorScenario" with the defaults on "iPhone8-11.2" + Then I should receive a request + And the request is a valid for the error reporting API + And the thread with id "0" contains the error reporting flag diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index 52ff718e0..d8cc3ea44 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -301,6 +301,7 @@ E7EC041A1F4CC97200C2E9D5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E79FEBE61F4CB1320048FAD6 /* Foundation.framework */; }; F4295168CDC7A77A832C9475 /* BugsnagApiClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F4295E2778677786239F2B28 /* BugsnagApiClient.m */; }; F429522FC5D3D54364D51F3F /* BugsnagApiClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F42950F4A741305B77E95389 /* BugsnagApiClient.h */; }; + F42952D83435C02F8D891C40 /* BugsnagThreadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F429551527EAE3AFE1F605FE /* BugsnagThreadTest.m */; }; F42953297D561ACAB878DEB8 /* BugsnagFileStore.m in Sources */ = {isa = PBXBuildFile; fileRef = F42958B2E67C338E3086EAC2 /* BugsnagFileStore.m */; }; F42954D219B725C18DA1084F /* BugsnagSessionTrackingApiClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F4295FBD23F478FC6216A006 /* BugsnagSessionTrackingApiClient.m */; }; F42954D2337A7CAE1FE9B308 /* BugsnagFileStore.m in Sources */ = {isa = PBXBuildFile; fileRef = F42958B2E67C338E3086EAC2 /* BugsnagFileStore.m */; }; @@ -592,6 +593,7 @@ F42954ACC6FFDDE3C8471495 /* BugsnagSessionFileStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagSessionFileStore.m; path = ../Source/BugsnagSessionFileStore.m; sourceTree = SOURCE_ROOT; }; F42954B7D892334E7551F0F3 /* RegisterErrorDataTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegisterErrorDataTest.m; sourceTree = ""; }; F42955025DBE1DCEFD928CAA /* BugsnagSessionFileStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagSessionFileStore.h; path = ../Source/BugsnagSessionFileStore.h; sourceTree = SOURCE_ROOT; }; + F429551527EAE3AFE1F605FE /* BugsnagThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagThreadTest.m; sourceTree = ""; }; F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagKSCrashSysInfoParserTest.m; sourceTree = ""; }; F42958B2E67C338E3086EAC2 /* BugsnagFileStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagFileStore.m; path = ../Source/BugsnagFileStore.m; sourceTree = SOURCE_ROOT; }; F4295E2778677786239F2B28 /* BugsnagApiClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagApiClient.m; path = ../Source/BugsnagApiClient.m; sourceTree = SOURCE_ROOT; }; @@ -729,6 +731,7 @@ 8A2C8F291C6BBD2300846019 /* TestsInfo.plist */, F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */, F42954B7D892334E7551F0F3 /* RegisterErrorDataTest.m */, + F429551527EAE3AFE1F605FE /* BugsnagThreadTest.m */, ); name = Tests; path = BugsnagTests; @@ -1241,6 +1244,7 @@ E78C1EF11FCC2F1700B976D3 /* BugsnagSessionTrackerTest.m in Sources */, F4295995C3259BF7D9730BC4 /* BugsnagKSCrashSysInfoParserTest.m in Sources */, F4295F017754324FD52CCE46 /* RegisterErrorDataTest.m in Sources */, + F42952D83435C02F8D891C40 /* BugsnagThreadTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOS/BugsnagTests/BugsnagThreadTest.m b/iOS/BugsnagTests/BugsnagThreadTest.m new file mode 100644 index 000000000..e7d4852d5 --- /dev/null +++ b/iOS/BugsnagTests/BugsnagThreadTest.m @@ -0,0 +1,90 @@ +// +// Created by Jamie Lynch on 02/08/2018. +// Copyright (c) 2018 Bugsnag. All rights reserved. +// + +#import +#import + +#import "BugsnagCrashReport.h" + +@interface BugsnagThreadTest : XCTestCase +@end + +@interface BugsnagCrashReport () +- (NSArray *)serializeThreadsWithException:(NSMutableDictionary *)exception; + +@property(nonatomic, readonly, copy, nullable) NSArray *threads; +@end + +@implementation BugsnagThreadTest + +- (void)testEmptyThreads { + BugsnagCrashReport *report = [self generateReportWithThreads:@[]]; + NSArray *threads = [report serializeThreadsWithException:nil]; + XCTAssertTrue(threads.count == 0); +} + +- (void)testThreadSerialisation { + NSArray *trace = @[ + @{ + @"backtrace": @{ + @"contents": @[ + @{ + @"instruction_addr": @4438096107, + @"object_addr": @4438048768, + @"object_name": @"Bugsnag Test App", + @"symbol_addr": @4438048768, + @"symbol_name": @"_mh_execute_header", + } + ], + @"skipped": @NO, + }, + @"crashed": @YES, + @"current_thread": @YES, + @"index": @0 + }, + @{ + @"backtrace": @{ + @"contents": @[ + @{ + @"instruction_addr": @4510040722, + @"object_addr": @4509921280, + @"object_name": @"libsystem_kernel.dylib", + @"symbol_addr": @4510040712, + @"symbol_name": @"__workq_kernreturn", + } + ], + @"skipped": @NO, + }, + @"crashed": @NO, + @"current_thread": @NO, + @"index": @1 + }, + ]; + + BugsnagCrashReport *report = [self generateReportWithThreads:trace]; + NSArray *threads = [report serializeThreadsWithException:nil]; + XCTAssertTrue(threads.count == 2); + + // first thread is crashed, should be serialised and contain 'errorReportingThread' flag + NSDictionary *firstThread = threads[0]; + XCTAssertEqualObjects(@0, firstThread[@"id"]); + XCTAssertEqualObjects(@"cocoa", firstThread[@"type"]); + XCTAssertNotNil(firstThread[@"stacktrace"]); + XCTAssertTrue(firstThread[@"errorReportingThread"]); + + // second thread is not crashed, should not contain 'errorReportingThread' flag + NSDictionary *secondThread = threads[1]; + XCTAssertEqualObjects(@1, secondThread[@"id"]); + XCTAssertEqualObjects(@"cocoa", secondThread[@"type"]); + XCTAssertNotNil(secondThread[@"stacktrace"]); + XCTAssertNil(secondThread[@"errorReportingThread"]); +} + +- (BugsnagCrashReport *)generateReportWithThreads:(NSArray *)threads { + return [[BugsnagCrashReport alloc] initWithKSReport:@{@"crash": @{@"threads": threads}}]; +} + + +@end \ No newline at end of file