Skip to content

Close SQLite connection leak on Database init failure (#27)#32

Merged
jkalias merged 1 commit into
mainfrom
claude/fix-database-init-leak
Jul 7, 2026
Merged

Close SQLite connection leak on Database init failure (#27)#32
jkalias merged 1 commit into
mainfrom
claude/fix-database-init-leak

Conversation

@jkalias

@jkalias jkalias commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Database::Database(const char*) leaked the sqlite3* handle on two error
paths:

  • sqlite3_open_v2 can allocate a handle even when it returns an error
    (everything except OOM), and that handle was discarded unclosed before
    throwing.
  • If any CreateTableQuery::Execute() throws, the constructor exits via
    exception. The object was never fully constructed, so ~Database does not
    run and the already-open connection leaks; Initialize() never assigns
    instance_ in this case either, so nothing else can close it.

Close db_ on both paths before the exception propagates: explicitly on the
open-failure branch, and via a try/catch around table creation. Because the
constructor throw prevents ~Database from running at all, there is no
double-close risk. sqlite3_close(nullptr) is a documented no-op, so this is
safe even when db_ never got assigned.

This completes #27 - the destructor and Instance() null-check were already
added in #28.

Adds a regression test that fails Initialize() with a path whose parent
directory doesn't exist (fails identically on POSIX/Windows via
SQLITE_OPEN_CREATE not creating missing directories), and confirms
Initialize() can subsequently succeed with clean, recoverable state.
Verified under LeakSanitizer: reproduced a real 1400-byte leak on the
pre-fix code and confirmed zero leaks (full suite, 65/65 tests) with the fix
applied.

Database::Database(const char*) leaked the sqlite3* handle on two error
paths:

- sqlite3_open_v2 can allocate a handle even when it returns an error
  (everything except OOM), and that handle was discarded unclosed before
  throwing.
- If any CreateTableQuery::Execute() throws, the constructor exits via
  exception. The object was never fully constructed, so ~Database does not
  run and the already-open connection leaks; Initialize() never assigns
  instance_ in this case either, so nothing else can close it.

Close db_ on both paths before the exception propagates: explicitly on the
open-failure branch, and via a try/catch around table creation. Because the
constructor throw prevents ~Database from running at all, there is no
double-close risk. sqlite3_close(nullptr) is a documented no-op, so this is
safe even when db_ never got assigned.

This completes #27 - the destructor and Instance() null-check were already
added in #28.

Adds a regression test that fails Initialize() with a path whose parent
directory doesn't exist (fails identically on POSIX/Windows via
SQLITE_OPEN_CREATE not creating missing directories), and confirms
Initialize() can subsequently succeed with clean, recoverable state.
Verified under LeakSanitizer: reproduced a real 1400-byte leak on the
pre-fix code and confirmed zero leaks (full suite, 65/65 tests) with the fix
applied.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUt3c1wdseRtRSSXfg3MCK
@jkalias jkalias merged commit af3a81d into main Jul 7, 2026
15 checks passed
@jkalias jkalias deleted the claude/fix-database-init-leak branch July 7, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants