From 416b73190ad862e9c23845e1e3acfd2b0eb12e5d Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 30 Jul 2026 14:17:44 +0200 Subject: [PATCH 1/2] perf(core): Use build-time class availability Consult class availability data injected by build tooling before falling back to reflection. Keep the map nullable so consumers without supporting tooling preserve current behavior. Refs JAVA-654 Co-Authored-By: Codex --- .../main/java/io/sentry/util/LoadClass.java | 14 ++++++++ .../test/java/io/sentry/util/LoadClassTest.kt | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/sentry/src/main/java/io/sentry/util/LoadClass.java b/sentry/src/main/java/io/sentry/util/LoadClass.java index 2c39cace39b..c56d3412f85 100644 --- a/sentry/src/main/java/io/sentry/util/LoadClass.java +++ b/sentry/src/main/java/io/sentry/util/LoadClass.java @@ -4,6 +4,7 @@ import io.sentry.ILogger; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,6 +12,9 @@ @Open public class LoadClass { + // Populated by the Sentry Android Gradle plugin for class names it can resolve at build time. + private static @Nullable Map classAvailability; + /** * Loads and initializes a class via reflection. Use this when you intend to actually use the * class (e.g. instantiate it or invoke its methods). The returned class is fully initialized, so @@ -57,6 +61,16 @@ public class LoadClass { * @return true if the class is on the classpath */ public boolean isClassAvailable(final @NotNull String clazz, final @Nullable ILogger logger) { + final @Nullable Map availability = classAvailability; + if (availability != null) { + final @Nullable Boolean available = availability.get(clazz); + if (available != null) { + if (!available && logger != null) { + logger.log(SentryLevel.INFO, "Class not available: " + clazz); + } + return available; + } + } return loadClass(clazz, logger, false) != null; } diff --git a/sentry/src/test/java/io/sentry/util/LoadClassTest.kt b/sentry/src/test/java/io/sentry/util/LoadClassTest.kt index 7a8bc802049..a5117fc4dea 100644 --- a/sentry/src/test/java/io/sentry/util/LoadClassTest.kt +++ b/sentry/src/test/java/io/sentry/util/LoadClassTest.kt @@ -1,5 +1,6 @@ package io.sentry.util +import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertNotNull @@ -7,6 +8,33 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class LoadClassTest { + @Test + fun `isClassAvailable uses known build-time results and reflects unknown classes`() { + setClassAvailability( + mapOf( + "io.sentry.SentryEvent" to false, + "io.sentry.ThisClassDoesNotExist" to true, + ) + ) + + try { + val loadClass = LoadClass() + assertThat(loadClass.isClassAvailable("io.sentry.SentryEvent", null as io.sentry.ILogger?)) + .isFalse() + assertThat( + loadClass.isClassAvailable( + "io.sentry.ThisClassDoesNotExist", + null as io.sentry.ILogger?, + ) + ) + .isTrue() + assertThat(loadClass.isClassAvailable("io.sentry.Sentry", null as io.sentry.ILogger?)) + .isTrue() + } finally { + setClassAvailability(null) + } + } + @Test fun `loadClass returns the class when it is available`() { assertNotNull(LoadClass().loadClass("io.sentry.SentryEvent", null)) @@ -47,6 +75,12 @@ class LoadClassTest { assertTrue(LoadClassInitFlag.initialized) } + + private fun setClassAvailability(availability: Any?) { + val field = LoadClass::class.java.getDeclaredField("classAvailability") + field.isAccessible = true + field.set(null, availability) + } } private object IsClassAvailableNoInitFlag { From 6b596d51b060b228566c8694389ac78ce7c2c88b Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 30 Jul 2026 15:11:53 +0200 Subject: [PATCH 2/2] ref(core): Make class availability package-private Allow package-local tests to set class availability without reflection. Refs LINEAR-JAVA-654 Co-Authored-By: Codex --- sentry/src/main/java/io/sentry/util/LoadClass.java | 2 +- sentry/src/test/java/io/sentry/util/LoadClassTest.kt | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/sentry/src/main/java/io/sentry/util/LoadClass.java b/sentry/src/main/java/io/sentry/util/LoadClass.java index c56d3412f85..bf179fad459 100644 --- a/sentry/src/main/java/io/sentry/util/LoadClass.java +++ b/sentry/src/main/java/io/sentry/util/LoadClass.java @@ -13,7 +13,7 @@ public class LoadClass { // Populated by the Sentry Android Gradle plugin for class names it can resolve at build time. - private static @Nullable Map classAvailability; + static @Nullable Map classAvailability; /** * Loads and initializes a class via reflection. Use this when you intend to actually use the diff --git a/sentry/src/test/java/io/sentry/util/LoadClassTest.kt b/sentry/src/test/java/io/sentry/util/LoadClassTest.kt index a5117fc4dea..4cb921feff8 100644 --- a/sentry/src/test/java/io/sentry/util/LoadClassTest.kt +++ b/sentry/src/test/java/io/sentry/util/LoadClassTest.kt @@ -10,12 +10,11 @@ import kotlin.test.assertTrue class LoadClassTest { @Test fun `isClassAvailable uses known build-time results and reflects unknown classes`() { - setClassAvailability( + LoadClass.classAvailability = mapOf( "io.sentry.SentryEvent" to false, "io.sentry.ThisClassDoesNotExist" to true, ) - ) try { val loadClass = LoadClass() @@ -31,7 +30,7 @@ class LoadClassTest { assertThat(loadClass.isClassAvailable("io.sentry.Sentry", null as io.sentry.ILogger?)) .isTrue() } finally { - setClassAvailability(null) + LoadClass.classAvailability = null } } @@ -75,12 +74,6 @@ class LoadClassTest { assertTrue(LoadClassInitFlag.initialized) } - - private fun setClassAvailability(availability: Any?) { - val field = LoadClass::class.java.getDeclaredField("classAvailability") - field.isAccessible = true - field.set(null, availability) - } } private object IsClassAvailableNoInitFlag {