Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: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);
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Wait for thread to cancel"];
thread.endExpectation = expectation;
[thread cancel];
[self waitForExpectations:@[ expectation ] timeout:1];
}

- (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: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);
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