From ed11f35abeeeb517047a12c20c2edbfcea20fe0f Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Fri, 15 Jul 2022 16:19:41 +0200 Subject: [PATCH 01/16] fix: Remove Sentry keys from cached HTTP request headers (#1975) When an HTTP request is happening without a span, it used to just reuse the existing headers, which can include an old and unrelated traceId. This caused weird behavior where the request would seem to happen from a totally different part of the app. --- CHANGELOG.md | 6 ++++++ Sources/Sentry/SentryNetworkTracker.m | 6 +++++- .../Performance/Network/SentryNetworkTrackerTests.swift | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b8aae73ad..21ec493d326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Remove Sentry keys from cached HTTP request headers (#1975) + ## 7.21.0 ### Features diff --git a/Sources/Sentry/SentryNetworkTracker.m b/Sources/Sentry/SentryNetworkTracker.m index 471e6a9c261..482520f5d38 100644 --- a/Sources/Sentry/SentryNetworkTracker.m +++ b/Sources/Sentry/SentryNetworkTracker.m @@ -284,7 +284,11 @@ - (nullable NSDictionary *)addTraceHeader:(nullable NSDictionary *)headers id span = SentrySDK.currentHub.scope.span; if (span == nil) { - return headers; + // Remove the Sentry keys from the cached headers (cached by NSURLSession itself), + // because it could contain a completely unrelated trace id from a previous request. + NSMutableDictionary *existingHeaders = headers.mutableCopy; + [existingHeaders removeObjectsForKeys:@[ SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER ]]; + return [existingHeaders copy]; } NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:headers]; diff --git a/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift index f06e2593c5c..5f4d75ed22c 100644 --- a/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift @@ -456,6 +456,14 @@ class SentryNetworkTrackerTests: XCTestCase { XCTAssertNotNil(headers?["baggage"]) XCTAssertNotNil(headers?["sentry-trace"]) } + + func test_RemoveExistingTraceHeader_WhenNoSpan() { + let sut = fixture.getSut() + let headers = sut.addTraceHeader(["a": "a", "baggage": "baggage", "sentry-trace": "sentry-trace"]) + XCTAssertEqual(headers?.count, 1) + XCTAssertNil(headers?["baggage"]) + XCTAssertNil(headers?["sentry-trace"]) + } func test_AddTraceHeader_NoTransaction() { let sut = fixture.getSut() From 9640294b631b5d0d3aaf55cc46144e6344fc080d Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Mon, 18 Jul 2022 09:16:23 +0200 Subject: [PATCH 02/16] fix: read free_memory when the event is captured, not only at SDK startup (#1962) When we capture an event, we now enhance the data with the actual free memory at that time. Until now we only used the value from when the SDK was initiated. --- CHANGELOG.md | 4 +++ Sources/Sentry/SentryClient.m | 33 ++++++++++++++++--- Sources/Sentry/SentryCrashWrapper.m | 6 ++++ Sources/Sentry/include/SentryCrashWrapper.h | 2 ++ .../Monitors/SentryCrashMonitor_System.h | 2 ++ .../Monitors/SentryCrashMonitor_System.m | 6 ++++ Tests/SentryTests/SentryClient+TestInit.h | 3 +- Tests/SentryTests/SentryClientTests.swift | 17 +++++++++- .../SentryCrash/TestSentryCrashWrapper.h | 2 ++ .../SentryCrash/TestSentryCrashWrapper.m | 6 ++++ 10 files changed, 75 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ec493d326..d022abecfee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Read free_memory when the event is captured, not only at SDK startup (#1962) + ### Fixes - Remove Sentry keys from cached HTTP request headers (#1975) diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index a43164aab0d..9b0cebff1e5 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -5,6 +5,7 @@ #import "SentryCrashDefaultMachineContextWrapper.h" #import "SentryCrashIntegration.h" #import "SentryCrashStackEntryMapper.h" +#import "SentryCrashWrapper.h" #import "SentryDebugImageProvider.h" #import "SentryDefaultCurrentDateProvider.h" #import "SentryDependencyContainer.h" @@ -55,6 +56,7 @@ @property (nonatomic, strong) SentryThreadInspector *threadInspector; @property (nonatomic, strong) id random; @property (nonatomic, weak) id attachmentProcessor; +@property (nonatomic, strong) SentryCrashWrapper *crashWrapper; @end @@ -101,6 +103,8 @@ - (_Nullable instancetype)initWithOptions:(SentryOptions *)options options:options]; self.random = [SentryDependencyContainer sharedInstance].random; + + self.crashWrapper = [SentryCrashWrapper sharedInstance]; } return self; } @@ -111,6 +115,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options fileManager:(SentryFileManager *)fileManager threadInspector:(SentryThreadInspector *)threadInspector random:(id)random + crashWrapper:(SentryCrashWrapper *)crashWrapper { self = [self initWithOptions:options]; @@ -118,6 +123,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options self.fileManager = fileManager; self.threadInspector = threadInspector; self.random = random; + self.crashWrapper = crashWrapper; return self; } @@ -487,10 +493,13 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event event = [scope applyToEvent:event maxBreadcrumb:self.options.maxBreadcrumbs]; - // Remove free_memory if OOM as free_memory stems from the current run and not of the time of - // the OOM. if ([self isOOM:event isCrashEvent:isCrashEvent]) { + // Remove free_memory if OOM as free_memory stems from the current run and not of + // the time of the OOM. [self removeFreeMemoryFromDeviceContext:event]; + } else { + // Store the actual free memory at the time of this event + [self storeFreeMemoryToDeviceContext:event]; } // With scope applied, before running callbacks run: @@ -638,7 +647,24 @@ - (BOOL)isOOM:(SentryEvent *)event isCrashEvent:(BOOL)isCrashEvent [exception.mechanism.type isEqualToString:SentryOutOfMemoryMechanismType]; } +- (void)storeFreeMemoryToDeviceContext:(SentryEvent *)event +{ + [self modifyDeviceContext:event + block:^(NSMutableDictionary *device) { + device[SentryDeviceContextFreeMemoryKey] = + @(self.crashWrapper.freeMemory); + }]; +} + - (void)removeFreeMemoryFromDeviceContext:(SentryEvent *)event +{ + [self modifyDeviceContext:event + block:^(NSMutableDictionary *device) { + [device removeObjectForKey:SentryDeviceContextFreeMemoryKey]; + }]; +} + +- (void)modifyDeviceContext:(SentryEvent *)event block:(void (^)(NSMutableDictionary *))block { if (nil == event.context || event.context.count == 0 || nil == event.context[@"device"]) { return; @@ -647,9 +673,8 @@ - (void)removeFreeMemoryFromDeviceContext:(SentryEvent *)event NSMutableDictionary *context = [[NSMutableDictionary alloc] initWithDictionary:event.context]; NSMutableDictionary *device = [[NSMutableDictionary alloc] initWithDictionary:context[@"device"]]; - [device removeObjectForKey:SentryDeviceContextFreeMemoryKey]; + block(device); context[@"device"] = device; - event.context = context; } diff --git a/Sources/Sentry/SentryCrashWrapper.m b/Sources/Sentry/SentryCrashWrapper.m index 0a91a6f336a..075e7d67c6c 100644 --- a/Sources/Sentry/SentryCrashWrapper.m +++ b/Sources/Sentry/SentryCrashWrapper.m @@ -1,6 +1,7 @@ #import "SentryCrashWrapper.h" #import "SentryCrash.h" #import "SentryCrashMonitor_AppState.h" +#import "SentryCrashMonitor_System.h" #import "SentryHook.h" #import #import @@ -69,6 +70,11 @@ - (NSDictionary *)systemInfo return sharedInfo; } +- (NSInteger)freeMemory +{ + return sentrycrashcm_system_freememory(); +} + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryCrashWrapper.h b/Sources/Sentry/include/SentryCrashWrapper.h index 2dbbf601a37..a728ecaf7c6 100644 --- a/Sources/Sentry/include/SentryCrashWrapper.h +++ b/Sources/Sentry/include/SentryCrashWrapper.h @@ -30,6 +30,8 @@ SENTRY_NO_INIT - (NSDictionary *)systemInfo; +- (NSInteger)freeMemory; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.h b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.h index b9a870f42bb..5c9c657cff8 100644 --- a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.h +++ b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.h @@ -39,6 +39,8 @@ SentryCrashMonitorAPI *sentrycrashcm_system_getAPI(void); bool sentrycrash_isSimulatorBuild(void); +uint64_t sentrycrashcm_system_freememory(void); + #ifdef __cplusplus } #endif diff --git a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m index c4d2f202bf6..a623bc1b38a 100644 --- a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m +++ b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m @@ -199,6 +199,12 @@ return 0; } +uint64_t +sentrycrashcm_system_freememory(void) +{ + return freeMemory(); +} + static uint64_t usableMemory(void) { diff --git a/Tests/SentryTests/SentryClient+TestInit.h b/Tests/SentryTests/SentryClient+TestInit.h index b999516ca80..12ddda1f54b 100644 --- a/Tests/SentryTests/SentryClient+TestInit.h +++ b/Tests/SentryTests/SentryClient+TestInit.h @@ -14,7 +14,8 @@ SentryClient (TestInit) transportAdapter:(SentryTransportAdapter *)transportAdapter fileManager:(SentryFileManager *)fileManager threadInspector:(SentryThreadInspector *)threadInspector - random:(id)random; + random:(id)random + crashWrapper:(SentryCrashWrapper *)crashWrapper; @end diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index d7dfe566dad..6555e3dfad1 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -27,6 +27,7 @@ class SentryClientTest: XCTestCase { let trace = SentryTracer(transactionContext: TransactionContext(name: "SomeTransaction", operation: "SomeOperation"), hub: nil) let transaction: Transaction + let crashWrapper = TestSentryCrashWrapper.sharedInstance() init() { session = SentrySession(releaseName: "release") @@ -59,7 +60,7 @@ class SentryClientTest: XCTestCase { ]) configureOptions(options) - client = Client(options: options, transportAdapter: transportAdapter, fileManager: fileManager, threadInspector: threadInspector, random: random) + client = Client(options: options, transportAdapter: transportAdapter, fileManager: fileManager, threadInspector: threadInspector, random: random, crashWrapper: crashWrapper) } catch { XCTFail("Options could not be created") } @@ -497,6 +498,20 @@ class SentryClientTest: XCTestCase { } } + func testCaptureCrash_FreeMemory() { + let event = TestData.event + event.threads = nil + event.debugMeta = nil + + fixture.crashWrapper.internalFreeMemory = 123_456 + fixture.getSut().captureCrash(event, with: fixture.scope) + + assertLastSentEventWithAttachment { actual in + let eventFreeMemory = actual.context?["device"]?["free_memory"] as? Int + XCTAssertEqual(eventFreeMemory, 123_456) + } + } + func testCaptureErrorWithUserInfo() { let expectedValue = "val" let error = NSError(domain: "domain", code: 0, userInfo: ["key": expectedValue]) diff --git a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h index ed1dbc0e929..9409f554bf3 100644 --- a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h +++ b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h @@ -25,6 +25,8 @@ SENTRY_NO_INIT @property (nonatomic, assign) BOOL closeCalled; +@property (nonatomic, assign) NSInteger internalFreeMemory; + @end NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m index d7b26356226..a6998a70f09 100644 --- a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m +++ b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m @@ -14,6 +14,7 @@ + (instancetype)sharedInstance instance.internalIsApplicationInForeground = YES; instance.installAsyncHooksCalled = NO; instance.closeCalled = NO; + instance.internalFreeMemory = 0; return instance; } @@ -57,4 +58,9 @@ - (NSDictionary *)systemInfo return @{}; } +- (NSInteger)freeMemory +{ + return self.internalFreeMemory; +} + @end From 37e9eabde0406a2a61b752f099cab0e5e08187fe Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Mon, 18 Jul 2022 10:17:23 +0200 Subject: [PATCH 03/16] ref: Fix linter error (#1981) --- Sources/Sentry/SentryCrashWrapper.m | 2 +- Sources/Sentry/include/SentryCrashWrapper.h | 2 +- Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h | 2 +- Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Sentry/SentryCrashWrapper.m b/Sources/Sentry/SentryCrashWrapper.m index 075e7d67c6c..3c1b829be27 100644 --- a/Sources/Sentry/SentryCrashWrapper.m +++ b/Sources/Sentry/SentryCrashWrapper.m @@ -70,7 +70,7 @@ - (NSDictionary *)systemInfo return sharedInfo; } -- (NSInteger)freeMemory +- (uint64_t)freeMemory { return sentrycrashcm_system_freememory(); } diff --git a/Sources/Sentry/include/SentryCrashWrapper.h b/Sources/Sentry/include/SentryCrashWrapper.h index a728ecaf7c6..6db7eaf3be5 100644 --- a/Sources/Sentry/include/SentryCrashWrapper.h +++ b/Sources/Sentry/include/SentryCrashWrapper.h @@ -30,7 +30,7 @@ SENTRY_NO_INIT - (NSDictionary *)systemInfo; -- (NSInteger)freeMemory; +- (uint64_t)freeMemory; @end diff --git a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h index 9409f554bf3..6ec3dc2a255 100644 --- a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h +++ b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.h @@ -25,7 +25,7 @@ SENTRY_NO_INIT @property (nonatomic, assign) BOOL closeCalled; -@property (nonatomic, assign) NSInteger internalFreeMemory; +@property (nonatomic, assign) uint64_t internalFreeMemory; @end diff --git a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m index a6998a70f09..1f9bd4a1ac0 100644 --- a/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m +++ b/Tests/SentryTests/SentryCrash/TestSentryCrashWrapper.m @@ -58,7 +58,7 @@ - (NSDictionary *)systemInfo return @{}; } -- (NSInteger)freeMemory +- (uint64_t)freeMemory { return self.internalFreeMemory; } From 5bddfe2f25e05a61de21e56e08b51feea6198172 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 19 Jul 2022 09:41:02 +0200 Subject: [PATCH 04/16] meta: Add vscode to gitignore (#1987) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 683eab7aac7..b23da2ff8a4 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ Package.resolved # generated by `swiftlint generate-docs` /rule_docs/ + +# Visual Studio Code +.vscode/ From 167de8bea5a0effef3aaa5c99c540088de30b361 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Jul 2022 11:28:04 +0200 Subject: [PATCH 05/16] build(deps): bump actions/stale from 5.0.0 to 5.1.0 (#1985) Bumps [actions/stale](https://github.com/actions/stale) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/3cc123766321e9f15a6676375c154ccffb12a358...532554b8a8498a0e006fbcde824b048728c4178f) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index dbf528e3458..a0a0dda1bec 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@3cc123766321e9f15a6676375c154ccffb12a358 + - uses: actions/stale@532554b8a8498a0e006fbcde824b048728c4178f with: repo-token: ${{ github.token }} days-before-stale: 21 From 64a7645a598b90e217be42333594c981a3ddc642 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 19 Jul 2022 17:16:49 +0200 Subject: [PATCH 06/16] test: Increase wait timeout for ANR tests (#1988) The SentryANRTrackerTests sometimes fail because the test expectation times out. Increase the timeout from 0.3 to 1.0 seconds in case the CI is busy and the test slows down. --- Tests/SentryTests/Integrations/ANR/SentryANRTrackerTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SentryTests/Integrations/ANR/SentryANRTrackerTests.swift b/Tests/SentryTests/Integrations/ANR/SentryANRTrackerTests.swift index 41d9573f6e1..3fee4b9e24f 100644 --- a/Tests/SentryTests/Integrations/ANR/SentryANRTrackerTests.swift +++ b/Tests/SentryTests/Integrations/ANR/SentryANRTrackerTests.swift @@ -7,7 +7,7 @@ class SentryANRTrackerTests: XCTestCase, SentryANRTrackerDelegate { private var fixture: Fixture! private var anrDetectedExpectation: XCTestExpectation! private var anrStoppedExpectation: XCTestExpectation! - private let waitTimeout: TimeInterval = 0.3 + private let waitTimeout: TimeInterval = 1.0 private class Fixture { let timeoutInterval: TimeInterval = 5 From 678172142ac1d10f5ed7978f69d16ab03e801057 Mon Sep 17 00:00:00 2001 From: Indragie Karunaratne Date: Tue, 19 Jul 2022 08:30:31 -0700 Subject: [PATCH 07/16] fix: Collect samples for idle threads in iOS profiler (#1978) Remove the check that skips collecting a sample for an idle thread. We were seeing an issue where we found function frames in profiles that were taking way longer than they should, because of missing samples in between. That's because we were intentionally skipping collecting samples when the thread was idle (which is frequently the case). Not collecting a sample when the thread is idle means that we will have irregular sample timestamps, which cause an incorrect calculation of the function durations. Co-authored-by: Sentry Github Bot --- CHANGELOG.md | 1 + Sources/Sentry/SentryBacktrace.cpp | 3 -- .../Profiling/SentryBacktraceTests.mm | 2 +- .../Profiling/SentrySamplingProfilerTests.mm | 31 +++++++++++++++++-- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d022abecfee..88b671cc441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - Remove Sentry keys from cached HTTP request headers (#1975) +- Collect samples for idle threads in iOS profiler (#1978) ## 7.21.0 diff --git a/Sources/Sentry/SentryBacktrace.cpp b/Sources/Sentry/SentryBacktrace.cpp index f95e129a9f7..6c791bc42d9 100644 --- a/Sources/Sentry/SentryBacktrace.cpp +++ b/Sources/Sentry/SentryBacktrace.cpp @@ -107,9 +107,6 @@ namespace profiling { { const auto pair = ThreadHandle::allExcludingCurrent(); for (const auto &thread : pair.first) { - if (thread->isIdle()) { - continue; - } Backtrace bt; auto metadata = cache->metadataForThread(*thread); if (metadata.threadID == 0) { diff --git a/Tests/SentryTests/Profiling/SentryBacktraceTests.mm b/Tests/SentryTests/Profiling/SentryBacktraceTests.mm index a8c3d8a96ff..cd647d0ad96 100644 --- a/Tests/SentryTests/Profiling/SentryBacktraceTests.mm +++ b/Tests/SentryTests/Profiling/SentryBacktraceTests.mm @@ -109,7 +109,7 @@ } void * -threadEntry(__unused void *ptr) +threadEntry(void *ptr) { if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr) != 0) { return nullptr; diff --git a/Tests/SentryTests/Profiling/SentrySamplingProfilerTests.mm b/Tests/SentryTests/Profiling/SentrySamplingProfilerTests.mm index 713cf3d557b..65170d49e01 100644 --- a/Tests/SentryTests/Profiling/SentrySamplingProfilerTests.mm +++ b/Tests/SentryTests/Profiling/SentrySamplingProfilerTests.mm @@ -25,8 +25,19 @@ - (void)testProfiling { const auto cache = std::make_shared(); const std::uint32_t samplingRateHz = 300; - const auto profiler - = std::make_shared([](__unused auto backtrace) {}, samplingRateHz); + + pthread_t idleThread; + XCTAssertEqual(pthread_create(&idleThread, nullptr, idleThreadEntry, nullptr), 0); + int numIdleSamples = 0; + + const auto profiler = std::make_shared( + [&](auto &backtrace) { + const auto thread = backtrace.threadMetadata.threadID; + if (thread == pthread_mach_thread_np(idleThread)) { + numIdleSamples++; + } + }, + samplingRateHz); XCTAssertFalse(profiler->isSampling()); std::uint64_t start = 0; @@ -42,6 +53,22 @@ - (void)testProfiling XCTAssertGreaterThan(start, static_cast(0)); XCTAssertGreaterThan(std::chrono::duration_cast(duration).count(), 0); XCTAssertGreaterThan(profiler->numSamples(), static_cast(0)); + XCTAssertGreaterThan(numIdleSamples, 0); +} + +static void * +idleThreadEntry(__unused void *ptr) +{ + // Wait on a condition variable that will never be signaled to make the thread idle. + pthread_cond_t cv; + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + if (pthread_cond_init(&cv, NULL) != 0) { + return nullptr; + } + if (pthread_cond_wait(&cv, &mutex) != 0) { + return nullptr; + } + return nullptr; } @end From be6bf25c46bcf167ae6bd5f557d525f4ba427346 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 21 Jul 2022 04:25:44 -0300 Subject: [PATCH 08/16] fix: Remove variable values from Core Data queries (#1982) Created a parser for NSPredicate in order to be able to remove variable and other constant values from the query to avoid leaking PII. Co-authored-by: Philipp Hofmann --- CHANGELOG.md | 1 + Sentry.xcodeproj/project.pbxproj | 12 ++ Sources/Sentry/SentryCoreDataTracker.m | 21 ++- Sources/Sentry/SentryPredicateDescriptor.m | 143 ++++++++++++++++ .../include/SentryPredicateDescriptor.h | 16 ++ .../CoreData/SentryCoreDataTrackerTest.swift | 4 +- .../SentryPredicateDescriptorTests.swift | 152 ++++++++++++++++++ .../SentryTests/SentryTests-Bridging-Header.h | 1 + 8 files changed, 341 insertions(+), 9 deletions(-) create mode 100644 Sources/Sentry/SentryPredicateDescriptor.m create mode 100644 Sources/Sentry/include/SentryPredicateDescriptor.h create mode 100644 Tests/SentryTests/SentryPredicateDescriptorTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b671cc441..d174cc62a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Add enableAutoBreadcrumbTracking option (#1958) - Automatic nest spans with the UI life cycle (#1959) - Upload frame rendering timestamps to correlate to sampled backtraces (#1910) +- Remove PII from auto-generated core data spans (#1982) ### Fixes diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index eb39e332236..c99b50976f4 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -663,6 +663,9 @@ D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9928000E23007E326E /* SentryUIApplication.h */; }; D8C67E9C28000E24007E326E /* SentryScreenshot.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9A28000E23007E326E /* SentryScreenshot.h */; }; D8CE69BC277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D8CE69BB277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m */; }; + D8F6A2472885512100320515 /* SentryPredicateDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */; }; + D8F6A24B2885515C00320515 /* SentryPredicateDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */; }; + D8F6A24E288553A800320515 /* SentryPredicateDescriptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F6A24C2885534E00320515 /* SentryPredicateDescriptorTests.swift */; }; D8FFE50C2703DBB400607131 /* SwizzlingCallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */; }; /* End PBXBuildFile section */ @@ -1398,6 +1401,9 @@ D8C67E9928000E23007E326E /* SentryUIApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryUIApplication.h; path = include/SentryUIApplication.h; sourceTree = ""; }; D8C67E9A28000E23007E326E /* SentryScreenshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryScreenshot.h; path = include/SentryScreenshot.h; sourceTree = ""; }; D8CE69BB277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryFileIOTrackingIntegrationObjCTests.m; sourceTree = ""; }; + D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryPredicateDescriptor.m; sourceTree = ""; }; + D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryPredicateDescriptor.h; path = include/SentryPredicateDescriptor.h; sourceTree = ""; }; + D8F6A24C2885534E00320515 /* SentryPredicateDescriptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryPredicateDescriptorTests.swift; sourceTree = ""; }; D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwizzlingCallTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -2601,6 +2607,8 @@ D85852B427ECEEDA00C6D8AE /* SentryScreenshot.m */, D8C67E9928000E23007E326E /* SentryUIApplication.h */, D85852B827EDDC5900C6D8AE /* SentryUIApplication.m */, + D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */, + D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */, ); name = Tools; sourceTree = ""; @@ -2665,6 +2673,7 @@ isa = PBXGroup; children = ( D81FDF10280EA0080045E0E4 /* SentryScreenShotTests.swift */, + D8F6A24C2885534E00320515 /* SentryPredicateDescriptorTests.swift */, ); name = Tools; sourceTree = ""; @@ -2978,6 +2987,7 @@ 6334314120AD9AE40077E581 /* SentryMechanism.h in Headers */, 03F84D2827DD414C008FE43F /* SentryCPU.h in Headers */, 7B610D642512399600B0B5D9 /* SentryHub+Private.h in Headers */, + D8F6A24B2885515C00320515 /* SentryPredicateDescriptor.h in Headers */, 639FCF9C1EBC7F9500778193 /* SentryThread.h in Headers */, 63FE716B20DA4C1100CDBAE8 /* SentryCrashJSONCodecObjC.h in Headers */, 63AA76A51EB9CBC200D153DE /* SentryDsn.h in Headers */, @@ -3168,6 +3178,7 @@ 7BE912AD272162D900E49E62 /* SentryNoOpSpan.m in Sources */, 63FE710D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c in Sources */, 63FE70E120DA4C1000CDBAE8 /* SentryCrashMonitor_CPPException.cpp in Sources */, + D8F6A2472885512100320515 /* SentryPredicateDescriptor.m in Sources */, A839D89A24864BA8003B7AFD /* SentrySystemEventBreadcrumbs.m in Sources */, 7D082B8323C628790029866B /* SentryMeta.m in Sources */, 63FE710720DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c in Sources */, @@ -3397,6 +3408,7 @@ 8EA05EED267C2AB200C82B30 /* SentryNetworkTrackerTests.swift in Sources */, 7BA840A024A1EC6E00B718AA /* SentrySDKTests.swift in Sources */, 8E4A038525F76A7600000D77 /* Logger.swift in Sources */, + D8F6A24E288553A800320515 /* SentryPredicateDescriptorTests.swift in Sources */, 035E73CE27D5790A005EEB11 /* SentryThreadMetadataCacheTests.mm in Sources */, 7BEFB044270B0F630025F808 /* SentryTracerObjCTests.m in Sources */, 7B0002322477F0520035FEF1 /* SentrySessionTests.m in Sources */, diff --git a/Sources/Sentry/SentryCoreDataTracker.m b/Sources/Sentry/SentryCoreDataTracker.m index c8636533e81..8141ad62396 100644 --- a/Sources/Sentry/SentryCoreDataTracker.m +++ b/Sources/Sentry/SentryCoreDataTracker.m @@ -2,11 +2,22 @@ #import "SentryCoreDataTracker.h" #import "SentryHub+Private.h" #import "SentryLog.h" +#import "SentryPredicateDescriptor.h" #import "SentrySDK+Private.h" #import "SentryScope+Private.h" #import "SentrySpanProtocol.h" -@implementation SentryCoreDataTracker +@implementation SentryCoreDataTracker { + SentryPredicateDescriptor *predicateDescriptor; +} + +- (instancetype)init +{ + if (self = [super init]) { + predicateDescriptor = [[SentryPredicateDescriptor alloc] init]; + } + return self; +} - (NSArray *)managedObjectContext:(NSManagedObjectContext *)context executeFetchRequest:(NSFetchRequest *)request @@ -116,7 +127,8 @@ - (NSString *)descriptionFromRequest:(NSFetchRequest *)request [[NSMutableString alloc] initWithFormat:@"SELECT '%@'", request.entityName]; if (request.predicate) { - [result appendFormat:@" WHERE %@", [self predicateDescription:request.predicate]]; + [result appendFormat:@" WHERE %@", + [predicateDescriptor predicateDescription:request.predicate]]; } if (request.sortDescriptors.count > 0) { @@ -126,11 +138,6 @@ - (NSString *)descriptionFromRequest:(NSFetchRequest *)request return result; } -- (NSString *)predicateDescription:(NSPredicate *)predicate -{ - return predicate.predicateFormat; -} - - (NSString *)sortDescription:(NSArray *)sortList { NSMutableArray *fields = [[NSMutableArray alloc] initWithCapacity:sortList.count]; diff --git a/Sources/Sentry/SentryPredicateDescriptor.m b/Sources/Sentry/SentryPredicateDescriptor.m new file mode 100644 index 00000000000..ca6c3a77f95 --- /dev/null +++ b/Sources/Sentry/SentryPredicateDescriptor.m @@ -0,0 +1,143 @@ +#import "SentryPredicateDescriptor.h" + +@implementation SentryPredicateDescriptor + +- (NSString *)predicateDescription:(NSPredicate *)predicate +{ + if ([predicate isKindOfClass:[NSCompoundPredicate class]]) { + return [self compoundPredicateDescription:(NSCompoundPredicate *)predicate]; + } else if ([predicate isKindOfClass:[NSComparisonPredicate class]]) { + return [self comparisonPredicateDescription:(NSComparisonPredicate *)predicate]; + } else if ([predicate isKindOfClass:[NSExpression class]]) { + return [self expressionDescription:(NSExpression *)predicate]; + } + + return @""; +} + +- (NSString *)compoundPredicateDescription:(NSCompoundPredicate *)predicate +{ + + NSMutableArray *expressions = + [[NSMutableArray alloc] initWithCapacity:predicate.subpredicates.count]; + + for (NSPredicate *sub in predicate.subpredicates) { + if ([sub isKindOfClass:[NSCompoundPredicate class]]) { + [expressions + addObject:[NSString stringWithFormat:@"(%@)", [self predicateDescription:sub]]]; + } else { + [expressions addObject:[self predicateDescription:sub]]; + } + } + + if (expressions.count == 1) { + return [NSString stringWithFormat:@"%@ %@", + [self compoundPredicateTypeDescription:predicate.compoundPredicateType], + expressions.firstObject]; + } + + return [expressions + componentsJoinedByString:[self + compoundPredicateTypeDescription:predicate + .compoundPredicateType]]; +} + +- (NSString *)comparisonPredicateDescription:(NSComparisonPredicate *)predicate +{ + NSString *operator= [self predicateOperatorTypeDescription:predicate.predicateOperatorType]; + + if (operator== nil) { + return @""; + } + + return [NSString stringWithFormat:@"%@ %@ %@",[self expressionDescription:predicate.leftExpression] , + operator, + [self expressionDescription:predicate.rightExpression] + ]; +} + +- (NSString *)expressionDescription:(NSExpression *)predicate +{ + switch (predicate.expressionType) { + case NSConstantValueExpressionType: + return @"%@"; + case NSAggregateExpressionType: + if ([predicate.collection isKindOfClass:[NSArray class]]) { + __block NSMutableArray *items = + [[NSMutableArray alloc] initWithCapacity:[predicate.collection count]]; + [predicate.collection enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [items addObject:[self expressionDescription:obj]]; + }]; + return [NSString stringWithFormat:@"{%@}", [items componentsJoinedByString:@", "]]; + } else { + return @"%@"; + } + break; + case NSConditionalExpressionType: + if (@available(macOS 10.11, *)) { + return [NSString + stringWithFormat:@"TERNARY(%@,%@,%@)", + [self comparisonPredicateDescription:(NSComparisonPredicate *)predicate.predicate], + [self expressionDescription:predicate.trueExpression], + [self expressionDescription:predicate.falseExpression]]; + } else { + // this is not supposed to happen, NSConditionalExpressionType was introduced in + // macOS 10.11 but we need this version check because cocoapod lint check is failing + // without it. + return @""; + } + default: + return predicate.description; + } +} + +- (NSString *)compoundPredicateTypeDescription:(NSCompoundPredicateType)compountType +{ + switch (compountType) { + case NSAndPredicateType: + return @" AND "; + case NSOrPredicateType: + return @" OR "; + case NSNotPredicateType: + return @"NOT"; + default: + return @", "; + } +} + +- (NSString *)predicateOperatorTypeDescription:(NSPredicateOperatorType)operator +{ + switch (operator) { + case NSLessThanPredicateOperatorType: + return @"<"; + case NSLessThanOrEqualToPredicateOperatorType: + return @"<="; + case NSGreaterThanPredicateOperatorType: + return @">"; + case NSGreaterThanOrEqualToPredicateOperatorType: + return @">="; + case NSEqualToPredicateOperatorType: + return @"=="; + case NSNotEqualToPredicateOperatorType: + return @"!="; + case NSMatchesPredicateOperatorType: + return @"MATCHES"; + case NSBeginsWithPredicateOperatorType: + return @"BEGINSWITH"; + case NSEndsWithPredicateOperatorType: + return @"ENDSWITH"; + case NSInPredicateOperatorType: + return @"IN"; + case NSContainsPredicateOperatorType: + return @"CONTAINS"; + case NSBetweenPredicateOperatorType: + return @"BETWEEN"; + case NSLikePredicateOperatorType: + return @"LIKE"; + // NSCustomSelectorPredicateOperatorType is Not supported for CoreData + default: + return nil; + } +} + +@end diff --git a/Sources/Sentry/include/SentryPredicateDescriptor.h b/Sources/Sentry/include/SentryPredicateDescriptor.h new file mode 100644 index 00000000000..50bb90c9b97 --- /dev/null +++ b/Sources/Sentry/include/SentryPredicateDescriptor.h @@ -0,0 +1,16 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Used to transform an NSPredicate into a human-friendly string. + * This class is used for CoreData and omits variable values + * and doesn't convert CoreData unsupported instructions. + */ +@interface SentryPredicateDescriptor : NSObject + +- (NSString *)predicateDescription:(NSPredicate *)predicate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/Integrations/Performance/CoreData/SentryCoreDataTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/CoreData/SentryCoreDataTrackerTest.swift index 65f7881c62d..b63d1148ceb 100644 --- a/Tests/SentryTests/Integrations/Performance/CoreData/SentryCoreDataTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/CoreData/SentryCoreDataTrackerTest.swift @@ -44,7 +44,7 @@ class SentryCoreDataTrackerTests: XCTestCase { func test_FetchRequest_WithPredicate() { let fetch = NSFetchRequest(entityName: "TestEntity") fetch.predicate = NSPredicate(format: "field1 = %@ and field2 = %@", argumentArray: ["First Argument", 2]) - assertRequest(fetch, expectedDescription: "SELECT 'TestEntity' WHERE field1 == \"First Argument\" AND field2 == 2") + assertRequest(fetch, expectedDescription: "SELECT 'TestEntity' WHERE field1 == %@ AND field2 == %@") } func test_FetchRequest_WithSortAscending() { @@ -69,7 +69,7 @@ class SentryCoreDataTrackerTests: XCTestCase { let fetch = NSFetchRequest(entityName: "TestEntity") fetch.predicate = NSPredicate(format: "field1 = %@", argumentArray: ["First Argument"]) fetch.sortDescriptors = [NSSortDescriptor(key: "field1", ascending: false)] - assertRequest(fetch, expectedDescription: "SELECT 'TestEntity' WHERE field1 == \"First Argument\" SORT BY field1 DESCENDING") + assertRequest(fetch, expectedDescription: "SELECT 'TestEntity' WHERE field1 == %@ SORT BY field1 DESCENDING") } func test_Save_1Insert_1Entity() { diff --git a/Tests/SentryTests/SentryPredicateDescriptorTests.swift b/Tests/SentryTests/SentryPredicateDescriptorTests.swift new file mode 100644 index 00000000000..b309e00a397 --- /dev/null +++ b/Tests/SentryTests/SentryPredicateDescriptorTests.swift @@ -0,0 +1,152 @@ +import XCTest + +class SentryPredicateDescriptorTests: XCTestCase { + + private class Fixture { + func getSut() -> SentryPredicateDescriptor { + return SentryPredicateDescriptor() + } + } + + private var fixture: Fixture! + + override func setUp() { + super.setUp() + fixture = Fixture() + } + + func test_lessThanOperator() { + let pred = NSPredicate(format: "field1 < %@", argumentArray: [0]) + assertPredicate(predicate: pred, expectedResult: "field1 < %@") + } + + func test_lessThanOrEqualOperator() { + let pred = NSPredicate(format: "field1 <= %@") + assertPredicate(predicate: pred, expectedResult: "field1 <= %@") + } + + func test_greaterThanOperator() { + let pred = NSPredicate(format: "field1 > %@") + assertPredicate(predicate: pred, expectedResult: "field1 > %@") + } + + func test_greaterThanOrEqualOperator() { + let pred = NSPredicate(format: "field1 >= %@") + assertPredicate(predicate: pred, expectedResult: "field1 >= %@") + } + + func test_equalOperator() { + let pred = NSPredicate(format: "field1 = %@") + assertPredicate(predicate: pred, expectedResult: "field1 == %@") + } + + func test_notEqualOperator() { + let pred = NSPredicate(format: "field1 != %@") + assertPredicate(predicate: pred, expectedResult: "field1 != %@") + } + + func test_MatchesOperator() { + let pred = NSPredicate(format: "field1 matches %@") + assertPredicate(predicate: pred, expectedResult: "field1 MATCHES %@") + } + + func test_beginsWithOperator() { + let pred = NSPredicate(format: "field1 beginsWith %@") + assertPredicate(predicate: pred, expectedResult: "field1 BEGINSWITH %@") + } + + func test_endsWithOperator() { + let pred = NSPredicate(format: "field1 endsWith %@") + assertPredicate(predicate: pred, expectedResult: "field1 ENDSWITH %@") + } + + func test_inOperator() { + let pred = NSPredicate(format: "field1 in %@") + assertPredicate(predicate: pred, expectedResult: "field1 IN %@") + } + + func test_containsOperator() { + let pred = NSPredicate(format: "field1 contains %@") + assertPredicate(predicate: pred, expectedResult: "field1 CONTAINS %@") + } + + func test_betweenOperator() { + let pred = NSPredicate(format: "field1 between %@") + assertPredicate(predicate: pred, expectedResult: "field1 BETWEEN %@") + } + + func test_likeOperator() { + let pred = NSPredicate(format: "field1 like %@") + assertPredicate(predicate: pred, expectedResult: "field1 LIKE %@") + } + + func test_andCompound() { + let pred = NSPredicate(format: "field1 = %@ and field2 = %@") + assertPredicate(predicate: pred, expectedResult: "field1 == %@ AND field2 == %@") + } + + func test_orCompound() { + let pred = NSPredicate(format: "field1 = %@ or field2 = %@") + assertPredicate(predicate: pred, expectedResult: "field1 == %@ OR field2 == %@") + } + + func test_notCompound() { + let pred = NSPredicate(format: "not field1 = %@") + assertPredicate(predicate: pred, expectedResult: "NOT field1 == %@") + } + + func test_AggregateExpression() { + let pred = NSPredicate(format: "field1 in {1,2,3,4}") + assertPredicate(predicate: pred, expectedResult: "field1 IN {%@, %@, %@, %@}") + } + + func test_ternaryExpression() { + let pred = NSPredicate(format: "ternary(field1 = %@ , 1 , 2) == 1") + assertPredicate(predicate: pred, expectedResult: "TERNARY(field1 == %@,%@,%@) == %@") + } + + func test_compoundInCompound() { + let pred = NSPredicate(format: "field1 = 1 And field2 = 2 or field2 = 1") + assertPredicate(predicate: pred, expectedResult: "(field1 == %@ AND field2 == %@) OR field2 == %@") + } + + func test_compoundInCompound_2() { + let pred = NSPredicate(format: "field1 = 1 And (field3 = 2 or field2 = 1)") + assertPredicate(predicate: pred, expectedResult: "field1 == %@ AND (field3 == %@ OR field2 == %@)") + } + + func test_UNKNOWN() { + let pred = NSPredicate { _, _ in + return false + } + assertPredicate(predicate: pred, expectedResult: "") + } + + func test_invalidCompound() { + guard let invalidCompound = NSCompoundPredicate.LogicalType(rawValue: 6) else { + XCTFail("Could not create invalid compound type") + return + } + + let pred = NSCompoundPredicate(type: invalidCompound, subpredicates: [NSComparisonPredicate(format: "field1 == 1"), NSComparisonPredicate(format: "field2 == 2")]) + + assertPredicate(predicate: pred, expectedResult: "field1 == %@, field2 == %@") + } + + func test_invalidComparison() { + let pred = NSComparisonPredicate(leftExpression: NSExpression(format: "field1"), rightExpression: NSExpression(format: "1"), customSelector: #selector(compareFunction(_:_:))) + + assertPredicate(predicate: pred, expectedResult: "") + } + + @objc + func compareFunction(_ item1: AnyObject, _ item2: AnyObject) -> Bool { + return item1 === item2 + } + + func assertPredicate(predicate: NSPredicate, expectedResult: String ) { + let sut = fixture.getSut() + XCTAssertEqual(sut.predicateDescription(predicate), expectedResult) + } + +} diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 1b7f1c26471..be7b50be616 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -111,6 +111,7 @@ #import "SentryOutOfMemoryTrackingIntegration.h" #import "SentryPerformanceTracker.h" #import "SentryPerformanceTrackingIntegration.h" +#import "SentryPredicateDescriptor.h" #import "SentryQueueableRequestManager.h" #import "SentryRandom.h" #import "SentryRateLimitParser.h" From 204fa838e7c5467ad32fe35cfb8556db74ca5890 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 21 Jul 2022 09:41:26 +0200 Subject: [PATCH 09/16] build(deps): bump fastlane from 2.207.0 to 2.208.0 (#1989) --- Gemfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0a54e1252d0..00e4ee455b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,13 +17,13 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.603.0) - aws-sdk-core (3.131.2) + aws-partitions (1.608.0) + aws-sdk-core (3.131.3) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.57.0) + aws-sdk-kms (1.58.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.114.0) @@ -116,7 +116,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.207.0) + fastlane (2.208.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -160,7 +160,7 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.24.0) + google-apis-androidpublisher_v3 (0.25.0) google-apis-core (>= 0.7, < 2.a) google-apis-core (0.7.0) addressable (~> 2.5, >= 2.5.1) @@ -175,7 +175,7 @@ GEM google-apis-core (>= 0.7, < 2.a) google-apis-playcustomapp_v1 (0.10.0) google-apis-core (>= 0.7, < 2.a) - google-apis-storage_v1 (0.17.0) + google-apis-storage_v1 (0.18.0) google-apis-core (>= 0.7, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) From 825b2e1f8aa0569f29f45b7ca2e2a72b41637660 Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Thu, 21 Jul 2022 13:53:41 +0200 Subject: [PATCH 10/16] feat: Provide private access to SentryOptions for hybrid SDKs (#1991) Hybrid SDKs can now access the options of the current client, or will get an empty SentryOptions object back if no hub/client was set. --- CHANGELOG.md | 1 + Sources/Sentry/PrivateSentrySDKOnly.m | 11 +++++++++++ Sources/Sentry/Public/PrivateSentrySDKOnly.h | 5 ++++- Sources/Sentry/include/SentryHub+Private.h | 2 ++ Tests/SentryTests/PrivateSentrySDKOnlyTests.swift | 15 +++++++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d174cc62a21..66c5c6e581a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Read free_memory when the event is captured, not only at SDK startup (#1962) +- Provide private access to SentryOptions for hybrid SDKs (#1991) ### Fixes diff --git a/Sources/Sentry/PrivateSentrySDKOnly.m b/Sources/Sentry/PrivateSentrySDKOnly.m index 5c58e87f326..6721601659e 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.m +++ b/Sources/Sentry/PrivateSentrySDKOnly.m @@ -1,5 +1,7 @@ #import "PrivateSentrySDKOnly.h" +#import "SentryClient.h" #import "SentryDebugImageProvider.h" +#import "SentryHub+Private.h" #import "SentryInstallation.h" #import "SentryMeta.h" #import "SentrySDK+Private.h" @@ -46,6 +48,15 @@ + (NSString *)installationID return [SentryInstallation id]; } ++ (SentryOptions *)options +{ + SentryOptions *options = [[SentrySDK currentHub] client].options; + if (options != nil) { + return options; + } + return [[SentryOptions alloc] init]; +} + + (SentryOnAppStartMeasurementAvailable)onAppStartMeasurementAvailable { return _onAppStartMeasurmentAvailable; diff --git a/Sources/Sentry/Public/PrivateSentrySDKOnly.h b/Sources/Sentry/Public/PrivateSentrySDKOnly.h index f82869ecdb6..234192ca2c0 100644 --- a/Sources/Sentry/Public/PrivateSentrySDKOnly.h +++ b/Sources/Sentry/Public/PrivateSentrySDKOnly.h @@ -2,7 +2,8 @@ #import "SentryDefines.h" -@class SentryEnvelope, SentryDebugMeta, SentryAppStartMeasurement, SentryScreenFrames; +@class SentryEnvelope, SentryDebugMeta, SentryAppStartMeasurement, SentryScreenFrames, + SentryOptions; NS_ASSUME_NONNULL_BEGIN @@ -53,6 +54,8 @@ typedef void (^SentryOnAppStartMeasurementAvailable)( @property (class, nonatomic, readonly, copy) NSString *installationID; +@property (class, nonatomic, readonly, copy) SentryOptions *options; + /** * If enabled, the SDK won't send the app start measurement with the first transaction. Instead, if * enableAutoPerformanceTracking is enabled, the SDK measures the app start and then calls diff --git a/Sources/Sentry/include/SentryHub+Private.h b/Sources/Sentry/include/SentryHub+Private.h index c957c49a969..d41dd154b3d 100644 --- a/Sources/Sentry/include/SentryHub+Private.h +++ b/Sources/Sentry/include/SentryHub+Private.h @@ -8,6 +8,8 @@ NS_ASSUME_NONNULL_BEGIN @interface SentryHub (Private) +- (SentryClient *_Nullable)client; + - (void)captureCrashEvent:(SentryEvent *)event; - (void)captureCrashEvent:(SentryEvent *)event withScope:(SentryScope *)scope; diff --git a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift index 9f454b49c27..56e04281b1a 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -62,6 +62,21 @@ class PrivateSentrySDKOnlyTests: XCTestCase { PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = true XCTAssertTrue(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) } + + func testOptions() { + let options = Options() + options.dsn = TestConstants.dsnAsString(username: "SentryFramesTrackingIntegrationTests") + let client = TestClient(options: options) + SentrySDK.setCurrentHub(TestHub(client: client, andScope: nil)) + + XCTAssertEqual(PrivateSentrySDKOnly.options, options) + } + + func testDefaultOptions() { + XCTAssertNotNil(PrivateSentrySDKOnly.options) + XCTAssertNil(PrivateSentrySDKOnly.options.dsn) + XCTAssertEqual(PrivateSentrySDKOnly.options.enabled, true) + } #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) From f3a8907c70f0f901253018e24336d0c1331e165e Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 22 Jul 2022 11:36:56 +0200 Subject: [PATCH 11/16] ci: Update available GH actions Xcode link (#1997) The current links point to a fixed commit. When GH adds new Xcode versions we don't see them as the commit points to the past. This is fixed by pointing to the latest version in the readme. --- scripts/ci-select-xcode.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/ci-select-xcode.sh b/scripts/ci-select-xcode.sh index 0ae159db75f..b70aaa977d5 100755 --- a/scripts/ci-select-xcode.sh +++ b/scripts/ci-select-xcode.sh @@ -1,8 +1,8 @@ #!/bin/bash # For available Xcode versions see: -# - https://github.com/actions/virtual-environments/blob/6a2f3acb8890efd4b6ba9344d5f73af25e7a2bcf/images/macos/macos-10.15-Readme.md?plain=1#L254-L266 -# - https://github.com/actions/virtual-environments/blob/6a2f3acb8890efd4b6ba9344d5f73af25e7a2bcf/images/macos/macos-11-Readme.md?plain=1#L248-L253 +# - https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md#xcode +# - https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11-Readme.md#xcode set -euo pipefail @@ -10,4 +10,3 @@ set -euo pipefail XCODE_VERSION="${1:-13.2.1}" sudo xcode-select -s /Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer - From 9b7bfbec42ef5fab3f122c77c573e0fbc56bb330 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 22 Jul 2022 11:37:08 +0200 Subject: [PATCH 12/16] test: Make message for waitForExistence required (#1998) Add parameter message to waitForExistence to make it required to use an assertion message so we know what is wrong when waitForExistence times out. --- .../iOS-SwiftUITests/LaunchUITests.swift | 23 +++++++++---------- .../tvOS-SwiftUITests/LaunchUITests.swift | 7 +++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Samples/iOS-Swift/iOS-SwiftUITests/LaunchUITests.swift b/Samples/iOS-Swift/iOS-SwiftUITests/LaunchUITests.swift index 2151f15ac51..2e229082195 100644 --- a/Samples/iOS-Swift/iOS-SwiftUITests/LaunchUITests.swift +++ b/Samples/iOS-Swift/iOS-SwiftUITests/LaunchUITests.swift @@ -22,24 +22,24 @@ class LaunchUITests: XCTestCase { func testBreadcrumbData() { let breadcrumbLabel = app.staticTexts["breadcrumbLabel"] - XCTAssertTrue(breadcrumbLabel.waitForExistence(), "Breadcrumb label not found.") + breadcrumbLabel.waitForExistence("Breadcrumb label not found.") XCTAssertEqual(breadcrumbLabel.label, "{ category: ui.lifecycle, parentViewController: UINavigationController, beingPresented: false, window_isKeyWindow: true, is_window_rootViewController: false }") } func testLoremIpsum() { app.buttons["loremIpsumButton"].tap() - XCTAssertTrue(app.textViews.firstMatch.waitForExistence(), "Lorem Ipsum not loaded.") + app.textViews.firstMatch.waitForExistence("Lorem Ipsum not loaded.") } func testNavigationTransaction() { app.buttons["testNavigationTransactionButton"].tap() - XCTAssertTrue(app.images.firstMatch.waitForExistence(), "Navigation transaction not loaded.") + app.images.firstMatch.waitForExistence("Navigation transaction not loaded.") assertApp() } func testShowNib() { app.buttons["showNibButton"].tap() - XCTAssertTrue(app.buttons["lonelyButton"].waitForExistence(), "Nib ViewController not loaded.") + app.buttons["lonelyButton"].waitForExistence("Nib ViewController not loaded.") assertApp() } @@ -57,7 +57,7 @@ class LaunchUITests: XCTestCase { func testShowTableView() { app.buttons["showTableViewButton"].tap() - XCTAssertTrue(app.navigationBars.buttons.element(boundBy: 0).waitForExistence(), "TableView not loaded.") + app.navigationBars.buttons.element(boundBy: 0).waitForExistence("TableView not loaded.") assertApp() } @@ -65,7 +65,7 @@ class LaunchUITests: XCTestCase { app.buttons["showSplitViewButton"].tap() let app = XCUIApplication() - XCTAssertTrue(app.navigationBars["iOS_Swift.SecondarySplitView"].buttons["Root ViewController"].waitForExistence(), "SplitView not loaded.") + app.navigationBars["iOS_Swift.SecondarySplitView"].buttons["Root ViewController"].waitForExistence("SplitView not loaded.") // This validation is currently not working on iOS 10. if #available(iOS 11.0, *) { @@ -74,12 +74,12 @@ class LaunchUITests: XCTestCase { } private func waitForExistenseOfMainScreen() { - XCTAssertTrue(app.buttons["captureMessageButton"].waitForExistence(), "Home Screen doesn't exist.") + app.buttons["captureMessageButton"].waitForExistence( "Home Screen doesn't exist.") } private func checkSlowAndFrozenFrames() { let frameStatsLabel = app.staticTexts["framesStatsLabel"] - XCTAssertTrue(frameStatsLabel.waitForExistence(), "Frame statistics message not found.") + frameStatsLabel.waitForExistence("Frame statistics message not found.") let frameStatsAsStringArray = frameStatsLabel.label.components(separatedBy: CharacterSet.decimalDigits.inverted) let frameStats = frameStatsAsStringArray.filter { $0 != "" }.map { Int($0) } @@ -99,7 +99,7 @@ class LaunchUITests: XCTestCase { private func assertApp() { let confirmation = app.staticTexts["ASSERT_MESSAGE"] let errorMessage = app.staticTexts["ASSERT_ERROR"] - XCTAssertTrue(confirmation.waitForExistence(), "Assertion Message Not Found") + confirmation.waitForExistence("Assertion Message Not Found") XCTAssertTrue(confirmation.label == "ASSERT: SUCCESS", errorMessage.label) } @@ -108,8 +108,7 @@ class LaunchUITests: XCTestCase { extension XCUIElement { - @discardableResult - func waitForExistence() -> Bool { - self.waitForExistence(timeout: TimeInterval(10)) + func waitForExistence(_ message: String) { + XCTAssertTrue(self.waitForExistence(timeout: TimeInterval(10)), message) } } diff --git a/Samples/tvOS-Swift/tvOS-SwiftUITests/LaunchUITests.swift b/Samples/tvOS-Swift/tvOS-SwiftUITests/LaunchUITests.swift index a7d789cbbba..d4e4619ab23 100644 --- a/Samples/tvOS-Swift/tvOS-SwiftUITests/LaunchUITests.swift +++ b/Samples/tvOS-Swift/tvOS-SwiftUITests/LaunchUITests.swift @@ -17,14 +17,13 @@ class LaunchUITests: XCTestCase { } func testLaunch() throws { - XCTAssertTrue(app.buttons["captureMessageButton"].waitForExistence(), "Home Screen doesn't exist.") + app.buttons["captureMessageButton"].waitForExistence("Home Screen doesn't exist.") } } extension XCUIElement { - @discardableResult - func waitForExistence() -> Bool { - self.waitForExistence(timeout: TimeInterval(10)) + func waitForExistence(_ message: String) { + XCTAssertTrue(self.waitForExistence(timeout: TimeInterval(10)), message) } } From 2e5ea2c9ca91a75c625fe6548c54a3a2daf8eb08 Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Mon, 25 Jul 2022 12:00:19 +0200 Subject: [PATCH 13/16] fix: Respect existing baggage header instead of overwriting it (#1995) SentryNetworkTracker adds a baggage header with some sentry keys and values, but it does so by completely overwriting any possibly existing baggage header. And when we remove the baggage header when SentrySDK.currentHub.scope.span is nil, we also do that by removing the entire baggage header instead of only the sentry-trace_id key that we actually need to remove. Now we respect the existing baggage header, if it's there, by modifying it. --- CHANGELOG.md | 1 + .../iOS-Swift/Base.lproj/Main.storyboard | 6 ++--- Sources/Sentry/SentryBaggage.m | 13 +++++++--- Sources/Sentry/SentryNetworkTracker.m | 26 +++++++++++++++++-- Sources/Sentry/SentrySerialization.m | 23 ++++++++++++++++ Sources/Sentry/include/SentryBaggage.h | 1 + Sources/Sentry/include/SentrySerialization.h | 1 + .../Helper/SentrySerializationTests.swift | 15 +++++++++++ .../Network/SentryNetworkTrackerTests.swift | 25 +++++++++++++++++- .../Transaction/SentryBaggageTests.swift | 6 +++++ 10 files changed, 108 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c5c6e581a..61721450c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Remove Sentry keys from cached HTTP request headers (#1975) - Collect samples for idle threads in iOS profiler (#1978) +- Respect existing baggage header instead of overwriting it (#1995) ## 7.21.0 diff --git a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard index 7abd363eab9..4ea2d30186f 100644 --- a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard +++ b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard @@ -17,7 +17,7 @@ - + @@ -193,7 +193,7 @@ - + @@ -201,7 +201,7 @@ -