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
8 changes: 4 additions & 4 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
6304360B1EC0595B00C4D3FA /* SentryNSDataUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 630436091EC0595B00C4D3FA /* SentryNSDataUtils.m */; };
630436101EC0600A00C4D3FA /* SentrySerializable.h in Headers */ = {isa = PBXBuildFile; fileRef = 6304360F1EC0600A00C4D3FA /* SentrySerializable.h */; settings = {ATTRIBUTES = (Public, ); }; };
630436161EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 630436151EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m */; };
630C01941EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 630C01931EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m */; };
630C01941EC3402C00C52CEF /* SentryCrashReportConverterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 630C01931EC3402C00C52CEF /* SentryCrashReportConverterTests.m */; };
630C01961EC341D600C52CEF /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 630C01951EC341D600C52CEF /* Resources */; };
631501BB1EE6F30B00512C5B /* SentrySwizzleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 631501BA1EE6F30B00512C5B /* SentrySwizzleTests.m */; };
631E6D331EBC679C00712345 /* SentryQueueableRequestManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 631E6D311EBC679C00712345 /* SentryQueueableRequestManager.h */; };
Expand Down Expand Up @@ -1391,7 +1391,7 @@
6304360D1EC05CEF00C4D3FA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
6304360F1EC0600A00C4D3FA /* SentrySerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySerializable.h; path = Public/SentrySerializable.h; sourceTree = "<group>"; };
630436151EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryNSDataCompressionTests.m; sourceTree = "<group>"; };
630C01931EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryKSCrashReportConverterTests.m; sourceTree = "<group>"; };
630C01931EC3402C00C52CEF /* SentryCrashReportConverterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashReportConverterTests.m; sourceTree = "<group>"; };
630C01951EC341D600C52CEF /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = "<group>"; };
631501BA1EE6F30B00512C5B /* SentrySwizzleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentrySwizzleTests.m; sourceTree = "<group>"; };
631E6D311EBC679C00712345 /* SentryQueueableRequestManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryQueueableRequestManager.h; path = include/SentryQueueableRequestManager.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2974,7 +2974,7 @@
8431D4572BE175A1009EAEC1 /* SentryContinuousProfiler+Test.h */,
639889D21EDF06C100EA7442 /* SentryTests-Bridging-Header.h */,
63B819131EC352A7002FDF4C /* SentryInterfacesTests.m */,
630C01931EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m */,
630C01931EC3402C00C52CEF /* SentryCrashReportConverterTests.m */,
630436151EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m */,
63EED6C22237989300E02400 /* SentryOptionsTest.m */,
7B569DFF2590EEF600B653FC /* SentryScope+Equality.m */,
Expand Down Expand Up @@ -6150,7 +6150,7 @@
7BBD18A2244EE2FD00427C76 /* TestResponseFactory.swift in Sources */,
628B89022D841D7F004B6F2A /* SentryDateUtilsTests.swift in Sources */,
D808FB8B281BCE96009A2A33 /* TestSentrySwizzleWrapper.swift in Sources */,
630C01941EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m in Sources */,
630C01941EC3402C00C52CEF /* SentryCrashReportConverterTests.m in Sources */,
7B59398424AB481B0003AAD2 /* NotificationCenterTestCase.swift in Sources */,
7B0A542E2521C62400A71716 /* SentryFrameRemoverTests.swift in Sources */,
7BE912B12721C76000E49E62 /* SentryPerformanceTrackingIntegrationTests.swift in Sources */,
Expand Down
18 changes: 18 additions & 0 deletions Sources/Sentry/Public/SentryThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ NS_ASSUME_NONNULL_BEGIN

SENTRY_NO_INIT

#if SDK_V9
/**
* Number of the thread.
*
* Can be nil for threads in recrash reports where the thread index information is not available.
*/
@property (nullable, nonatomic, copy) NSNumber *threadId;
#else
/**
* Number of the thread
*/
@property (nonatomic, copy) NSNumber *threadId;
#endif // SDK_V9

/**
* Name (if available) of the thread
Expand All @@ -50,12 +59,21 @@ SENTRY_NO_INIT
*/
@property (nullable, nonatomic, copy) NSNumber *isMain;

#if SDK_V9
/**
* Initializes a SentryThread with its id
* @param threadId NSNumber or nil if thread index is not available (e.g., recrash reports)
* @return SentryThread
*/
- (instancetype)initWithThreadId:(nullable NSNumber *)threadId;
#else
/**
* Initializes a SentryThread with its id
* @param threadId NSNumber
* @return SentryThread
*/
- (instancetype)initWithThreadId:(NSNumber *)threadId;
#endif // SDK_V9

@end

Expand Down
4 changes: 3 additions & 1 deletion Sources/Sentry/SentryANRTrackingIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import "SentryEvent.h"
#import "SentryException.h"
#import "SentryHub+Private.h"
#import "SentryInternalDefines.h"
#import "SentryLogC.h"
#import "SentryMechanism.h"
#import "SentrySDK+Private.h"
Expand Down Expand Up @@ -154,7 +155,8 @@ - (void)anrDetectedWithType:(enum SentryANRType)type
// recover the debug images. The client would also attach the debug images when directly
// capturing the app hang event. Still, we attach them already now to ensure all app hang events
// have debug images cause it's easy to mess this up in the future.
event.debugMeta = [self.debugImageProvider getDebugImagesFromCacheForThreads:event.threads];
event.debugMeta = [self.debugImageProvider
getDebugImagesFromCacheForThreads:SENTRY_UNWRAP_NULLABLE(NSArray, event.threads)];

#if SENTRY_HAS_UIKIT
# if SDK_V9
Expand Down
5 changes: 3 additions & 2 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,9 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event
BOOL debugMetaNotAttached = !(nil != event.debugMeta && event.debugMeta.count > 0);
if (!isFatalEvent && shouldAttachStacktrace && debugMetaNotAttached
&& event.threads != nil) {
event.debugMeta =
[self.debugImageProvider getDebugImagesFromCacheForThreads:event.threads];
event.debugMeta = [self.debugImageProvider
getDebugImagesFromCacheForThreads:SENTRY_UNWRAP_NULLABLE(
NSArray<SentryThread *>, event.threads)];
}
}

Expand Down
79 changes: 59 additions & 20 deletions Sources/Sentry/SentryCrashReportConverter.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ - (instancetype)initWithReport:(NSDictionary *)report inAppLogic:(SentryInAppLog
// here. For more details please check out SentryCrashScopeObserver.
NSMutableDictionary *userContextMerged =
[[NSMutableDictionary alloc] initWithDictionary:userContextUnMerged];
[userContextMerged addEntriesFromDictionary:report[@"sentry_sdk_scope"]];
[userContextMerged addEntriesFromDictionary:report[@"sentry_sdk_scope"] ?: @{}];
[userContextMerged removeObjectForKey:@"sentry_sdk_scope"];
self.userContext = userContextMerged;

Expand Down Expand Up @@ -108,8 +108,9 @@ - (SentryEvent *_Nullable)convertReportToEvent
if ([self.report[@"report"][@"timestamp"] isKindOfClass:NSNumber.class]) {
event.timestamp = [NSDate
dateWithTimeIntervalSince1970:[self.report[@"report"][@"timestamp"] integerValue]];
} else {
event.timestamp = sentry_fromIso8601String(self.report[@"report"][@"timestamp"]);
} else if ([self.report[@"report"][@"timestamp"] isKindOfClass:NSString.class]) {
event.timestamp = sentry_fromIso8601String(
SENTRY_UNWRAP_NULLABLE(NSString, self.report[@"report"][@"timestamp"]));
}
event.threads = [self convertThreads];
event.debugMeta = [self debugMetaForThreads:event.threads];
Expand All @@ -119,7 +120,7 @@ - (SentryEvent *_Nullable)convertReportToEvent
event.environment = self.userContext[@"environment"];

NSMutableDictionary *mutableContext =
[[NSMutableDictionary alloc] initWithDictionary:self.userContext[@"context"]];
[[NSMutableDictionary alloc] initWithDictionary:self.userContext[@"context"] ?: @{}];
if (self.userContext[@"traceContext"]) {
mutableContext[@"trace"] = self.userContext[@"traceContext"];
}
Expand Down Expand Up @@ -188,11 +189,16 @@ - (SentryUser *_Nullable)convertUser
for (NSDictionary *storedCrumb in storedBreadcrumbs) {
SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc]
initWithLevel:[self sentryLevelFromString:storedCrumb[@"level"]]
category:storedCrumb[@"category"]];
category:storedCrumb[@"category"]
?: @"default"]; // The default value is the same as the one in
// SentryBreadcrumb.init
crumb.message = storedCrumb[@"message"];
crumb.type = storedCrumb[@"type"];
crumb.origin = storedCrumb[@"origin"];
crumb.timestamp = sentry_fromIso8601String(storedCrumb[@"timestamp"]);
if ([storedCrumb[@"timestamp"] isKindOfClass:NSString.class]) {
crumb.timestamp = sentry_fromIso8601String(
SENTRY_UNWRAP_NULLABLE(NSString, storedCrumb[@"timestamp"]));
}
crumb.data = storedCrumb[@"data"];
[breadcrumbs addObject:crumb];
}
Expand Down Expand Up @@ -248,14 +254,32 @@ - (NSDictionary *)binaryImageForAddress:(uintptr_t)address
return result;
}

/**
* Creates a SentryThread from crash report thread data at the specified index.
*
* This method includes defensive null handling to prevent crashes when processing
* malformed crash reports. The original bug was that invalid thread index types
* could cause crashes when accessing threadId.intValue for isMain calculation.
*/
- (SentryThread *_Nullable)threadAtIndex:(NSInteger)threadIndex
{
if (threadIndex >= [self.threads count]) {
return nil;
}
NSDictionary *threadDictionary = self.threads[threadIndex];

SentryThread *thread = [[SentryThread alloc] initWithThreadId:threadDictionary[@"index"]];
// Thread index validation: We must support nil/missing indexes for backward compatibility
// with recrash reports (see testRecrashReport_WithThreadIsStringInsteadOfDict), but we
// must reject invalid types when present to prevent crashes from calling .intValue on
// non-NSNumber objects. This fixes a bug where malformed crash reports could cause
// crashes in the converter itself when accessing threadId.intValue for isMain calculation.
id threadIndexObj = threadDictionary[@"index"];
if (threadIndexObj != nil && ![threadIndexObj isKindOfClass:[NSNumber class]]) {
SENTRY_LOG_ERROR(@"Thread index is not a number: %@", threadIndexObj);
return nil;
}
SentryThread *thread =
[[SentryThread alloc] initWithThreadId:SENTRY_UNWRAP_NULLABLE(NSNumber, threadIndexObj)];
// We only want to add the stacktrace if this thread hasn't crashed
thread.stacktrace = [self stackTraceForThreadIndex:threadIndex];
if (thread.stacktrace.frames.count == 0) {
Expand All @@ -266,7 +290,10 @@ - (SentryThread *_Nullable)threadAtIndex:(NSInteger)threadIndex
thread.current = threadDictionary[@"current_thread"];
thread.name = threadDictionary[@"name"];
// We don't have access to the MachineContextWrapper but we know first thread is always the main
thread.isMain = [NSNumber numberWithBool:thread.threadId.intValue == 0];
// Use null-safe check: threadIndexObj can be nil for recrash reports, and calling intValue on
// a nil NSNumber would return 0, incorrectly marking threads without indexes as main threads.
thread.isMain =
[NSNumber numberWithBool:threadIndexObj != nil && [threadIndexObj intValue] == 0];
if (nil == thread.name) {
thread.name = threadDictionary[@"dispatch_queue"];
}
Expand Down Expand Up @@ -357,9 +384,11 @@ - (SentryDebugMeta *)debugMetaFromBinaryImageDictionary:(NSDictionary *)sourceIm

for (SentryThread *thread in threads) {
for (SentryFrame *frame in thread.stacktrace.frames) {
if (frame.imageAddress && ![imageNames containsObject:frame.imageAddress]) {
[imageNames addObject:frame.imageAddress];
NSString *_Nullable nullableImageAddress = frame.imageAddress;
if (nullableImageAddress == nil) {
continue;
}
[imageNames addObject:SENTRY_UNWRAP_NULLABLE(NSString, nullableImageAddress)];
}
}

Expand Down Expand Up @@ -399,19 +428,25 @@ - (SentryDebugMeta *)debugMetaFromBinaryImageDictionary:(NSDictionary *)sourceIm
self.exceptionContext[@"mach"][@"exception"],
self.exceptionContext[@"mach"][@"code"],
self.exceptionContext[@"mach"][@"subcode"]]
type:self.exceptionContext[@"mach"][@"exception_name"]];
type:self.exceptionContext[@"mach"][@"exception_name"]
?: @"Mach Exception"]; // The fallback value is best-attempt in case the exception
// name is not available
} else if ([exceptionType isEqualToString:@"signal"]) {
exception =
[[SentryException alloc] initWithValue:[NSString stringWithFormat:@"Signal %@, Code %@",
self.exceptionContext[@"signal"][@"signal"],
self.exceptionContext[@"signal"][@"code"]]
type:self.exceptionContext[@"signal"][@"name"]];
type:self.exceptionContext[@"signal"][@"name"]
?: @"Signal Exception"]; // The fallback value is best-attempt in case the
// exception name is not available
} else if ([exceptionType isEqualToString:@"user"]) {
NSString *exceptionReason =
[NSString stringWithFormat:@"%@", self.exceptionContext[@"reason"]];
exception = [[SentryException alloc]
initWithValue:exceptionReason
type:self.exceptionContext[@"user_reported"][@"name"]];
exception =
[[SentryException alloc] initWithValue:exceptionReason
type:self.exceptionContext[@"user_reported"][@"name"]
?: @"User Reported Exception"]; // The fallback value is best-attempt in case
// the exception name is not available

NSRange match = [exceptionReason rangeOfString:@":"];
if (match.location != NSNotFound) {
Expand Down Expand Up @@ -454,7 +489,9 @@ - (SentryException *)parseNSException
}

return [[SentryException alloc] initWithValue:[NSString stringWithFormat:@"%@", reason]
type:self.exceptionContext[@"nsexception"][@"name"]];
type:self.exceptionContext[@"nsexception"][@"name"]
?: @"NSException"]; // The fallback value is best-attempt in case the exception name is
// not available
}

- (void)enhanceValueFromNotableAddresses:(SentryException *)exception
Expand All @@ -464,15 +501,15 @@ - (void)enhanceValueFromNotableAddresses:(SentryException *)exception
return;
}
NSDictionary *crashedThread = self.threads[self.crashedThreadIndex];
NSDictionary *notableAddresses = crashedThread[@"notable_addresses"];
NSDictionary *_Nullable notableAddresses = crashedThread[@"notable_addresses"];
NSMutableOrderedSet *reasons = [[NSMutableOrderedSet alloc] init];
if (nil != notableAddresses) {
for (id key in notableAddresses) {
NSDictionary *content = notableAddresses[key];
if ([content[@"type"] isEqualToString:@"string"] && nil != content[@"value"]) {
// if there are less than 3 slashes it shouldn't be a filepath
if ([[content[@"value"] componentsSeparatedByString:@"/"] count] < 3) {
[reasons addObject:content[@"value"]];
[reasons addObject:SENTRY_UNWRAP_NULLABLE(NSString, content[@"value"])];
}
}
}
Expand All @@ -497,11 +534,13 @@ - (void)enhanceValueFromCrashInfoMessage:(SentryException *)exception

for (NSDictionary *binaryImage in libSwiftCoreBinaryImages) {
if (binaryImage[@"crash_info_message"] != nil) {
[crashInfoMessages addObject:binaryImage[@"crash_info_message"]];
[crashInfoMessages
addObject:SENTRY_UNWRAP_NULLABLE(NSString, binaryImage[@"crash_info_message"])];
}

if (binaryImage[@"crash_info_message2"] != nil) {
[crashInfoMessages addObject:binaryImage[@"crash_info_message2"]];
[crashInfoMessages
addObject:SENTRY_UNWRAP_NULLABLE(NSString, binaryImage[@"crash_info_message2"])];
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/SentryThread.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@

@implementation SentryThread

#if SDK_V9
- (instancetype)initWithThreadId:(nullable NSNumber *)threadId
#else
- (instancetype)initWithThreadId:(NSNumber *)threadId
#endif // SDK_V9
{
self = [super init];
if (self) {
Expand Down
Loading
Loading