From 1c4327f4ad404aaf041389f1d772d1ef08234bb3 Mon Sep 17 00:00:00 2001 From: Itay Brenner Date: Tue, 7 Oct 2025 17:41:02 -0300 Subject: [PATCH 1/3] chore: Add tests for SentryCrashContext --- Sentry.xcodeproj/project.pbxproj | 4 + .../Tools/SentryCrashMachineContext_Apple.h | 4 +- .../SentryCrashMachineContextTests.m | 122 ++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 Tests/SentryTests/SentryCrash/SentryCrashMachineContextTests.m diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index dac12580cb1..cc835c57321 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -1015,6 +1015,7 @@ F4DCC9E52E4AACE0008ECE45 /* SentrySDKSettings+Equality.m in Sources */ = {isa = PBXBuildFile; fileRef = F4DCC9E42E4AACE0008ECE45 /* SentrySDKSettings+Equality.m */; }; F4E1E9812E8C2B150007B080 /* SentryDateUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E1E9802E8C2B150007B080 /* SentryDateUtil.swift */; }; F4E3DCCB2E1579240093CB80 /* SentryScopePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */; }; + F4EF69232E95ABE800B6B46A /* SentryCrashMachineContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */; }; F4FE9DBD2E621F100014FED5 /* SentryRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DBC2E621F100014FED5 /* SentryRandom.swift */; }; F4FE9DFD2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */; }; F4FE9DFE2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */; }; @@ -2356,6 +2357,7 @@ F4DCC9E42E4AACE0008ECE45 /* SentrySDKSettings+Equality.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentrySDKSettings+Equality.m"; sourceTree = ""; }; F4E1E9802E8C2B150007B080 /* SentryDateUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDateUtil.swift; sourceTree = ""; }; F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryScopePersistentStore.swift; sourceTree = ""; }; + F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashMachineContextTests.m; sourceTree = ""; }; F4FE9DBC2E621F100014FED5 /* SentryRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRandom.swift; sourceTree = ""; }; F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDefaultObjCRuntimeWrapper.swift; sourceTree = ""; }; F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryObjCRuntimeWrapper.swift; sourceTree = ""; }; @@ -3315,6 +3317,7 @@ 621655652DB12A8900810504 /* SentryCrashMach-OTests.m */, 628B45332DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm */, 629258542DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift */, + F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */, ); path = SentryCrash; sourceTree = ""; @@ -6198,6 +6201,7 @@ F4AACD612E01ACE800DDDD1E /* SentryCrashDynamicLinkerTests.m in Sources */, 7B6438A726A70DDB000D0F65 /* UIViewControllerSentryTests.swift in Sources */, D8BC28D62C00C6DF0054DA4D /* StringExtensionsTests.swift in Sources */, + F4EF69232E95ABE800B6B46A /* SentryCrashMachineContextTests.m in Sources */, 15E0A8F0240F638200F044E3 /* SentrySerializationNilTests.m in Sources */, 0A2D8D8728992260008720F6 /* SentryBaseIntegrationTests.swift in Sources */, 7B6D135C27F4605D00331ED2 /* TestEnvelopeRateLimitDelegate.swift in Sources */, diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext_Apple.h b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext_Apple.h index f32ec6933fb..9a42a29e468 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext_Apple.h +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext_Apple.h @@ -43,9 +43,11 @@ extern "C" { # define STRUCT_MCONTEXT_L _STRUCT_MCONTEXT #endif +#define SENTRY_CRASH_MAX_THREADS 100 + typedef struct SentryCrashMachineContext { thread_t thisThread; - thread_t allThreads[100]; + thread_t allThreads[SENTRY_CRASH_MAX_THREADS]; int threadCount; bool isCrashedContext; bool isCurrentThread; diff --git a/Tests/SentryTests/SentryCrash/SentryCrashMachineContextTests.m b/Tests/SentryTests/SentryCrash/SentryCrashMachineContextTests.m new file mode 100644 index 00000000000..4d64ab88519 --- /dev/null +++ b/Tests/SentryTests/SentryCrash/SentryCrashMachineContextTests.m @@ -0,0 +1,122 @@ +#import + +#import "SentryCrashMachineContext.h" +#import "SentryCrashMachineContext_Apple.h" +#import "TestThread.h" +#import + +@interface SentryCrashMachineContextTests : XCTestCase +@end + +@implementation SentryCrashMachineContextTests + +- (void)testGetContextForThread_NonCrashedContext_DoesNotPopulateThreadList +{ + // Create a test thread + NSObject *notificationObject = [[NSObject alloc] init]; + TestThread *thread = [[TestThread alloc] init]; + thread.notificationObject = notificationObject; + + XCTestExpectation *exp = [self expectationWithDescription:@"thread started"]; + [NSNotificationCenter.defaultCenter + addObserverForName:@"io.sentry.test.TestThread.main" + object:notificationObject + queue:nil + usingBlock:^(NSNotification *_Nonnull __unused notification) { + [NSNotificationCenter.defaultCenter + removeObserver:self + name:@"io.sentry.test.TestThread.main" + object:notificationObject]; + [exp fulfill]; + }]; + + [thread start]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + + kern_return_t kr; + kr = thread_suspend(thread.thread); + XCTAssertTrue(kr == KERN_SUCCESS, @"Thread suspension failed"); + + // Get context for a non-crashed thread + SentryCrashMC_NEW_CONTEXT(machineContext); + bool result = sentrycrashmc_getContextForThread(thread.thread, machineContext, NO); + + XCTAssertTrue(result, @"Failed to get context for thread"); + XCTAssertFalse( + sentrycrashmc_isCrashedContext(machineContext), @"Should not be marked as crashed context"); + + // For non-crashed contexts, thread list should not be populated (will be 0) + int threadCount = sentrycrashmc_getThreadCount(machineContext); + XCTAssertEqual( + threadCount, 0, @"Thread count should be 0 for non-crashed context, got %d", threadCount); + + thread_resume(thread.thread); + [thread cancel]; +} + +- (void)testGetContextForThread_WithManyThreads +{ + NSInteger numberOfThreads = 10; + NSMutableArray *threads = [NSMutableArray arrayWithCapacity:numberOfThreads]; + NSMutableArray *expectations = + [NSMutableArray arrayWithCapacity:numberOfThreads]; + + for (int i = 0; i < numberOfThreads; i++) { + NSObject *notificationObject = [[NSObject alloc] init]; + TestThread *thread = [[TestThread alloc] init]; + thread.notificationObject = notificationObject; + + XCTestExpectation *exp = + [self expectationWithDescription:[NSString stringWithFormat:@"thread %d started", i]]; + [expectations addObject:exp]; + + [NSNotificationCenter.defaultCenter + addObserverForName:@"io.sentry.test.TestThread.main" + object:notificationObject + queue:nil + usingBlock:^(NSNotification *_Nonnull __unused notification) { + [NSNotificationCenter.defaultCenter + removeObserver:self + name:@"io.sentry.test.TestThread.main" + object:notificationObject]; + [exp fulfill]; + }]; + + [threads addObject:thread]; + [thread start]; + } + + [self waitForExpectations:expectations timeout:2]; + + // Suspend the first thread and get its context + TestThread *firstThread = threads[0]; + kern_return_t kr = thread_suspend(firstThread.thread); + XCTAssertTrue(kr == KERN_SUCCESS, @"Thread suspension failed"); + + // Get context for the crashed thread + SentryCrashMC_NEW_CONTEXT(machineContext); + bool result = sentrycrashmc_getContextForThread(firstThread.thread, machineContext, YES); + + XCTAssertTrue(result, @"Failed to get context for thread"); + + // Verify that thread list includes all our test threads + int threadCount = sentrycrashmc_getThreadCount(machineContext); + XCTAssertTrue( + threadCount >= 10, @"Thread count should include all test threads, got %d", threadCount); + XCTAssertTrue(threadCount <= SENTRY_CRASH_MAX_THREADS, + @"Thread count should not exceed maximum of SENTRY_CRASH_MAX_THREADS, got %d", threadCount); + + // Verify that all our threads are in the list + for (TestThread *thread in threads) { + int threadIndex = sentrycrashmc_indexOfThread(machineContext, thread.thread); + XCTAssertTrue(threadIndex >= 0, @"Thread should be found in the list"); + } + + // Clean up + thread_resume(firstThread.thread); + for (TestThread *thread in threads) { + [thread cancel]; + } +} + +@end From 5c0aaad5fcc7750ed4c3454c57baa48eed17ea3f Mon Sep 17 00:00:00 2001 From: Itay Brenner Date: Tue, 7 Oct 2025 18:02:14 -0300 Subject: [PATCH 2/3] fix: Fix thread deallocation --- .../SentryCrash/Recording/Tools/SentryCrashMachineContext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c index cc1781847ea..41fcf507097 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c @@ -70,7 +70,7 @@ getThreadList(SentryCrashMachineContext *context) SENTRY_ASYNC_SAFE_LOG_ERROR("task_threads: %s", mach_error_string(kr)); return false; } - SENTRY_ASYNC_SAFE_LOG_TRACE("Got %d threads", context->threadCount); + SENTRY_ASYNC_SAFE_LOG_TRACE("Got %d threads", actualThreadCount); int threadCount = (int)actualThreadCount; int maxThreadCount = sizeof(context->allThreads) / sizeof(context->allThreads[0]); if (threadCount > maxThreadCount) { @@ -84,7 +84,7 @@ getThreadList(SentryCrashMachineContext *context) context->threadCount = threadCount; for (mach_msg_type_number_t i = 0; i < actualThreadCount; i++) { - mach_port_deallocate(thisTask, context->allThreads[i]); + mach_port_deallocate(thisTask, threads); } vm_deallocate(thisTask, (vm_address_t)threads, sizeof(thread_t) * actualThreadCount); From 753a665214533c8c1dab7071d605d5db8ccf6fd0 Mon Sep 17 00:00:00 2001 From: Itay Brenner Date: Wed, 8 Oct 2025 14:21:39 -0300 Subject: [PATCH 3/3] Fix typo --- Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c index 41fcf507097..83f4a1ee059 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c @@ -84,7 +84,7 @@ getThreadList(SentryCrashMachineContext *context) context->threadCount = threadCount; for (mach_msg_type_number_t i = 0; i < actualThreadCount; i++) { - mach_port_deallocate(thisTask, threads); + mach_port_deallocate(thisTask, threads[i]); } vm_deallocate(thisTask, (vm_address_t)threads, sizeof(thread_t) * actualThreadCount);