Skip to content

fix: catch CoreAnimation exceptions in redaction - #8537

Merged
philprime merged 13 commits into
mainfrom
philprime/catch-animation-exceptions-7810
Jul 29, 2026
Merged

fix: catch CoreAnimation exceptions in redaction#8537
philprime merged 13 commits into
mainfrom
philprime/catch-animation-exceptions-7810

Conversation

@philprime

@philprime philprime commented Jul 23, 2026

Copy link
Copy Markdown
Member

📜 Description

Session Replay could crash the host app with an uncaught NSInvalidArgumentException (-[NSConcreteValue doubleValue]: unrecognized selector) while SentryUIRedactBuilder walked the layer tree to build redaction regions.

Root cause: redactRegionsFor samples view.layer.presentation() and mapRedactRegion reads layer.sublayers. Reading sublayers on a presentation layer makes Core Animation resolve the presentation layers of its children, interpolating any running animation. A malformed CABasicAnimation — one whose endpoints mix a scalar NSNumber with an NSValue boxing a struct (CGPoint/CGRect/CATransform3D) — makes Core Animation interpolate via the NSNumber code path and send doubleValue to 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() and layer.sublayers — through a small Objective-C @try/@catch shim (SentryObjCExceptionHelper), via the safePresentationLayer(of:) and safeSublayers(of:) helpers. This covers all three call sites: redactRegionsFor (root presentation layer), mapRedactRegion (sublayer traversal), and isOpaque(_:) (which also sampled presentation()). 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 excludedViewClasses and also covers UIView-less CALayers (e.g. AVPlayerViewController internals, 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.
  • sublayers throws → 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 mirrors isViewSubtreeIgnored, which already redacts the full region when it must skip a crash-prone subtree. In the extreme case where the root layer's sublayers throws, 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:

Previous 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-mismatched CABasicAnimation on 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 the layer.sublayers site (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:

  • 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.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • If I added a new public API, I also added it to the SentryObjC wrapper.

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 philprime self-assigned this Jul 23, 2026
@philprime philprime added the run-full-ci Allows gated GitHub Action workflows to run for a labelled pull request label Jul 23, 2026
@philprime
philprime marked this pull request as ready for review July 23, 2026 16:36
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift Outdated
@philprime
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.
@philprime
philprime marked this pull request as ready for review July 24, 2026 06:33
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift Outdated
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
@sentry

sentry Bot commented Jul 24, 2026

Copy link
Copy Markdown

📲 Install Builds

iOS

🔗 App Name App ID Version Configuration
SDK-Size io.sentry.sample.SDK-Size 9.23.0 (1) Release

⚙️ sentry-cocoa Build Distribution Settings

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1220.89 ms 1258.44 ms 37.55 ms
Size 24.14 KiB 1.25 MiB 1.23 MiB

Baseline results on branch: main

Startup times

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

Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift Outdated

@philipphofmann philipphofmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for fixing this. I added a few comments.

Comment thread Sources/Sentry/SentryObjCExceptionHelper.m
Comment thread Sources/Sentry/SentryObjCExceptionHelper.m
Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
@philprime
philprime requested a review from philipphofmann July 29, 2026 12:05
@philprime
philprime enabled auto-merge (squash) July 29, 2026 12:05

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

Fix All in Cursor

❌ 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.

Comment thread Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
Comment thread Sources/Sentry/SentryObjCExceptionHelper.m

@philipphofmann philipphofmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@philprime
philprime merged commit fd30f64 into main Jul 29, 2026
285 of 290 checks passed
@philprime
philprime deleted the philprime/catch-animation-exceptions-7810 branch July 29, 2026 13:39
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/)
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/)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved run-full-ci Allows gated GitHub Action workflows to run for a labelled pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session Replay crash: NSConcreteValue doubleValue in mapRedactRegion during RN navigation transitions

3 participants