Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

SQLCipherStatement.close() never closes its Cursor, leaking a CursorWindow per Room query

Open Beginner friendly
#97 1 comment 0 reactions 0 assignees View on GitHub

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
Tech stack
android, java, sqlite

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from sqlcipher/sqlcipher-android

All issues in sqlcipher/sqlcipher-android

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.