From 1548c9c5981cd256c3a74e9d7e0dc029eea7c240 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Sat, 6 Jun 2026 08:48:18 +0200 Subject: [PATCH 01/14] feat(plugin): Auto-wrap SQLiteDriver with SentrySQLiteDriver for Room users (GRADLE-107) Adds a new ASM bytecode method visitor that lets us auto-wrap all occurrences of SQLiteDriver with SentrySQLiteDriver whenever the driver is passed to Room.DatabaseBuilder.setDriver(...). For instance: val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName") .setDriver(AndroidSQLiteDriver()) .build() becomes: val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName") .setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver())) .build() The wrapping policy is naive in that every SQLiteDriver passed to setDriver() is wrapped. That's deliberate because SentrySQLiteDriver protects against double-wrapping internally, which lets us keep our visitor implementation simple. Preconditions: 1. InstrumentationFeature.DATABASE is enabled 2. The owning app is using a version of sentry-android-sqlite that includes SentrySQLiteDriver Coverage: - Auto-wraps SQLiteDriver for all Room users (sole Room access point is via its Room.DatabaseBuilder.setDriver() method). - SQLDelight users don't need driver auto-wrapping (they still use SupportSQLiteOpenHelper, which we already auto-wrap). - The few developers who use SQLiteDriver directly will need to wrap it manually. --- CHANGELOG.md | 10 +- gradle/libs.versions.toml | 5 +- plugin-build/build.gradle.kts | 11 +- .../TracingInstrumentationExtension.kt | 13 +- .../SpanAddingClassVisitorFactory.kt | 3 + .../sqlite/driver/AndroidXSQLiteDriver.kt | 90 ++++++++++ .../driver/visitor/SetDriverMethodVisitor.kt | 34 ++++ .../gradle/services/SentryModulesService.kt | 13 ++ .../io/sentry/android/gradle/util/Versions.kt | 1 + .../kotlin/androidx/sqlite/SQLiteDriver.kt | 3 + .../gradle/instrumentation/VisitorTest.kt | 15 ++ .../sqlite/driver/AndroidXSQLiteDriverTest.kt | 71 ++++++++ .../driver/SQLiteDriverBytecodeTestUtil.kt | 55 ++++++ .../SetDriverMethodInstrumentableTest.kt | 48 +++++ .../visitor/SetDriverMethodVisitorTest.kt | 84 +++++++++ .../gradle/integration/SentryPluginTest.kt | 139 +++++++++++++++ .../gradle/util/SentryModulesServiceTest.kt | 164 ++++++++++++++++++ .../io/sentry/sqlite/SentrySQLiteDriver.kt | 9 + .../androidxRoom/RoomDatabase$Builder.class | Bin 0 -> 30384 bytes .../androidxRoom/RoomDatabase3$Builder.class | Bin 0 -> 26703 bytes .../androidxSqliteDriver/NoSetDriver.class | Bin 0 -> 269 bytes 21 files changed, 759 insertions(+), 9 deletions(-) create mode 100644 plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt create mode 100644 plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt create mode 100644 plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt create mode 100644 plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt create mode 100644 plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt create mode 100644 plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SetDriverMethodInstrumentableTest.kt create mode 100644 plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt create mode 100644 plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt create mode 100644 plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt create mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase$Builder.class create mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase3$Builder.class create mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqliteDriver/NoSetDriver.class diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf1f4b2a..e5b6b6f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ ## Unreleased -### Dependencies +### Features -- Bump mockito-kotlin from `com.nhaarman.mockitokotlin2:2.2.0` to `org.mockito.kotlin:5.4.0` ([#1286](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1286)) +- Auto-instrument SQLiteDriver for Room users ([#1285](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1285)) + - Gated on `sentry-android-sqlite` >= 8.44.0 and the existing `tracingInstrumentation` `DATABASE` feature + - For users of the `androidx.sqlite.driver.SupportSQLiteDriver` bridge, auto-instrumentation wraps only the `SupportSQLiteOpenHelper` consumed by the bridge and not the bridge itself (avoids duplicate spans) ### Fixes @@ -12,6 +14,10 @@ - This fixed the issue where sentry-cli could not be found (`A problem occurred starting process 'command ../sentry-cliXXX.exe'`) - Defer the telemetry default-org lookup to execution time so the configuration cache no longer re-runs `sentry-cli` on every build ([#1263](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1263)) +### Dependencies + +- Bump mockito-kotlin from `com.nhaarman.mockitokotlin2:2.2.0` to `org.mockito.kotlin:5.4.0` ([#1286](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1286)) + ## 6.10.0 ### Features diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75831f373..4693b4e53 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,8 +60,11 @@ sentryAndroidOkhttp = { group = "io.sentry", name = "sentry-android-okhttp", ver sentrySpringBootJakarta = { group = "io.sentry", name = "sentry-spring-boot-starter-jakarta", version.ref = "sentry" } # test -mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } arscLib = { group = "io.github.reandroid", name = "ARSCLib", version = "1.1.4" } +mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } +# Room & Room 3 runtime versions must match RoomDatabase$Builder bytecode fixtures (see SQLiteDriverBytecodeTestUtil) +roomRuntimeAndroid = { group = "androidx.room", name = "room-runtime-android", version = "2.7.0" } +room3RuntimeAndroid = { group = "androidx.room3", name = "room3-runtime-android", version = "3.0.0-alpha06" } zip4j = { group = "net.lingala.zip4j", name = "zip4j", version = "2.11.5" } # samples diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index 38232b324..117b0007a 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -55,12 +55,17 @@ dependencies { testImplementation(libs.asmCommons) // we need these dependencies for tests, because the bytecode verifier also analyzes superclasses + testImplementationAar(libs.roomRuntimeAndroid) + testImplementationAar(libs.room3RuntimeAndroid) + testImplementationAar(libs.sentryAndroid) + testImplementationAar(libs.sentryAndroidOkhttp) testImplementationAar(libs.sqlite) testImplementationAar(libs.sqliteFramework) - testRuntimeOnly(files(androidSdkPath)) - testImplementationAar(libs.sentryAndroid) + + testImplementation(libs.sample.coroutines.core) testImplementation(libs.sentryOkhttp) - testImplementationAar(libs.sentryAndroidOkhttp) + + testRuntimeOnly(files(androidSdkPath)) // Needed to read contents from APK/Source Bundles testImplementation(libs.arscLib) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt index d51ca26f4..a7ee1171a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt @@ -75,9 +75,16 @@ open class TracingInstrumentationExtension @Inject constructor(objects: ObjectFa enum class InstrumentationFeature(val integrationName: String) { /** - * When enabled the SDK will create spans for any CRUD operation performed by - * 'androidx.sqlite.db.SupportSQLiteOpenHelper' and 'androidx.room'. This feature uses bytecode - * manipulation. + * When enabled the SDK will create spans for database operations at two levels: + * + * **SQL statement execution** (`db.sql.query` spans): Wraps the low-level db open helper or + * driver so each SQL statement produces one or more spans. + * + * **DAO method execution** (`db.sql.room` spans): Wraps each public method on Room's generated + * `@Dao` `_Impl` classes, measuring the full DAO call end-to-end (transaction management, query + * execution, and cursor processing). Only for Room users and only on versions prior to Room 2.7. + * + * This feature uses bytecode manipulation. */ DATABASE("DatabaseInstrumentation"), diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt index fd4699f9c..0a22d2b68 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt @@ -10,6 +10,7 @@ import io.sentry.android.gradle.instrumentation.androidx.compose.ComposeNavigati import io.sentry.android.gradle.instrumentation.androidx.room.AndroidXRoomDao import io.sentry.android.gradle.instrumentation.androidx.sqlite.AndroidXSQLiteOpenHelper import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.AndroidXSQLiteDatabase +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.AndroidXSQLiteDriver import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement import io.sentry.android.gradle.instrumentation.appstart.Application import io.sentry.android.gradle.instrumentation.appstart.ContentProvider @@ -90,10 +91,12 @@ abstract class SpanAddingClassVisitorFactory : ChainedInstrumentable( listOfNotNull( AndroidXSQLiteOpenHelper().takeIf { sentryModulesService.isNewDatabaseInstrEnabled() }, + AndroidXSQLiteDriver().takeIf { sentryModulesService.isSQLiteDriverInstrEnabled() }, AndroidXSQLiteDatabase().takeIf { sentryModulesService.isOldDatabaseInstrEnabled() }, AndroidXSQLiteStatement(androidXSqliteFrameWorkVersion).takeIf { sentryModulesService.isOldDatabaseInstrEnabled() }, + // Note that DAO spans no longer work on Room 2.7+ or Room 3.0+ due to Room API changes. AndroidXRoomDao().takeIf { sentryModulesService.isNewDatabaseInstrEnabled() || sentryModulesService.isOldDatabaseInstrEnabled() diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt new file mode 100644 index 000000000..f207e5ae1 --- /dev/null +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt @@ -0,0 +1,90 @@ +package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver + +import com.android.build.api.instrumentation.ClassContext +import io.sentry.android.gradle.instrumentation.ClassInstrumentable +import io.sentry.android.gradle.instrumentation.CommonClassVisitor +import io.sentry.android.gradle.instrumentation.MethodContext +import io.sentry.android.gradle.instrumentation.MethodInstrumentable +import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor.SetDriverMethodVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor + +/** + * Auto-instruments `SQLiteDriver` for all Room users by wrapping any driver passed to + * `RoomDatabase.Builder.setDriver(SQLiteDriver)`. + * + * In other words, this: + * ```kotlin + * val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName") + * .setDriver(AndroidSQLiteDriver()) + * .build() + * ``` + * + * becomes: + * ```kotlin + * val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName") + * .setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver())) + * .build() + * ``` + * + * `SentrySQLiteDriver` protects against duplicate wrappings, allowing the visitor to wrap + * `SQLiteDriver` unconditionally. + * + * Coverage is limited to Room because SQLDelight + * [doesn't support `SQLiteDriver`](https://github.com/sqldelight/sqldelight/issues/6072) (it uses + * `SupportSQLiteOpenHelper`, which we auto-instrument via + * [AndroidXSQLiteOpenHelper][io.sentry.android.gradle.instrumentation.androidx.sqlite.AndroidXSQLiteOpenHelper]). + * To keep our implementation simple and build times fast, developers who use `SQLiteDriver` + * directly are expected to wrap it themselves. + */ +class AndroidXSQLiteDriver : ClassInstrumentable { + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + val currentClassName = instrumentableContext.currentClassData.className + + return CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = currentClassName.substringAfterLast('.'), + methodInstrumentables = listOf(SetDriverMethodInstrumentable()), + parameters = parameters, + ) + } + + override fun isInstrumentable(data: ClassContext): Boolean = + data.currentClassData.className in TARGET_CLASSES + + companion object { + + // Currently covers Room 2 and Room 3 packages. Update as needed. + internal val TARGET_CLASSES = + setOf("androidx.room.RoomDatabase\$Builder", "androidx.room3.RoomDatabase\$Builder") + } +} + +class SetDriverMethodInstrumentable : MethodInstrumentable { + + override val fqName: String + get() = SET_DRIVER + + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = SetDriverMethodVisitor(apiVersion, originalVisitor, instrumentableContext) + + override fun isInstrumentable(data: MethodContext): Boolean = + data.name == SET_DRIVER && data.descriptor?.startsWith(SET_DRIVER_DESCRIPTOR_PREFIX) == true + + companion object { + internal const val SET_DRIVER = "setDriver" + internal const val SET_DRIVER_DESCRIPTOR_PREFIX = "(Landroidx/sqlite/SQLiteDriver;)" + } +} diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt new file mode 100644 index 000000000..7fe22cbee --- /dev/null +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt @@ -0,0 +1,34 @@ +package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor + +import io.sentry.android.gradle.instrumentation.MethodContext +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Type +import org.objectweb.asm.commons.AdviceAdapter +import org.objectweb.asm.commons.Method + +class SetDriverMethodVisitor( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : + AdviceAdapter( + apiVersion, + originalVisitor, + instrumentableContext.access, + instrumentableContext.name, + instrumentableContext.descriptor, + ) { + + override fun onMethodEnter() { + loadArg(0) + invokeStatic(Type.getType(SENTRY_SQLITE_DRIVER_TYPE), Method(CREATE, SENTRY_CREATE_DESCRIPTOR)) + storeArg(0) + } + + companion object { + internal const val CREATE = "create" + internal const val SENTRY_CREATE_DESCRIPTOR = + "(Landroidx/sqlite/SQLiteDriver;)Landroidx/sqlite/SQLiteDriver;" + internal const val SENTRY_SQLITE_DRIVER_TYPE = "Lio/sentry/sqlite/SentrySQLiteDriver;" + } +} diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt index 29ba71bb3..62bf25c3f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt @@ -72,6 +72,19 @@ abstract class SentryModulesService : sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_SQLITE, SentryVersions.VERSION_SQLITE) && parameters.features.get().contains(InstrumentationFeature.DATABASE) + /** + * Returns true when the owning app uses a version of sentry-android-sqlite that contains + * `SentrySQLiteDriver` and the DATABASE feature is enabled. + * + * Room version is not gated here: Room < 2.7 has no matching `setDriver` method, so + * instrumentation is a no-op. Method descriptor matching is the safety boundary. + */ + fun isSQLiteDriverInstrEnabled(): Boolean = + sentryModules.isAtLeast( + SentryModules.SENTRY_ANDROID_SQLITE, + SentryVersions.VERSION_SQLITE_DRIVER, + ) && parameters.features.get().contains(InstrumentationFeature.DATABASE) + fun isOldDatabaseInstrEnabled(): Boolean = !isNewDatabaseInstrEnabled() && sentryModules.isAtLeast( diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt index 8485bde93..d5ec03683 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt @@ -33,6 +33,7 @@ internal object SentryVersions { internal val VERSION_LOGCAT = SemVer(6, 17, 0) internal val VERSION_APP_START = SemVer(7, 1, 0) internal val VERSION_SQLITE = SemVer(6, 21, 0) + internal val VERSION_SQLITE_DRIVER = SemVer(8, 44, 0) internal val VERSION_ANDROID_OKHTTP_LISTENER = SemVer(6, 20, 0) internal val VERSION_OKHTTP = SemVer(7, 0, 0) } diff --git a/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt b/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt new file mode 100644 index 000000000..187e5e6a8 --- /dev/null +++ b/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt @@ -0,0 +1,3 @@ +package androidx.sqlite + +interface SQLiteDriver diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt index 56ba455bf..819d56cc3 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt @@ -5,6 +5,7 @@ import io.sentry.android.gradle.instrumentation.androidx.compose.ComposeNavigati import io.sentry.android.gradle.instrumentation.androidx.room.AndroidXRoomDao import io.sentry.android.gradle.instrumentation.androidx.sqlite.AndroidXSQLiteOpenHelper import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.AndroidXSQLiteDatabase +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.AndroidXSQLiteDriver import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement import io.sentry.android.gradle.instrumentation.appstart.Application import io.sentry.android.gradle.instrumentation.appstart.ContentProvider @@ -122,6 +123,20 @@ class VisitorTest( AndroidXSQLiteStatement(SemVer(2, 3, 0)), null, ), + // RoomDatabase$Builder fixtures: see SQLiteDriverBytecodeTestUtil (extracted from published + // AARs). + arrayOf( + "androidxRoom", + "RoomDatabase\$Builder", + AndroidXSQLiteDriver(), + TestClassContext("androidx.room.RoomDatabase\$Builder"), + ), + arrayOf( + "androidxRoom", + "RoomDatabase3\$Builder", + AndroidXSQLiteDriver(), + TestClassContext("androidx.room3.RoomDatabase\$Builder"), + ), roomDaoTestParameters("DeleteAndReturnUnit"), roomDaoTestParameters("InsertAndReturnLong"), roomDaoTestParameters("InsertAndReturnUnit"), diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt new file mode 100644 index 000000000..bcc844eeb --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt @@ -0,0 +1,71 @@ +@file:Suppress("UnstableApiUsage") + +package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver + +import io.sentry.android.gradle.instrumentation.ChainedInstrumentable +import io.sentry.android.gradle.instrumentation.fakes.TestClassContext +import io.sentry.android.gradle.instrumentation.fakes.TestSpanAddingParameters +import java.io.FileInputStream +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes + +class AndroidXSQLiteDriverTest { + + @get:Rule val tmpDir = TemporaryFolder() + + private val instrumentable = AndroidXSQLiteDriver() + + @Test + fun `isInstrumentable returns true for RoomDatabase Builder classes`() { + assertTrue( + instrumentable.isInstrumentable(TestClassContext("androidx.room.RoomDatabase\$Builder")) + ) + assertTrue( + instrumentable.isInstrumentable(TestClassContext("androidx.room3.RoomDatabase\$Builder")) + ) + } + + @Test + fun `isInstrumentable returns false for unrelated classes`() { + assertFalse(instrumentable.isInstrumentable(TestClassContext("com.example.RoomConfig"))) + assertFalse(instrumentable.isInstrumentable(TestClassContext("io.sentry.Sentry"))) + assertFalse(instrumentable.isInstrumentable(TestClassContext("com.example.FakeSetDriver"))) + } + + @Test + fun `ChainedInstrumentable does not instrument unrelated classes`() { + val className = "com.example.NoSetDriver" + val originalBytes = loadNoSetDriverFixtureBytes() + val instrumentedBytes = instrumentThroughChain(className, originalBytes) + + assertEquals(0, SQLiteDriverBytecodeTestUtil.countWrapCalls(instrumentedBytes)) + } + + private fun instrumentThroughChain(className: String, bytes: ByteArray): ByteArray { + val classReader = ClassReader(bytes) + val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) + val classVisitor = + ChainedInstrumentable(listOf(instrumentable)) + .getVisitor( + TestClassContext(className), + Opcodes.ASM9, + classWriter, + parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root), + ) + classReader.accept(classVisitor, ClassReader.SKIP_FRAMES) + return classWriter.toByteArray() + } + + private fun loadNoSetDriverFixtureBytes(): ByteArray = + FileInputStream( + "src/test/resources/testFixtures/instrumentation/androidxSqliteDriver/NoSetDriver.class" + ) + .use { it.readBytes() } +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt new file mode 100644 index 000000000..918f6d5c1 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt @@ -0,0 +1,55 @@ +package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver + +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor.SetDriverMethodVisitor +import java.io.FileInputStream +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode + +internal object SQLiteDriverBytecodeTestUtil { + + private const val FIXTURES_ROOT = "src/test/resources/testFixtures/instrumentation/androidxRoom" + + /** + * Room `Builder` bytecode fixtures extracted from published AARs: + * - `RoomDatabase$Builder.class`: `androidx.room:room-runtime-android:2.7.0` + * - `RoomDatabase3$Builder.class`: `androidx.room3:room3-runtime-android:3.0.0-alpha06` + * + * Extract from Google Maven by unzipping each AAR's `classes.jar` and copying + * `androidx/room/RoomDatabase$Builder.class` (or `androidx/room3/...`). + * + * `VisitorTest` needs matching Room runtime AARs (and coroutines) on the test classpath so ASM + * can resolve types referenced by the real bytecode. + */ + private val CLASS_NAME_TO_FIXTURE = + mapOf( + "androidx.room.RoomDatabase\$Builder" to "RoomDatabase\$Builder", + "androidx.room3.RoomDatabase\$Builder" to "RoomDatabase3\$Builder", + ) + + fun loadRoomBuilderFixture(className: String): ByteArray { + val fixtureName = + CLASS_NAME_TO_FIXTURE[className] ?: error("No committed fixture for class $className") + return FileInputStream("$FIXTURES_ROOT/$fixtureName.class").use { it.readBytes() } + } + + fun isWrapCall(insn: MethodInsnNode): Boolean = + insn.opcode == Opcodes.INVOKESTATIC && + insn.owner == Type.getType(SetDriverMethodVisitor.SENTRY_SQLITE_DRIVER_TYPE).internalName && + insn.name == SetDriverMethodVisitor.CREATE && + insn.desc == SetDriverMethodVisitor.SENTRY_CREATE_DESCRIPTOR + + fun isSetDriverDescriptor(descriptor: String): Boolean = + descriptor.startsWith(SetDriverMethodInstrumentable.SET_DRIVER_DESCRIPTOR_PREFIX) + + fun countWrapCalls(bytes: ByteArray): Int { + val classNode = ClassNode().also { ClassReader(bytes).accept(it, 0) } + return classNode.methods.sumOf(::countWrapCalls) + } + + fun countWrapCalls(method: MethodNode): Int = + method.instructions.filterIsInstance().count(::isWrapCall) +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SetDriverMethodInstrumentableTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SetDriverMethodInstrumentableTest.kt new file mode 100644 index 000000000..421dabeb0 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SetDriverMethodInstrumentableTest.kt @@ -0,0 +1,48 @@ +package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver + +import io.sentry.android.gradle.instrumentation.MethodContext +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.Test +import org.objectweb.asm.Opcodes + +class SetDriverMethodInstrumentableTest { + + private val instrumentable = SetDriverMethodInstrumentable() + + @Test + fun `isInstrumentable returns true for setDriver with SQLiteDriver parameter`() { + assertTrue( + instrumentable.isInstrumentable( + methodContext( + SetDriverMethodInstrumentable.SET_DRIVER, + "${SetDriverMethodInstrumentable.SET_DRIVER_DESCRIPTOR_PREFIX}Landroidx/room/RoomDatabase\$Builder;", + ) + ) + ) + } + + @Test + fun `isInstrumentable returns false for unrelated method names`() { + assertFalse(instrumentable.isInstrumentable(methodContext("build", "()V"))) + } + + @Test + fun `isInstrumentable returns false for setDriver with non-SQLiteDriver descriptor`() { + assertFalse( + instrumentable.isInstrumentable( + methodContext(SetDriverMethodInstrumentable.SET_DRIVER, "(Ljava/lang/Object;)V") + ) + ) + } + + @Test + fun `isInstrumentable returns false when descriptor is null`() { + assertFalse( + instrumentable.isInstrumentable(methodContext(SetDriverMethodInstrumentable.SET_DRIVER, null)) + ) + } + + private fun methodContext(name: String, descriptor: String?) = + MethodContext(Opcodes.ACC_PUBLIC, name, descriptor, null, null) +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt new file mode 100644 index 000000000..10e3d0f58 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt @@ -0,0 +1,84 @@ +@file:Suppress("UnstableApiUsage") + +package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor + +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.AndroidXSQLiteDriver +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SQLiteDriverBytecodeTestUtil +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SetDriverMethodInstrumentable +import io.sentry.android.gradle.instrumentation.fakes.TestClassContext +import io.sentry.android.gradle.instrumentation.fakes.TestSpanAddingParameters +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode + +class SetDriverMethodVisitorTest { + + @get:Rule val tmpDir = TemporaryFolder() + + @Test + fun `wraps the driver parameter at the start of Room 2_x Builder setDriver`() { + assertSetDriverWrappedOnce("androidx.room.RoomDatabase\$Builder") + } + + @Test + fun `wraps the driver parameter at the start of Room 3_x Builder setDriver`() { + assertSetDriverWrappedOnce("androidx.room3.RoomDatabase\$Builder") + } + + private fun assertSetDriverWrappedOnce(className: String) { + val instrumentedBytes = instrument(className) + val setDriverMethod = findSetDriverMethod(instrumentedBytes) + + assertEquals( + 1, + SQLiteDriverBytecodeTestUtil.countWrapCalls(setDriverMethod), + "setDriver should contain exactly one wrap", + ) + assertTrue( + wrapPrecedesOriginalBody(setDriverMethod), + "SentrySQLiteDriver.create() must run before the original setDriver body", + ) + } + + private fun instrument(className: String): ByteArray { + val bytes = SQLiteDriverBytecodeTestUtil.loadRoomBuilderFixture(className) + val classReader = ClassReader(bytes) + val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) + val classVisitor = + AndroidXSQLiteDriver() + .getVisitor( + TestClassContext(className), + Opcodes.ASM9, + classWriter, + parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root), + ) + classReader.accept(classVisitor, ClassReader.SKIP_FRAMES) + return classWriter.toByteArray() + } + + private fun findSetDriverMethod(bytes: ByteArray): MethodNode { + val classNode = ClassNode().also { ClassReader(bytes).accept(it, 0) } + return classNode.methods.first { + it.name == SetDriverMethodInstrumentable.SET_DRIVER && + SQLiteDriverBytecodeTestUtil.isSetDriverDescriptor(it.desc) + } + } + + private fun wrapPrecedesOriginalBody(method: MethodNode): Boolean { + val realInsns = method.instructions.toArray().filter { it.opcode >= 0 } + val wrapIndex = + realInsns.indexOfFirst { it is MethodInsnNode && SQLiteDriverBytecodeTestUtil.isWrapCall(it) } + assertTrue(wrapIndex >= 0, "setDriver has no SentrySQLiteDriver.create call") + val returnIndex = + realInsns.indexOfFirst { it.opcode == Opcodes.ARETURN || it.opcode == Opcodes.RETURN } + return wrapIndex < returnIndex + } +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index 50c8c5b0e..cfbe17c63 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -16,11 +16,13 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue +import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.TaskOutcome import org.gradle.util.GradleVersion import org.hamcrest.CoreMatchers.`is` import org.junit.Assert.assertThrows import org.junit.Assume.assumeThat +import org.junit.Ignore import org.junit.Test class SentryPluginTest : @@ -529,6 +531,87 @@ class SentryPluginTest : } } + @Ignore(SQLITE_DRIVER_IGNORE_REASON) + @Test + fun `applies sqliteDriver instrumentable when sentry gate passes with room2`() { + val build = + buildDatabaseInstrumentation(SQLITE, ROOM2_AT_DRIVER_FLOOR, SENTRY_ANDROID_SQLITE_DRIVER) + + assertInstrumentableChain( + build, + "AndroidXSQLiteOpenHelper", + "AndroidXSQLiteDriver", + "AndroidXRoomDao", + ) + } + + @Ignore(SQLITE_DRIVER_IGNORE_REASON) + @Test + fun `applies sqliteDriver instrumentable when sentry gate passes with room3`() { + val build = + buildDatabaseInstrumentation(SQLITE, ROOM3_AT_DRIVER_FLOOR, SENTRY_ANDROID_SQLITE_DRIVER) + + assertInstrumentableChain( + build, + "AndroidXSQLiteOpenHelper", + "AndroidXSQLiteDriver", + "AndroidXRoomDao", + ) + } + + @Test + fun `does not apply sqliteDriver instrumentable when sentry gate fails without room on classpath`() { + val build = + buildDatabaseInstrumentation(SQLITE_OLD, SENTRY_ANDROID_SQLITE_OPEN_HELPER, minSdk = null) + + assertInstrumentableChain(build, "AndroidXSQLiteOpenHelper", "AndroidXRoomDao") + } + + @Test + fun `does not apply sqliteDriver instrumentable when sentry gate fails with room2`() { + val build = + buildDatabaseInstrumentation(SQLITE, ROOM2_AT_DRIVER_FLOOR, SENTRY_ANDROID_SQLITE_OPEN_HELPER) + + assertInstrumentableChain(build, "AndroidXSQLiteOpenHelper", "AndroidXRoomDao") + } + + @Test + fun `does not apply sqliteDriver instrumentable when sentry gate fails with room3`() { + val build = + buildDatabaseInstrumentation(SQLITE, ROOM3_AT_DRIVER_FLOOR, SENTRY_ANDROID_SQLITE_OPEN_HELPER) + + assertInstrumentableChain(build, "AndroidXSQLiteOpenHelper", "AndroidXRoomDao") + } + + @Ignore(SQLITE_DRIVER_IGNORE_REASON) + @Test + fun `applies sqliteDriver instrumentable when sentry gate passes without room on classpath`() { + val build = buildDatabaseInstrumentation(SQLITE, SENTRY_ANDROID_SQLITE_DRIVER) + + assertInstrumentableChain( + build, + "AndroidXSQLiteOpenHelper", + "AndroidXSQLiteDriver", + "AndroidXRoomDao", + ) + } + + @Ignore(SQLITE_DRIVER_IGNORE_REASON) + @Test + fun `applies sqliteDriver instrumentable when sentry gate passes with room below 2_7`() { + val build = + buildDatabaseInstrumentation(SQLITE, ROOM2_BELOW_DRIVER_FLOOR, SENTRY_ANDROID_SQLITE_DRIVER) + + assertInstrumentableChain( + build, + "AndroidXSQLiteOpenHelper", + "AndroidXSQLiteDriver", + "AndroidXRoomDao", + ) + } + + // Database path when sentry-android-sqlite is absent (old AndroidXSQLiteDatabase/Statement). + // Orthogonal to the SQLiteDriver gate matrix above. @Test fun `apply old Database instrumentable when app does not depend on sentry-android-sqlite`() { applyTracingInstrumentation( @@ -1106,6 +1189,7 @@ class SentryPluginTest : excludes: Set = emptySet(), sdkVersion: String = "7.1.0", forceInstrumentDependencies: Boolean = true, + minSdk: Int? = null, ) { appBuildFile.appendText( // language=Groovy @@ -1131,8 +1215,63 @@ class SentryPluginTest : excludes = ["${excludes.joinToString()}"] } } + ${ + minSdk?.let { + """ + android { + defaultConfig { + minSdkVersion $it + } + } + """ + } ?: "" + } """ .trimIndent() ) } + + private fun buildDatabaseInstrumentation( + vararg dependencies: String, + minSdk: Int? = DRIVER_PATH_MIN_SDK, + ): BuildResult { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.DATABASE), + dependencies = dependencies.toSet(), + appStart = false, + logcat = false, + minSdk = minSdk, + ) + return runner.appendArguments(":app:assembleDebug", "--info").build() + } + + private fun assertInstrumentableChain(build: BuildResult, vararg expected: String) { + assertEquals(expected.toList(), instrumentables(build)) + } + + private fun instrumentables(build: BuildResult): List { + val line = + build.output.lines().first { + it.contains("[sentry] Instrumentable: ChainedInstrumentable(instrumentables=") + } + val prefix = "ChainedInstrumentable(instrumentables=" + val start = line.indexOf(prefix) + prefix.length + val end = line.lastIndexOf(')') + return line.substring(start, end).split(", ").filter { it.isNotEmpty() } + } + + companion object { + private const val SQLITE_DRIVER_IGNORE_REASON = + "Placeholder version VERSION_SQLITE_DRIVER not yet on Maven" + + private const val SQLITE = "androidx.sqlite:sqlite:2.6.2" + private const val SQLITE_OLD = "androidx.sqlite:sqlite:2.0.0" + private const val SENTRY_ANDROID_SQLITE_OPEN_HELPER = "io.sentry:sentry-android-sqlite:6.21.0" + private const val SENTRY_ANDROID_SQLITE_DRIVER = "io.sentry:sentry-android-sqlite:8.44.0" + private const val ROOM2_AT_DRIVER_FLOOR = "androidx.room:room-runtime:2.7.0" + private const val ROOM2_BELOW_DRIVER_FLOOR = "androidx.room:room-runtime:2.6.1" + private const val ROOM3_AT_DRIVER_FLOOR = "androidx.room3:room3-runtime:3.0.0-alpha06" + /** androidx.sqlite 2.6.x and room3-runtime both require minSdk 23 in the test fixture. */ + private const val DRIVER_PATH_MIN_SDK = 23 + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt new file mode 100644 index 000000000..74266bdef --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt @@ -0,0 +1,164 @@ +package io.sentry.android.gradle.util + +import io.sentry.android.gradle.extensions.InstrumentationFeature +import io.sentry.android.gradle.services.SentryModulesService +import java.io.File +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.gradle.api.artifacts.ModuleIdentifier +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class SentryModulesServiceTest { + + class Fixture { + + data class Sut(val service: SentryModulesService, val project: org.gradle.api.Project) + + fun getSut( + tmpDir: File, + features: Set = emptySet(), + sentryModules: Map = emptyMap(), + ): Sut { + val fakeProject = ProjectBuilder.builder().withProjectDir(tmpDir).build() + + val featureProvider = fakeProject.provider { features } + val logcatEnabled = fakeProject.provider { true } + val sourceContextEnabled = fakeProject.provider { false } + val dexguardEnabled = fakeProject.provider { false } + val appStartEnabled = fakeProject.provider { false } + + val serviceProvider = + SentryModulesService.register( + fakeProject, + featureProvider, + logcatEnabled, + sourceContextEnabled, + dexguardEnabled, + appStartEnabled, + ) + val service = serviceProvider.get() + service.sentryModules = sentryModules + return Sut(service, fakeProject) + } + } + + @get:Rule val testProjectDir = TemporaryFolder() + + private val fixture = Fixture() + + private fun sqliteDriverSentryModules(): Map = + mapOf(SentryModules.SENTRY_ANDROID_SQLITE to SentryVersions.VERSION_SQLITE_DRIVER) + + @Test + fun `isSQLiteDriverInstrEnabled is true when sentry-android-sqlite meets threshold and DATABASE is enabled`() { + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = sqliteDriverSentryModules(), + ) + + assertTrue(service.isSQLiteDriverInstrEnabled()) + } + + @Test + fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is absent from classpath`() { + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = emptyMap(), + ) + + assertFalse(service.isSQLiteDriverInstrEnabled()) + } + + @Test + fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is below VERSION_SQLITE`() { + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = mapOf(SentryModules.SENTRY_ANDROID_SQLITE to SentryVersions.VERSION_SQLITE), + ) + + assertFalse(service.isSQLiteDriverInstrEnabled()) + } + + @Test + fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is one minor below VERSION_SQLITE_DRIVER`() { + val belowThreshold = + SemVer( + SentryVersions.VERSION_SQLITE_DRIVER.major, + SentryVersions.VERSION_SQLITE_DRIVER.minor - 1, + SentryVersions.VERSION_SQLITE_DRIVER.patch, + ) + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = mapOf(SentryModules.SENTRY_ANDROID_SQLITE to belowThreshold), + ) + + assertFalse(service.isSQLiteDriverInstrEnabled()) + } + + @Test + fun `isSQLiteDriverInstrEnabled is false when DATABASE is disabled`() { + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = emptySet(), + sentryModules = sqliteDriverSentryModules(), + ) + + assertFalse(service.isSQLiteDriverInstrEnabled()) + } + + @Test + fun `VERSION_SQLITE_DRIVER is greater than or equal to VERSION_SQLITE`() { + // Gating relies on the presence of the open helper whenever the driver is present: both + // instrumentables fire together and we rely on SentrySQLiteDriver.create to dedup the + // SupportSQLiteDriver bridge case. + assertTrue(SentryVersions.VERSION_SQLITE_DRIVER >= SentryVersions.VERSION_SQLITE) + } + + @Test + fun `between VERSION_SQLITE and VERSION_SQLITE_DRIVER only the open-helper path is on`() { + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = + mapOf( + SentryModules.SENTRY_ANDROID_SQLITE to SentryVersions.VERSION_SQLITE, + SentryModules.SENTRY_ANDROID_CORE to SentryVersions.VERSION_PERFORMANCE, + ), + ) + + assertTrue(service.isNewDatabaseInstrEnabled()) + assertFalse(service.isSQLiteDriverInstrEnabled()) + assertFalse(service.isOldDatabaseInstrEnabled()) + } + + @Test + fun `at VERSION_SQLITE_DRIVER the open-helper path is also on and the old path is off`() { + val (service, _) = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = + mapOf( + SentryModules.SENTRY_ANDROID_SQLITE to SentryVersions.VERSION_SQLITE_DRIVER, + SentryModules.SENTRY_ANDROID_CORE to SentryVersions.VERSION_PERFORMANCE, + ), + ) + + assertTrue(service.isSQLiteDriverInstrEnabled()) + assertTrue(service.isNewDatabaseInstrEnabled()) // superset relationship + assertFalse(service.isOldDatabaseInstrEnabled()) // suppressed by !isNewDatabaseInstrEnabled() + } +} diff --git a/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt b/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt new file mode 100644 index 000000000..7cd08376b --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt @@ -0,0 +1,9 @@ +package io.sentry.sqlite + +import androidx.sqlite.SQLiteDriver + +class SentrySQLiteDriver private constructor(private val delegate: SQLiteDriver) : SQLiteDriver { + companion object { + @JvmStatic fun create(delegate: SQLiteDriver): SQLiteDriver = SentrySQLiteDriver(delegate) + } +} diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase$Builder.class b/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase$Builder.class new file mode 100644 index 0000000000000000000000000000000000000000..88e8733f1fb42234ff347606f9b788771abcea9b GIT binary patch literal 30384 zcmdUY349dCmG^r+Gd-ixNQ^F!#5I5bok$1}AWk8`NFXj@FyMoc)WDdLMxGvFaP0LF zUmuQdoW#!g*>P~<#EBhaaD0T=i5*hi#@L(h z_xa~m_f%C^z4x#8UcGu%)qMGl&pt~;3zdURGowQT$wX}6NOLlg7;N5-KWn3@XkS#< zYSxUz;saXJPt4T1d`J7H(aSDdx#`mA;b?O_I&`3UOW&nhe`*<1I5W*pUM6*UY$%pm z$)we8I+RGoV?)hJZGRlEG;i#TM|FLfqpsdmGB$KznN{%8!-LKHM~3=SvBZ$xyxx3j zS!R*e+@BaqX+x>zPH{hyT2{A{X?T=&mhafHY~`D8Yf^jN>{r(&jx{)1ZoA+wdO(PVT`LqVpwwGO<^zH_9QJy5qZ zL}4lk(gZ4H@=KhLqykjVzDoqTMk0vpd$!;G>#p2B!$z=5CrkI}cQ&o_vMQ(9A z*T|T{RL~!d$NQrFhja*y(YAbD6j&CbX*9in6pG;G)X`xLeS26-f_6PbGmWfSOmjw; z&YJGTh(M$#F`)TrHrnerf5(=djvZZ{OtqHm_Acqg^*&%iO|7?L0Tl> zD;>U<{e5Y!1|XDd#KcF_f*)NZKF{%C&+ zL@`CImPfN_A=*n9iq|ee<*9=)Xhw}iNiIn>jWx`|DqEtnD0Wacnxz#6b8Y@LjAlbc zHB%f$sr&GtY_TUgJdEDh2=VHNIv>zd8|4rnGnwk0w#Xo{&O$1yk2KmJqyaj>RFcIQ z#IQf9MN^uP$qrrDpnyx9m0M;lKTKDg;GbbN;uvt7dq;+c zlbWt?!hk78)5FpD2vm52sYx(hy{V&dqy((dDL_{+O~y;3;Q$%4TFRxPW3dyWE9oks z@BeTvJ&yq;pI(Q66sLto$Rg81vrl4)W|5F@S1vt=*R1XcIkh1`H!y`PO)zLGHuTvd zDMUBX%>tYM!!-X*XoaXuD8;Nyg%BBTeE@**435sVC-c;W%l?kdS-=-U&9&&DU?1bj zn}!k~Jko_acr1aKdN2F7mCw9?s>RE2fkE40` z`uS9EVO17OQ!ebBpf5o8z`eC()loOobfd{pISbKy=)FRQ?}d?g6Ef^*M2K>IrYJk| z-4eZ!>8f#HvZqaN)>!3(BTVYO!*U6xD@iaE=`Ug~-1{7Gc0o z3~NIhwD_==TyJ@Tdu(UxNV9%fJeJa$2m0jTlt`v}w{1eg7W=Il^R>Jr89i{x|61i1 zj+HuwQBazu(dKMnx*zjrEJP2|M}?l8z|b^qw`AcdbYza9BUyQF{b<9)COR-+&mdqB zyKDERNtW>td%DtWm&+IWEpJ<87SutT8cg~hVVdD~*6iFgt$zA6##UWSwaiIYh#n)o zfF7mKKpNBTA3_D%0Wr-yiAjodgtqKwnpoRa=kTn}LYUBkmST#4*|H<>#k4uI_g%A5N6~!6srPNI(7-A{4f(jV zZr%hq=ETXJ;Ra9hAOmV1SKp8 zA_@G=t}{UY31gWaBD&&n?Lai%kvuRmh>69zBmLU2h_3tTx0uJL5=NKmW5$SP<{MG&KL_b|^cOe}5?PDBQ6~Ua0f6~8dR>6|-?n8lgP6I4JDK8E zGhYbnf^n@t_ItKRl$n(w+p5woJ{+K`L|kHk~4t|2%_L;YIU5S*SE!V+Lw7dU_B4A!*z&uXL47@SvsYAz%2b0Ck%50=ecJdru zJKs_TmJQAH>((8?j1B8JSdEDb8)hu#VT~azHD-Ss)CPDm)24Aj&l?y4|0-L`^8+)Z zBQV{Ww%-ib?ntcF^i*;LI(}HQCpJ)WYfxCLA;{T}==lI#9{(VMq=@=5?c1&m$D{pP zr0-}XfhD+jB02zrk0-HyV;KHnYhf{xN4NH)8gccUfCh z#@F1u>vU*t-tS~c0X*`zlOXhhX-dwKpjODj%(34!7J_SQ_ac7a9xt69ptZpS8r_s< zQA>K4jNiV|n~f1?^h1X-7r7Dwyb6B&ThwpCuIT6``RKSP?C#-KysGT&@-ucL)Yb0E z8?<@(mbFlOTlj?hoGYJrwGc52Yt@ixqdi*Zia=ZC@|{L)efw&&5SuryWH|T{A~3@c z0Y_>^UoT*qJ(f<`OgmmigEJ2Uuvdj^c*PHZr;*bnh8WA1VR(^O-QY?jEJw}yC+oCNdUH#7UPC>Jte z#-riwxv0z#49rY`wYg(in&J^inznMWemnE zSFCz0Ow&7ST)RNbXmmapTg(=@cCQ5(5$^H%&*Z=%QJGm85(SsvxgW{imRSZ#6tPYB z_O$i2-MN=A#Rj(Xv12n|i?Q)-smQ`?TDoh}nZ5W66~`!S&Uu%A`_hgFTJ zlQltjfRj*iS2Chuk--r?CFUS2S43I@jF{LcOSm@wA=GnNSe46}+Kqu>ym&_@%to-I zZQM~MM-Pd9H{;qlax=OfK63#|txaNr2j6z;$GNBrq0F@c_v=KgXS_{OHroQQ>Gq`A;4}j!xYm^@Z`s=l5V9)|HANsBRjOfk^6LG=)DR9&WNABI6=pFYj1%LJWK$ zrVmF`*vp-q7vOuvWKL?tc$O%qj2q_YE!KeXcv(JLISK^`soF}Kvxr+~7ofRG(Alnx z{k>vquWgpc*fTlGtHM|`*>vQpJcA7Q@d0a(F*Xm%g%Mb?F&Fn>27I-3I6VM6W>1Tw z$^L_g-6pkW&0M&_uKE6hJEFSntor$*2+lz&yS5msj+PgT`G&QSF{+;*;)nQSK|aAB z$BtaP)nw*r5A4Y0PhgldBbw>3pmT$_XwTs!^kR2+#!sw~@FV=GAb*lSja31=z4Qi< z{o>%k<%#5>=JnQ7<}Md=2Z^5_3-V{g4)oR>^@#18W9*ReGqy5Z5;D&G-1a)As{EBhC zuJj-~N-H(Hgk4$IW!XK~VUo4l{wUTWux?>QjuXkAX!4Mj#0eX*VDY&iKh4* znA46%thpM4f-Oyu;`GX?+-Nxv8Ib~K(j$hD6t*X%7gmiUMv#=3M(rYOPTp#6@9q@a zZ|ps1b{Apu%VFo_$y^pSEP%CF*I^GZc4ckwO!rpWB8;QSX8*ks3tV1qz6nG*e0tNRzGPDy*jib9+tT|b2&xTVNq6oyrjFE!QAKgVE=!wHG_VXa1( z>r0X&aH9vcOOje#!{JO2WOpDmL9S1y4?U)}OqzOOa;_texV4Qe!Iu`O6;GVJGckV$NNA<_cHoM0L)mYJ8wr7vy#kAw& z0!7-KH*E;By9kmy+6j{^SR7Pk!jx++ZH?^?<7wL)fxU5NR*dMs3i50GYj~;{8qdcu zMs0A|b(U@vvGigY5_&>$_|7F|j=1~s1`T2L)@QIc5#32Z^dtZ(ju?q1Y_>YDB zd;SwswOf0flI`ceV6!W{mG3WLlW8+1L|HiN5JQ-tu&Qy0R(ziCw zCZCT}t$sd>iRXSSZg&g~7&}N(F|2Z1D%LeP9IqKL{3RJhNbfW)HK_f#q7tNamKaOxwVS*GQqsLEc!O^(JJPD=JME> zGwE^gXsr>`Q|o|;^ajPTKBF@bjit`;7*pcS!XRV(0INYnzjM$PA~4wq0mxkUgnuEb&0M(4t!r@M zgl@*7ano}a%Mg}^tdo1suw{NCcCa}8Z9Bu=H+FK7i&iMWKp$L$3}thgL#UbK`wNDz z?pJDH+@xK}j2Bo@Zoe`Iv;1-0Jik&4}gVhdo$7QN$m@DdI!o0iXR)j>Nb&{R2cBuWx|CSmB|(gawX0}(*Z|gKD!dpwzK*WN|wkZB`^Wc z6?iKYm{@t{n)*QC%r#-IS6Y--rNy&mX`rMu6!umJO1-7Zk~7z|_(}s|wc1DyP@zp<9ADY%Tm-`GgiK%p33lg zS3LosBXF7yD&~gIbLEwWHvl;`z@IlsVf*$tezRcjm~lz0MTWzXP?*1}eqj^PM?JDR-x! z>`s)Ofs(tjO1e_)lGWi0oF!X>;oYcV573#1`e$b3n)SO%2}Jg~>M&%a6ue9eUzm|o zf!Zgcbqkp(StW!xSEGb1iMlV!sNrJddr)^Ie2FV35a93}{O_XJUDHve}p? zv#}|;s+gK9ahgn=?yOhn4$yNaNo8>+g~K)S)!EL(VxudaNn!M=V6x7cSRAfL6AehR z;V%z2n%vB5X>yh=4>!9q9B;Ik8S~xz7l&K38CZ~=v@kO%99|@8^*h@ShudW0VrSyS z@DjtGx3q(|rOvD{gjF!S+?}{WCb~MXIJ`20g6AGmvCBglfCtMvSd6Wu*6^GDdKES) z8G|`u2aZVq#^rJm9B9gl{rJ}y=Ww#3NiG-6a$fujzL*oReqj%uBU%D_Y}Z2gWAbZ5 zSutW)wPQpl{7Mge=rIal`=!e<#5~&V^<+;Mja3Q1vJH!8a)APK0sEbCqfdSXsQYb4 zyeU^CHRt-YTs2m9!EJRiZ=L>_cL>wZdaEa03njhyGE~2|R_hx%u(}9|OF?h!z)&=WQ(frQ?M46+CuL%K4BPlS z(w{CvG@?^3zfQonRJ6?_gMC_3?jIM@p6HLpcSe&jvGw0ftjVm){u-I9(xToJ79{Y+ zAoI0g`d9)!%VgHEGY2)Ht|5FgOU7En(afOHC1TDEjmp(OGy~_d#2%vQR4P9GM2d1D zekt$-FT!1g+6Hki6(4*e&Pc=Oog8U}vK+qMWY;m@E^p-FYi4#{tDPt6SHw49GU`&q zR>rI}Y+lSt!#2f?v{LbTG`p<@8ExS>T1I(A9O%kS6X)@>(r_$Q(u*&jIr1bA;@FWR zP4KLU@1BXf*g%z$S1LY5=14PeSWO)^-3wiGmx_;sjhcrqgxPtET<Nx zoNSP^;fqU-_6>URy_u{ue80o4yUng!mKPh8vdZJ^WL6qJJ%%TxxB`e~tOKP~fIO6xttv<=q|&*gLpu0GFE8pJj1c?XSn zZlo(bYw2pwI=aELo{o9WquV_jlzpgsp}{*{LgE+E9a8@=QsPNymLi{oK8cCw9a00m zhIUfG?u7!78byAU*Hl%Ea`7^nnpcLdoNWf&9>d7=HHCI|{ircaP(j>JAbQ@-y=~5aoDZCvNs;H*Ups=y@ z9fHoyO`xpZ>uK{?PLes2Fo3Cq;_Q36k)9 z9lg`!Cut&H-B5aC7UIWp0saaV1-bzGD**i!s`9+z0Nh8nn1Bc9T_)hwHu2STgEWf< z;Mb1@_;qw^HsFH5u>e0txd8tu0RI^P|2Y7E4S@d?fWPJf{0va3*nBPTrXG{6JvoOCemy~-I3VS_N;q}r)JXd?Y4$xJ4pDpF@H$itw`8^nS z*wn-0W6Fs`;_splWOsm|cB~G#FINY6XVD~Y4NdjVrdi%ORD<8O-Z`!g_^{mp|2Ibm zNcyun;Dfn4;6tN#!2R@q$ z9|q>*CiE4yyjS28oWk`nxQPnfj*0ahx2w1I}wPq~Bx#--ueHnqZ6 zq25mm8Y+CP?f$06X?>OdaeDkTefCLuR-s*1%IEEr>wSFU>{pQ9-q7&uX?pPtz4Qcq z#b(u`^r5vXQqIEo`2D2#=_UDBVP>K>Ie^arf2y|w2&_Q|c2b*nEp5Q_X775s*n1vb zg1mn3d3LAPPz}98UxkYjrY-a}Q#`lR*G-HDY>Woz5&Dvi(I<0bWEib)&_9|O9X0!J zMnmbhT^NDOu7)S+I{?CF&@oteF&uig(`0WiO~G@-+iUk_7z5XLO*$vgKbdsSu<4vZ z-=go?bbfOzI=@fV^s$#6AA^)ZSMeV+)=y$uTd{5AyPSIaKC;WmpY%UC0oAXtF88t9g<` zf{%fdyH0k@319!Ca5|KR(+6QtUZ}bH$f!~H)547 z8LQ}*a?)ck{cmG1&C1)xH2d;r`e88r2{8RhF#QOa{sfr*G?;!AOg{{!A9XU#s?D_D zW;*azm@cr@;TfPQ+?mF-Iy62`dtRXOr?_x89(R^;(J2n2!?F3coGd*ty0uA|sF~z7 zIu?K7p5gCVROn2D{lXT7Lc7z+`yABjc^L68P>J`8z~M!VL(kJ3?@Kfnc@5r|B>#r0 zP2&o1EmSSS6HQfX981-B5?7jn46PQbOmiE`c#2z+VVA(Bzro_n@~aHa76>k;lFy)A z{B<&yk?iDenlK68AA+wR!DRfHrr|!r`zm<+A^3Y$@+Exr5-Q=TvNwzIz4d8kZ_c$v zcrHFSA?DmtgtbZZPpL;zkqysto-5(J&&S_%nr=BcOAJ&NF$|YOcZa zY@O%u+`TB_jJ%ZabZ#=O^3`TPO?Qm`g6bwgb<^aNJm1zWgLDBi`6g4)H-#qnrc$|Y z8ddqGQ#I12`KH@22PurnxR`MGsf-txFi*E(p3Vy~eYaqq=OU1K(O9Tr*ccsEUo%i` z0jl$XYAeWV2CA(tR2SQ*E;(0J+j610XiQWE)#ISL8mM*v)iofy6R55Qs;hzOS{JHI zZB*Oe6sp(qGG0!ql+~Be?ZUBI4fR~@Qcss>H4UECyf)!kbyoP@PSj6qxo|j5m4?&A zYfZhy2thNX^^w}?i&BZNk7oN2>A=0+r_nN`t@3G-K{4l>jeimWA610VlwhdokgOI{ z6R$B@U2O}{YJ6;<(-xo=Nd6VqlBK(6IJ!bz#Y?34RKKN$4e>Y|k>bD~jyoI-#WZnld zh5yt7x$cErHyUnKy7xpdf_I9*1n;gABkP43(=;KdMZS;1Xq^$dUCu+0Pm(31hFo?hkb|hmKJNw3Chq<2z zOm?m?g=i*v=YY%Vz{1pwK3DPkUWVcL3Qh67LR0ZP!Lbi!cT*Ybg3xwvmG=yyT0Ut1^n&B>b9&P%|F^K!L@cB->!H|_`3xiqNO(h$le)mppH>o6M-0WKew z5j^~U`4py$`2!NdB8u_{O$c?n_jN;cO^5d8u^{}A?ZM=V3Qfl1hRtN!$dlSeL3J@r zP?vzGHY!(_*xie$5Q~78@B#~11hhPmLPu1H@6YlA9r6eve1JbB6OtDc9RHvxCM)ps zvKOfYw_`7oc=bL@f>xrsoz{;wZ^H|GRYQBg(^fF$Ub+|s_w0U=s&;wWf@S>K_MkK> zLDQ)0dWxUkjUo$B#Ss*nxT}nxZ7-B%3e7TbDNga{+e1(B^Si48RiRV-g5<(i1#{M3 zfO@CiOZPb1DJ{0!p^U#w?Gq$%6HMZs;;#t$FB3J2da4Re@z+f9;A~0qD!liS1_pQUeu2WL`CGUZ zoZ;gqXgbK^3rAsFF#Mg}RRvX{)BFR0uwAZ5Qcz@4aEgCywN4Kg87}e_Eh=sw;k6ETtC9qglp#${O`EEVUr$^ zqz6pW!If|Hta7~BgCY=ZEK@o##`D#4Dr}+)`@oB}2MDc2s`h*gw^4kL&KSr&Q%Hr8Y!EQf4 z@j4aZ?`nRXD%c}#g}j>2BFGjL7LlKj@l+BI2wW($&(f?MIo<&M8UK6z%G}Bhls$_D z^1_Vw?J5da^MAv#%4R}n-QA4Uq$pIuC{%eC|AJc1%1AV|&riO+em;Jd=H{#mq#q|= zKq<%nDDGG4{7S=LC}5C@HE2ist5M)}D#l+70e)NoTxanqchxS0JPHPsllb4SGyxD% zCoS^RRQA&xztSwIbr&xjtGIk)p94}+AHQb&FCZu1RzJ?KU_n}$MmJ$s##$;?*HMMK zo+9dbG*8_?OVut~t#;D}wTF7utrS(aV?64m5%qk!Qr$`KR4<^L)V*|zdLiA0`&}q= zm%4@SR`=2UY9BqVM(G)~pI%n?<2sIOL_U8_)#&R;`?h+3zNa3fpQ$nW1@3=V^I~lNS|YCz+%;Syyh1(99qQ%WqaNX{xbIWn!9(hm ze5Lvie6xBLA5*X4cd6I%ZMfgAUdMOhewTVRA6KvE2h|(+A@yed7@kk7$M|XW7CxiC zi@%`W%3o4%<1Zuao9dnXZS^kx4%+&zypH3p;X2MgQ19WNsPExFsqa-v)%W0fw^FX& zt5mA@Db?!xlVBnL9Z)u@4=CHzk0|@p2bF%@lj;fO zTJ<62dfe|;KdwBWKCFBc_mk?Ulqc0sE6=EpDxX&$Q=V5pqr9j-uDqEoaPx$r2INl5c6 z*W#&CK0Sb^D)HpmN(D+ao(eoY6jCNDQ{dL$t^7@yD%PUte&tt?{*9QGJfnO=nT{t9 zy{tU1MDXNQHX-OBw(Iz?QDD9@1MjMy>nW(rRA#}OfgfUcSf*d~pwq8HltqJ|vmIW& z0xdfaRCM9q?Zv-%i2KIXRkR5AO-SF2`xbA3(t!I`wLsa9KikmzwK}$V#)h3EnAG>>5{$V=~9+E0tAZbnK-AI_=YyHM%lOS2`g^I&ITwmweua+tBAoRITF`c41 zO%u7iS*I&?x=QEmI`7bVmrk$iyho>B>oilRzv=AJ=|P=7s?+ynZ4Elr>Rh68sZK4j zhNRAiby}>`5}j6y6hQ!5JWr?Z>C~+=$~Wtj(CH?fZr15NI=xp{*6Dn;&e!VnE1f{Y zr8*7jbcIgW>2!lmcjiV@r?2FiON>~bH3 HviE-gtPZZ2 literal 0 HcmV?d00001 diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase3$Builder.class b/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase3$Builder.class new file mode 100644 index 0000000000000000000000000000000000000000..57cc73de25f9e4c6a06f1beae49d4a2d5969c725 GIT binary patch literal 26703 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 Horm^64Sg@Q literal 0 HcmV?d00001 From 2ff8ac580542559cbe57f8d459b6abb9c805a257 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 10 Jun 2026 23:28:54 +0200 Subject: [PATCH 02/14] Minor doc updates, DRYing, and test refinements --- .../TracingInstrumentationExtension.kt | 2 +- .../sqlite/driver/AndroidXSQLiteDriver.kt | 8 ++-- .../driver/visitor/SetDriverMethodVisitor.kt | 3 +- .../gradle/services/SentryModulesService.kt | 2 +- .../kotlin/androidx/sqlite/SQLiteDriver.kt | 9 +++++ .../sqlite/driver/AndroidXSQLiteDriverTest.kt | 40 +++++++++++++++---- .../visitor/SetDriverMethodVisitorTest.kt | 11 +++-- .../gradle/integration/SentryPluginTest.kt | 15 +++---- .../gradle/util/SentryModulesServiceTest.kt | 30 ++++---------- .../io/sentry/sqlite/SentrySQLiteDriver.kt | 5 +++ 10 files changed, 76 insertions(+), 49 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt index a7ee1171a..2ff69cd0a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt @@ -82,7 +82,7 @@ enum class InstrumentationFeature(val integrationName: String) { * * **DAO method execution** (`db.sql.room` spans): Wraps each public method on Room's generated * `@Dao` `_Impl` classes, measuring the full DAO call end-to-end (transaction management, query - * execution, and cursor processing). Only for Room users and only on versions prior to Room 2.7. + * execution, and cursor processing). Only for Room users on < Room 2.7. * * This feature uses bytecode manipulation. */ diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt index f207e5ae1..f3872b8a9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt @@ -10,6 +10,9 @@ import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor.S import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor +/** JVM type descriptor for `androidx.sqlite.SQLiteDriver`. */ +internal const val SQLITE_DRIVER_TYPE_DESCRIPTOR = "Landroidx/sqlite/SQLiteDriver;" + /** * Auto-instruments `SQLiteDriver` for all Room users by wrapping any driver passed to * `RoomDatabase.Builder.setDriver(SQLiteDriver)`. @@ -70,9 +73,6 @@ class AndroidXSQLiteDriver : ClassInstrumentable { class SetDriverMethodInstrumentable : MethodInstrumentable { - override val fqName: String - get() = SET_DRIVER - override fun getVisitor( instrumentableContext: MethodContext, apiVersion: Int, @@ -85,6 +85,6 @@ class SetDriverMethodInstrumentable : MethodInstrumentable { companion object { internal const val SET_DRIVER = "setDriver" - internal const val SET_DRIVER_DESCRIPTOR_PREFIX = "(Landroidx/sqlite/SQLiteDriver;)" + internal const val SET_DRIVER_DESCRIPTOR_PREFIX = "($SQLITE_DRIVER_TYPE_DESCRIPTOR)" } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt index 7fe22cbee..1dfdf62ff 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt @@ -1,6 +1,7 @@ package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor import io.sentry.android.gradle.instrumentation.MethodContext +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SQLITE_DRIVER_TYPE_DESCRIPTOR import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Type import org.objectweb.asm.commons.AdviceAdapter @@ -28,7 +29,7 @@ class SetDriverMethodVisitor( companion object { internal const val CREATE = "create" internal const val SENTRY_CREATE_DESCRIPTOR = - "(Landroidx/sqlite/SQLiteDriver;)Landroidx/sqlite/SQLiteDriver;" + "($SQLITE_DRIVER_TYPE_DESCRIPTOR)$SQLITE_DRIVER_TYPE_DESCRIPTOR" internal const val SENTRY_SQLITE_DRIVER_TYPE = "Lio/sentry/sqlite/SentrySQLiteDriver;" } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt index 62bf25c3f..83101a6fa 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt @@ -77,7 +77,7 @@ abstract class SentryModulesService : * `SentrySQLiteDriver` and the DATABASE feature is enabled. * * Room version is not gated here: Room < 2.7 has no matching `setDriver` method, so - * instrumentation is a no-op. Method descriptor matching is the safety boundary. + * instrumentation is a no-op. */ fun isSQLiteDriverInstrEnabled(): Boolean = sentryModules.isAtLeast( diff --git a/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt b/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt index 187e5e6a8..7e3944dcf 100644 --- a/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt +++ b/plugin-build/src/test/kotlin/androidx/sqlite/SQLiteDriver.kt @@ -1,3 +1,12 @@ package androidx.sqlite +/** + * Minimal stub of `androidx.sqlite.SQLiteDriver` so ASM can resolve the type referenced by the + * instrumented bytecode. + * + * Must not coexist with androidx.sqlite >= 2.5 on the test classpath: that version introduces the + * real `SQLiteDriver`, which would collide with this stub. Safe today because `libs.sqlite` is + * pinned below 2.5 and `testImplementationAar` does not pull transitive dependencies (Room 2.7+ + * depends on androidx.sqlite 2.5+, but `Aar2JarPlugin` sets `isTransitive = false`). + */ interface SQLiteDriver diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt index bcc844eeb..458413a67 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriverTest.kt @@ -3,6 +3,7 @@ package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver import io.sentry.android.gradle.instrumentation.ChainedInstrumentable +import io.sentry.android.gradle.instrumentation.ClassInstrumentable import io.sentry.android.gradle.instrumentation.fakes.TestClassContext import io.sentry.android.gradle.instrumentation.fakes.TestSpanAddingParameters import java.io.FileInputStream @@ -48,21 +49,44 @@ class AndroidXSQLiteDriverTest { assertEquals(0, SQLiteDriverBytecodeTestUtil.countWrapCalls(instrumentedBytes)) } - private fun instrumentThroughChain(className: String, bytes: ByteArray): ByteArray { + @Test + fun `does not wrap when a visited class has no setDriver method`() { + // The Room < 2.7 production path: RoomDatabase$Builder matches the allowlist (so getVisitor + // runs), but the class has no setDriver(SQLiteDriver) and must pass through without a wrap. + val instrumentedBytes = + instrumentDirectly("com.example.NoSetDriver", loadNoSetDriverFixtureBytes()) + + assertEquals(0, SQLiteDriverBytecodeTestUtil.countWrapCalls(instrumentedBytes)) + } + + private fun instrumentThroughChain(className: String, bytes: ByteArray): ByteArray = + instrument(ChainedInstrumentable(listOf(instrumentable)), className, bytes) + + private fun instrumentDirectly(className: String, bytes: ByteArray): ByteArray = + instrument(instrumentable, className, bytes) + + private fun instrument( + classInstrumentable: ClassInstrumentable, + className: String, + bytes: ByteArray, + ): ByteArray { val classReader = ClassReader(bytes) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) val classVisitor = - ChainedInstrumentable(listOf(instrumentable)) - .getVisitor( - TestClassContext(className), - Opcodes.ASM9, - classWriter, - parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root), - ) + classInstrumentable.getVisitor( + TestClassContext(className), + Opcodes.ASM9, + classWriter, + parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root), + ) classReader.accept(classVisitor, ClassReader.SKIP_FRAMES) return classWriter.toByteArray() } + /** + * `NoSetDriver.class`: hand-compiled `public class NoSetDriver { int unrelated(int) }`. Shape is + * irrelevant / any class without `setDriver(SQLiteDriver)` works. + */ private fun loadNoSetDriverFixtureBytes(): ByteArray = FileInputStream( "src/test/resources/testFixtures/instrumentation/androidxSqliteDriver/NoSetDriver.class" diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt index 10e3d0f58..92f728473 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt @@ -16,6 +16,7 @@ import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode @@ -77,8 +78,12 @@ class SetDriverMethodVisitorTest { val wrapIndex = realInsns.indexOfFirst { it is MethodInsnNode && SQLiteDriverBytecodeTestUtil.isWrapCall(it) } assertTrue(wrapIndex >= 0, "setDriver has no SentrySQLiteDriver.create call") - val returnIndex = - realInsns.indexOfFirst { it.opcode == Opcodes.ARETURN || it.opcode == Opcodes.RETURN } - return wrapIndex < returnIndex + val firstOriginalBodyIndex = + realInsns.indexOfFirst { + (it is MethodInsnNode && !SQLiteDriverBytecodeTestUtil.isWrapCall(it)) || + it is FieldInsnNode + } + assertTrue(firstOriginalBodyIndex >= 0, "setDriver fixture has no recognizable original body") + return wrapIndex < firstOriginalBodyIndex } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index cfbe17c63..18d5de702 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -4,6 +4,7 @@ import io.sentry.BuildConfig import io.sentry.android.gradle.extensions.InstrumentationFeature import io.sentry.android.gradle.util.AgpVersions import io.sentry.android.gradle.util.SemVer +import io.sentry.android.gradle.util.SentryVersions import io.sentry.android.gradle.verifyDebugMetaPropertiesNotInApk import io.sentry.android.gradle.verifyDependenciesReportAndroid import io.sentry.android.gradle.verifyIntegrationList @@ -561,8 +562,7 @@ class SentryPluginTest : @Test fun `does not apply sqliteDriver instrumentable when sentry gate fails without room on classpath`() { - val build = - buildDatabaseInstrumentation(SQLITE_OLD, SENTRY_ANDROID_SQLITE_OPEN_HELPER, minSdk = null) + val build = buildDatabaseInstrumentation(SQLITE, SENTRY_ANDROID_SQLITE_OPEN_HELPER) assertInstrumentableChain(build, "AndroidXSQLiteOpenHelper", "AndroidXRoomDao") } @@ -1231,16 +1231,13 @@ class SentryPluginTest : ) } - private fun buildDatabaseInstrumentation( - vararg dependencies: String, - minSdk: Int? = DRIVER_PATH_MIN_SDK, - ): BuildResult { + private fun buildDatabaseInstrumentation(vararg dependencies: String): BuildResult { applyTracingInstrumentation( features = setOf(InstrumentationFeature.DATABASE), dependencies = dependencies.toSet(), appStart = false, logcat = false, - minSdk = minSdk, + minSdk = DRIVER_PATH_MIN_SDK, ) return runner.appendArguments(":app:assembleDebug", "--info").build() } @@ -1265,9 +1262,9 @@ class SentryPluginTest : "Placeholder version VERSION_SQLITE_DRIVER not yet on Maven" private const val SQLITE = "androidx.sqlite:sqlite:2.6.2" - private const val SQLITE_OLD = "androidx.sqlite:sqlite:2.0.0" private const val SENTRY_ANDROID_SQLITE_OPEN_HELPER = "io.sentry:sentry-android-sqlite:6.21.0" - private const val SENTRY_ANDROID_SQLITE_DRIVER = "io.sentry:sentry-android-sqlite:8.44.0" + private val SENTRY_ANDROID_SQLITE_DRIVER = + "io.sentry:sentry-android-sqlite:${SentryVersions.VERSION_SQLITE_DRIVER}" private const val ROOM2_AT_DRIVER_FLOOR = "androidx.room:room-runtime:2.7.0" private const val ROOM2_BELOW_DRIVER_FLOOR = "androidx.room:room-runtime:2.6.1" private const val ROOM3_AT_DRIVER_FLOOR = "androidx.room3:room3-runtime:3.0.0-alpha06" diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt index 74266bdef..6ada4e95d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt @@ -15,13 +15,11 @@ class SentryModulesServiceTest { class Fixture { - data class Sut(val service: SentryModulesService, val project: org.gradle.api.Project) - fun getSut( tmpDir: File, features: Set = emptySet(), sentryModules: Map = emptyMap(), - ): Sut { + ): SentryModulesService { val fakeProject = ProjectBuilder.builder().withProjectDir(tmpDir).build() val featureProvider = fakeProject.provider { features } @@ -41,7 +39,7 @@ class SentryModulesServiceTest { ) val service = serviceProvider.get() service.sentryModules = sentryModules - return Sut(service, fakeProject) + return service } } @@ -54,7 +52,7 @@ class SentryModulesServiceTest { @Test fun `isSQLiteDriverInstrEnabled is true when sentry-android-sqlite meets threshold and DATABASE is enabled`() { - val (service, _) = + val service = fixture.getSut( tmpDir = testProjectDir.root, features = setOf(InstrumentationFeature.DATABASE), @@ -66,7 +64,7 @@ class SentryModulesServiceTest { @Test fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is absent from classpath`() { - val (service, _) = + val service = fixture.getSut( tmpDir = testProjectDir.root, features = setOf(InstrumentationFeature.DATABASE), @@ -76,18 +74,6 @@ class SentryModulesServiceTest { assertFalse(service.isSQLiteDriverInstrEnabled()) } - @Test - fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is below VERSION_SQLITE`() { - val (service, _) = - fixture.getSut( - tmpDir = testProjectDir.root, - features = setOf(InstrumentationFeature.DATABASE), - sentryModules = mapOf(SentryModules.SENTRY_ANDROID_SQLITE to SentryVersions.VERSION_SQLITE), - ) - - assertFalse(service.isSQLiteDriverInstrEnabled()) - } - @Test fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is one minor below VERSION_SQLITE_DRIVER`() { val belowThreshold = @@ -96,7 +82,7 @@ class SentryModulesServiceTest { SentryVersions.VERSION_SQLITE_DRIVER.minor - 1, SentryVersions.VERSION_SQLITE_DRIVER.patch, ) - val (service, _) = + val service = fixture.getSut( tmpDir = testProjectDir.root, features = setOf(InstrumentationFeature.DATABASE), @@ -108,7 +94,7 @@ class SentryModulesServiceTest { @Test fun `isSQLiteDriverInstrEnabled is false when DATABASE is disabled`() { - val (service, _) = + val service = fixture.getSut( tmpDir = testProjectDir.root, features = emptySet(), @@ -128,7 +114,7 @@ class SentryModulesServiceTest { @Test fun `between VERSION_SQLITE and VERSION_SQLITE_DRIVER only the open-helper path is on`() { - val (service, _) = + val service = fixture.getSut( tmpDir = testProjectDir.root, features = setOf(InstrumentationFeature.DATABASE), @@ -146,7 +132,7 @@ class SentryModulesServiceTest { @Test fun `at VERSION_SQLITE_DRIVER the open-helper path is also on and the old path is off`() { - val (service, _) = + val service = fixture.getSut( tmpDir = testProjectDir.root, features = setOf(InstrumentationFeature.DATABASE), diff --git a/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt b/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt index 7cd08376b..39140f805 100644 --- a/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt +++ b/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt @@ -2,6 +2,11 @@ package io.sentry.sqlite import androidx.sqlite.SQLiteDriver +/** + * Minimal stub of `io.sentry.sqlite.SentrySQLiteDriver` so ASM can resolve the `INVOKESTATIC + * io/sentry/sqlite/SentrySQLiteDriver.create` emitted by [SetDriverMethodVisitor]. The `@JvmStatic + * create(SQLiteDriver)` shape here mirrors the SDK contract the visitor depends on. + */ class SentrySQLiteDriver private constructor(private val delegate: SQLiteDriver) : SQLiteDriver { companion object { @JvmStatic fun create(delegate: SQLiteDriver): SQLiteDriver = SentrySQLiteDriver(delegate) From c1a418f46bbd16fbfd4dc946d7db715a3b313fa1 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 11 Jun 2026 09:42:31 +0200 Subject: [PATCH 03/14] Address runningcode's comments --- plugin-build/build.gradle.kts | 5 ++--- .../gradle/extensions/TracingInstrumentationExtension.kt | 4 ++-- .../sentry/android/gradle/services/SentryModulesService.kt | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index 117b0007a..8ed121079 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -57,12 +57,11 @@ dependencies { // we need these dependencies for tests, because the bytecode verifier also analyzes superclasses testImplementationAar(libs.roomRuntimeAndroid) testImplementationAar(libs.room3RuntimeAndroid) + testImplementation(libs.sample.coroutines.core) testImplementationAar(libs.sentryAndroid) - testImplementationAar(libs.sentryAndroidOkhttp) testImplementationAar(libs.sqlite) testImplementationAar(libs.sqliteFramework) - - testImplementation(libs.sample.coroutines.core) + testImplementationAar(libs.sentryAndroidOkhttp) testImplementation(libs.sentryOkhttp) testRuntimeOnly(files(androidSdkPath)) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt index 2ff69cd0a..f23176c34 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt @@ -77,8 +77,8 @@ enum class InstrumentationFeature(val integrationName: String) { /** * When enabled the SDK will create spans for database operations at two levels: * - * **SQL statement execution** (`db.sql.query` spans): Wraps the low-level db open helper or - * driver so each SQL statement produces one or more spans. + * **SQL statement execution** (`db.sql.query` spans): Wraps the low-level `SQLiteDriver` or + * `SupportSQLiteOpenHelper` so each SQL statement produces one or more spans. * * **DAO method execution** (`db.sql.room` spans): Wraps each public method on Room's generated * `@Dao` `_Impl` classes, measuring the full DAO call end-to-end (transaction management, query diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt index 83101a6fa..5e8ed76e9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt @@ -76,8 +76,8 @@ abstract class SentryModulesService : * Returns true when the owning app uses a version of sentry-android-sqlite that contains * `SentrySQLiteDriver` and the DATABASE feature is enabled. * - * Room version is not gated here: Room < 2.7 has no matching `setDriver` method, so - * instrumentation is a no-op. + * For simplicity we avoid gating on the Room version and rely on visitor behavior instead + * (no-ops for versions of Room without a `SQLiteDriver`). */ fun isSQLiteDriverInstrEnabled(): Boolean = sentryModules.isAtLeast( From 2ee2ad8bcbccc4a441e7d07d35fb7c9da32e591c Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Jun 2026 18:39:03 +0200 Subject: [PATCH 04/14] Address TODOs now that the SentrySQLiteDriver has been published in 8.44.1 --- CHANGELOG.md | 16 +++++++++++----- gradle/libs.versions.toml | 2 +- .../gradle/services/SentryModulesService.kt | 4 ++-- .../io/sentry/android/gradle/util/Versions.kt | 2 +- .../gradle/integration/SentryPluginTest.kt | 8 -------- .../gradle/util/SentryModulesServiceTest.kt | 18 ++++++++++++++++++ .../io/sentry/sqlite/SentrySQLiteDriver.kt | 14 -------------- 7 files changed, 33 insertions(+), 31 deletions(-) delete mode 100644 plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbbce973..d7fbc5211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,19 @@ ### Features - Auto-instrument SQLiteDriver for Room users ([#1285](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1285)) - - Gated on `sentry-android-sqlite` >= 8.44.0 and the existing `tracingInstrumentation` `DATABASE` feature + - Gated on `sentry-android-sqlite` >= 8.45.0 and the existing `tracingInstrumentation` `DATABASE` feature - For users of the `androidx.sqlite.driver.SupportSQLiteDriver` bridge, auto-instrumentation wraps only the `SupportSQLiteOpenHelper` consumed by the bridge and not the bridge itself (avoids duplicate spans) +### Dependencies + +- Bump Android SDK from v8.43.2 to v8.45.0 ([#TODO](https://github.com/getsentry/sentry-android-gradle-plugin/pull/TODO)) + - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#TODO) + - [diff](https://github.com/getsentry/sentry-java/compare/8.43.2...8.45.0) + +## 6.12.0 + +### Dependencies + - Bump CLI from v3.5.0 to v3.5.1 ([#1300](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1300)) - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#351) - [diff](https://github.com/getsentry/sentry-cli/compare/3.5.0...3.5.1) @@ -28,10 +38,6 @@ - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#8432) - [diff](https://github.com/getsentry/sentry-java/compare/8.43.1...8.43.2) -### Dependencies - -- Bump mockito-kotlin from `com.nhaarman.mockitokotlin2:2.2.0` to `org.mockito.kotlin:5.4.0` ([#1286](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1286)) - ## 6.10.0 ### Features diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01d9d3997..a5dfbce93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ agp = "8.10.1" asm = "9.4" # // compatibility matrix -> https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/instrumentation/InstrumentationContext#apiversion ktfmt = "0.51" sqlite = "2.1.0" -sentry = "8.43.2" +sentry = "8.44.1" # Pinned to the last release that shipped sentry-android-okhttp; the module was removed # in 8.0.0, so there is no newer version to update to. Used only to verify the legacy # okhttp instrumentation path in tests. diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt index 5e8ed76e9..b7d5aacf6 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt @@ -76,8 +76,8 @@ abstract class SentryModulesService : * Returns true when the owning app uses a version of sentry-android-sqlite that contains * `SentrySQLiteDriver` and the DATABASE feature is enabled. * - * For simplicity we avoid gating on the Room version and rely on visitor behavior instead - * (no-ops for versions of Room without a `SQLiteDriver`). + * For simplicity we avoid gating on the Room version and rely on visitor behavior instead (no-ops + * for versions of Room without a `SQLiteDriver`). */ fun isSQLiteDriverInstrEnabled(): Boolean = sentryModules.isAtLeast( diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt index d5ec03683..e005e25d0 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt @@ -33,7 +33,7 @@ internal object SentryVersions { internal val VERSION_LOGCAT = SemVer(6, 17, 0) internal val VERSION_APP_START = SemVer(7, 1, 0) internal val VERSION_SQLITE = SemVer(6, 21, 0) - internal val VERSION_SQLITE_DRIVER = SemVer(8, 44, 0) + internal val VERSION_SQLITE_DRIVER = SemVer(8, 44, 1) internal val VERSION_ANDROID_OKHTTP_LISTENER = SemVer(6, 20, 0) internal val VERSION_OKHTTP = SemVer(7, 0, 0) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index 18d5de702..fb444b2bc 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -23,7 +23,6 @@ import org.gradle.util.GradleVersion import org.hamcrest.CoreMatchers.`is` import org.junit.Assert.assertThrows import org.junit.Assume.assumeThat -import org.junit.Ignore import org.junit.Test class SentryPluginTest : @@ -532,7 +531,6 @@ class SentryPluginTest : } } - @Ignore(SQLITE_DRIVER_IGNORE_REASON) @Test fun `applies sqliteDriver instrumentable when sentry gate passes with room2`() { val build = @@ -546,7 +544,6 @@ class SentryPluginTest : ) } - @Ignore(SQLITE_DRIVER_IGNORE_REASON) @Test fun `applies sqliteDriver instrumentable when sentry gate passes with room3`() { val build = @@ -583,7 +580,6 @@ class SentryPluginTest : assertInstrumentableChain(build, "AndroidXSQLiteOpenHelper", "AndroidXRoomDao") } - @Ignore(SQLITE_DRIVER_IGNORE_REASON) @Test fun `applies sqliteDriver instrumentable when sentry gate passes without room on classpath`() { val build = buildDatabaseInstrumentation(SQLITE, SENTRY_ANDROID_SQLITE_DRIVER) @@ -596,7 +592,6 @@ class SentryPluginTest : ) } - @Ignore(SQLITE_DRIVER_IGNORE_REASON) @Test fun `applies sqliteDriver instrumentable when sentry gate passes with room below 2_7`() { val build = @@ -1258,9 +1253,6 @@ class SentryPluginTest : } companion object { - private const val SQLITE_DRIVER_IGNORE_REASON = - "Placeholder version VERSION_SQLITE_DRIVER not yet on Maven" - private const val SQLITE = "androidx.sqlite:sqlite:2.6.2" private const val SENTRY_ANDROID_SQLITE_OPEN_HELPER = "io.sentry:sentry-android-sqlite:6.21.0" private val SENTRY_ANDROID_SQLITE_DRIVER = diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt index 6ada4e95d..15c9ee33f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesServiceTest.kt @@ -74,6 +74,24 @@ class SentryModulesServiceTest { assertFalse(service.isSQLiteDriverInstrEnabled()) } + @Test + fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is one patch below VERSION_SQLITE_DRIVER`() { + val belowThreshold = + SemVer( + SentryVersions.VERSION_SQLITE_DRIVER.major, + SentryVersions.VERSION_SQLITE_DRIVER.minor, + SentryVersions.VERSION_SQLITE_DRIVER.patch - 1, + ) + val service = + fixture.getSut( + tmpDir = testProjectDir.root, + features = setOf(InstrumentationFeature.DATABASE), + sentryModules = mapOf(SentryModules.SENTRY_ANDROID_SQLITE to belowThreshold), + ) + + assertFalse(service.isSQLiteDriverInstrEnabled()) + } + @Test fun `isSQLiteDriverInstrEnabled is false when sentry-android-sqlite is one minor below VERSION_SQLITE_DRIVER`() { val belowThreshold = diff --git a/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt b/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt deleted file mode 100644 index 39140f805..000000000 --- a/plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.sentry.sqlite - -import androidx.sqlite.SQLiteDriver - -/** - * Minimal stub of `io.sentry.sqlite.SentrySQLiteDriver` so ASM can resolve the `INVOKESTATIC - * io/sentry/sqlite/SentrySQLiteDriver.create` emitted by [SetDriverMethodVisitor]. The `@JvmStatic - * create(SQLiteDriver)` shape here mirrors the SDK contract the visitor depends on. - */ -class SentrySQLiteDriver private constructor(private val delegate: SQLiteDriver) : SQLiteDriver { - companion object { - @JvmStatic fun create(delegate: SQLiteDriver): SQLiteDriver = SentrySQLiteDriver(delegate) - } -} From f8708df4f2338ce428140b4d9cb5d8a7d36efda9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 22 Jun 2026 14:47:57 +0200 Subject: [PATCH 05/14] Add comment linking to SentrySQLiteDriver --- .../androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt index 1dfdf62ff..fdbc90950 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt @@ -28,6 +28,8 @@ class SetDriverMethodVisitor( companion object { internal const val CREATE = "create" + // Must match SentrySQLiteDriver.create(SQLiteDriver): SQLiteDriver in sentry-android-sqlite: + // https://github.com/getsentry/sentry-java/blob/main/sentry-android-sqlite/src/main/java/io/sentry/sqlite/SentrySQLiteDriver.kt internal const val SENTRY_CREATE_DESCRIPTOR = "($SQLITE_DRIVER_TYPE_DESCRIPTOR)$SQLITE_DRIVER_TYPE_DESCRIPTOR" internal const val SENTRY_SQLITE_DRIVER_TYPE = "Lio/sentry/sqlite/SentrySQLiteDriver;" From 52c11f306f2f7c1c8d4bf11c76cd5eb51d27967e Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 22 Jun 2026 15:21:51 +0200 Subject: [PATCH 06/14] Move CHANGELOG entry under Unreleased --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef09644e6..0f0f75f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,15 @@ # Changelog -## 6.12.0 +## Unreleased ### Features - Auto-instrument SQLiteDriver for Room users ([#1285](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1285)) - - Gated on `sentry-android-sqlite` >= 8.45.0 and the existing `tracingInstrumentation` `DATABASE` feature + - Gated on `sentry-android-sqlite` >= 8.45.0 and the existing `tracingInstrumentation` `DATABASE` feature (enabled by default) - For users of the `androidx.sqlite.driver.SupportSQLiteDriver` bridge, auto-instrumentation wraps only the `SupportSQLiteOpenHelper` consumed by the bridge and not the bridge itself (avoids duplicate spans) +## 6.12.0 + ### Dependencies - Bump Android SDK from v8.43.2 to v8.45.0 ([#TODO](https://github.com/getsentry/sentry-android-gradle-plugin/pull/TODO)) From fad9e97b719b67a19cfda121673d006e5be6bf09 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 23 Jun 2026 16:23:41 +0200 Subject: [PATCH 07/14] Refine inline comments + fix CHANGELOG --- CHANGELOG.md | 2 -- .../androidx/sqlite/driver/AndroidXSQLiteDriver.kt | 6 ++++-- .../sqlite/driver/visitor/SetDriverMethodVisitor.kt | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0f75f39..d90982e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,6 @@ - Gated on `sentry-android-sqlite` >= 8.45.0 and the existing `tracingInstrumentation` `DATABASE` feature (enabled by default) - For users of the `androidx.sqlite.driver.SupportSQLiteDriver` bridge, auto-instrumentation wraps only the `SupportSQLiteOpenHelper` consumed by the bridge and not the bridge itself (avoids duplicate spans) -## 6.12.0 - ### Dependencies - Bump Android SDK from v8.43.2 to v8.45.0 ([#TODO](https://github.com/getsentry/sentry-android-gradle-plugin/pull/TODO)) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt index f3872b8a9..f882bb4e0 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/AndroidXSQLiteDriver.kt @@ -19,15 +19,17 @@ internal const val SQLITE_DRIVER_TYPE_DESCRIPTOR = "Landroidx/sqlite/SQLiteDrive * * In other words, this: * ```kotlin + * val someDriver = AndroidSQLiteDriver() * val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName") - * .setDriver(AndroidSQLiteDriver()) + * .setDriver(someDriver) * .build() * ``` * * becomes: * ```kotlin + * val someDriver = AndroidSQLiteDriver() * val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName") - * .setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver())) + * .setDriver(SentrySQLiteDriver.create(someDriver)) * .build() * ``` * diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt index fdbc90950..06dd12516 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt @@ -21,6 +21,8 @@ class SetDriverMethodVisitor( ) { override fun onMethodEnter() { + // Inject at the start of RoomDatabase.Builder.setDriver(driver) so the method + // receives SentrySQLiteDriver.create(driver) instead of the raw driver (arg0). loadArg(0) invokeStatic(Type.getType(SENTRY_SQLITE_DRIVER_TYPE), Method(CREATE, SENTRY_CREATE_DESCRIPTOR)) storeArg(0) From 69ddb95b2f6e990022d4c7f640fdee4b29738eb0 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 23 Jun 2026 21:34:15 +0200 Subject: [PATCH 08/14] test: Load Room Builder bytecode from test dependencies Replace checked-in RoomDatabase$Builder fixtures with classpath loading from room-runtime-android and room3-runtime-android AARs so tests track Dependabot version bumps automatically. Co-Authored-By: Claude Sonnet 4 Co-authored-by: Cursor --- gradle/libs.versions.toml | 2 +- .../gradle/instrumentation/VisitorTest.kt | 24 +++++++++---- .../driver/SQLiteDriverBytecodeTestUtil.kt | 32 ++++++++++-------- .../androidxRoom/RoomDatabase$Builder.class | Bin 30384 -> 0 bytes .../androidxRoom/RoomDatabase3$Builder.class | Bin 26703 -> 0 bytes 5 files changed, 35 insertions(+), 23 deletions(-) delete mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase$Builder.class delete mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase3$Builder.class diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a5dfbce93..12a51e0f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -62,7 +62,7 @@ sentrySpringBootJakarta = { group = "io.sentry", name = "sentry-spring-boot-star # test arscLib = { group = "io.github.reandroid", name = "ARSCLib", version = "1.1.4" } mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } -# Room & Room 3 runtime versions must match RoomDatabase$Builder bytecode fixtures (see SQLiteDriverBytecodeTestUtil) +# Room runtime versions used by SQLiteDriverBytecodeTestUtil to load RoomDatabase$Builder bytecode from the test classpath roomRuntimeAndroid = { group = "androidx.room", name = "room-runtime-android", version = "2.7.0" } room3RuntimeAndroid = { group = "androidx.room3", name = "room3-runtime-android", version = "3.0.0-alpha06" } zip4j = { group = "net.lingala.zip4j", name = "zip4j", version = "2.11.5" } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt index 819d56cc3..3a3cbdd57 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt @@ -6,6 +6,7 @@ import io.sentry.android.gradle.instrumentation.androidx.room.AndroidXRoomDao import io.sentry.android.gradle.instrumentation.androidx.sqlite.AndroidXSQLiteOpenHelper import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.AndroidXSQLiteDatabase import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.AndroidXSQLiteDriver +import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SQLiteDriverBytecodeTestUtil import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement import io.sentry.android.gradle.instrumentation.appstart.Application import io.sentry.android.gradle.instrumentation.appstart.ContentProvider @@ -50,11 +51,20 @@ class VisitorTest( fun `instrumented class passes Java verifier`() { // first we read the original bytecode and pass it through the ClassWriter, so it computes // MAXS for us automatically (that's what AGP will do as well) - val inputStream = - FileInputStream( - "src/test/resources/testFixtures/instrumentation/" + "$instrumentedProject/$className.class" - ) - val classReader = ClassReader(inputStream) + val inputBytes = + if ( + classContext != null && + SQLiteDriverBytecodeTestUtil.isRoomBuilderClass(classContext.currentClassData.className) + ) { + SQLiteDriverBytecodeTestUtil.loadRoomBuilderFixture(classContext.currentClassData.className) + } else { + FileInputStream( + "src/test/resources/testFixtures/instrumentation/" + + "$instrumentedProject/$className.class" + ) + .use { it.readBytes() } + } + val classReader = ClassReader(inputBytes) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) val classContext = this.classContext ?: TestClassContext(instrumentable.fqName) val classVisitor = @@ -123,8 +133,8 @@ class VisitorTest( AndroidXSQLiteStatement(SemVer(2, 3, 0)), null, ), - // RoomDatabase$Builder fixtures: see SQLiteDriverBytecodeTestUtil (extracted from published - // AARs). + // RoomDatabase$Builder bytecode: loaded from Room runtime AARs on the test classpath (see + // SQLiteDriverBytecodeTestUtil). arrayOf( "androidxRoom", "RoomDatabase\$Builder", diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt index 918f6d5c1..9d97e43c8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt @@ -1,7 +1,6 @@ package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor.SetDriverMethodVisitor -import java.io.FileInputStream import org.objectweb.asm.ClassReader import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -11,29 +10,32 @@ import org.objectweb.asm.tree.MethodNode internal object SQLiteDriverBytecodeTestUtil { - private const val FIXTURES_ROOT = "src/test/resources/testFixtures/instrumentation/androidxRoom" - /** - * Room `Builder` bytecode fixtures extracted from published AARs: - * - `RoomDatabase$Builder.class`: `androidx.room:room-runtime-android:2.7.0` - * - `RoomDatabase3$Builder.class`: `androidx.room3:room3-runtime-android:3.0.0-alpha06` - * - * Extract from Google Maven by unzipping each AAR's `classes.jar` and copying - * `androidx/room/RoomDatabase$Builder.class` (or `androidx/room3/...`). + * Room `Builder` bytecode loaded from published AARs on the test classpath: + * - `androidx.room.RoomDatabase$Builder`: `androidx.room:room-runtime-android` + * - `androidx.room3.RoomDatabase$Builder`: `androidx.room3:room3-runtime-android` * * `VisitorTest` needs matching Room runtime AARs (and coroutines) on the test classpath so ASM * can resolve types referenced by the real bytecode. */ - private val CLASS_NAME_TO_FIXTURE = + private val CLASS_NAME_TO_RESOURCE = mapOf( - "androidx.room.RoomDatabase\$Builder" to "RoomDatabase\$Builder", - "androidx.room3.RoomDatabase\$Builder" to "RoomDatabase3\$Builder", + "androidx.room.RoomDatabase\$Builder" to "androidx/room/RoomDatabase\$Builder.class", + "androidx.room3.RoomDatabase\$Builder" to "androidx/room3/RoomDatabase\$Builder.class", ) + fun isRoomBuilderClass(className: String): Boolean = className in CLASS_NAME_TO_RESOURCE + fun loadRoomBuilderFixture(className: String): ByteArray { - val fixtureName = - CLASS_NAME_TO_FIXTURE[className] ?: error("No committed fixture for class $className") - return FileInputStream("$FIXTURES_ROOT/$fixtureName.class").use { it.readBytes() } + val resourcePath = + CLASS_NAME_TO_RESOURCE[className] ?: error("No Room Builder fixture for class $className") + val stream = + SQLiteDriverBytecodeTestUtil::class.java.classLoader.getResourceAsStream(resourcePath) + ?: error( + "Could not load $resourcePath from test classpath. " + + "Ensure roomRuntimeAndroid and room3RuntimeAndroid are on the test classpath." + ) + return stream.use { it.readBytes() } } fun isWrapCall(insn: MethodInsnNode): Boolean = diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase$Builder.class b/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase$Builder.class deleted file mode 100644 index 88e8733f1fb42234ff347606f9b788771abcea9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30384 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 HviE-gtPZZ2 diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase3$Builder.class b/plugin-build/src/test/resources/testFixtures/instrumentation/androidxRoom/RoomDatabase3$Builder.class deleted file mode 100644 index 57cc73de25f9e4c6a06f1beae49d4a2d5969c725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26703 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 Date: Tue, 23 Jun 2026 21:54:13 +0200 Subject: [PATCH 09/14] test: Load SQLite and core bytecode from test dependencies Replace checked-in AndroidX SQLite framework and TypefaceCompatUtil fixtures with classpath loading from sqlite-framework and androidx.core AARs on the test classpath. Co-Authored-By: Claude Sonnet 4 Co-authored-by: Cursor --- gradle/libs.versions.toml | 2 +- plugin-build/build.gradle.kts | 1 + .../driver/visitor/SetDriverMethodVisitor.kt | 4 +- .../InstrumentationBytecodeTestUtil.kt | 39 ++++++++++++++++++ .../gradle/instrumentation/VisitorTest.kt | 23 +++++++---- .../FrameworkSQLiteDatabase.class | Bin 9451 -> 0 bytes .../FrameworkSQLiteOpenHelperFactory.class | Bin 1880 -> 0 bytes .../FrameworkSQLiteStatement.class | Bin 1186 -> 0 bytes .../fileIO/TypefaceCompatUtil.class | Bin 6167 -> 0 bytes 9 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt delete mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteDatabase.class delete mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteOpenHelperFactory.class delete mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteStatement.class delete mode 100644 plugin-build/src/test/resources/testFixtures/instrumentation/fileIO/TypefaceCompatUtil.class diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12a51e0f4..c02a8f79e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,9 +60,9 @@ sentryAndroidOkhttp = { group = "io.sentry", name = "sentry-android-okhttp", ver sentrySpringBootJakarta = { group = "io.sentry", name = "sentry-spring-boot-starter-jakarta", version.ref = "sentry" } # test +androidxCore = { group = "androidx.core", name = "core", version = "1.15.0" } arscLib = { group = "io.github.reandroid", name = "ARSCLib", version = "1.1.4" } mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } -# Room runtime versions used by SQLiteDriverBytecodeTestUtil to load RoomDatabase$Builder bytecode from the test classpath roomRuntimeAndroid = { group = "androidx.room", name = "room-runtime-android", version = "2.7.0" } room3RuntimeAndroid = { group = "androidx.room3", name = "room3-runtime-android", version = "3.0.0-alpha06" } zip4j = { group = "net.lingala.zip4j", name = "zip4j", version = "2.11.5" } diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index 8ed121079..34c308b1a 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { testImplementation(libs.asmCommons) // we need these dependencies for tests, because the bytecode verifier also analyzes superclasses + testImplementationAar(libs.androidxCore) testImplementationAar(libs.roomRuntimeAndroid) testImplementationAar(libs.room3RuntimeAndroid) testImplementation(libs.sample.coroutines.core) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt index 06dd12516..cb03a9108 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt @@ -21,8 +21,8 @@ class SetDriverMethodVisitor( ) { override fun onMethodEnter() { - // Inject at the start of RoomDatabase.Builder.setDriver(driver) so the method - // receives SentrySQLiteDriver.create(driver) instead of the raw driver (arg0). + // We inject our wrapper at the start of RoomDatabase.Builder.setDriver(driver) so that the + // method receives SentrySQLiteDriver.create(driver) instead of the raw driver (arg0). loadArg(0) invokeStatic(Type.getType(SENTRY_SQLITE_DRIVER_TYPE), Method(CREATE, SENTRY_CREATE_DESCRIPTOR)) storeArg(0) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt new file mode 100644 index 000000000..fe8833bdc --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt @@ -0,0 +1,39 @@ +package io.sentry.android.gradle.instrumentation + +internal object InstrumentationBytecodeTestUtil { + + /** + * Instrumentation bytecode loaded from published AARs on the test classpath: + * - AndroidX SQLite framework classes: `androidx.sqlite:sqlite-framework` + * - `TypefaceCompatUtil`: `androidx.core:core` + */ + private val FIXTURE_TO_RESOURCE = + mapOf( + "androidxSqlite/FrameworkSQLiteOpenHelperFactory" to + "androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.class", + "androidxSqlite/FrameworkSQLiteDatabase" to + "androidx/sqlite/db/framework/FrameworkSQLiteDatabase.class", + "androidxSqlite/FrameworkSQLiteStatement" to + "androidx/sqlite/db/framework/FrameworkSQLiteStatement.class", + "fileIO/TypefaceCompatUtil" to "androidx/core/graphics/TypefaceCompatUtil.class", + ) + + fun hasClasspathFixture(instrumentedProject: String, className: String): Boolean = + fixtureKey(instrumentedProject, className) in FIXTURE_TO_RESOURCE + + fun loadClasspathFixture(instrumentedProject: String, className: String): ByteArray { + val fixtureKey = fixtureKey(instrumentedProject, className) + val resourcePath = + FIXTURE_TO_RESOURCE[fixtureKey] ?: error("No classpath fixture for $fixtureKey") + val stream = + InstrumentationBytecodeTestUtil::class.java.classLoader.getResourceAsStream(resourcePath) + ?: error( + "Could not load $resourcePath from test classpath. " + + "Ensure sqliteFramework and androidxCore are on the test classpath." + ) + return stream.use { it.readBytes() } + } + + private fun fixtureKey(instrumentedProject: String, className: String) = + "$instrumentedProject/$className" +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt index 3a3cbdd57..f946e660f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt @@ -52,17 +52,22 @@ class VisitorTest( // first we read the original bytecode and pass it through the ClassWriter, so it computes // MAXS for us automatically (that's what AGP will do as well) val inputBytes = - if ( + when { classContext != null && - SQLiteDriverBytecodeTestUtil.isRoomBuilderClass(classContext.currentClassData.className) - ) { - SQLiteDriverBytecodeTestUtil.loadRoomBuilderFixture(classContext.currentClassData.className) - } else { - FileInputStream( - "src/test/resources/testFixtures/instrumentation/" + - "$instrumentedProject/$className.class" + SQLiteDriverBytecodeTestUtil.isRoomBuilderClass( + classContext.currentClassData.className + ) -> + SQLiteDriverBytecodeTestUtil.loadRoomBuilderFixture( + classContext.currentClassData.className ) - .use { it.readBytes() } + InstrumentationBytecodeTestUtil.hasClasspathFixture(instrumentedProject, className) -> + InstrumentationBytecodeTestUtil.loadClasspathFixture(instrumentedProject, className) + else -> + FileInputStream( + "src/test/resources/testFixtures/instrumentation/" + + "$instrumentedProject/$className.class" + ) + .use { it.readBytes() } } val classReader = ClassReader(inputBytes) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteDatabase.class b/plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteDatabase.class deleted file mode 100644 index d8cb1aeb217b0a921c203c1428973acf04bacad6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9451 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 A`Tzg` diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteStatement.class b/plugin-build/src/test/resources/testFixtures/instrumentation/androidxSqlite/FrameworkSQLiteStatement.class deleted file mode 100644 index 668bf025dd9b7a6aa0f66230846a01619420f3bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1186 zcmb7D+iuf95IyUpagsVMw541Ng|;+yN-a@tQh^sxP%8^j8T!77mtb)0u(qN66G$L& zk$B((_$b7z;{-Jr(Q38ho!L2OXU~p*{`&p{zztlhqlV=gs<>Ro6|5+;s?3@)SCv`U z(bQq;uo!CnhawO?o(Klh;h`rZ-?KfQ@D7iK9ls9zMA+`0LmxMxw7v+FZHCHSKlGFP z3>(cOqs)DV@`K2uqh=jH6pzz>N64qV6HsK%iMl-4=h9bx9xNv>{FveP=!k<+L0-px zE_q+PjpQr4JzD);X4M{5xb z-yZ~GFBS4#JCaTm_81lp2i-*aS-5FBFZmm82R!WA!zjsCccWBxMcY?bUiw$zn+j>5 zftG=37&P<`EY2A?ugnE3G2F}_?ReTSY#j&pL`FSQnR=uE<)(u{B$EmC z8La=cM$Wpeo@4!py@zFL?HWi*nsZ7XYWoV!24*NSORGoFXaLhz>l>KW`U33}xf)K8 zok5B03Z}416l*w1-r_Kw=;p~IAQp(0(IC}x(p#AiRISg@zM}Mpv7Vt#(EpUNDJ13P zY)m~`&T;)PNBKSF4XdhTn9V{wz4sWeWqElGQy(VqTZMRPm12Bl5`U)~D^124 Q=pQG}zgLLAm}Oo14MJr7?EnA( diff --git a/plugin-build/src/test/resources/testFixtures/instrumentation/fileIO/TypefaceCompatUtil.class b/plugin-build/src/test/resources/testFixtures/instrumentation/fileIO/TypefaceCompatUtil.class deleted file mode 100644 index 58e3b5b53c5d76028e9a92f047956cc69867ec52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6167 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 From ee9a192178bd6d3202ad1109709dd2ce6ec9dac0 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Jun 2026 00:43:33 +0200 Subject: [PATCH 10/14] Bump sqlite test dep to 2.3.0 for delegate field coverage Align sqlite-framework bytecode and FrameworkSQLiteStatement SemVer so VisitorTest exercises the post-2.3.0 instrumentation path. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c02a8f79e..5e11faa6e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ agp = "8.10.1" asm = "9.4" # // compatibility matrix -> https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/instrumentation/InstrumentationContext#apiversion ktfmt = "0.51" -sqlite = "2.1.0" +sqlite = "2.3.0" sentry = "8.44.1" # Pinned to the last release that shipped sentry-android-okhttp; the module was removed # in 8.0.0, so there is no newer version to update to. Used only to verify the legacy From dd74f6fa3e5f4b382e9cc9c301ea8bc522975228 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 29 Jun 2026 09:29:00 +0200 Subject: [PATCH 11/14] Derive Room Builder + SQLite bytecode paths from FQN --- plugin-build/gradle.lockfile | 5 +- plugin-build/gradle/verification-metadata.xml | 24 +++++++ .../InstrumentationBytecodeTestUtil.kt | 40 +++--------- .../gradle/instrumentation/VisitorTest.kt | 62 +++++++++++-------- .../driver/SQLiteDriverBytecodeTestUtil.kt | 31 ---------- .../visitor/SetDriverMethodVisitorTest.kt | 6 +- 6 files changed, 75 insertions(+), 93 deletions(-) diff --git a/plugin-build/gradle.lockfile b/plugin-build/gradle.lockfile index dc202c615..b2041cf4b 100644 --- a/plugin-build/gradle.lockfile +++ b/plugin-build/gradle.lockfile @@ -1,12 +1,13 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +androidx.core:core:1.15.0=testImplementationAar androidx.databinding:databinding-common:8.10.1=testRuntimeClasspath androidx.databinding:databinding-compiler-common:8.10.1=testRuntimeClasspath androidx.room3:room3-runtime-android:3.0.0-alpha06=testImplementationAar androidx.room:room-runtime-android:2.7.0=testImplementationAar -androidx.sqlite:sqlite-framework:2.1.0=testImplementationAar -androidx.sqlite:sqlite:2.1.0=testImplementationAar +androidx.sqlite:sqlite-framework:2.3.0=testImplementationAar +androidx.sqlite:sqlite:2.3.0=testImplementationAar com.android.databinding:baseLibrary:8.10.1=testRuntimeClasspath com.android.tools.analytics-library:crash:31.10.1=testRuntimeClasspath com.android.tools.analytics-library:protos:31.10.1=testRuntimeClasspath diff --git a/plugin-build/gradle/verification-metadata.xml b/plugin-build/gradle/verification-metadata.xml index 402e9968f..2479ada4a 100644 --- a/plugin-build/gradle/verification-metadata.xml +++ b/plugin-build/gradle/verification-metadata.xml @@ -5,6 +5,14 @@ false + + + + + + + + @@ -45,6 +53,14 @@ + + + + + + + + @@ -53,6 +69,14 @@ + + + + + + + + diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt index fe8833bdc..201332287 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/InstrumentationBytecodeTestUtil.kt @@ -2,38 +2,12 @@ package io.sentry.android.gradle.instrumentation internal object InstrumentationBytecodeTestUtil { - /** - * Instrumentation bytecode loaded from published AARs on the test classpath: - * - AndroidX SQLite framework classes: `androidx.sqlite:sqlite-framework` - * - `TypefaceCompatUtil`: `androidx.core:core` - */ - private val FIXTURE_TO_RESOURCE = - mapOf( - "androidxSqlite/FrameworkSQLiteOpenHelperFactory" to - "androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.class", - "androidxSqlite/FrameworkSQLiteDatabase" to - "androidx/sqlite/db/framework/FrameworkSQLiteDatabase.class", - "androidxSqlite/FrameworkSQLiteStatement" to - "androidx/sqlite/db/framework/FrameworkSQLiteStatement.class", - "fileIO/TypefaceCompatUtil" to "androidx/core/graphics/TypefaceCompatUtil.class", - ) - - fun hasClasspathFixture(instrumentedProject: String, className: String): Boolean = - fixtureKey(instrumentedProject, className) in FIXTURE_TO_RESOURCE - - fun loadClasspathFixture(instrumentedProject: String, className: String): ByteArray { - val fixtureKey = fixtureKey(instrumentedProject, className) - val resourcePath = - FIXTURE_TO_RESOURCE[fixtureKey] ?: error("No classpath fixture for $fixtureKey") - val stream = - InstrumentationBytecodeTestUtil::class.java.classLoader.getResourceAsStream(resourcePath) - ?: error( - "Could not load $resourcePath from test classpath. " + - "Ensure sqliteFramework and androidxCore are on the test classpath." - ) - return stream.use { it.readBytes() } + fun loadClasspathFixture(className: String): ByteArray? { + val resourcePath = className.replace('.', '/') + ".class" + return InstrumentationBytecodeTestUtil::class + .java + .classLoader + .getResourceAsStream(resourcePath) + ?.use { it.readBytes() } } - - private fun fixtureKey(instrumentedProject: String, className: String) = - "$instrumentedProject/$className" } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt index f946e660f..a0f07d07b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt @@ -6,7 +6,6 @@ import io.sentry.android.gradle.instrumentation.androidx.room.AndroidXRoomDao import io.sentry.android.gradle.instrumentation.androidx.sqlite.AndroidXSQLiteOpenHelper import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.AndroidXSQLiteDatabase import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.AndroidXSQLiteDriver -import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SQLiteDriverBytecodeTestUtil import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement import io.sentry.android.gradle.instrumentation.appstart.Application import io.sentry.android.gradle.instrumentation.appstart.ContentProvider @@ -53,21 +52,11 @@ class VisitorTest( // MAXS for us automatically (that's what AGP will do as well) val inputBytes = when { - classContext != null && - SQLiteDriverBytecodeTestUtil.isRoomBuilderClass( + classContext != null -> + InstrumentationBytecodeTestUtil.loadClasspathFixture( classContext.currentClassData.className - ) -> - SQLiteDriverBytecodeTestUtil.loadRoomBuilderFixture( - classContext.currentClassData.className - ) - InstrumentationBytecodeTestUtil.hasClasspathFixture(instrumentedProject, className) -> - InstrumentationBytecodeTestUtil.loadClasspathFixture(instrumentedProject, className) - else -> - FileInputStream( - "src/test/resources/testFixtures/instrumentation/" + - "$instrumentedProject/$className.class" - ) - .use { it.readBytes() } + ) ?: loadFilesystemFixture() + else -> loadFilesystemFixture() } val classReader = ClassReader(inputBytes) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) @@ -108,6 +97,12 @@ class VisitorTest( ) } + private fun loadFilesystemFixture(): ByteArray = + FileInputStream( + "src/test/resources/testFixtures/instrumentation/" + "$instrumentedProject/$className.class" + ) + .use { it.readBytes() } + @After fun printLogs() { // only print bytecode when running locally @@ -126,29 +121,39 @@ class VisitorTest( fun parameters() = listOf( arrayOf( - "androidxSqlite", + "androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory", "FrameworkSQLiteOpenHelperFactory", AndroidXSQLiteOpenHelper(), - null, + TestClassContext( + TestClassData( + className = "androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory", + interfaces = listOf("androidx.sqlite.db.SupportSQLiteOpenHelper\$Factory"), + ) + ), ), - arrayOf("androidxSqlite", "FrameworkSQLiteDatabase", AndroidXSQLiteDatabase(), null), arrayOf( - "androidxSqlite", + "androidx.sqlite.db.framework.FrameworkSQLiteDatabase", + "FrameworkSQLiteDatabase", + AndroidXSQLiteDatabase(), + TestClassContext("androidx.sqlite.db.framework.FrameworkSQLiteDatabase"), + ), + arrayOf( + "androidx.sqlite.db.framework.FrameworkSQLiteStatement", "FrameworkSQLiteStatement", AndroidXSQLiteStatement(SemVer(2, 3, 0)), - null, + TestClassContext("androidx.sqlite.db.framework.FrameworkSQLiteStatement"), ), - // RoomDatabase$Builder bytecode: loaded from Room runtime AARs on the test classpath (see - // SQLiteDriverBytecodeTestUtil). + // RoomDatabase$Builder bytecode: loaded by FQN from Room runtime AARs on the test + // classpath. arrayOf( - "androidxRoom", + "androidx.room.RoomDatabase\$Builder", "RoomDatabase\$Builder", AndroidXSQLiteDriver(), TestClassContext("androidx.room.RoomDatabase\$Builder"), ), arrayOf( - "androidxRoom", - "RoomDatabase3\$Builder", + "androidx.room3.RoomDatabase\$Builder", + "RoomDatabase\$Builder", AndroidXSQLiteDriver(), TestClassContext("androidx.room3.RoomDatabase\$Builder"), ), @@ -187,7 +192,12 @@ class VisitorTest( kspTracksDaoTestParameters("insertAll"), kspTracksDaoTestParameters("update"), arrayOf("fileIO", "SQLiteCopyOpenHelper", WrappingInstrumentable(), null), - arrayOf("fileIO", "TypefaceCompatUtil", WrappingInstrumentable(), null), + arrayOf( + "androidx.core.graphics.TypefaceCompatUtil", + "TypefaceCompatUtil", + WrappingInstrumentable(), + TestClassContext("androidx.core.graphics.TypefaceCompatUtil"), + ), arrayOf( "fileIO", "Test", diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt index eabc0ab25..b30c0386f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/SQLiteDriverBytecodeTestUtil.kt @@ -1,7 +1,6 @@ package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor.SetDriverMethodVisitor -import java.io.FileInputStream import org.objectweb.asm.ClassReader import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -11,36 +10,6 @@ import org.objectweb.asm.tree.MethodNode internal object SQLiteDriverBytecodeTestUtil { - private const val FIXTURES_ROOT = "src/test/resources/testFixtures/instrumentation/androidxRoom" - - /** - * Room `Builder` bytecode loaded from published AARs on the test classpath: - * - `androidx.room.RoomDatabase$Builder`: `androidx.room:room-runtime-android` - * - `androidx.room3.RoomDatabase$Builder`: `androidx.room3:room3-runtime-android` - * - * `VisitorTest` needs matching Room runtime AARs (and coroutines) on the test classpath so ASM - * can resolve types referenced by the real bytecode. - */ - private val CLASS_NAME_TO_RESOURCE = - mapOf( - "androidx.room.RoomDatabase\$Builder" to "androidx/room/RoomDatabase\$Builder.class", - "androidx.room3.RoomDatabase\$Builder" to "androidx/room3/RoomDatabase\$Builder.class", - ) - - fun isRoomBuilderClass(className: String): Boolean = className in CLASS_NAME_TO_RESOURCE - - fun loadRoomBuilderFixture(className: String): ByteArray { - val resourcePath = - CLASS_NAME_TO_RESOURCE[className] ?: error("No Room Builder fixture for class $className") - val stream = - SQLiteDriverBytecodeTestUtil::class.java.classLoader.getResourceAsStream(resourcePath) - ?: error( - "Could not load $resourcePath from test classpath. " + - "Ensure roomRuntimeAndroid and room3RuntimeAndroid are on the test classpath." - ) - return stream.use { it.readBytes() } - } - fun isWrapCall(insn: MethodInsnNode): Boolean = insn.opcode == Opcodes.INVOKESTATIC && insn.owner == Type.getType(SetDriverMethodVisitor.SENTRY_SQLITE_DRIVER_TYPE).internalName && diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt index 92f728473..30e6843ae 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitorTest.kt @@ -2,6 +2,7 @@ package io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.visitor +import io.sentry.android.gradle.instrumentation.InstrumentationBytecodeTestUtil import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.AndroidXSQLiteDriver import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SQLiteDriverBytecodeTestUtil import io.sentry.android.gradle.instrumentation.androidx.sqlite.driver.SetDriverMethodInstrumentable @@ -50,7 +51,10 @@ class SetDriverMethodVisitorTest { } private fun instrument(className: String): ByteArray { - val bytes = SQLiteDriverBytecodeTestUtil.loadRoomBuilderFixture(className) + val bytes = + requireNotNull(InstrumentationBytecodeTestUtil.loadClasspathFixture(className)) { + "Could not load $className from test classpath" + } val classReader = ClassReader(bytes) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) val classVisitor = From 5bd6d3c7c08572c2d216b510d4560febf4cb1793 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:47:55 +0200 Subject: [PATCH 12/14] chore(deps): update actions/setup-java digest to 1bcf9fb (#1346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/integration-tests-sentry-cli.yml | 2 +- .github/workflows/pre-merge.yaml | 2 +- .github/workflows/publish-snapshot.yml | 2 +- .github/workflows/test-matrix-agp-gradle.yaml | 2 +- .github/workflows/update-deps.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9abe46065..60df617b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Java - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 with: distribution: 'temurin' java-version: '17' diff --git a/.github/workflows/integration-tests-sentry-cli.yml b/.github/workflows/integration-tests-sentry-cli.yml index 1f34adfd8..eef3f2a73 100644 --- a/.github/workflows/integration-tests-sentry-cli.yml +++ b/.github/workflows/integration-tests-sentry-cli.yml @@ -27,7 +27,7 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Java - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 with: distribution: 'temurin' java-version: '17' diff --git a/.github/workflows/pre-merge.yaml b/.github/workflows/pre-merge.yaml index 9c4410eb0..84cac8425 100644 --- a/.github/workflows/pre-merge.yaml +++ b/.github/workflows/pre-merge.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Java Version - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 with: distribution: 'temurin' java-version: '17' diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index 2c3cea529..1b62ecd35 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -24,7 +24,7 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Java - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 with: distribution: 'temurin' java-version: '17' diff --git a/.github/workflows/test-matrix-agp-gradle.yaml b/.github/workflows/test-matrix-agp-gradle.yaml index 9577b7810..4f2569395 100644 --- a/.github/workflows/test-matrix-agp-gradle.yaml +++ b/.github/workflows/test-matrix-agp-gradle.yaml @@ -59,7 +59,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Java Version - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 with: distribution: 'temurin' java-version: ${{ matrix.java }} diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml index 0a6a018ba..55ac66d23 100644 --- a/.github/workflows/update-deps.yml +++ b/.github/workflows/update-deps.yml @@ -37,7 +37,7 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Java - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 with: distribution: 'temurin' java-version: '17' From 5e1850357e9164e2b396b296d05cb71c7e6b307b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 10:21:42 +0200 Subject: [PATCH 13/14] chore(deps): update actions/setup-python digest to ece7cb0 (#1347) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/integration-tests-sentry-cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests-sentry-cli.yml b/.github/workflows/integration-tests-sentry-cli.yml index eef3f2a73..72860fe38 100644 --- a/.github/workflows/integration-tests-sentry-cli.yml +++ b/.github/workflows/integration-tests-sentry-cli.yml @@ -17,7 +17,7 @@ jobs: SENTRY_URL: http://127.0.0.1:8000 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: '3.14.6' From cc07b7dbdfc632b845820b3ae46f5486be497067 Mon Sep 17 00:00:00 2001 From: Chris Aigner <25478494+christophaigner@users.noreply.github.com> Date: Tue, 30 Jun 2026 10:55:54 +0200 Subject: [PATCH 14/14] docs: Add AI Use section to CONTRIBUTING.md (#1345) Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca6aaefce..831fd7ded 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,3 +80,8 @@ $ ./gradlew --stop ``` and try again. + + +# AI Use + +You are welcome to use whatever tools you prefer for making a contribution. However, any changes you propose have to be reviewed and tested by you, a human, first, before you submit a pull request with them for the Sentry team to review. If we feel like that did not happen, we will close the PR outright. For example, we will not review visibly AI-generated PRs from an agent instructed to look for and "fix" open issues in the repo. This aligns with our SDK principle: [every line has an owner](https://develop.sentry.dev/sdk/getting-started/principles/#every-line-has-an-owner).