[Enhancement] Cursor not closed

Author: XW3iCreated Dec 22, 2025Updated Dec 22, 2025

Summary

The method getAllCars() opens a Cursor via SQLiteDatabase#rawQuery and never closes it. This results in a resource leak of the underlying cursor/window and associated file descriptors.

  • File: Android-Debug-Database.git/sample-app/src/main/java/com/sample/database/CarDBHelper.java
  • Class: CarDBHelper
  • Method: public ArrayList<String> getAllCars() , public int count()
  • Lines: 109-122, 124-133

Impact

  • Stability: Unclosed cursors leak native resources. Accumulating these leaks may triggers a CursorWindowAllocationException, causing the app to crash.
  • Memory: Each leaked cursor holds onto a CursorWindow (typically 2MB buffer)

Code Analysis

Current implementation:

java
public ArrayList<String> getAllCars() {
    ArrayList<String> arrayList = new ArrayList<>();

    SQLiteDatabase db = this.getReadableDatabase();
    Cursor res = db.rawQuery("select * from cars", null);
    res.moveToFirst();

    while (!res.isAfterLast()) {
        arrayList.add(res.getString(res.getColumnIndex(CARS_COLUMN_NAME)));
        res.moveToNext();
    }
    return arrayList; // Cursor `res` is never closed
}

public int count() {
    SQLiteDatabase db = getReadableDatabase();
    Cursor cursor = db.rawQuery("select * from cars", null);
    if (cursor != null && cursor.getCount() > 0) {
        cursor.moveToFirst();
        return cursor.getInt(0);
    } else {
        return 0;
    }
}

Issues:

  • The Cursor res is never closed on any path.
  • moveToFirst() is called without checking the return value, which can cause incorrect iteration if the result set is empty.
  • Uses select * when only name is needed, increasing memory usage and column-index fragility.

Suggested Fix

Using try-with-resources (API 19+) to ensure cursors are automatically closed is recommended.

  • method getAllCars():
java
public ArrayList<String> getAllCars() {
    ArrayList<String> list = new ArrayList<>();
    SQLiteDatabase db = getReadableDatabase();
    try (Cursor c = db.rawQuery("select name from cars", null)) {
        while (!c.isAfterLast()) {
            list.add(res.getString(res.getColumnIndex(CARS_COLUMN_NAME)));
            c.moveToNext();
        }
    }
    return list;
}
  • method count():
java
public int count() {
    SQLiteDatabase db = getReadableDatabase();
    // Fix: Use try-with-resources or try-finally
    try (Cursor c = db.rawQuery("select count(*) from cars", null)) {
        if (c.moveToFirst()) {
            return c.getInt(0);
        }
        return 0;
    }
}

Additional Affected Locations

The same resource leak pattern (unclosed Cursor from rawQuery) exists in the following locations.

Module: sample-app (src/main/java/com/sample/database/)

  • ContactDBHelper.java: getAllCotacts() (Lines ~111-124), count() (Lines ~126-135)
  • ExtTestDBHelper.java: count() (Lines ~57-66)

Module: sample-app-encrypt (src/main/java/com/sample/encrypt/database/)

  • CarDBHelper.java: getAllCars() (Lines ~109-122)
  • ContactDBHelper.java: getAllCotacts() (Lines ~111-124), count() (Lines ~126-135)
  • ExtTestDBHelper.java: count() (Lines ~57-66)

Context & Acknowledgement: This issue was identified during our academic research on Java resource management. We have manually reviewed this finding. Thank you for maintaining this open-source project! We hope this report helps.

Source: amitshekhariitbhu/Android-Debug-Database