fix: catch CoreAnimation exceptions in redaction - #8537
Merged
Conversation
Session Replay could crash the host app with an uncaught NSInvalidArgumentException (-[NSConcreteValue doubleValue]) while SentryUIRedactBuilder walked the layer tree to build redaction regions. Resolving a presentation layer that has a malformed running CABasicAnimation makes Core Animation raise an Objective-C exception, which unwinds through the Swift traversal frames and force-kills the app. This is reachable via React Navigation transitions, AVPlayerViewController and iOS 26 Liquid Glass layers, and cannot be handled generically by excludedViewClasses. Route the layer.sublayers and presentation() accesses through an Objective-C try/catch (SentryObjCExceptionHelper) so a throwing subtree is skipped instead of crashing. Fixes #7810
philprime
marked this pull request as ready for review
July 23, 2026 16:36
philprime
requested review from
NinjaLikesCheez,
itaybre,
noahsmartin and
philipphofmann
as code owners
July 23, 2026 16:36
…tion-exceptions-7810
philprime
marked this pull request as draft
July 24, 2026 06:26
Use internal import for _SentryPrivate to match the module and fix the inconsistent implementation-only import build failure. Route isOpaque through safePresentationLayer so the last unguarded presentation() call cannot crash during traversal. Redact the whole subtree when sublayers access throws instead of skipping it, so unreadable content is not left unmasked.
…tion-exceptions-7810
philprime
marked this pull request as ready for review
July 24, 2026 06:33
📲 Install BuildsiOS
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 2fe9650 | 1228.33 ms | 1260.78 ms | 32.46 ms |
| a8fc1c8 | 1226.83 ms | 1248.19 ms | 21.36 ms |
| 27850be | 1222.65 ms | 1254.23 ms | 31.58 ms |
| 241d6ee | 1228.92 ms | 1258.47 ms | 29.55 ms |
| 6307f8b | 1216.23 ms | 1247.74 ms | 31.52 ms |
| 66415de | 1221.21 ms | 1253.28 ms | 32.07 ms |
| 62cb5fd | 1219.45 ms | 1250.56 ms | 31.11 ms |
| c4d02bd | 1219.63 ms | 1248.68 ms | 29.05 ms |
| 8bb6aeb | 1232.15 ms | 1260.78 ms | 28.63 ms |
| dff20e3 | 1225.19 ms | 1258.66 ms | 33.47 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 2fe9650 | 24.14 KiB | 1.17 MiB | 1.15 MiB |
| a8fc1c8 | 24.14 KiB | 1.23 MiB | 1.20 MiB |
| 27850be | 24.14 KiB | 1.18 MiB | 1.16 MiB |
| 241d6ee | 24.14 KiB | 1.24 MiB | 1.22 MiB |
| 6307f8b | 24.14 KiB | 1.23 MiB | 1.20 MiB |
| 66415de | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| 62cb5fd | 24.14 KiB | 1.22 MiB | 1.20 MiB |
| c4d02bd | 24.14 KiB | 1.17 MiB | 1.14 MiB |
| 8bb6aeb | 24.14 KiB | 1.17 MiB | 1.15 MiB |
| dff20e3 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
Previous results on branch: philprime/catch-animation-exceptions-7810
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| c92080d | 1204.35 ms | 1245.94 ms | 41.59 ms |
| eb2bb44 | 1228.63 ms | 1262.96 ms | 34.33 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| c92080d | 24.14 KiB | 1.26 MiB | 1.23 MiB |
| eb2bb44 | 24.14 KiB | 1.24 MiB | 1.22 MiB |
NinjaLikesCheez
approved these changes
Jul 24, 2026
philipphofmann
left a comment
Member
There was a problem hiding this comment.
Thanks for fixing this. I added a few comments.
6 tasks
philprime
enabled auto-merge (squash)
July 29, 2026 12:05
There was a problem hiding this comment.
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.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d4c3607. Configure here.
3 tasks
philprime
added a commit
to getsentry/sentry-docs
that referenced
this pull request
Jul 30, 2026
<!-- Use this checklist to make sure your PR is ready for merge. You may delete any sections you don't need. --> ## DESCRIBE YOUR PR Add troubleshooting guidance for Session Replay regions that become completely masked when Core Animation raises during layer traversal. The entry explains the privacy-safe fallback, identifies mismatched `CABasicAnimation` endpoint types as a possible cause, provides incorrect and corrected Swift examples, and shows the related SDK warning. This documents the behavior introduced by getsentry/sentry-cocoa#8537. ## IS YOUR CHANGE URGENT? Help us prioritize incoming PRs by letting us know when the change needs to go live. - [ ] Urgent deadline (GA date, etc.): <!-- ENTER DATE HERE --> - [ ] Other deadline: <!-- ENTER DATE HERE --> - [x] None: Not urgent, can wait up to 1 week+ ## SLA - Teamwork makes the dream work, so please add a reviewer to your PRs. - Please give the docs team up to 1 week to review your PR unless you've added an urgent due date to it. Thanks in advance for your help! ## PRE-MERGE CHECKLIST _Make sure you've checked the following before merging your changes:_ - [ ] Checked Vercel preview for correctness, including links - [ ] PR was reviewed and approved by any necessary SMEs (subject matter experts) - [ ] PR was reviewed and approved by a member of the [Sentry docs team](https://github.com/orgs/getsentry/teams/docs) ## EXTRA RESOURCES - [Sentry Docs contributor guide](https://docs.sentry.io/contributing/)
6 tasks
philprime
added a commit
to getsentry/sentry-docs
that referenced
this pull request
Jul 31, 2026
<!-- Use this checklist to make sure your PR is ready for merge. You may delete any sections you don't need. --> ## DESCRIBE YOUR PR Follow-up to #18861 addressing the [review feedback](#18861 (comment)): the troubleshooting entry for completely masked replays now states that the privacy-safe fallback applies starting with Sentry Cocoa **9.24.0**, the release that shipped the fix (getsentry/sentry-cocoa#8537). ## IS YOUR CHANGE URGENT? Help us prioritize incoming PRs by letting us know when the change needs to go live. - [ ] Urgent deadline (GA date, etc.): <!-- ENTER DATE HERE --> - [ ] Other deadline: <!-- ENTER DATE HERE --> - [x] None: Not urgent, can wait up to 1 week+ ## SLA - Teamwork makes the dream work, so please add a reviewer to your PRs. - Please give the docs team up to 1 week to review your PR unless you've added an urgent due date to it. Thanks in advance for your help! ## PRE-MERGE CHECKLIST *Make sure you've checked the following before merging your changes:* - [ ] Checked Vercel preview for correctness, including links - [ ] PR was reviewed and approved by any necessary SMEs (subject matter experts) - [ ] PR was reviewed and approved by a member of the [Sentry docs team](https://github.com/orgs/getsentry/teams/docs) ## EXTRA RESOURCES - [Sentry Docs contributor guide](https://docs.sentry.io/contributing/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

📜 Description
Session Replay could crash the host app with an uncaught
NSInvalidArgumentException(-[NSConcreteValue doubleValue]: unrecognized selector) whileSentryUIRedactBuilderwalked the layer tree to build redaction regions.Root cause:
redactRegionsForsamplesview.layer.presentation()andmapRedactRegionreadslayer.sublayers. Readingsublayerson a presentation layer makes Core Animation resolve the presentation layers of its children, interpolating any running animation. A malformedCABasicAnimation— one whose endpoints mix a scalarNSNumberwith anNSValueboxing a struct (CGPoint/CGRect/CATransform3D) — makes Core Animation interpolate via theNSNumbercode path and senddoubleValueto the boxed struct, raising an Objective-C exception. That exception unwinds through the Swift traversal frames (which cannot catch Objective-C exceptions) and force-kills the app.Fix: route every Core Animation access point that can trigger this —
presentation()andlayer.sublayers— through a small Objective-C@try/@catchshim (SentryObjCExceptionHelper), via thesafePresentationLayer(of:)andsafeSublayers(of:)helpers. This covers all three call sites:redactRegionsFor(root presentation layer),mapRedactRegion(sublayer traversal), andisOpaque(_:)(which also sampledpresentation()). If Core Animation raises, the SDK recovers instead of crashing, so a passive Session Replay screenshot can never crash the host app.This is independent of
excludedViewClassesand also covers UIView-lessCALayers (e.g.AVPlayerViewControllerinternals, iOS 26 Liquid Glass layers), which the class-name allowlist cannot reach.🎨 Masking behavior when Core Animation raises
Because this runs in a privacy-sensitive Session Replay path, the two access points recover in the direction that never undermasks (never leaves content visible that should be hidden):
presentation()throws → fall back to the model layer. No content is dropped: the subtree is still fully traversed and redacted. The only difference is geometry — we use the layer's resting/target frame instead of the in-flight animated frame, so a screenshot taken mid-animation may place the mask slightly off from the on-screen position during the brief animation window. The content region itself is never skipped.sublayersthrows → overmask the whole subtree. We cannot enumerate the children to decide what needs redacting, so instead of skipping the subtree (which would leak any unmasked text/images inside it) we redact the entire layer bounds. This mirrorsisViewSubtreeIgnored, which already redacts the full region when it must skip a crash-prone subtree. In the extreme case where the root layer'ssublayersthrows, the whole screen is masked — a degraded but privacy-safe screenshot, which is strictly preferable to leaking the full unmasked screen.In short: on a Core Animation failure we may briefly overmask, never undermask.
💡 Motivation and Context
Fixes #7810.
The same
-[NSConcreteValue doubleValue]crash has been reported repeatedly with different triggers, all sharing this root cause:expo-videoAVPlayerViewControllerfullscreenSentryViewPhotographercrash in SwiftUI apps (~2% of sessions)UIImagePickerController) on iOS 26react-native-videofullscreen; same stack tracePrevious mitigations added per-view-class allowlisting (
excludedViewClasses), which cannot fix the crash generically. This change makes the traversal itself defensive.💚 How did you test it?
Added two tests under
Tests/SentryTests/ViewCapture/:SentryUIRedactBuilderTests+RealAnimationCrash.swift— makes Core Animation itself raise the exception via a type-mismatchedCABasicAnimationon a view in a rendered key window, reproducing the exact reported stack trace and-[NSConcreteValue doubleValue]failure. This is the proof of root cause.SentryUIRedactBuilderTests+LayerTraversalCrash.swift— deterministic guard that injects the exception at thelayer.sublayerssite (and a root-layer variant). It asserts both that traversal survives and that the masking contract holds: sibling content is still redacted, and the crashing subtree is overmasked with a full-bounds redact region (whole screen for the root-layer variant).Both crash before the fix and pass after it.
📝 Checklist
You have to check all boxes before merging:
sendDefaultPIIis enabled.