Skip to content
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
4 changes: 4 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -2356,6 +2357,7 @@
F4DCC9E42E4AACE0008ECE45 /* SentrySDKSettings+Equality.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentrySDKSettings+Equality.m"; sourceTree = "<group>"; };
F4E1E9802E8C2B150007B080 /* SentryDateUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDateUtil.swift; sourceTree = "<group>"; };
F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryScopePersistentStore.swift; sourceTree = "<group>"; };
F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashMachineContextTests.m; sourceTree = "<group>"; };
F4FE9DBC2E621F100014FED5 /* SentryRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRandom.swift; sourceTree = "<group>"; };
F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDefaultObjCRuntimeWrapper.swift; sourceTree = "<group>"; };
F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryObjCRuntimeWrapper.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3315,6 +3317,7 @@
621655652DB12A8900810504 /* SentryCrashMach-OTests.m */,
628B45332DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm */,
629258542DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift */,
F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */,
);
path = SentryCrash;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
134 changes: 134 additions & 0 deletions Tests/SentryTests/SentryCrash/SentryCrashMachineContextTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#import <XCTest/XCTest.h>

#import "SentryCrashMachineContext.h"
#import "SentryCrashMachineContext_Apple.h"
#import "TestThread.h"
#import <mach/mach.h>

@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:5 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);
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Wait for thread to cancel"];
thread.endExpectation = expectation;
[thread cancel];
[self waitForExpectations:@[ expectation ] timeout:5];
}

- (void)testGetContextForThread_WithManyThreads
{
NSInteger numberOfThreads = 10;
NSMutableArray<TestThread *> *threads = [NSMutableArray arrayWithCapacity:numberOfThreads];
NSMutableArray<XCTestExpectation *> *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:5];

// 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);
NSMutableArray<XCTestExpectation *> *finishExpectations =
[NSMutableArray arrayWithCapacity:threads.count];
for (TestThread *thread in threads) {
thread.endExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Wait for thread to cancel"];
[finishExpectations addObject:thread.endExpectation];
[thread cancel];
}

// Wait for all threads to finish (up to 10 seconds)
[self waitForExpectations:finishExpectations timeout:10];
}

@end
2 changes: 2 additions & 0 deletions Tests/SentryTests/SentryCrash/TestThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
//

#import <Foundation/Foundation.h>
#import <XCTest/XCTestExpectation.h>
#import <mach/mach_types.h>

@interface TestThread : NSThread

@property (nonatomic, readwrite, assign) thread_t thread;
@property (nonatomic, strong) NSObject *notificationObject;
@property (nonatomic, strong) XCTestExpectation *endExpectation;

@end
4 changes: 4 additions & 0 deletions Tests/SentryTests/SentryCrash/TestThread.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ - (void)main SENTRY_DISABLE_THREAD_SANITIZER("Known data race to fix")
while (!self.isCancelled) {
[[self class] sleepForTimeInterval:0.1];
}

if (self.endExpectation) {
[self.endExpectation fulfill];
}
}

@end
Loading