Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1548c9c
feat(plugin): Auto-wrap SQLiteDriver with SentrySQLiteDriver for Room…
0xadam-brown Jun 6, 2026
2ff8ac5
Minor doc updates, DRYing, and test refinements
0xadam-brown Jun 10, 2026
4dcb9a6
Merge branch 'main' into feat/wrap-sqlite-driver
0xadam-brown Jun 17, 2026
c1a418f
Address runningcode's comments
0xadam-brown Jun 11, 2026
2ee2ad8
Address TODOs now that the SentrySQLiteDriver has been published in 8…
0xadam-brown Jun 19, 2026
f8708df
Add comment linking to SentrySQLiteDriver
0xadam-brown Jun 22, 2026
629f2b1
Merge branch 'main' into feat/wrap-sqlite-driver
0xadam-brown Jun 22, 2026
52c11f3
Move CHANGELOG entry under Unreleased
0xadam-brown Jun 22, 2026
fad9e97
Refine inline comments + fix CHANGELOG
0xadam-brown Jun 23, 2026
69ddb95
test: Load Room Builder bytecode from test dependencies
0xadam-brown Jun 23, 2026
aeecebf
test: Load SQLite and core bytecode from test dependencies
0xadam-brown Jun 23, 2026
ee9a192
Bump sqlite test dep to 2.3.0 for delegate field coverage
0xadam-brown Jun 23, 2026
8219370
Merge branch 'main' into chore/deps-not-binaries
0xadam-brown Jun 29, 2026
dd74f6f
Derive Room Builder + SQLite bytecode paths from FQN
0xadam-brown Jun 29, 2026
5bd6d3c
chore(deps): update actions/setup-java digest to 1bcf9fb (#1346)
renovate[bot] Jun 30, 2026
5e18503
chore(deps): update actions/setup-python digest to ece7cb0 (#1347)
renovate[bot] Jun 30, 2026
cc07b7d
docs: Add AI Use section to CONTRIBUTING.md (#1345)
christophaigner Jun 30, 2026
addc144
Merge branch 'main' into chore/deps-not-binaries
0xadam-brown Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.46.0"
# 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
Expand Down Expand Up @@ -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 & 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" }
Expand Down
1 change: 1 addition & 0 deletions plugin-build/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions plugin-build/gradle.lockfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
24 changes: 24 additions & 0 deletions plugin-build/gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
<verify-signatures>false</verify-signatures>
</configuration>
<components>
<component group="androidx.core" name="core" version="1.15.0">
<artifact name="core-1.15.0.aar">
<sha256 value="432b85a1974076e14b487ece4a28c59a84f1b9efc3fc8be72cd7f05d32055e51" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-1.15.0.module">
<sha256 value="e8a6c386e1765d870012fed2221173d4a2e8d2fd5aec732c51afb4a9f4684519" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.databinding" name="databinding-common" version="8.10.1">
<artifact name="databinding-common-8.10.1.jar">
<sha256 value="66cab82639dac0f6c2433464c093b074d608c4bb887ec38a9b8bc4ac98126732" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -45,6 +53,14 @@
<sha256 value="a9fe19b196fb25df41719ea6f1bc53f380eaed4bd5e651ec6fd75bcbeeb2a9ee" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.sqlite" name="sqlite" version="2.3.0">
<artifact name="sqlite-2.3.0.aar">
<sha256 value="d3d37e2403c523a5cee1983b27a6de7ad8dcbb502f446c3f76fca50ed73ce562" origin="Generated by Gradle"/>
</artifact>
<artifact name="sqlite-2.3.0.module">
<sha256 value="258022d6b5883c90ec024394cd9c466dc950a7978ab14d940336ab2664c722d2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.sqlite" name="sqlite-framework" version="2.1.0">
<artifact name="sqlite-framework-2.1.0.aar">
<sha256 value="8673737fdb2efbad91aeaeed1927ebb29212d36a867d93b9639c8069019f8a1e" origin="Generated by Gradle"/>
Expand All @@ -53,6 +69,14 @@
<sha256 value="ecd1fa9421e442cec59b4e642a51f8398e8d16bb5fcc92ac6fbb085ecc73685c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.sqlite" name="sqlite-framework" version="2.3.0">
<artifact name="sqlite-framework-2.3.0.aar">
<sha256 value="8b5bd3acef01ea781a8545bdf710b1c06182417669a1a68cd0ae4a536cdae827" origin="Generated by Gradle"/>
</artifact>
<artifact name="sqlite-framework-2.3.0.module">
<sha256 value="0b21ef275c000fe14bc73dfbf7b7699a289f3dc813c758ef37848269113e1cad" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.android" name="signflinger" version="8.10.1">
<artifact name="signflinger-8.10.1.jar">
<sha256 value="c1dca2c683634ee1a294298f9c7179578af6a86e080bdc40f961915bc5c8142f" origin="Generated by Gradle"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.sentry.android.gradle.instrumentation

internal object InstrumentationBytecodeTestUtil {

fun loadClasspathFixture(className: String): ByteArray? {
val resourcePath = className.replace('.', '/') + ".class"
return InstrumentationBytecodeTestUtil::class
.java
.classLoader
.getResourceAsStream(resourcePath)
?.use { it.readBytes() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,15 @@ 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 =
when {
classContext != null ->
InstrumentationBytecodeTestUtil.loadClasspathFixture(
classContext.currentClassData.className
) ?: loadFilesystemFixture()
else -> loadFilesystemFixture()
}
val classReader = ClassReader(inputBytes)
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
val classContext = this.classContext ?: TestClassContext(instrumentable.fqName)
val classVisitor =
Expand Down Expand Up @@ -93,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
Expand All @@ -111,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 fixtures: see SQLiteDriverBytecodeTestUtil (extracted from published
// AARs).
// 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"),
),
Expand Down Expand Up @@ -172,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"),
Comment thread
0xadam-brown marked this conversation as resolved.
),
arrayOf(
"fileIO",
"Test",
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,31 +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 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 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading