Skip to content

fix(anr): Use Proguard ID from origination ANR process with ANR profie chunks - #5852

Merged
0xadam-brown merged 2 commits into
mainfrom
fix/anr-deobfuscation
Jul 29, 2026
Merged

fix(anr): Use Proguard ID from origination ANR process with ANR profie chunks#5852
0xadam-brown merged 2 commits into
mainfrom
fix/anr-deobfuscation

Conversation

@0xadam-brown

@0xadam-brown 0xadam-brown commented Jul 29, 2026

Copy link
Copy Markdown
Member

📜 Description

Prior to this PR, we'd always (rightly) bind originating process Proguard IDs to ANR events, but we'd (wrongly) bind current Proguard ID to ANR profile chunks. Usually the originating Proguard ID == the current Proguard ID, and so the discrepancy didn't matter. But the two differ in the situation when a user updates the host app after the ANR occurs but before it's reported. When that happens, deobfuscation for ANR profile chunks in the Sentry UI can break.

This PR ensures both the ANR event and the ANR profile chunk receive the same originating Proguard ID by:

  1. having the entity that determines the originating Proguard ID (ApplicationExitInfoEventProcessor) pass that ID to the profile chunk pipeline; and
  2. updating our DebugMeta.buildDebugMeta(ProfileChunk, ...) method so that the profile chunk pipeline honors (rather than clobbers) the Proguard ID the event processor bakes into the DebugMeta owned by the incoming profile chunk.

💡 Motivation and Context

(Sorry for the wall of text ahead, but it's worth a quick read! Yours truly wrote it to make sure my LLM's approach was sane, so it should be easy to follow. It lays out the problem in detail and our possible options – including closing this PR and living with the edge case.)

Background

We report ANRs from app processs launched after each ANR occurs. Usually the same app installation is used with both processes and so the Proguard ID used to deobfuscate stack traces is the same for each. But not always! A user could have installed a new version of the app in between, so we need to make sure we grab the Proguard ID associated with the original process.

Nicely, logic to do that already exists in ApplicationExitInfoEventProcessor (link). Not so nicely, the Proguard ID produced by that logic isn't used by the profile chunk we associate with the ANR event – or at least it wasn't prior to this PR. (The profile chunk would always use the Proguard ID for the current process.)

The upshot is that app owners would see properly deobfuscated stack traces for the ANR event but possibly broken / still-obfuscated stack traces for the profile chunk in the rare case that someone installs a new version of the host app before the ANR can be reported out.

So what's going on?

ANRs are composites of two separate entities: an ANR event proper and an ANR profile chunk. That distinction goes all the way down, as the Sentry UI displays ANR events and profile chunks in different dashboards. Each entity owns a DebugMeta instance, which is the data structure used to associate a Proguard ID with the entity it belongs to.

In the SDK, the ApplicationExitInfoEventProcessor owns the pipeline for constructing both the ANR event and its profile chunk. It goes like this:

  1. User launches the app after an ANR was recorded.
  2. ApplicationExitInfoEventProcessor recreates the ANR event proper + creates DebugMeta for the event with the Proguard ID from the originating process.
  3. ApplicationExitInfoEventProcessor recreates the profile chunk associated with the ANR and then passes it on to SentryClient.captureProfileChunk().
  4. SentryClient creates DebugMeta for the chunk with the Proguard ID from the current process (which is usually fine but is wrong in our edge case). SentryClient then sends the profile chunk to Relay.
  5. ApplicationExitInfoEventProcessor binds the profile chunk ID to the ANR event and then sends the ANR event to Relay.

Step (2) receives the DebugMeta instance with the correct Proguard ID from ApplicationExitInfoEventProcessor. But our profile chunk processing ignores it and instead selects the Proguard ID from the current process. That’s because ApplicationExitInfoEventProcessor delegates to the existing profile chunk pipeline used for non-ANR profiles, and non-ANR profiles are always concerned with the current app process. (Note that the profile chunk pipeline is shared by the Java. Ideally that’d mean it’s Android-agnostic, but that isn’t quite the case atm, eg, it knows about Proguard IDs.)

What should we do?

Possible approaches:

[A] Introduce the concept of “originating process" into our profile chunk pipeline.

[B] Update the profile chunk API to accept a pre-selected Proguard ID (whether directly as a string param, via a Context/Hint-esque parameter, or by lookup).

[C] Include the originating Proguard ID inside the chunk already passed to our profile chunk pipeline by ApplicationExitInfoEventProcessor + update DebugMeta.builder(incomingAnrDebugMeta, …) so that it honors that ID rather than clobbering it.

[D] Do nothing / close this PR and live with the edge case of potentially (but not inevitably) broken or misleading ANR profile chunk stack traces.

The path I chose

This PR implements [C].

Approach [A] sounded best in the abstract, but it’d be a big lift as a number of the inputs ApplicationExitInfoEventProcessor relies on to decide which Proguard ID to use are either Android-specific or aren’t currently available to our profile chunk pipeline. Option [B] was a more explicit version of [C] but at the cost of modifying the public API in ways that are, at best, debatable.

Option [D] is legit, esp if you look at this PR and think the fix is more of a maintenance burden than it’s worth. (Chime in if you do!)

Addresses: JAVA-549

💚 How did you test it?

Unit tests.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • 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.

🔮 Next steps

…le chunks

We report ANRs from the app process launched _after_ the ANR occurs. Usually the same app installation is used to launch both processes and so the Proguard ID is the same for each. But not always! The user could have installed a new version of the app in between, so we need to make sure we grab the Proguard ID associated with the original process.

Prior to this commit, we would always use the originating Proguard ID with the ANR event but not with the linked ANR profile chunk. Instead, the profile chunk would always use the current process Proguard ID. This commit fixes that by updating the profile chunk pipeline so it respects any incoming debug images (including Proguard ones) in the DebugMeta bound to the profile chunk passed to IScopes.createProfileChunk().
@0xadam-brown
0xadam-brown force-pushed the fix/anr-deobfuscation branch from 28c28de to 75a9e65 Compare July 29, 2026 10:38
@sentry

sentry Bot commented Jul 29, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.50.0 (1) release

⚙️ sentry-android Build Distribution Settings

@0xadam-brown
0xadam-brown marked this pull request as ready for review July 29, 2026 10:56
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 307.77 ms 362.67 ms 54.90 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
6b019b7 319.84 ms 333.15 ms 13.31 ms
c3ee041 310.64 ms 361.90 ms 51.26 ms
d501a7e 348.06 ms 431.42 ms 83.36 ms
3998a95 415.94 ms 478.54 ms 62.60 ms
ed33deb 343.30 ms 362.41 ms 19.10 ms
5b1a06b 310.56 ms 362.79 ms 52.22 ms
91bb874 310.68 ms 359.24 ms 48.56 ms
0ee65e9 317.37 ms 366.50 ms 49.13 ms
6edfca2 305.52 ms 432.78 ms 127.26 ms
5e269de 292.83 ms 379.12 ms 86.29 ms

App size

Revision Plain With Sentry Diff
6b019b7 0 B 0 B 0 B
c3ee041 0 B 0 B 0 B
d501a7e 0 B 0 B 0 B
3998a95 1.58 MiB 2.10 MiB 532.96 KiB
ed33deb 1.58 MiB 2.13 MiB 559.52 KiB
5b1a06b 0 B 0 B 0 B
91bb874 1.58 MiB 2.13 MiB 559.07 KiB
0ee65e9 0 B 0 B 0 B
6edfca2 1.58 MiB 2.13 MiB 559.07 KiB
5e269de 0 B 0 B 0 B

Previous results on branch: fix/anr-deobfuscation

Startup times

Revision Plain With Sentry Diff
a108580 315.29 ms 358.20 ms 42.92 ms

App size

Revision Plain With Sentry Diff
a108580 0 B 0 B 0 B

@0xadam-brown 0xadam-brown added the deep-dive PR needs a thorough review of design, behavior, and edge cases label Jul 29, 2026

@markushi markushi 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!

Comment thread sentry/src/main/java/io/sentry/protocol/DebugMeta.java Outdated
Comment thread sentry/src/main/java/io/sentry/protocol/DebugMeta.java
Co-authored-by: Markus Hintersteiner <markus.hintersteiner@sentry.io>
@0xadam-brown
0xadam-brown enabled auto-merge (squash) July 29, 2026 14:33
@0xadam-brown
0xadam-brown merged commit 1b40080 into main Jul 29, 2026
71 of 72 checks passed
@0xadam-brown
0xadam-brown deleted the fix/anr-deobfuscation branch July 29, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-dive PR needs a thorough review of design, behavior, and edge cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants