Skip to content

Commit 8ea8282

Browse files
committed
Add regression test for deserialize lock leak on malloc failure
sqlite3_malloc(0) returns NULL; passing an empty binary to deserialize exercises that path. The unfixed NIF returns without releasing the connection lock, causing the subsequent close/1 call to deadlock.
1 parent ed96c73 commit 8ea8282

1 file changed

Lines changed: 16 additions & 0 deletions

File tree

test/exqlite/sqlite3_test.exs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,22 @@ defmodule Exqlite.Sqlite3Test do
950950
end
951951
end
952952

953+
# Targets the missing lock release on sqlite3_malloc failure in exqlite_deserialize:
954+
# sqlite3_malloc(0) returns NULL, so an empty binary triggers the error path.
955+
# Without the fix the lock is never released and any subsequent call deadlocks.
956+
test "deserialize malloc failure releases the connection lock" do
957+
{:ok, conn} = Sqlite3.open(":memory:")
958+
959+
# sqlite3_malloc(0) → NULL → hits the error return. Bug: lock not released.
960+
assert {:error, _} = Sqlite3.deserialize(conn, "main", <<>>)
961+
962+
# close/1 needs the lock. If it was leaked above, this will deadlock.
963+
task = Task.async(fn -> Sqlite3.close(conn) end)
964+
result = Task.yield(task, 500)
965+
Task.shutdown(task, :brutal_kill)
966+
assert {:ok, :ok} = result, "close deadlocked after failed deserialize (lock not released)"
967+
end
968+
953969
# Targets the missing NULL guard in exqlite_set_update_hook (unfixed NIF):
954970
# Acquires the lock then calls sqlite3_update_hook(conn->db, ...) without
955971
# a NULL check → sqlite3_update_hook(NULL, ...) → segfault.

0 commit comments

Comments
 (0)