Skip to content

fix: Narrow Throwable catches where LinkageError is expected (JAVA-552) - #6562

Draft
runningcode wants to merge 2 commits into
9.x.xfrom
no/java-552-narrow-unsafe-throwable-catches
Draft

runningcode wants to merge 2 commits into
9.x.xfrom
no/java-552-narrow-unsafe-throwable-catches

Conversation

@runningcode

Copy link
Copy Markdown
Contributor

📜 Description

ExceptionUtils.rethrowIfFatal rethrows LinkageError. At the 28 catch (Throwable) sites in
this PR, a LinkageError is the expected failure, not a fatal one. Each one comes from a native
library that failed to load, a missing optional dependency, or a mismatched Compose or androidx
version. This PR makes those sites safe before rethrowIfFatal is added anywhere else:

  • Narrowed to the types the guarded code throws, with no broad catch left:
    • LoadClass, the Spotlight probe in SentryOptions, and ProfilingServiceLoader
    • NdkIntegration and the SentryNdk native library load
    • AppState, the AbstractMethodError case from fix: AbstractMethodError when getting Lifecycle #2228
    • The reflection in replay's Windows.kt, Nodes.kt and ComposeViewHierarchyNode, and in
      SentryComposeHelper
  • Kept broad, because the code calls app views, user integrations or JNI. These catch
    LinkageError first, then call rethrowIfFatal in the catch (Throwable) and say in a comment
    why it's broad:
    • Integration.register in Sentry.init
    • ViewHierarchyEventProcessor and SentryWindowCallback
    • DebugImagesLoader
    • The OTel SentryContextStorageProvider
    • The Compose traversal in replay's Nodes.kt and ComposeViewHierarchyNode
  • UncaughtExceptionHandlerIntegration: stays broad without rethrowIfFatal. A rethrow there
    would skip the chained default handler. Only a comment is added.

It also fixes three bugs found during the audit:

  • QueueFile deleted its file when it hit an OutOfMemoryError.
  • Installation wrapped an OutOfMemoryError in a RuntimeException.
  • The Ktor client integration swallowed coroutine cancellation while reading the response body
    for a failed-request event.

💡 Motivation and Context

This is the first part of the catch (Throwable) audit. About 176 other sites are boundaries
that should call rethrowIfFatal, and about 103 should be narrowed. Doing that mechanically before
this PR lands would crash apps that work today, for example:

  • When the NDK library fails to load
  • When Sentry.init registers an integration whose optional dependency is missing
  • On every touch, when the app uses a Compose version that doesn't match

💚 How did you test it?

  • New unit tests:
    • SentryTest: an integration that throws a LinkageError or a RuntimeException is skipped
      and the next one still registers; an OutOfMemoryError propagates out of Sentry.init.
    • LoadClassTest: a class whose static initializer throws returns null.
  • Ran the unit tests of sentry, sentry-android-core, sentry-android-ndk,
    sentry-android-replay, sentry-compose, sentry-opentelemetry-bootstrap,
    sentry-ktor-client and sentry-async-profiler, plus detekt.

📝 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

  • Narrow the sites where the thrown types are known. Most are RejectedExecutionException around
    executor.submit, file I/O, and parsing.
  • Add rethrowIfFatal and a comment to the boundary sites, one PR per module group.

🤖 Generated with Claude Code

ExceptionUtils.rethrowIfFatal rethrows LinkageError, but at these sites a LinkageError is
the expected failure: a native library that failed to load, an optional dependency that is
missing, or a Compose or androidx version that doesn't match. Catch it explicitly first, and
narrow the rest to the types the guarded code throws. Where the code calls app views, user
integrations or JNI, keep the broad catch and call rethrowIfFatal after the LinkageError arm.

Also stop QueueFile from deleting its file on an OutOfMemoryError, stop Installation from
wrapping one in a RuntimeException, and stop the Ktor client integration from swallowing
coroutine cancellation while reading the response body.

Refs JAVA-552

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Nov 11, 2026

Copy link
Copy Markdown

JAVA-552

@sentry

sentry Bot commented Nov 11, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-android Build Distribution Settings

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant