diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 3c7188b3c..f4e8fad22 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -560,6 +560,10 @@ 010993B1273D2F6200128BBE /* BugsnagFeatureFlagStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 010993B0273D2F6100128BBE /* BugsnagFeatureFlagStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 010993B2273D2F6200128BBE /* BugsnagFeatureFlagStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 010993B0273D2F6100128BBE /* BugsnagFeatureFlagStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 010993B3273D2F6200128BBE /* BugsnagFeatureFlagStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 010993B0273D2F6100128BBE /* BugsnagFeatureFlagStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 010F80C228645B4200D6569E /* BSGDefinesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 010F80C128645B4200D6569E /* BSGDefinesTests.m */; }; + 010F80C328645B4200D6569E /* BSGDefinesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 010F80C128645B4200D6569E /* BSGDefinesTests.m */; }; + 010F80C428645B4200D6569E /* BSGDefinesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 010F80C128645B4200D6569E /* BSGDefinesTests.m */; }; + 010F80C528645B4200D6569E /* BSGDefinesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 010F80C128645B4200D6569E /* BSGDefinesTests.m */; }; 010FF28425ED2A8D00E4F2B0 /* BSGAppHangDetector.h in Headers */ = {isa = PBXBuildFile; fileRef = 010FF28225ED2A8D00E4F2B0 /* BSGAppHangDetector.h */; }; 010FF28525ED2A8D00E4F2B0 /* BSGAppHangDetector.h in Headers */ = {isa = PBXBuildFile; fileRef = 010FF28225ED2A8D00E4F2B0 /* BSGAppHangDetector.h */; }; 010FF28625ED2A8D00E4F2B0 /* BSGAppHangDetector.h in Headers */ = {isa = PBXBuildFile; fileRef = 010FF28225ED2A8D00E4F2B0 /* BSGAppHangDetector.h */; }; @@ -1542,6 +1546,7 @@ 0109939B273D13D800128BBE /* BSGFeatureFlagStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGFeatureFlagStore.m; sourceTree = ""; }; 010993A3273D188B00128BBE /* BSGFeatureFlagStoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGFeatureFlagStoreTests.m; sourceTree = ""; }; 010993B0273D2F6100128BBE /* BugsnagFeatureFlagStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BugsnagFeatureFlagStore.h; sourceTree = ""; }; + 010F80C128645B4200D6569E /* BSGDefinesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGDefinesTests.m; sourceTree = ""; }; 010FF28225ED2A8D00E4F2B0 /* BSGAppHangDetector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGAppHangDetector.h; sourceTree = ""; }; 010FF28325ED2A8D00E4F2B0 /* BSGAppHangDetector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGAppHangDetector.m; sourceTree = ""; }; 012482A225627B51003F7243 /* UIKitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIKitTests.m; sourceTree = ""; }; @@ -2017,6 +2022,7 @@ 008966BD2486D43500DC48C2 /* BSGClientObserverTests.m */, 00896A3F2486DBDD00DC48C2 /* BSGConfigurationBuilderTests.m */, 008966C62486D43600DC48C2 /* BSGConnectivityTest.m */, + 010F80C128645B4200D6569E /* BSGDefinesTests.m */, 01BDB1CE25DEBF4600A91FAF /* BSGEventUploadKSCrashReportOperationTests.m */, 010993A3273D188B00128BBE /* BSGFeatureFlagStoreTests.m */, 01847DAB26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m */, @@ -2071,17 +2077,17 @@ 016875C5258D003200DFFF69 /* NSUserDefaultsStub.m */, 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */, 008966B72486D43500DC48C2 /* report.json */, - CB6419B325A7419400613D25 /* v0_files */, 004E35392487B375007FBAE4 /* Tests-Bridging-Header.h */, CBA22499251E429C00B87416 /* TestSupport.h */, CBA2249A251E429C00B87416 /* TestSupport.m */, 01935AE0275E68E9007498B3 /* UIApplicationStub.h */, 01935AE1275E68E9007498B3 /* UIApplicationStub.m */, + 012482A225627B51003F7243 /* UIKitTests.m */, 013D9CCF26C5262F0077F0AD /* UISceneStub.h */, 013D9CD026C5262F0077F0AD /* UISceneStub.m */, 01E8765C256684E700F4B70A /* URLSessionMock.h */, 01E8765D256684E700F4B70A /* URLSessionMock.m */, - 012482A225627B51003F7243 /* UIKitTests.m */, + CB6419B325A7419400613D25 /* v0_files */, ); path = BugsnagTests; sourceTree = ""; @@ -3208,6 +3214,7 @@ 008967872486D43700DC48C2 /* KSCrashSentry_NSException_Tests.m in Sources */, 0089679C2486D43700DC48C2 /* KSFileUtils_Tests.m in Sources */, 008966EE2486D43700DC48C2 /* BugsnagClientPayloadInfoTest.m in Sources */, + 010F80C228645B4200D6569E /* BSGDefinesTests.m in Sources */, 01BDB1F525DEBFB200A91FAF /* BSGEventUploadKSCrashReportOperationTests.m in Sources */, 008967782486D43700DC48C2 /* BSG_KSMachHeadersTests.m in Sources */, 0089673F2486D43700DC48C2 /* BugsnagAppTest.m in Sources */, @@ -3391,6 +3398,7 @@ 004E35402487B3BE007FBAE4 /* BugsnagSwiftConfigurationTests.swift in Sources */, 008966F52486D43700DC48C2 /* BugsnagThreadTests.m in Sources */, 0089679D2486D43700DC48C2 /* KSFileUtils_Tests.m in Sources */, + 010F80C328645B4200D6569E /* BSGDefinesTests.m in Sources */, 008967A32486D43700DC48C2 /* KSCrashSentry_Signal_Tests.m in Sources */, 010993A5273D188B00128BBE /* BSGFeatureFlagStoreTests.m in Sources */, 004E353B2487B3B3007FBAE4 /* BugsnagSwiftPublicAPITests.swift in Sources */, @@ -3514,6 +3522,7 @@ 0089679B2486D43700DC48C2 /* FileBasedTestCase.m in Sources */, 008967922486D43700DC48C2 /* KSJSONCodec_Tests.m in Sources */, 008967742486D43700DC48C2 /* KSSysCtl_Tests.m in Sources */, + 010F80C428645B4200D6569E /* BSGDefinesTests.m in Sources */, CBDD6D0F25AC3EFF00A2E12B /* BSGStorageMigratorTests.m in Sources */, 008967502486D43700DC48C2 /* BugsnagPluginTest.m in Sources */, 008967142486D43700DC48C2 /* BugsnagEventTests.m in Sources */, @@ -3797,6 +3806,7 @@ CB28F0B028294D4F003AB200 /* KSFileUtils_Tests.m in Sources */, CB28F0DC282A4BEE003AB200 /* BugsnagSessionTrackerTest.m in Sources */, CB28F0B728294DE1003AB200 /* BSGFeatureFlagStoreTests.m in Sources */, + 010F80C528645B4200D6569E /* BSGDefinesTests.m in Sources */, CB28F0A028294D44003AB200 /* BSG_KSFileTests.m in Sources */, CB28F09E28294D44003AB200 /* BSG_KSMachHeadersTests.m in Sources */, CB28F0D0282A4A2E003AB200 /* BugsnagClientPayloadInfoTest.m in Sources */, diff --git a/Bugsnag/Helpers/BSGDefines.h b/Bugsnag/Helpers/BSGDefines.h index e523df78f..9255df9a1 100644 --- a/Bugsnag/Helpers/BSGDefines.h +++ b/Bugsnag/Helpers/BSGDefines.h @@ -31,4 +31,7 @@ // Capabilities dependent upon previously defined capabilities #define BSG_HAVE_APP_HANG_DETECTION (BSG_HAVE_MACH_THREADS) +// Reference: http://iphonedevwiki.net/index.php/CoreFoundation.framework +#define kCFCoreFoundationVersionNumber_iOS_12_0 1556.00 + #endif /* BSGDefines_h */ diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c index 0bfaa5c50..fa7a05fa5 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c @@ -895,6 +895,7 @@ void bsg_kscrw_i_writeThread(const BSG_KSCrashReportWriter *const writer, const integer_t threadRunState, const bool writeNotableAddresses) { bool isCrashedThread = thread == crash->offendingThread; + bool isSelfThread = thread == bsg_ksmachthread_self(); BSG_STRUCT_MCONTEXT_L machineContextBuffer; uintptr_t backtraceBuffer[BSG_kMaxBacktraceDepth]; int backtraceLength = sizeof(backtraceBuffer) / sizeof(*backtraceBuffer); @@ -926,7 +927,17 @@ void bsg_kscrw_i_writeThread(const BSG_KSCrashReportWriter *const writer, writer->addBooleanElement(writer, BSG_KSCrashField_Crashed, isCrashedThread); writer->addBooleanElement(writer, BSG_KSCrashField_CurrentThread, - thread == bsg_ksmachthread_self()); + isSelfThread); + + // Fetching the current thread name is only safe as of libpthread-330.201.1 + // which was used in ios+tvos 12 and macos 10.14 + if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_12_0) { + if (isSelfThread) { + char buff[100]; + bsg_ksmachgetThreadName(thread, buff, sizeof(buff)); + writer->addStringElement(writer, BSG_KSCrashField_Name, buff); + } + } if (isCrashedThread && machineContext != NULL) { bsg_kscrw_i_writeStackOverflow(writer, BSG_KSCrashField_Stack, machineContext, skippedEntries > 0); diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c index 523501676..f9fd801a2 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c @@ -276,7 +276,8 @@ pthread_t bsg_ksmachpthreadFromMachThread(const thread_t thread) { bool bsg_ksmachgetThreadName(const thread_t thread, char *const buffer, size_t bufLength) { - // WARNING: This implementation is no longer async-safe! + // WARNING: This implementation is only async-safe for the current thread + // as of libpthread-330.201.1, and is still unsafe for other threads. const pthread_t pthread = pthread_from_mach_thread_np(thread); if (pthread == NULL) { diff --git a/Bugsnag/Payload/BugsnagThread.m b/Bugsnag/Payload/BugsnagThread.m index 744f48676..5193412b6 100644 --- a/Bugsnag/Payload/BugsnagThread.m +++ b/Bugsnag/Payload/BugsnagThread.m @@ -108,6 +108,7 @@ - (instancetype)initWithThread:(NSDictionary *)thread binaryImages:(NSArray *)bi if ((self = [super init])) { _errorReportingThread = [thread[@BSG_KSCrashField_Crashed] boolValue]; _id = [thread[@BSG_KSCrashField_Index] stringValue]; + _name = thread[@BSG_KSCrashField_Name]; _type = BSGThreadTypeCocoa; _state = thread[@BSG_KSCrashField_State]; _crashInfoMessage = [thread[@BSG_KSCrashField_CrashInfoMessage] copy]; diff --git a/Tests/BugsnagTests/BSGDefinesTests.m b/Tests/BugsnagTests/BSGDefinesTests.m new file mode 100644 index 000000000..e68b13b38 --- /dev/null +++ b/Tests/BugsnagTests/BSGDefinesTests.m @@ -0,0 +1,27 @@ +// +// BSGDefinesTests.m +// Bugsnag +// +// Created by Nick Dowell on 23/06/2022. +// Copyright © 2022 Bugsnag Inc. All rights reserved. +// + +#import + +#import "BSGDefines.h" + +@interface BSGDefinesTests : XCTestCase + +@end + +@implementation BSGDefinesTests + +- (void)testCoreFoundationVersion { + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { + XCTAssertGreaterThanOrEqual(kCFCoreFoundationVersionNumber, kCFCoreFoundationVersionNumber_iOS_12_0); + } else { + XCTAssertLessThan(kCFCoreFoundationVersionNumber, kCFCoreFoundationVersionNumber_iOS_12_0); + } +} + +@end diff --git a/features/fixtures/shared/scenarios/CxxBareThrowScenario.mm b/features/fixtures/shared/scenarios/CxxBareThrowScenario.mm index ee6d0a021..37a3f4e8f 100644 --- a/features/fixtures/shared/scenarios/CxxBareThrowScenario.mm +++ b/features/fixtures/shared/scenarios/CxxBareThrowScenario.mm @@ -15,6 +15,7 @@ @interface CxxBareThrowScenario : Scenario @implementation CxxBareThrowScenario - (void)run { + [[NSThread mainThread] setName:@"œ´¨ø“‘"]; try { throw; } catch (...) { diff --git a/features/fixtures/shared/scenarios/CxxExceptionOverrideScenario.mm b/features/fixtures/shared/scenarios/CxxExceptionOverrideScenario.mm index 8ef280418..888d07d16 100644 --- a/features/fixtures/shared/scenarios/CxxExceptionOverrideScenario.mm +++ b/features/fixtures/shared/scenarios/CxxExceptionOverrideScenario.mm @@ -43,6 +43,7 @@ - (void)startBugsnag { } - (void)run { + [[NSThread mainThread] setName:@"BSG MAIN THREAD"]; [self crash]; } diff --git a/features/fixtures/shared/scenarios/CxxExceptionScenario.mm b/features/fixtures/shared/scenarios/CxxExceptionScenario.mm index 143511ac0..168d426d7 100644 --- a/features/fixtures/shared/scenarios/CxxExceptionScenario.mm +++ b/features/fixtures/shared/scenarios/CxxExceptionScenario.mm @@ -43,6 +43,7 @@ - (void)startBugsnag { } - (void)run { + [[NSThread mainThread] setName:@"потік"]; [self crash]; } diff --git a/features/fixtures/shared/scenarios/CxxUnexpectedScenario.mm b/features/fixtures/shared/scenarios/CxxUnexpectedScenario.mm index 716823f53..7f6fd4fe9 100644 --- a/features/fixtures/shared/scenarios/CxxUnexpectedScenario.mm +++ b/features/fixtures/shared/scenarios/CxxUnexpectedScenario.mm @@ -15,6 +15,7 @@ @interface CxxUnexpectedScenario : Scenario @implementation CxxUnexpectedScenario - (void)run { + [[NSThread mainThread] setName:@"BSG MAIN THREAD"]; std::unexpected(); } diff --git a/features/fixtures/shared/scenarios/ObjCExceptionOverrideScenario.m b/features/fixtures/shared/scenarios/ObjCExceptionOverrideScenario.m index 3ca67958e..4b25d51de 100644 --- a/features/fixtures/shared/scenarios/ObjCExceptionOverrideScenario.m +++ b/features/fixtures/shared/scenarios/ObjCExceptionOverrideScenario.m @@ -42,6 +42,7 @@ - (void)startBugsnag { } - (void)run __attribute__((noreturn)) { + [[NSThread mainThread] setName:@"メインスレッド"]; @throw [NSException exceptionWithName:NSGenericException reason:@"An uncaught exception! SCREAM." userInfo:@{NSLocalizedDescriptionKey: @"I'm in your program, catching your exceptions!"}]; } diff --git a/features/fixtures/shared/scenarios/ObjCExceptionScenario.m b/features/fixtures/shared/scenarios/ObjCExceptionScenario.m index 07a10a2a5..4e889d96b 100644 --- a/features/fixtures/shared/scenarios/ObjCExceptionScenario.m +++ b/features/fixtures/shared/scenarios/ObjCExceptionScenario.m @@ -42,6 +42,7 @@ - (void)startBugsnag { } - (void)run __attribute__((noreturn)) { + [[NSThread mainThread] setName:@"BSG MAIN THREAD"]; @throw [NSException exceptionWithName:NSGenericException reason:@"An uncaught exception! SCREAM." userInfo:@{NSLocalizedDescriptionKey: @"I'm in your program, catching your exceptions!"}]; } diff --git a/features/steps/reusable_steps.rb b/features/steps/reusable_steps.rb index da8399504..4ec455998 100644 --- a/features/steps/reusable_steps.rb +++ b/features/steps/reusable_steps.rb @@ -10,8 +10,16 @@ step(step_text) if platform.downcase == Maze::Helper.get_current_platform end -Then(/^on (iOS|macOS) (\d+) and later, (.+)/) do |platform, version, step_text| - step(step_text) if platform.downcase == Maze::Helper.get_current_platform && Maze.config.os_version >= version +Then(/^on (\w+) ([0-9.]+) and later, (.+)/) do |test_platform, test_version, step_text| + actual_platform = Maze::Helper.get_current_platform + unless %w[ios macos].include? actual_platform + raise "Unexpected platform #{actual_platform}, this step can only handle ios and macos" + end + + actual_version = Maze.config.os_version + next unless test_platform.downcase == actual_platform && actual_version >= test_version.to_f + + step(step_text) end Then(/^on !(iOS|macOS|watchOS), (.+)/) do |platform, step_text| diff --git a/features/unhandled_cpp_exception.feature b/features/unhandled_cpp_exception.feature index 277d45be1..6795efccf 100644 --- a/features/unhandled_cpp_exception.feature +++ b/features/unhandled_cpp_exception.feature @@ -14,6 +14,8 @@ 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" + And on iOS 12 and later, the event "threads.0.name" equals "потік" + And on macOS 10.14 and later, the event "threads.0.name" equals "потік" Scenario: Throwing a C++ exception with unhandled override When I run "CxxExceptionOverrideScenario" and relaunch the crashed app @@ -27,6 +29,8 @@ Feature: Thrown C++ exceptions are captured by Bugsnag And the event "unhandled" is false And the event "severityReason.unhandledOverridden" is true And the event "severityReason.type" equals "unhandledException" + And on iOS 12 and later, the event "threads.0.name" equals "BSG MAIN THREAD" + And on macOS 10.14 and later, the event "threads.0.name" equals "BSG MAIN THREAD" Scenario: Throwing without an exception When I run "CxxBareThrowScenario" and relaunch the crashed app @@ -36,6 +40,8 @@ Feature: Thrown C++ exceptions are captured by Bugsnag And the exception "errorClass" equals "std::terminate" And the exception "message" equals "throw may have been called without an exception" And the "method" of stack frame 2 equals "-[CxxBareThrowScenario run]" + And on iOS 12 and later, the event "threads.0.name" equals "œ´¨ø“‘" + And on macOS 10.14 and later, the event "threads.0.name" equals "œ´¨ø“‘" Scenario: Causing an unexpected event When I run "CxxUnexpectedScenario" and relaunch the crashed app @@ -45,3 +51,5 @@ Feature: Thrown C++ exceptions are captured by Bugsnag And the exception "errorClass" equals "std::terminate" And the exception "message" equals "throw may have been called without an exception" And the "method" of stack frame 4 equals "-[CxxUnexpectedScenario run]" + And on iOS 12 and later, the event "threads.0.name" equals "BSG MAIN THREAD" + And on macOS 10.14 and later, the event "threads.0.name" equals "BSG MAIN THREAD" diff --git a/features/unhandled_nsexception.feature b/features/unhandled_nsexception.feature index 859d5afd5..b499ccdbb 100644 --- a/features/unhandled_nsexception.feature +++ b/features/unhandled_nsexception.feature @@ -19,6 +19,8 @@ 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" + And on iOS 12 and later, the event "threads.0.name" equals "BSG MAIN THREAD" + And on macOS 10.14 and later, the event "threads.0.name" equals "BSG MAIN THREAD" Scenario: Throw a NSException with unhandled override When I run "ObjCExceptionOverrideScenario" and relaunch the crashed app @@ -37,3 +39,5 @@ Feature: Uncaught NSExceptions are captured by Bugsnag And the event "unhandled" is false And the event "severityReason.unhandledOverridden" is true And the event "severityReason.type" equals "unhandledException" + And on iOS 12 and later, the event "threads.0.name" equals "メインスレッド" + And on macOS 10.14 and later, the event "threads.0.name" equals "メインスレッド"