diff --git a/CHANGELOG.md b/CHANGELOG.md index a90666558b9..c3f13faeb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Crash in AppHangs when no threads (#2725) + ## 8.2.0 ### Features diff --git a/Sources/Sentry/SentryANRTrackingIntegration.m b/Sources/Sentry/SentryANRTrackingIntegration.m index 32ccc7fa3cd..8d9b5268501 100644 --- a/Sources/Sentry/SentryANRTrackingIntegration.m +++ b/Sources/Sentry/SentryANRTrackingIntegration.m @@ -8,6 +8,7 @@ #import "SentryEvent.h" #import "SentryException.h" #import "SentryHub+Private.h" +#import "SentryLog.h" #import "SentryMechanism.h" #import "SentrySDK+Private.h" #import "SentryStacktrace.h" @@ -58,11 +59,16 @@ - (void)anrDetected { SentryThreadInspector *threadInspector = SentrySDK.currentHub.getClient.threadInspector; - NSString *message = [NSString stringWithFormat:@"App hanging for at least %li ms.", - (long)(self.options.appHangTimeoutInterval * 1000)]; - NSArray *threads = [threadInspector getCurrentThreadsWithStackTrace]; + if (threads.count == 0) { + SENTRY_LOG_WARN(@"Getting current thread returned an empty list. Can't create AppHang " + @"event without a stacktrace."); + return; + } + + NSString *message = [NSString stringWithFormat:@"App hanging for at least %li ms.", + (long)(self.options.appHangTimeoutInterval * 1000)]; SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentryLevelError]; SentryException *sentryException = [[SentryException alloc] initWithValue:message type:@"App Hanging"]; diff --git a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift index 30cfca76964..0622b4c85bc 100644 --- a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift @@ -98,6 +98,15 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { XCTAssertTrue(threadsWithFrames > 1, "Not enough threads with frames") } } + + func testANRDetected_ButNoThreads_EventNotCaptured() { + givenInitializedTracker() + setUpThreadInspector(addThreads: false) + + Dynamic(sut).anrDetected() + + assertNoEventCaptured() + } private func givenInitializedTracker(isBeingTraced: Bool = false) { givenSdkWithHub() @@ -106,27 +115,32 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { sut.install(with: self.options) } - private func setUpThreadInspector() { + private func setUpThreadInspector(addThreads: Bool = true) { let threadInspector = TestThreadInspector.instance - let frame1 = Sentry.Frame() - frame1.function = "Second_frame_function" - - let thread1 = SentryThread(threadId: 0) - thread1.stacktrace = SentryStacktrace(frames: [frame1], registers: [:]) - thread1.current = true - - let frame2 = Sentry.Frame() - frame2.function = "main" - - let thread2 = SentryThread(threadId: 1) - thread2.stacktrace = SentryStacktrace(frames: [frame2], registers: [:]) - thread2.current = false - - threadInspector.allThreads = [ - thread2, - thread1 - ] + if addThreads { + + let frame1 = Sentry.Frame() + frame1.function = "Second_frame_function" + + let thread1 = SentryThread(threadId: 0) + thread1.stacktrace = SentryStacktrace(frames: [frame1], registers: [:]) + thread1.current = true + + let frame2 = Sentry.Frame() + frame2.function = "main" + + let thread2 = SentryThread(threadId: 1) + thread2.stacktrace = SentryStacktrace(frames: [frame2], registers: [:]) + thread2.current = false + + threadInspector.allThreads = [ + thread2, + thread1 + ] + } else { + threadInspector.allThreads = [] + } SentrySDK.currentHub().getClient()?.threadInspector = threadInspector } diff --git a/Tests/SentryTests/SentrySDKIntegrationTestsBase.swift b/Tests/SentryTests/SentrySDKIntegrationTestsBase.swift index cd771a3fd1c..b93d4edf093 100644 --- a/Tests/SentryTests/SentrySDKIntegrationTestsBase.swift +++ b/Tests/SentryTests/SentrySDKIntegrationTestsBase.swift @@ -33,6 +33,14 @@ class SentrySDKIntegrationTestsBase: XCTestCase { SentrySDK.setCurrentHub(SentryHub(client: nil, andScope: nil)) } + func assertNoEventCaptured() { + guard let client = SentrySDK.currentHub().getClient() as? TestClient else { + XCTFail("Hub Client is not a `TestClient`") + return + } + XCTAssertEqual(0, client.captureEventInvocations.count, "No event should be captured.") + } + func assertEventCaptured(_ callback: (Event?) -> Void) { guard let client = SentrySDK.currentHub().getClient() as? TestClient else { XCTFail("Hub Client is not a `TestClient`")