From 9c8450326b3240f80afbef99fa566fbed4955e22 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Mon, 14 Oct 2024 15:00:10 +0200 Subject: [PATCH 1/2] docs: Remove Nimble from test guidelines (#4431) The test guidelines in contributing.md still mention to use Nimble, but we removed Nimble with GH-4120. --- CONTRIBUTING.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c168323f19f..181e64141f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,9 +44,6 @@ Test guidelines: * Make use of the fixture pattern for test setup code. For examples, checkout [SentryClientTest](/Tests/SentryTests/SentryClientTest.swift) or [SentryHttpTransportTests](/Tests/SentryTests/SentryHttpTransportTests.swift). * Use [TestData](/Tests/SentryTests/Protocol/TestData.swift) when possible to avoid setting up data classes with test values. * Name the variable of the class you are testing `sut`, which stands for [system under test](https://en.wikipedia.org/wiki/System_under_test). -* We prefer using [Nimble](https://github.com/Quick/Nimble) over XCTest for test assertions. We can't use the latest Nimble version and are stuck -with [v10.0.0](https://github.com/Quick/Nimble/releases/tag/v10.0.0), cause it's the latest one that still supports Xcode 13.2.1, which we use in CI for -running our tests. [v11.0.0](https://github.com/Quick/Nimble/releases/tag/v11.0.0) already requires Swift 5.6 / Xcode 13.3. * When calling `SentrySDK.start` in a test, specify only the minimum integrations required to minimize side effects for tests and reduce flakiness. From a2cf26e16faa7bb44735a636672bcf76373d81ec Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Mon, 14 Oct 2024 15:06:28 +0200 Subject: [PATCH 2/2] impr: Speed up getBinaryImages (#4435) Add two new internal methods getDebugImagesFromCacheForFrames getDebugImagesFromCacheFrames to the SentryDebugImageProvider which use the significantly faster SentryBinaryImageCache. Fixes GH-4399 --- CHANGELOG.md | 1 + Sentry.xcodeproj/project.pbxproj | 4 + SentryTestUtils/TestClient.swift | 3 +- Sources/Sentry/SentryBinaryImageCache.m | 14 +++ Sources/Sentry/SentryClient.m | 10 +- Sources/Sentry/SentryDebugImageProvider.m | 72 +++++++++-- Sources/Sentry/SentryDependencyContainer.m | 11 +- Sources/Sentry/SentryThreadInspector.m | 1 - Sources/Sentry/SentryTracer.m | 6 +- .../HybridPublic/SentryBinaryImageCache.h | 5 + .../SentryDebugImageProvider+HybridSDKs.h | 29 +++++ .../include/HybridPublic/SentryFormatter.h | 13 ++ .../Helper/SentryFormatterTests.swift | 55 ++++++++ .../Helper/TestDebugImageProvider.swift | 14 +++ .../SentryBinaryImageCacheTests.swift | 23 ++-- Tests/SentryTests/SentryClient+TestInit.h | 2 + Tests/SentryTests/SentryClientTests.swift | 20 ++- .../SentryDebugImageProvider+TestInit.h | 6 +- .../SentryDebugImageProviderTests.swift | 118 +++++++++++++++++- .../SentryTests/SentryTests-Bridging-Header.h | 2 +- .../Transaction/SentryTracerTests.swift | 1 + 21 files changed, 375 insertions(+), 35 deletions(-) create mode 100644 Sources/Sentry/include/HybridPublic/SentryDebugImageProvider+HybridSDKs.h diff --git a/CHANGELOG.md b/CHANGELOG.md index bf55a5be505..e46223360ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ via the option `swizzleClassNameExclude`. - Serializing profile on a BG Thread (#4377) to avoid potentially slightly blocking the main thread. - Session Replay performance for SwiftUI (#4419) +- Speed up getBinaryImages (#4435) for finishing transactions and capturing events ## 8.38.0-beta.1 diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 7e2c516110e..77a3bc9c69c 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ 624688192C048EF10006179C /* SentryBaggageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624688182C048EF10006179C /* SentryBaggageSerialization.swift */; }; 626E2D4C2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626E2D4B2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift */; }; 6271ADF32BA06D9B0098D2E9 /* SentryInternalSerializable.h in Headers */ = {isa = PBXBuildFile; fileRef = 6271ADF22BA06D9B0098D2E9 /* SentryInternalSerializable.h */; }; + 6273513F2CBD14970021D100 /* SentryDebugImageProvider+HybridSDKs.h in Headers */ = {isa = PBXBuildFile; fileRef = 6273513E2CBD14970021D100 /* SentryDebugImageProvider+HybridSDKs.h */; }; 627E7589299F6FE40085504D /* SentryInternalDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 627E7588299F6FE40085504D /* SentryInternalDefines.h */; }; 62862B1C2B1DDBC8009B16E3 /* SentryDelayedFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 62862B1B2B1DDBC8009B16E3 /* SentryDelayedFrame.h */; }; 62862B1E2B1DDC35009B16E3 /* SentryDelayedFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 62862B1D2B1DDC35009B16E3 /* SentryDelayedFrame.m */; }; @@ -1081,6 +1082,7 @@ 624688182C048EF10006179C /* SentryBaggageSerialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBaggageSerialization.swift; sourceTree = ""; }; 626E2D4B2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilderTests.swift; sourceTree = ""; }; 6271ADF22BA06D9B0098D2E9 /* SentryInternalSerializable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryInternalSerializable.h; path = include/SentryInternalSerializable.h; sourceTree = ""; }; + 6273513E2CBD14970021D100 /* SentryDebugImageProvider+HybridSDKs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryDebugImageProvider+HybridSDKs.h"; path = "include/HybridPublic/SentryDebugImageProvider+HybridSDKs.h"; sourceTree = ""; }; 627E7588299F6FE40085504D /* SentryInternalDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryInternalDefines.h; path = include/SentryInternalDefines.h; sourceTree = ""; }; 62862B1B2B1DDBC8009B16E3 /* SentryDelayedFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDelayedFrame.h; path = include/SentryDelayedFrame.h; sourceTree = ""; }; 62862B1D2B1DDC35009B16E3 /* SentryDelayedFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDelayedFrame.m; sourceTree = ""; }; @@ -2317,6 +2319,7 @@ 7BA61CB8247BC57B00C130A8 /* SentryCrashDefaultBinaryImageProvider.h */, 7BA61CBA247BC5D800C130A8 /* SentryCrashDefaultBinaryImageProvider.m */, 7BA61CAA247BA98100C130A8 /* SentryDebugImageProvider.h */, + 6273513E2CBD14970021D100 /* SentryDebugImageProvider+HybridSDKs.h */, 7BA61CAC247BAA0B00C130A8 /* SentryDebugImageProvider.m */, 7BA61CBE247CEA8100C130A8 /* SentryFormatter.h */, 7BA61CC7247D125400C130A8 /* SentryThreadInspector.h */, @@ -4140,6 +4143,7 @@ 7DC8310A2398283C0043DD9A /* SentryCrashIntegration.h in Headers */, 63FE718320DA4C1100CDBAE8 /* SentryCrashReportFixer.h in Headers */, 03F84D2027DD414C008FE43F /* SentryStackBounds.hpp in Headers */, + 6273513F2CBD14970021D100 /* SentryDebugImageProvider+HybridSDKs.h in Headers */, 8E5D38E3261D4B57000D363D /* SentryPerformanceTrackingIntegration.h in Headers */, 63FE70F920DA4C1000CDBAE8 /* SentryCrashMonitor.h in Headers */, D8BD2E6829361A0F00D96C6A /* PrivatesHeader.h in Headers */, diff --git a/SentryTestUtils/TestClient.swift b/SentryTestUtils/TestClient.swift index eefe25b7281..2629d0f2897 100644 --- a/SentryTestUtils/TestClient.swift +++ b/SentryTestUtils/TestClient.swift @@ -16,13 +16,14 @@ public class TestClient: SentryClient { // Without this override we get a fatal error: use of unimplemented initializer // see https://stackoverflow.com/questions/28187261/ios-swift-fatal-error-use-of-unimplemented-initializer-init - public override init(options: Options, transportAdapter: SentryTransportAdapter, fileManager: SentryFileManager, deleteOldEnvelopeItems: Bool, threadInspector: SentryThreadInspector, random: SentryRandomProtocol, locale: Locale, timezone: TimeZone) { + public override init(options: Options, transportAdapter: SentryTransportAdapter, fileManager: SentryFileManager, deleteOldEnvelopeItems: Bool, threadInspector: SentryThreadInspector, debugImageProvider: SentryDebugImageProvider, random: SentryRandomProtocol, locale: Locale, timezone: TimeZone) { super.init( options: options, transportAdapter: transportAdapter, fileManager: fileManager, deleteOldEnvelopeItems: false, threadInspector: threadInspector, + debugImageProvider: debugImageProvider, random: random, locale: locale, timezone: timezone diff --git a/Sources/Sentry/SentryBinaryImageCache.m b/Sources/Sentry/SentryBinaryImageCache.m index 962440780af..0163aa18bbb 100644 --- a/Sources/Sentry/SentryBinaryImageCache.m +++ b/Sources/Sentry/SentryBinaryImageCache.m @@ -1,5 +1,6 @@ #import "SentryBinaryImageCache.h" #import "SentryCrashBinaryImageCache.h" +#include "SentryCrashUUIDConversion.h" #import "SentryDependencyContainer.h" #import "SentryInAppLogic.h" #import "SentryLog.h" @@ -59,7 +60,9 @@ - (void)binaryImageAdded:(const SentryCrashBinaryImage *)image SentryBinaryImageInfo *newImage = [[SentryBinaryImageInfo alloc] init]; newImage.name = imageName; + newImage.UUID = [SentryBinaryImageCache convertUUID:image->uuid]; newImage.address = image->address; + newImage.vmAddress = image->vmAddress; newImage.size = image->size; @synchronized(self) { @@ -80,6 +83,17 @@ - (void)binaryImageAdded:(const SentryCrashBinaryImage *)image } } ++ (NSString *_Nullable)convertUUID:(const unsigned char *const)value +{ + if (nil == value) { + return nil; + } + + char uuidBuffer[37]; + sentrycrashdl_convertBinaryImageUUID(value, uuidBuffer); + return [[NSString alloc] initWithCString:uuidBuffer encoding:NSASCIIStringEncoding]; +} + - (void)binaryImageRemoved:(const SentryCrashBinaryImage *)image { if (image == NULL) { diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index 324adb378fd..a31ac3abffa 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -5,7 +5,7 @@ #import "SentryCrashDefaultMachineContextWrapper.h" #import "SentryCrashIntegration.h" #import "SentryCrashStackEntryMapper.h" -#import "SentryDebugImageProvider.h" +#import "SentryDebugImageProvider+HybridSDKs.h" #import "SentryDependencyContainer.h" #import "SentryDispatchQueueWrapper.h" #import "SentryDsn.h" @@ -128,6 +128,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options fileManager:fileManager deleteOldEnvelopeItems:deleteOldEnvelopeItems threadInspector:threadInspector + debugImageProvider:[SentryDependencyContainer sharedInstance].debugImageProvider random:[SentryDependencyContainer sharedInstance].random locale:[NSLocale autoupdatingCurrentLocale] timezone:[NSCalendar autoupdatingCurrentCalendar].timeZone]; @@ -138,6 +139,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options fileManager:(SentryFileManager *)fileManager deleteOldEnvelopeItems:(BOOL)deleteOldEnvelopeItems threadInspector:(SentryThreadInspector *)threadInspector + debugImageProvider:(SentryDebugImageProvider *)debugImageProvider random:(id)random locale:(NSLocale *)locale timezone:(NSTimeZone *)timezone @@ -149,7 +151,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options self.fileManager = fileManager; self.threadInspector = threadInspector; self.random = random; - self.debugImageProvider = [SentryDependencyContainer sharedInstance].debugImageProvider; + self.debugImageProvider = debugImageProvider; self.locale = locale; self.timezone = timezone; self.attachmentProcessors = [[NSMutableArray alloc] init]; @@ -688,8 +690,8 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event BOOL debugMetaNotAttached = !(nil != event.debugMeta && event.debugMeta.count > 0); if (!isCrashEvent && shouldAttachStacktrace && debugMetaNotAttached && event.threads != nil) { - event.debugMeta = [self.debugImageProvider getDebugImagesForThreads:event.threads - isCrash:NO]; + event.debugMeta = + [self.debugImageProvider getDebugImagesFromCacheForThreads:event.threads]; } } diff --git a/Sources/Sentry/SentryDebugImageProvider.m b/Sources/Sentry/SentryDebugImageProvider.m index 011697b7b85..f12f908629c 100644 --- a/Sources/Sentry/SentryDebugImageProvider.m +++ b/Sources/Sentry/SentryDebugImageProvider.m @@ -1,8 +1,10 @@ #import "SentryDebugImageProvider.h" +#import "SentryBinaryImageCache.h" #import "SentryCrashDefaultBinaryImageProvider.h" #import "SentryCrashDynamicLinker.h" #import "SentryCrashUUIDConversion.h" #import "SentryDebugMeta.h" +#import "SentryDependencyContainer.h" #import "SentryFormatter.h" #import "SentryFrame.h" #import "SentryInternalDefines.h" @@ -14,6 +16,8 @@ @interface SentryDebugImageProvider () @property (nonatomic, strong) id binaryImageProvider; +@property (nonatomic, strong) SentryBinaryImageCache *binaryImageCache; + @end @implementation SentryDebugImageProvider @@ -23,16 +27,20 @@ - (instancetype)init SentryCrashDefaultBinaryImageProvider *provider = [[SentryCrashDefaultBinaryImageProvider alloc] init]; - self = [self initWithBinaryImageProvider:provider]; + self = [self + initWithBinaryImageProvider:provider + binaryImageCache:SentryDependencyContainer.sharedInstance.binaryImageCache]; return self; } /** Internal constructor for testing */ - (instancetype)initWithBinaryImageProvider:(id)binaryImageProvider + binaryImageCache:(SentryBinaryImageCache *)binaryImageCache { if (self = [super init]) { self.binaryImageProvider = binaryImageProvider; + self.binaryImageCache = binaryImageCache; } return self; } @@ -95,6 +103,55 @@ - (void)extractDebugImageAddressFromFrames:(NSArray *)frames return [self getDebugImagesForAddresses:imageAddresses isCrash:isCrash]; } +- (NSArray *)getDebugImagesFromCacheForFrames:(NSArray *)frames +{ + NSMutableSet *imageAddresses = [[NSMutableSet alloc] init]; + [self extractDebugImageAddressFromFrames:frames intoSet:imageAddresses]; + + return [self getDebugImagesForImageAddressesFromCache:imageAddresses]; +} + +- (NSArray *)getDebugImagesFromCacheForThreads:(NSArray *)threads +{ + NSMutableSet *imageAddresses = [[NSMutableSet alloc] init]; + + for (SentryThread *thread in threads) { + [self extractDebugImageAddressFromFrames:thread.stacktrace.frames intoSet:imageAddresses]; + } + + return [self getDebugImagesForImageAddressesFromCache:imageAddresses]; +} + +- (NSArray *)getDebugImagesForImageAddressesFromCache: + (NSSet *)imageAddresses +{ + NSMutableArray *result = [NSMutableArray array]; + + for (NSString *imageAddress in imageAddresses) { + const uint64_t imageAddressAsUInt64 = sentry_UInt64ForHexAddress(imageAddress); + SentryBinaryImageInfo *info = [self.binaryImageCache imageByAddress:imageAddressAsUInt64]; + if (info == nil) { + continue; + } + + SentryDebugMeta *debugMeta = [[SentryDebugMeta alloc] init]; + debugMeta.debugID = info.UUID; + debugMeta.type = SentryDebugImageType; + + if (info.vmAddress > 0) { + debugMeta.imageVmAddress = sentry_formatHexAddressUInt64(info.vmAddress); + } + + debugMeta.imageAddress = sentry_formatHexAddressUInt64(info.address); + debugMeta.imageSize = @(info.size); + debugMeta.codeFile = info.name; + + [result addObject:debugMeta]; + } + + return result; +} + - (NSArray *)getDebugImages { // maintains previous behavior for the same method call by also trying to gather crash info @@ -118,7 +175,7 @@ - (void)extractDebugImageAddressFromFrames:(NSArray *)frames - (SentryDebugMeta *)fillDebugMetaFrom:(SentryCrashBinaryImage)image { SentryDebugMeta *debugMeta = [[SentryDebugMeta alloc] init]; - debugMeta.debugID = [SentryDebugImageProvider convertUUID:image.uuid]; + debugMeta.debugID = [SentryBinaryImageCache convertUUID:image.uuid]; debugMeta.type = SentryDebugImageType; if (image.vmAddress > 0) { @@ -136,15 +193,4 @@ - (SentryDebugMeta *)fillDebugMetaFrom:(SentryCrashBinaryImage)image return debugMeta; } -+ (NSString *_Nullable)convertUUID:(const unsigned char *const)value -{ - if (nil == value) { - return nil; - } - - char uuidBuffer[37]; - sentrycrashdl_convertBinaryImageUUID(value, uuidBuffer); - return [[NSString alloc] initWithCString:uuidBuffer encoding:NSASCIIStringEncoding]; -} - @end diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index 19c95805841..2e24197e1a0 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -93,7 +93,6 @@ - (instancetype)init _random = [[SentryRandom alloc] init]; _threadWrapper = [[SentryThreadWrapper alloc] init]; _binaryImageCache = [[SentryBinaryImageCache alloc] init]; - _debugImageProvider = [[SentryDebugImageProvider alloc] init]; _dateProvider = [[SentryCurrentDateProvider alloc] init]; } return self; @@ -183,6 +182,16 @@ - (SentryThreadInspector *)threadInspector SENTRY_DISABLE_THREAD_SANITIZER( return _threadInspector; } +- (SentryDebugImageProvider *)debugImageProvider +{ + @synchronized(sentryDependencyContainerLock) { + if (_debugImageProvider == nil) { + _debugImageProvider = [[SentryDebugImageProvider alloc] init]; + } + return _debugImageProvider; + } +} + - (SentryExtraContextProvider *)extraContextProvider SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") { diff --git a/Sources/Sentry/SentryThreadInspector.m b/Sources/Sentry/SentryThreadInspector.m index 615b7706139..f92045f3359 100644 --- a/Sources/Sentry/SentryThreadInspector.m +++ b/Sources/Sentry/SentryThreadInspector.m @@ -1,5 +1,4 @@ #import "SentryThreadInspector.h" -#import "SentryBinaryImageCache.h" #import "SentryCrashDefaultMachineContextWrapper.h" #import "SentryCrashStackCursor.h" #include "SentryCrashStackCursor_MachineContext.h" diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 2001a21da35..427590ceb42 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -1,6 +1,6 @@ #import "PrivateSentrySDKOnly.h" #import "SentryClient.h" -#import "SentryDebugImageProvider.h" +#import "SentryDebugImageProvider+HybridSDKs.h" #import "SentryDependencyContainer.h" #import "SentryEvent+Private.h" #import "SentryFileManager.h" @@ -717,8 +717,8 @@ - (SentryTransaction *)toTransaction if (framesOfAllSpans.count > 0) { SentryDebugImageProvider *debugImageProvider = SentryDependencyContainer.sharedInstance.debugImageProvider; - transaction.debugMeta = [debugImageProvider getDebugImagesForFrames:framesOfAllSpans - isCrash:NO]; + transaction.debugMeta = + [debugImageProvider getDebugImagesFromCacheForFrames:framesOfAllSpans]; } #if SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/include/HybridPublic/SentryBinaryImageCache.h b/Sources/Sentry/include/HybridPublic/SentryBinaryImageCache.h index 46125e2185f..fe743f265ed 100644 --- a/Sources/Sentry/include/HybridPublic/SentryBinaryImageCache.h +++ b/Sources/Sentry/include/HybridPublic/SentryBinaryImageCache.h @@ -4,8 +4,11 @@ NS_ASSUME_NONNULL_BEGIN @interface SentryBinaryImageInfo : NSObject @property (nonatomic, strong) NSString *name; +@property (nonatomic, copy) NSString *UUID; +@property (nonatomic) uint64_t vmAddress; @property (nonatomic) uint64_t address; @property (nonatomic) uint64_t size; + @end /** @@ -23,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)pathForInAppInclude:(NSString *)inAppInclude; ++ (NSString *_Nullable)convertUUID:(const unsigned char *const)value; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/HybridPublic/SentryDebugImageProvider+HybridSDKs.h b/Sources/Sentry/include/HybridPublic/SentryDebugImageProvider+HybridSDKs.h new file mode 100644 index 00000000000..eab368e8314 --- /dev/null +++ b/Sources/Sentry/include/HybridPublic/SentryDebugImageProvider+HybridSDKs.h @@ -0,0 +1,29 @@ +#import "SentryDebugImageProvider.h" + +@class SentryDebugMeta; +@class SentryThread; +@class SentryFrame; + +NS_ASSUME_NONNULL_BEGIN + +@interface SentryDebugImageProvider () + +/** + * Returns a list of debug images that are being referenced by the given frames. + * This function uses the @c SentryBinaryImageCache which is significantly faster than @c + * SentryCrashDefaultBinaryImageProvider for retrieving binary image information. + */ +- (NSArray *)getDebugImagesFromCacheForFrames:(NSArray *)frames + NS_SWIFT_NAME(getDebugImagesFromCacheForFrames(frames:)); + +/** + * Returns a list of debug images that are being referenced in the given threads. + * This function uses the @c SentryBinaryImageCache which is significantly faster than @c + * SentryCrashDefaultBinaryImageProvider for retrieving binary image information. + */ +- (NSArray *)getDebugImagesFromCacheForThreads:(NSArray *)threads + NS_SWIFT_NAME(getDebugImagesFromCacheForThreads(threads:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/HybridPublic/SentryFormatter.h b/Sources/Sentry/include/HybridPublic/SentryFormatter.h index 362a22ed465..d31f41b4aca 100644 --- a/Sources/Sentry/include/HybridPublic/SentryFormatter.h +++ b/Sources/Sentry/include/HybridPublic/SentryFormatter.h @@ -41,3 +41,16 @@ sentry_formatHexAddressUInt64(uint64_t value) { return sentry_snprintfHexAddress(value); } + +static inline uint64_t +sentry_UInt64ForHexAddress(NSString *hexString) +{ + uint64_t value = 0; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + + if ([scanner scanHexLongLong:&value]) { + return value; + } else { + return 0; + } +} diff --git a/Tests/SentryTests/Helper/SentryFormatterTests.swift b/Tests/SentryTests/Helper/SentryFormatterTests.swift index ad181aa99e7..ce0d0e8cc26 100644 --- a/Tests/SentryTests/Helper/SentryFormatterTests.swift +++ b/Tests/SentryTests/Helper/SentryFormatterTests.swift @@ -21,4 +21,59 @@ final class SentryFormatterTests: XCTestCase { XCTAssertEqual(sentry_stringForUInt64(UInt64(input)), expected) } } + + func testParseHexAddress_Zero_ReturnsZero() { + XCTAssertEqual(UInt64(0), sentry_UInt64ForHexAddress("0")) + } + + func testParseHexAddress_Zero0x_ReturnsZero() { + XCTAssertEqual(UInt64(0), sentry_UInt64ForHexAddress("0x0")) + } + + func testParseHexAddress_One_ReturnsOne() { + XCTAssertEqual(UInt64(1), sentry_UInt64ForHexAddress("0x1")) + } + + func testParseHexAddress_F_Returns15() { + XCTAssertEqual(UInt64(15), sentry_UInt64ForHexAddress("0xF")) + } + + func testParseHexAddress_UInt64Max_ReturnsUInt64Max() { + let uIntMaxHexAddress = "0x18446744073709551615" + XCTAssertEqual(UInt64.max, sentry_UInt64ForHexAddress(uIntMaxHexAddress)) + } + + func testParseHexAddress_UInt64MaxPlusOne_ReturnsUInt64Max() { + let uIntMaxHexAddressPlusOne = "0x18446744073709551616" + XCTAssertEqual(UInt64.max, sentry_UInt64ForHexAddress(uIntMaxHexAddressPlusOne)) + } + + func testParseHexAddress_Overflow_ReturnsUInt64Max() { + let uIntMaxVastOverflow = "0xFFFFFFFFFFFFFFFFFFFF" + XCTAssertEqual(UInt64.max, sentry_UInt64ForHexAddress(uIntMaxVastOverflow)) + } + + func testParseHexAddress_G_Returns0() { + XCTAssertEqual(UInt64(0), sentry_UInt64ForHexAddress("0xG")) + } + + func testParseHexAddress_Garbage_Returns0() { + XCTAssertEqual(UInt64(0), sentry_UInt64ForHexAddress("hello")) + } + + func testParseHexAddress_MinusOne_Returns0() { + XCTAssertEqual(UInt64(0), sentry_UInt64ForHexAddress("-1")) + } + + func testParseHexAddress_WithLeading0x_ReturnsCorrectValue() { + XCTAssertEqual(UInt64(1_234_345), sentry_UInt64ForHexAddress("0x000000000012d5a9") ) + } + + func testParseHexAddress_WithLeading0X_ReturnsCorrectValue() { + XCTAssertEqual(UInt64(1_234_345), sentry_UInt64ForHexAddress("0X000000000012d5a9") ) + } + + func testParseHexAddress_WithoutLeading0x_ReturnsCorrectValue() { + XCTAssertEqual(UInt64(1_234_345), sentry_UInt64ForHexAddress("000000000012d5a9")) + } } diff --git a/Tests/SentryTests/Helper/TestDebugImageProvider.swift b/Tests/SentryTests/Helper/TestDebugImageProvider.swift index ed30b385c21..90d295d1b46 100644 --- a/Tests/SentryTests/Helper/TestDebugImageProvider.swift +++ b/Tests/SentryTests/Helper/TestDebugImageProvider.swift @@ -1,4 +1,5 @@ import Foundation +import SentryTestUtils class TestDebugImageProvider: SentryDebugImageProvider { var debugImages: [DebugMeta]? @@ -10,4 +11,17 @@ class TestDebugImageProvider: SentryDebugImageProvider { override func getDebugImagesCrashed(_ isCrash: Bool) -> [DebugMeta] { debugImages ?? super.getDebugImagesCrashed(isCrash) } + + var getDebugImagesFromCacheForFramesInvocations = Invocations() + override func getDebugImagesFromCacheForFrames(frames: [Frame]) -> [DebugMeta] { + getDebugImagesFromCacheForFramesInvocations.record(Void()) + + return debugImages ?? super.getDebugImagesFromCacheForFrames(frames: frames) + } + + var getDebugImagesFromCacheForThreadsInvocations = Invocations() + override func getDebugImagesFromCacheForThreads(threads: [SentryThread]) -> [DebugMeta] { + getDebugImagesFromCacheForThreadsInvocations.record(Void()) + return debugImages ?? super.getDebugImagesFromCacheForThreads(threads: threads) + } } diff --git a/Tests/SentryTests/SentryBinaryImageCacheTests.swift b/Tests/SentryTests/SentryBinaryImageCacheTests.swift index 48f88b6a9a5..49717313875 100644 --- a/Tests/SentryTests/SentryBinaryImageCacheTests.swift +++ b/Tests/SentryTests/SentryBinaryImageCacheTests.swift @@ -17,18 +17,22 @@ class SentryBinaryImageCacheTests: XCTestCase { } func testBinaryImageAdded() { - var binaryImage0 = createCrashBinaryImage(0) - var binaryImage1 = createCrashBinaryImage(100) - var binaryImage2 = createCrashBinaryImage(200) - var binaryImage3 = createCrashBinaryImage(400) + var binaryImage0 = createCrashBinaryImage(0, vmAddress: 0) + var binaryImage1 = createCrashBinaryImage(100, vmAddress: 100) + var binaryImage2 = createCrashBinaryImage(200, vmAddress: 200) + var binaryImage3 = createCrashBinaryImage(400, vmAddress: 400) sut.binaryImageAdded(&binaryImage1) XCTAssertEqual(sut.cache.count, 1) XCTAssertEqual(sut.cache.first?.name, "Expected Name at 100") + XCTAssertEqual(sut.cache.first?.uuid, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(sut.cache.first?.vmAddress, 100) sut.binaryImageAdded(&binaryImage3) XCTAssertEqual(sut.cache.count, 2) XCTAssertEqual(sut.cache.last?.name, "Expected Name at 400") + XCTAssertEqual(sut.cache.last?.uuid, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(sut.cache.last?.vmAddress, 400) sut.binaryImageAdded(&binaryImage2) XCTAssertEqual(sut.cache.count, 3) @@ -201,16 +205,21 @@ class SentryBinaryImageCacheTests: XCTestCase { waitForExpectations(timeout: 1) } - func createCrashBinaryImage(_ address: UInt) -> SentryCrashBinaryImage { + func createCrashBinaryImage(_ address: UInt, vmAddress: UInt64 = 0) -> SentryCrashBinaryImage { let name = "Expected Name at \(address)" let nameCString = name.withCString { strdup($0) } + + var uuidPointer = UnsafeMutablePointer(nil) + let uuidAsCharArray: [UInt8] = [132, 186, 235, 218, 173, 26, 51, 244, 179, 93, 138, 69, 245, 218, 243, 34] + uuidPointer = UnsafeMutablePointer.allocate(capacity: uuidAsCharArray.count) + uuidPointer?.initialize(from: uuidAsCharArray, count: uuidAsCharArray.count) let binaryImage = SentryCrashBinaryImage( address: UInt64(address), - vmAddress: 0, + vmAddress: vmAddress, size: 100, name: nameCString, - uuid: nil, + uuid: uuidPointer, cpuType: 1, cpuSubType: 1, majorVersion: 1, diff --git a/Tests/SentryTests/SentryClient+TestInit.h b/Tests/SentryTests/SentryClient+TestInit.h index 53f676931d3..4d5fe2c6e1a 100644 --- a/Tests/SentryTests/SentryClient+TestInit.h +++ b/Tests/SentryTests/SentryClient+TestInit.h @@ -6,6 +6,7 @@ @class SentryDispatchQueueWrapper; @class SentryThreadInspector; @class SentryTransportAdapter; +@class SentryDebugImageProvider; NS_ASSUME_NONNULL_BEGIN @@ -29,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN fileManager:(SentryFileManager *)fileManager deleteOldEnvelopeItems:(BOOL)deleteOldEnvelopeItems threadInspector:(SentryThreadInspector *)threadInspector + debugImageProvider:(SentryDebugImageProvider *)debugImageProvider random:(id)random locale:(NSLocale *)locale timezone:(NSTimeZone *)timezone; diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index f16a845f196..1eb478b3ee5 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -13,7 +13,7 @@ class SentryClientTest: XCTestCase { let transport: TestTransport let transportAdapter: TestTransportAdapter - let debugImageBuilder = SentryDebugImageProvider() + let debugImageProvider = TestDebugImageProvider() let threadInspector = TestThreadInspector.instance let session: SentrySession @@ -63,6 +63,8 @@ class SentryClientTest: XCTestCase { crashWrapper.internalFreeMemorySize = 123_456 crashWrapper.internalAppMemorySize = 234_567 + + debugImageProvider.debugImages = [TestData.debugImage] #if os(iOS) || targetEnvironment(macCatalyst) SentryDependencyContainer.sharedInstance().uiDeviceWrapper = deviceWrapper @@ -87,6 +89,7 @@ class SentryClientTest: XCTestCase { fileManager: fileManager, deleteOldEnvelopeItems: false, threadInspector: threadInspector, + debugImageProvider: debugImageProvider, random: random, locale: locale, timezone: timezone @@ -525,6 +528,18 @@ class SentryClientTest: XCTestCase { try assertValidErrorEvent(actual, error) } + func testCaptureEvent_RetrievesDebugMetaFromCache() throws { + let event = Event(level: SentryLevel.warning) + + let eventId = fixture.getSut().capture(event: event) + + eventId.assertIsNotEmpty() + + let actual = try lastSentEvent() + XCTAssertNotNil(actual.debugMeta) + XCTAssertEqual(1, fixture.debugImageProvider.getDebugImagesFromCacheForThreadsInvocations.count, "Client must retrieve debug images from cache.") + } + func testCaptureErrorWithEnum() throws { let eventId = fixture.getSut().capture(error: TestError.invalidTest) @@ -2004,8 +2019,9 @@ private extension SentryClientTest { } private func assertValidDebugMeta(actual: [DebugMeta]?, forThreads threads: [SentryThread]?) { - let debugMetas = fixture.debugImageBuilder.getDebugImages(for: threads ?? [], isCrash: false) + let debugMetas = fixture.debugImageProvider.getDebugImagesFromCacheForThreads(threads: threads ?? []) + XCTAssertEqual(debugMetas.count, actual?.count) XCTAssertEqual(debugMetas, actual ?? []) } diff --git a/Tests/SentryTests/SentryCrash/SentryDebugImageProvider+TestInit.h b/Tests/SentryTests/SentryCrash/SentryDebugImageProvider+TestInit.h index fbba34ef00a..c91d50a54f7 100644 --- a/Tests/SentryTests/SentryCrash/SentryDebugImageProvider+TestInit.h +++ b/Tests/SentryTests/SentryCrash/SentryDebugImageProvider+TestInit.h @@ -2,8 +2,12 @@ NS_ASSUME_NONNULL_BEGIN +@class SentryBinaryImageCache; + @interface SentryDebugImageProvider (TestInit) -- (instancetype)initWithBinaryImageProvider:(id)binaryImageProvider; + +- (instancetype)initWithBinaryImageProvider:(id)binaryImageProvider + binaryImageCache:(SentryBinaryImageCache *)binaryImageCache; @end diff --git a/Tests/SentryTests/SentryCrash/SentryDebugImageProviderTests.swift b/Tests/SentryTests/SentryCrash/SentryDebugImageProviderTests.swift index 14d578f2d09..4b729aca036 100644 --- a/Tests/SentryTests/SentryCrash/SentryDebugImageProviderTests.swift +++ b/Tests/SentryTests/SentryCrash/SentryDebugImageProviderTests.swift @@ -8,11 +8,25 @@ import XCTest class SentryDebugImageProviderTests: XCTestCase { private class Fixture { + + let cache: SentryBinaryImageCache + + init() { + cache = SentryBinaryImageCache() + } + func getSut(images: [SentryCrashBinaryImage] = []) -> SentryDebugImageProvider { let imageProvider = TestSentryCrashBinaryImageProvider() imageProvider.imageCount = images.count imageProvider.binaryImage = images - return SentryDebugImageProvider(binaryImageProvider: imageProvider) + + cache.start() + for image in images { + var i = image + cache.binaryImageAdded(&i) + } + + return SentryDebugImageProvider(binaryImageProvider: imageProvider, binaryImageCache: cache) } func getTestImages() -> [SentryCrashBinaryImage] { @@ -55,6 +69,11 @@ class SentryDebugImageProviderTests: XCTestCase { private let fixture = Fixture() + override func tearDown() { + fixture.cache.stop() + super.tearDown() + } + func testThreeImages() throws { let sut = fixture.getSut(images: fixture.getTestImages()) let actual = sut.getDebugImagesCrashed(false) @@ -156,6 +175,103 @@ class SentryDebugImageProviderTests: XCTestCase { XCTAssertEqual(actual.count, 0) } + + func testGetDebugImagesFromCacheForThreads() throws { + let sut = fixture.getSut(images: fixture.getTestImages()) + + let frame1 = Sentry.Frame() + frame1.imageAddress = "0x0000000105705000" + + let frame2 = Sentry.Frame() + frame2.imageAddress = "0x00000001410b1a00" + + let thread1 = SentryThread(threadId: NSNumber(value: 1)) + thread1.stacktrace = SentryStacktrace(frames: [frame1, frame2], registers: [:]) + + let thread2 = SentryThread(threadId: NSNumber(value: 2)) + thread2.stacktrace = SentryStacktrace(frames: [frame2], registers: [:]) + + let actual = sut.getDebugImagesFromCacheForThreads(threads: [thread1, thread2]) + + XCTAssertEqual(actual.count, 2) + let image1 = try XCTUnwrap(actual.first) + + XCTAssertEqual(image1.debugID, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(image1.type, SentryDebugImageType) + XCTAssertEqual(image1.imageVmAddress, "0x0000daf262294000") + XCTAssertEqual(image1.imageAddress, "0x00000001410b1a00") + XCTAssertEqual(image1.imageSize, 1_352_256) + XCTAssertEqual(image1.codeFile, "UIKit") + + let image2 = try XCTUnwrap(actual.last) + + XCTAssertEqual(image2.debugID, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(image2.type, SentryDebugImageType) + XCTAssertEqual(image2.imageVmAddress, "0x00007fff51af0000") + XCTAssertEqual(image2.imageAddress, "0x0000000105705000") + XCTAssertEqual(image2.imageSize, 352_256) + XCTAssertEqual(image2.codeFile, "dyld_sim") + } + + func testGetDebugImagesFromCacheForFrames() throws { + let sut = fixture.getSut(images: fixture.getTestImages()) + + let frame1 = Sentry.Frame() + frame1.imageAddress = "0x0000000105705000" + + let frame2 = Sentry.Frame() + frame2.imageAddress = "0x00000001410b1a00" + + let actual = sut.getDebugImagesFromCacheForFrames(frames: [frame1, frame2]) + + XCTAssertEqual(actual.count, 2) + let image1 = try XCTUnwrap(actual.first) + + XCTAssertEqual(image1.debugID, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(image1.type, SentryDebugImageType) + XCTAssertEqual(image1.imageVmAddress, "0x0000daf262294000") + XCTAssertEqual(image1.imageAddress, "0x00000001410b1a00") + XCTAssertEqual(image1.imageSize, 1_352_256) + XCTAssertEqual(image1.codeFile, "UIKit") + + let image2 = try XCTUnwrap(actual.last) + + XCTAssertEqual(image2.debugID, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(image2.type, SentryDebugImageType) + XCTAssertEqual(image2.imageVmAddress, "0x00007fff51af0000") + XCTAssertEqual(image2.imageAddress, "0x0000000105705000") + XCTAssertEqual(image2.imageSize, 352_256) + XCTAssertEqual(image2.codeFile, "dyld_sim") + } + + func testGetDebugImagesFromCacheForFrames_GarbageImageAddress() throws { + let sut = fixture.getSut(images: fixture.getTestImages()) + + let frame1 = Sentry.Frame() + frame1.imageAddress = "0x0000000105705000" + + let frame2 = Sentry.Frame() + frame2.imageAddress = "garbage" + + let actual = sut.getDebugImagesFromCacheForFrames(frames: [frame1, frame2]) + + XCTAssertEqual(actual.count, 1) + let image = try XCTUnwrap(actual.first) + XCTAssertEqual(image.debugID, "84BAEBDA-AD1A-33F4-B35D-8A45F5DAF322") + XCTAssertEqual(image.type, SentryDebugImageType) + XCTAssertEqual(image.imageVmAddress, "0x00007fff51af0000") + XCTAssertEqual(image.imageAddress, "0x0000000105705000") + XCTAssertEqual(image.imageSize, 352_256) + XCTAssertEqual(image.codeFile, "dyld_sim") + } + + func testGetDebugImagesFromCacheForThreads_EmptyArray() throws { + let sut = fixture.getSut(images: fixture.getTestImages()) + + let actual = sut.getDebugImagesFromCacheForThreads(threads: []) + + XCTAssertEqual(actual.count, 0) + } private static func createSentryCrashBinaryImage( address: UInt64 = 0, diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index e1cf2086be3..edef509d578 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -96,8 +96,8 @@ #import "SentryDataCategoryMapper.h" #import "SentryDateUtil.h" #import "SentryDateUtils.h" +#import "SentryDebugImageProvider+HybridSDKs.h" #import "SentryDebugImageProvider+TestInit.h" -#import "SentryDebugImageProvider.h" #import "SentryDebugMeta.h" #import "SentryDefaultObjCRuntimeWrapper.h" #import "SentryDefaultRateLimits.h" diff --git a/Tests/SentryTests/Transaction/SentryTracerTests.swift b/Tests/SentryTests/Transaction/SentryTracerTests.swift index 207d08043ac..105c47712f1 100644 --- a/Tests/SentryTests/Transaction/SentryTracerTests.swift +++ b/Tests/SentryTests/Transaction/SentryTracerTests.swift @@ -374,6 +374,7 @@ class SentryTracerTests: XCTestCase { XCTAssertEqual(transaction?.debugMeta?.count ?? 0, 1) XCTAssertEqual(transaction?.debugMeta?.first, TestData.debugImage) + XCTAssertEqual(1, debugImageProvider.getDebugImagesFromCacheForFramesInvocations.count, "Tracer must retrieve debug images from cache.") } func testDeadlineTimer_OnlyForAutoTransactions() {