Skip to content

Commit 0104d67

Browse files
0xadam-brownOpenCode
andcommitted
fix(android): Reduce SQLite cursor SDK crash false positives
Replace Kotlin interface delegation in SentryCrossProcessCursor with Android's CursorWrapper for ordinary Cursor methods. This keeps lazy-query span instrumentation on getCount, onMove, and fillWindow, but avoids generating Sentry-owned methods such as getString for pass-through cursor calls. When app database code throws from those ordinary cursor methods, the stack should now point at Android's cursor wrapper and the underlying SQLite failure instead of making SDK crash detection treat the event as caused by Sentry's SQLite integration. Co-Authored-By: OpenCode <noreply@opencode.ai>
1 parent 462dea2 commit 0104d67

2 files changed

Lines changed: 13 additions & 9 deletions

File tree

sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/SentryCrossProcessCursor.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ package io.sentry.android.sqlite
22

33
import android.database.CrossProcessCursor
44
import android.database.CursorWindow
5+
import android.database.CursorWrapper
56

67
/*
78
* SQLiteCursor executes the query lazily, when one of getCount() and onMove() is called.
89
* Also, by docs, fillWindow() can be used to fill the cursor with data.
910
* So we wrap these methods to create a span.
10-
* SQLiteCursor is never used directly in the code, but only the Cursor interface.
11-
* This means we can use CrossProcessCursor - that extends Cursor - as wrapper, since
12-
* CrossProcessCursor is an interface and we can use Kotlin delegation.
11+
* Ordinary Cursor methods are delegated through CursorWrapper to avoid adding Sentry frames to
12+
* app database exceptions that the wrapper did not instrument.
1313
*/
1414
internal class SentryCrossProcessCursor(
1515
private val delegate: CrossProcessCursor,
1616
private val spans: OpenHelperSpans,
1717
private val sql: String,
18-
) : CrossProcessCursor by delegate {
18+
) : CursorWrapper(delegate), CrossProcessCursor {
1919
// We have to start the span only the first time, regardless of how many times its methods get
2020
// called.
2121
private var isSpanStarted = false
@@ -36,6 +36,8 @@ internal class SentryCrossProcessCursor(
3636
return spans.performSql(sql) { delegate.onMove(oldPosition, newPosition) }
3737
}
3838

39+
override fun getWindow(): CursorWindow? = delegate.window
40+
3941
override fun fillWindow(position: Int, window: CursorWindow?) {
4042
if (isSpanStarted) {
4143
return delegate.fillWindow(position, window)

sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/SentryCrossProcessCursorTest.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.android.sqlite
22

33
import android.database.CrossProcessCursor
4+
import android.database.CursorWrapper
45
import io.sentry.IScopes
56
import io.sentry.ISpan
67
import io.sentry.SentryOptions
@@ -52,13 +53,14 @@ class SentryCrossProcessCursorTest {
5253

5354
cursor.fillWindow(0, mock())
5455
verify(fixture.mockCursor).fillWindow(eq(0), any())
56+
}
5557

56-
// Let's verify other methods are delegated, even if not explicitly
57-
cursor.close()
58-
verify(fixture.mockCursor).close()
58+
@Test
59+
fun `ordinary cursor methods are delegated by Android CursorWrapper`() {
60+
val getStringMethod =
61+
SentryCrossProcessCursor::class.java.getMethod("getString", Int::class.javaPrimitiveType!!)
5962

60-
cursor.getString(1)
61-
verify(fixture.mockCursor).getString(eq(1))
63+
assertEquals(CursorWrapper::class.java, getStringMethod.declaringClass)
6264
}
6365

6466
@Test

0 commit comments

Comments
 (0)