Skip to content

chore: Remove SentryDependencyContainer uses in SentryCrash#7514

Open
itaybre wants to merge 43 commits intomainfrom
itay/test
Open

chore: Remove SentryDependencyContainer uses in SentryCrash#7514
itaybre wants to merge 43 commits intomainfrom
itay/test

Conversation

@itaybre
Copy link
Contributor

@itaybre itaybre commented Feb 24, 2026

📜 Description

Isolate SentryCrash from Sentry SDK.

💡 Motivation and Context

  • Isolate SentryCrash
  • Remove uses of SentryDependencyContainer in sentrycrash

💚 How did you test it?

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

Created with GSD

Closes #7668

…e class

- Add concrete facade class bridging SDK services to SentryCrash
- Expose five required services: notificationCenterWrapper, dateProvider, crashReporter, uncaughtExceptionHandler, activeScreenSize
- Implement platform-conditional activeScreenSize for iOS/tvOS only
- Follow existing @objc bridging patterns from SentryNSNotificationCenterWrapper
- Inherit from NSObject and mark as @_spi(Private) for SDK-internal access
- Create SUMMARY.md documenting SentryCrashBridge facade implementation
- Update STATE.md with Phase 01 Plan 01 completion (50% progress)
- Record execution metrics: 252s duration, 1 file created, 1 task completed
- Add decisions: computed property pattern, platform-conditional compilation, @_spi(Private) access
…to SentryCrashIntegration

- Add bridge property to store SentryCrashBridge instance
- Create bridge before startCrashHandler to ensure services available
- Wire notificationCenterWrapper, dateProvider, and crashReporter from dependencies
- Update CrashIntegrationProvider to include DateProviderProvider and NotificationCenterProvider protocols
…ces to MockCrashDependencies

- Add dateProvider property bridging to SentryDependencyContainer
- Add notificationCenterWrapper property bridging to SentryDependencyContainer
- Fixes compilation error after adding DateProviderProvider and NotificationCenterProvider to CrashIntegrationProvider

This fix was required to unblock Task 2 (Deviation Rule 3 - blocking issue).
Plan 01-02 Summary:
- SentryCrashBridge integrated into SentryCrashIntegration
- Bridge created before crash handler installation with dependency injection
- Extended CrashIntegrationProvider with required protocol conformances
- Fixed MockCrashDependencies test compilation (deviation Rule 3)
- All 27 tests pass, iOS/macOS/tvOS compilation verified
- Duration: 9m 33s

Phase 01 now complete (2/2 plans, 825s total).
- Add bridge: SentryCrashBridge parameter to both production initializers
- Store bridge as private property for activeScreenSize access
- Convert lazy var systemInfo to immutable let initialized from bridge.crashReporter.systemInfo
- Replace SentryDependencyContainerSwiftHelper.activeScreenSize() with bridge.activeScreenSize()
- Update Dependencies.crashWrapper to create and pass bridge
- Preserve test-only initializer for backward compatibility
- Keep crashedLastLaunch and activeDurationSinceLastCrash unchanged (Phase 3)
- Add convenience initializer to TestSentryCrashWrapper that creates bridge
- Update SentryHubTests to pass bridge when using production initializer
- Remove systemInfo override from TestSentryCrashWrapper (now immutable let)
- All SentryCrashWrapperTests pass without modification
…ssionHandler

- Add bridge parameter to SentryCrashIntegrationSessionHandler constructors
- Replace container singleton dateProvider access with bridge.dateProvider
- Update CrashIntegrationSessionHandlerBuilder protocol signature
- Update SentryCrashIntegration to pass bridge to session builder
- Update MockCrashDependencies test implementation
- Remove SentryDependencyContainer reference from SessionHandler

Phase 02, Plan 02: SessionHandler now receives all dependencies through bridge
- Replace crashedLastLaunch container access with bridge.crashReporter
- Replace activeDurationSinceLastCrash container access with bridge.crashReporter
- Only test-only initializer retains container reference (acceptable)
- Add SentryCrashBridge property to SentryCrash.h with forward declaration
- Add NS_ASSUME_NONNULL_BEGIN/END to SentryCrash.h for nullability audit
- Mark nullable properties explicitly (userInfo, sink, onCrash, etc.)
- Update SentryCrash.m to use bridge.notificationCenterWrapper with fallback
- Add setBridge method to SentryCrashSwift.swift using KVC
- All 4 notificationCenterWrapper references now use bridge when available
…entryCrash

- Call crashReporter.setBridge(bridge) in SentryCrashIntegration init
- Bridge set before crash handler installation (before startCrashHandler)
- Bridge propagation complete: Integration → SentryCrashSwift → SentryCrash.m
- Add sentrycrashcm_nsexception_setBridge function declaration in header
- Add static g_bridge variable to store bridge reference
- Update setEnabled to use g_bridge.crashReporter.uncaughtExceptionHandler
- Add fallback to container when bridge not set
- Call sentrycrashcm_nsexception_setBridge in SentryCrash.m install method
- Wrap ObjC-specific code in __OBJC__ guards for C compatibility
- Bridge injection into SentryCrash and NSException monitor
- 5 container references eliminated with fallback compatibility
- 18.1 min execution time
- All 3 tasks committed atomically
- Add SentryCrashBridge forward declaration to SentryCrashInstallation.h
- Add bridge property to enable SDK services access without container
- Update 4 crashReporter references in SentryCrashInstallation.m to use bridge with container fallback
- Replace container singleton access in dealloc, install, uninstall, sendAllReportsWithCompletion
itaybre and others added 7 commits February 13, 2026 19:18
…ashIntegration

- Add bridge injection to installation after creation using KVC
- Bridge set before install() call to ensure crash handler has access
- Complete bridge propagation path: Integration -> Installation
- Bridge property on base class enables crashReporter access via facade
- 4 container references replaced with bridge-first fallback pattern
- Complete bridge propagation: Integration -> Installation -> lifecycle
- Duration: 26 min, 2 tasks, 3 files modified
Phase 3 (ObjC Isolation) verified and marked complete.
All 11 production container references converted to bridge-first pattern.
Verification passed 6/6 must-haves.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single plan with 2 tasks: run SentryCrash test suite and
confirm zero container references via grep + multi-platform builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ttern

Single plan to add Dependency Isolation section to develop-docs/SENTRYCRASH.md
covering facade rationale, five services, and guidance for adding new dependencies.
@itaybre itaybre added the ready-to-merge Use this label to trigger all PR workflows label Feb 24, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Internal Changes 🔧

  • (samples) Restructure iOS-Swift6 sample by philprime in #7656
  • Remove SentryDependencyContainer uses in SentryCrash by itaybre in #7514

🤖 This preview updates automatically when you update the PR.

@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 91.56627% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.349%. Comparing base (c9976e2) to head (df19413).
⚠️ Report is 128 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
Sources/Sentry/SentryThreadHandle.cpp 0.000% 3 Missing and 1 partial ⚠️
...t/Integrations/SentryCrash/SentryCrashBridge.swift 77.777% 2 Missing ⚠️
Sources/Swift/SentryCrash/SentryCrashWrapper.swift 91.666% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #7514       +/-   ##
=============================================
+ Coverage   85.307%   85.349%   +0.041%     
=============================================
  Files          480       486        +6     
  Lines        28620     28872      +252     
  Branches     12376     12521      +145     
=============================================
+ Hits         24415     24642      +227     
- Misses        4156      4183       +27     
+ Partials        49        47        -2     
Files with missing lines Coverage Δ
Sources/Sentry/SentryClient.m 98.905% <100.000%> (-0.020%) ⬇️
Sources/Sentry/SentryCrashReportConverter.m 96.253% <100.000%> (+0.134%) ⬆️
Sources/Sentry/SentryHub.m 96.268% <100.000%> (-0.219%) ⬇️
Sources/Sentry/SentryNoOpSpan.m 100.000% <100.000%> (ø)
Sources/Sentry/SentryPerformanceTracker.m 99.122% <100.000%> (ø)
Sources/Sentry/SentryStacktraceBuilder.m 74.358% <100.000%> (ø)
Sources/Sentry/SentryTracer.m 98.258% <ø> (+1.440%) ⬆️
Sources/Sentry/SentryTransaction.m 89.772% <100.000%> (ø)
...urces/Sentry/SentryUIEventTrackerTransactionMode.m 100.000% <100.000%> (ø)
...entryCrash/Installations/SentryCrashInstallation.m 49.397% <100.000%> (ø)
... and 10 more

... and 48 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c9976e2...df19413. Read the comment docs.

@itaybre itaybre changed the title Itay/test chore: Isolate SentryCrash Feb 24, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1190.76 ms 1212.47 ms 21.71 ms
Size 24.14 KiB 1.12 MiB 1.10 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
b8fcca6 1228.85 ms 1259.59 ms 30.74 ms
79e2bb8 1216.37 ms 1242.42 ms 26.05 ms
60ea6f8 1209.42 ms 1246.62 ms 37.21 ms
0286ae4 1230.42 ms 1261.39 ms 30.97 ms
14bb180 1222.75 ms 1252.47 ms 29.72 ms
fee8669 1220.50 ms 1231.84 ms 11.34 ms
a8ec727 1225.29 ms 1259.59 ms 34.30 ms
eaeb9bc 1223.18 ms 1256.45 ms 33.27 ms
84bc307 1211.52 ms 1237.09 ms 25.56 ms
7e93d85 1205.28 ms 1243.71 ms 38.44 ms

App size

Revision Plain With Sentry Diff
b8fcca6 24.14 KiB 1.08 MiB 1.06 MiB
79e2bb8 24.14 KiB 1.04 MiB 1.02 MiB
60ea6f8 24.14 KiB 1.12 MiB 1.10 MiB
0286ae4 24.14 KiB 1.12 MiB 1.10 MiB
14bb180 24.14 KiB 1.12 MiB 1.10 MiB
fee8669 24.14 KiB 1.10 MiB 1.08 MiB
a8ec727 24.14 KiB 1.12 MiB 1.10 MiB
eaeb9bc 24.14 KiB 1.12 MiB 1.10 MiB
84bc307 24.14 KiB 1.08 MiB 1.06 MiB
7e93d85 24.14 KiB 1.06 MiB 1.04 MiB

Previous results on branch: itay/test

Startup times

Revision Plain With Sentry Diff
22349e7 1225.47 ms 1259.74 ms 34.27 ms

App size

Revision Plain With Sentry Diff
22349e7 24.14 KiB 1.11 MiB 1.09 MiB

@itaybre itaybre marked this pull request as ready for review March 10, 2026 19:09
@github-actions github-actions bot mentioned this pull request Mar 10, 2026
7 tasks
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Bridge set after monitors already enabled during install
    • Moved sentrycrashcm_nsexception_setBridge call before sentrycrash_install to ensure bridge is available when monitors are enabled.
  • ✅ Fixed: Nullable bridge breaks synchronized dealloc cleanup
    • Added nil handler checks before @synchronized blocks in dealloc, install, and uninstall methods to prevent synchronization failure.

Create PR

Or push these changes by commenting:

@cursor push 2019a71872
Preview (2019a71872)
diff --git a/Sources/SentryCrash/Installations/SentryCrashInstallation.m b/Sources/SentryCrash/Installations/SentryCrashInstallation.m
--- a/Sources/SentryCrash/Installations/SentryCrashInstallation.m
+++ b/Sources/SentryCrash/Installations/SentryCrashInstallation.m
@@ -94,10 +94,12 @@
 - (void)dealloc
 {
     SentryCrashSwift *handler = self.bridge.crashReporter;
-    @synchronized(handler) {
-        if (g_crashHandlerData == self.crashHandlerData) {
-            g_crashHandlerData = NULL;
-            [handler removeOnCrash];
+    if (handler != nil) {
+        @synchronized(handler) {
+            if (g_crashHandlerData == self.crashHandlerData) {
+                g_crashHandlerData = NULL;
+                [handler removeOnCrash];
+            }
         }
     }
 }
@@ -163,23 +165,27 @@
 - (void)install:(NSString *)customCacheDirectory
 {
     SentryCrashSwift *handler = self.bridge.crashReporter;
-    @synchronized(handler) {
-        handler.basePath = customCacheDirectory;
-        g_crashHandlerData = self.crashHandlerData;
-        [handler setupOnCrash];
-        [handler install];
+    if (handler != nil) {
+        @synchronized(handler) {
+            handler.basePath = customCacheDirectory;
+            g_crashHandlerData = self.crashHandlerData;
+            [handler setupOnCrash];
+            [handler install];
+        }
     }
 }
 
 - (void)uninstall
 {
     SentryCrashSwift *handler = self.bridge.crashReporter;
-    @synchronized(handler) {
-        if (g_crashHandlerData == self.crashHandlerData) {
-            g_crashHandlerData = NULL;
-            [handler removeOnCrash];
+    if (handler != nil) {
+        @synchronized(handler) {
+            if (g_crashHandlerData == self.crashHandlerData) {
+                g_crashHandlerData = NULL;
+                [handler removeOnCrash];
+            }
+            [handler uninstall];
         }
-        [handler uninstall];
     }
 }
 

diff --git a/Sources/SentryCrash/Recording/SentryCrash.m b/Sources/SentryCrash/Recording/SentryCrash.m
--- a/Sources/SentryCrash/Recording/SentryCrash.m
+++ b/Sources/SentryCrash/Recording/SentryCrash.m
@@ -231,14 +231,14 @@
     NSString *pathEnd = [@"SentryCrash" stringByAppendingPathComponent:[self getBundleName]];
     NSString *installPath = [self.basePath stringByAppendingPathComponent:pathEnd];
 
+    // Set bridge for NSException monitor before enabling monitors
+    sentrycrashcm_nsexception_setBridge(self.bridge);
+
     _monitoring = sentrycrash_install(self.bundleName.UTF8String, installPath.UTF8String);
     if (self.monitoring == 0) {
         return false;
     }
 
-    // Set bridge for NSException monitor before enabling monitors
-    sentrycrashcm_nsexception_setBridge(self.bridge);
-
 #if SENTRY_HAS_UIKIT
     id<SentryNSNotificationCenterWrapper> notificationCenter
         = self.bridge.notificationCenterWrapper;

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

* SentryDependencyContainerSwiftHelper, providing a clean architectural boundary
* between layers.
*/
@objc @_spi(Private) public final class SentryCrashBridge: NSObject {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

h: I am not convinced of this architectural approach. SentryCrash is a library which was vendored in, so from a user perspective the dependency graph is App > SentrySDK > SentryCrash. But this bridge is now a link from SentryCrash > SentrySDK instead. Instead I believe it would make more sense to use protocols which are defined in SentryCrash and need to be injected by our SDK, or we use a delegate/callback pattern. I am thinking about how the architecture would look like if SentryCrash was a separate target or even an external package, and the SentryNSNotificationCenterWrapper would not be in that package but in ours.

Copy link
Member

@philipphofmann philipphofmann Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

I would approach this by defining Swift protocols that describe the interaction between the Cocoa SDK and the crash reporting component / SentryCrash.

These protocols should provide a generic crash-reporting interface without exposing SentryCrash-specific concepts. The Cocoa SDK would depend only on these protocols.

We then introduce an adapter that implements the protocol and translates the generic crash reporting interface to SentryCrash. Ideally, this keeps changes inside SentryCrash minimal and avoids it needing to be aware that it runs inside Sentry Cocoa. Another significant reason for not making too many changes in SentryCrash is that we can port changes and fixes from KSCrash back to SentryCrash. The more we modify SentryCrash, the more it deviates from KSCrash, and the harder it will be to migrate changes back or to do a potential switch. Which then also applies to any other crash reporter that we might use in the future.

Conceptually:

Cocoa SDK → Crash Reporting Protocol → SentryCrashReportAdapter → SentryCrash

Ideally, when you then swap out SentryCrash, for example, with KSCrash, you would only need to write a new adapter, but you don't need to change the crash reporting protocol. But I think it's okay if we're not fully there yet, because making that actually work is quite some effort. I think the main goal should be to have clear boundaries and make an optional switch in the future easier. If we, at some point, actually switch out the crash report with another one, for example, also on Swift Server only for Linux, it's acceptable to maybe have to adapt the protocol slightly, but it should not be too much effort.

And coming back to what Phil said about SentryCrash being an external package, I think we should work towards that goal. When you could theoretically move SentryCrash into its own target or external package, we know we achieved this goal because the dependencies would be only from the Cocoa SDK to SentryCrash, not the other way around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely agree with that, I wanted to make a slow approach towards the protocol approach.
This was the first step:

  • Remove SentryDependencyContainer uses
    • With this we have a better idea of what SentryCrash needs to use
  • Create the interfaces / protocols (probably bigger than just the bridge here)

The PR title may have been misleading, this doesn't reach full isolation yet

Copy link
Member

@philprime philprime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving this PR based on the discussion we had earlier in the Apple SDK team sync. Please rename the PR to explain that this does not properly isolate SentryCrash but it implements a bridge as a step towards it

@itaybre itaybre changed the title chore: Isolate SentryCrash chore: Remove SentryDependencyContainer uses in SentryCrash Mar 11, 2026
@sentry
Copy link

sentry bot commented Mar 12, 2026

Sentry Build Distribution

App Name App ID Version Configuration Install Page
App io.sentry.sample.SDK-Size 9.7.0 (1) Release Install Build

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

NSSetUncaughtExceptionHandler(&handleUncaughtException);
SentryDependencyContainer.sharedInstance.crashReporter.uncaughtExceptionHandler
= &handleUncaughtException;
g_bridge.crashReporter.uncaughtExceptionHandler = &handleUncaughtException;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nil bridge silently skips uncaught exception handler registration

High Severity

If g_bridge is nil when the NSException monitor is enabled, g_bridge.crashReporter.uncaughtExceptionHandler = &handleUncaughtException silently becomes a no-op due to ObjC nil messaging. The handler is still installed globally via NSSetUncaughtExceptionHandler, but the crash reporter's uncaughtExceptionHandler property won't be set. The previous code via SentryDependencyContainer.sharedInstance guaranteed non-nil access. No warning or error is logged for this failure.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: Isolate SentryCrash

3 participants