Skip to content

Commit 92621e9

Browse files
committed
Pass correct pointer to cleanup in ensure_vector_match error path
When the second vector fails to parse in ensure_vector_match(), the cleanup function for the first vector was called with 'a' (void**) instead of '*a' (void*). This caused sqlite3_free to be called with a stack address instead of the heap-allocated vector, resulting in a crash: malloc: Non-aligned pointer being freed Fatal error 6: Aborted The fix dereferences the pointer correctly, matching how cleanup is done in other error paths. This fix has a unit test that will crash without the patch.
1 parent a2dd24f commit 92621e9

File tree

2 files changed

+27
-1
lines changed

2 files changed

+27
-1
lines changed

sqlite-vec.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ int ensure_vector_match(sqlite3_value *aValue, sqlite3_value *bValue, void **a,
10161016
if (rc != SQLITE_OK) {
10171017
*outError = sqlite3_mprintf("Error reading 2nd vector: %s", error);
10181018
sqlite3_free(error);
1019-
aCleanup(a);
1019+
aCleanup(*a);
10201020
return SQLITE_ERROR;
10211021
}
10221022

tests/test-loadable.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,32 @@ def check(a, b, dtype=np.float32):
424424
assert vec_distance_cosine("[1.1, 1.0]", "[1.2, 1.2]") == 0.001131898257881403
425425

426426

427+
def test_ensure_vector_match_cleanup_on_second_vector_error():
428+
"""
429+
Test that ensure_vector_match properly cleans up the first vector
430+
when the second vector fails to parse.
431+
432+
This tests the fix for a bug where aCleanup(a) was called instead of
433+
aCleanup(*a), passing the wrong pointer to the cleanup function.
434+
435+
The bug only manifests when the first vector is parsed from JSON/TEXT
436+
(which uses sqlite3_free as cleanup) rather than BLOB (which uses noop).
437+
"""
438+
# Valid first vector as JSON text - this causes memory allocation
439+
# and sets cleanup to sqlite3_free
440+
valid_vector_json = "[1.0, 2.0, 3.0, 4.0]"
441+
442+
# Invalid second vector: 5 bytes, not divisible by 4 (sizeof float32)
443+
# This will fail in fvec_from_value with "invalid float32 vector BLOB length"
444+
invalid_vector = b"\x01\x02\x03\x04\x05"
445+
446+
with pytest.raises(sqlite3.OperationalError, match=r"^Error reading 2nd vector: invalid float32 vector BLOB length\. Must be divisible by 4, found 5$"):
447+
db.execute(
448+
"select vec_distance_cosine(?, ?)",
449+
[valid_vector_json, invalid_vector]
450+
).fetchone()
451+
452+
427453
def test_vec_distance_hamming():
428454
vec_distance_hamming = lambda *args: db.execute(
429455
"select vec_distance_hamming(vec_bit(?), vec_bit(?))", args

0 commit comments

Comments
 (0)