SQLCipherStatement.close() never closes its Cursor, leaking a CursorWindow per Room query
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Domain
- databases, mobile-dev
Research direction
The bug is in SQLCipherStatement, where close() sets closed = true without closing the cursor field that step() creates, while reset() already closes it and nulls the field. Start by reading the reset() and close() methods side by side and confirm that the cursor is never released on the close() path. Done means close() closes and nulls the cursor when it is open, before setting closed, and a test or a heap check shows the native memory is released after many point reads without a manual GC.
Written by the indexing model from the issue text.
Description
SQLCipherStatement.close() sets closed = true and never closes the Cursor that step() created. Only reset() closes it:
public void reset() {
if (cursor != null) {
cursor.close();
cursor = null;
}
stepped = false;
}
public void close() {
closed = true;
}
Room 3 (androidx.room3 3.0.3) prepares, steps and closes a statement per query, and it never calls reset() before close(). So every query leaves a CursorWindow for the finalizer to free.
Versions: sqlcipher-android 4.18.0 through 4.19.1 and the current default branch, with Room 3.0.3 and SQLCipherDriver.
Measured: we ran 20,000 point reads of one 20 KB row through Room with SQLCipherDriver, on Android emulators, with no manual GC.
| API 36 | API 29 | |
|---|---|---|
SQLCipherDriver as shipped |
+72 MB native heap | +244 MB native heap |
close() calling reset() first |
under 4 MB (0 KB after GC) | under 4 MB |
The framework's AndroidSQLiteDriver showed 24 KB over the same reads.
Workaround: we wrap the driver so that each statement's close() calls reset() first:
private class CursorClosingStatement(private val delegate: SQLiteStatement) : SQLiteStatement by delegate {
override fun close() {
delegate.reset()
delegate.close()
}
}
Suggested fix: in close(), close the cursor if it is open, as reset() does, before setting closed = true.
- Dominant language
- Java
- Stars
- 277
- Forks
- 39
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 1
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from sqlcipher/sqlcipher-android
-
Difficulty 4/5 3-5 days Newbie friendliness 32/100
-
Difficulty 4/5 3-5 days Newbie friendliness 32/100
sqlcipher/sqlcipher-android#95 · 4 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
-
Difficulty 4/5 3-5 days Newbie friendliness 30/100
sqlcipher/sqlcipher-android#23 · 2 comments ·
All issues in sqlcipher/sqlcipher-android
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
floci-io/floci#5369 · 1 comment ·
Maintainers usually reply within 1 day
-
area-integrations
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
bug IIIF interoperability
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
bcgov/nr-forest-client#2532 ·
Maintainers usually reply within 1 day