Skip to content

Commit 875f29a

Browse files
stevemurrclaude
authored andcommitted
Fix multiple memory leaks in vec0 operations (INSERT, DELETE, KNN queries)
This commit applies fixes from PR asg017#258 and additional memory leak fixes identified in issues asg017#245, asg017#259, and asg017#265. Memory leak fixes include: 1. vec_slice: Early return without calling cleanup(vector) - now uses goto done 2. vec_eachFilter: pzErrMsg allocated but not freed on error 3. vec0_free: Missing cleanup for partition, auxiliary, and metadata column names 4. vec0_init: zSql not freed before goto error, pNew not freed on error path 5. vec0_metadata_filter_text: rowids not freed on error after allocation 6. vec0Filter_knn: knn_data not freed on cleanup when error occurs, array cleanup missing on error paths for metadata in-list processing 7. vec0_write_metadata_value: zSql not freed after sqlite3_prepare_v2 8. vec0Update_Insert: zSql not freed after sqlite3_prepare_v2 9. vec0Update_Delete_ClearMetadata: zSql not freed, stmt not finalized on error 10. vec0Update_UpdateAuxColumn: zSql not freed after sqlite3_prepare_v2 11. vec_static_blob_entriesFilter: Complete rewrite to properly handle all error paths - knn_data, queryVector, topk_rowids, distances, candidates, taken not properly freed on errors These fixes address memory corruption and database corruption issues that occurred during vec0 INSERT/DELETE operations and KNN queries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> (cherry picked from commit 12259fa)
1 parent 563a3e6 commit 875f29a

File tree

2 files changed

+165
-19
lines changed

2 files changed

+165
-19
lines changed

sqlite-vec.c

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,7 +1649,7 @@ static void vec_slice(sqlite3_context *context, int argc,
16491649
i8 *out = sqlite3_malloc(outSize);
16501650
if (!out) {
16511651
sqlite3_result_error_nomem(context);
1652-
return;
1652+
goto done;
16531653
}
16541654
memset(out, 0, outSize);
16551655
for (size_t i = 0; i < n; i++) {
@@ -1672,7 +1672,7 @@ static void vec_slice(sqlite3_context *context, int argc,
16721672
u8 *out = sqlite3_malloc(outSize);
16731673
if (!out) {
16741674
sqlite3_result_error_nomem(context);
1675-
return;
1675+
goto done;
16761676
}
16771677
memset(out, 0, outSize);
16781678
for (size_t i = 0; i < n / CHAR_BIT; i++) {
@@ -2535,6 +2535,7 @@ static int vec_eachFilter(sqlite3_vtab_cursor *pVtabCursor, int idxNum,
25352535
int rc = vector_from_value(argv[0], &pCur->vector, &pCur->dimensions,
25362536
&pCur->vector_type, &pCur->cleanup, &pzErrMsg);
25372537
if (rc != SQLITE_OK) {
2538+
sqlite3_free(pzErrMsg);
25382539
return SQLITE_ERROR;
25392540
}
25402541
pCur->iRowid = 0;
@@ -3616,6 +3617,24 @@ void vec0_free(vec0_vtab *p) {
36163617
sqlite3_free(p->vector_columns[i].name);
36173618
p->vector_columns[i].name = NULL;
36183619
}
3620+
3621+
for (int i = 0; i < p->numPartitionColumns; i++) {
3622+
sqlite3_free(p->paritition_columns[i].name);
3623+
p->paritition_columns[i].name = NULL;
3624+
}
3625+
3626+
for (int i = 0; i < p->numAuxiliaryColumns; i++) {
3627+
sqlite3_free(p->auxiliary_columns[i].name);
3628+
p->auxiliary_columns[i].name = NULL;
3629+
}
3630+
3631+
for (int i = 0; i < p->numMetadataColumns; i++) {
3632+
sqlite3_free(p->shadowMetadataChunksNames[i]);
3633+
p->shadowMetadataChunksNames[i] = NULL;
3634+
3635+
sqlite3_free(p->metadata_columns[i].name);
3636+
p->metadata_columns[i].name = NULL;
3637+
}
36193638
}
36203639

36213640
int vec0_num_defined_user_columns(vec0_vtab *p) {
@@ -5143,6 +5162,7 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv,
51435162
goto error;
51445163
}
51455164
rc = sqlite3_prepare_v2(db, zSql, -1, &stmt, NULL);
5165+
sqlite3_free(zSql);
51465166
if ((rc != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) {
51475167
sqlite3_finalize(stmt);
51485168
*pzErr = sqlite3_mprintf(
@@ -5160,6 +5180,7 @@ static int vec0_init(sqlite3 *db, void *pAux, int argc, const char *const *argv,
51605180

51615181
error:
51625182
vec0_free(pNew);
5183+
sqlite3_free(pNew);
51635184
return SQLITE_ERROR;
51645185
}
51655186

@@ -6074,6 +6095,7 @@ int vec0_metadata_filter_text(vec0_vtab * p, sqlite3_value * value, const void *
60746095
rc = sqlite3_blob_read(rowidsBlob, rowids, sqlite3_blob_bytes(rowidsBlob), 0);
60756096
if(rc != SQLITE_OK) {
60766097
sqlite3_blob_close(rowidsBlob);
6098+
sqlite3_free(rowids);
60776099
return rc;
60786100
}
60796101
sqlite3_blob_close(rowidsBlob);
@@ -7114,12 +7136,14 @@ int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum,
71147136
i64 v = sqlite3_value_int64(entry);
71157137
rc = array_append(&item.array, &v);
71167138
if (rc != SQLITE_OK) {
7139+
array_cleanup(&item.array);
71177140
goto cleanup;
71187141
}
71197142
}
71207143

71217144
if (rc != SQLITE_DONE) {
71227145
vtab_set_error(&p->base, "Error fetching next value in `x in (...)` integer expression");
7146+
array_cleanup(&item.array);
71237147
goto cleanup;
71247148
}
71257149

@@ -7139,17 +7163,33 @@ int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum,
71397163
entry.zString = sqlite3_mprintf("%.*s", n, s);
71407164
if(!entry.zString) {
71417165
rc = SQLITE_NOMEM;
7166+
// Clean up already-added text entries
7167+
for(size_t j = 0; j < item.array.length; j++) {
7168+
sqlite3_free(((struct Vec0MetadataInTextEntry*)item.array.z)[j].zString);
7169+
}
7170+
array_cleanup(&item.array);
71427171
goto cleanup;
71437172
}
71447173
entry.n = n;
71457174
rc = array_append(&item.array, &entry);
71467175
if (rc != SQLITE_OK) {
7176+
sqlite3_free(entry.zString);
7177+
// Clean up already-added text entries
7178+
for(size_t j = 0; j < item.array.length; j++) {
7179+
sqlite3_free(((struct Vec0MetadataInTextEntry*)item.array.z)[j].zString);
7180+
}
7181+
array_cleanup(&item.array);
71477182
goto cleanup;
71487183
}
71497184
}
71507185

71517186
if (rc != SQLITE_DONE) {
71527187
vtab_set_error(&p->base, "Error fetching next value in `x in (...)` text expression");
7188+
// Clean up text entries
7189+
for(size_t j = 0; j < item.array.length; j++) {
7190+
sqlite3_free(((struct Vec0MetadataInTextEntry*)item.array.z)[j].zString);
7191+
}
7192+
array_cleanup(&item.array);
71537193
goto cleanup;
71547194
}
71557195

@@ -7163,6 +7203,13 @@ int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum,
71637203

71647204
rc = array_append(aMetadataIn, &item);
71657205
if(rc != SQLITE_OK) {
7206+
// Clean up item.array since it wasn't added to aMetadataIn
7207+
if(p->metadata_columns[item.metadata_idx].kind == VEC0_METADATA_COLUMN_KIND_TEXT) {
7208+
for(size_t j = 0; j < item.array.length; j++) {
7209+
sqlite3_free(((struct Vec0MetadataInTextEntry*)item.array.z)[j].zString);
7210+
}
7211+
}
7212+
array_cleanup(&item.array);
71667213
goto cleanup;
71677214
}
71687215
}
@@ -7217,6 +7264,12 @@ int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum,
72177264

72187265
sqlite3_free(aMetadataIn);
72197266

7267+
// Free knn_data if not assigned to cursor (error case)
7268+
if (rc != SQLITE_OK && knn_data) {
7269+
vec0_query_knn_data_clear(knn_data);
7270+
sqlite3_free(knn_data);
7271+
}
7272+
72207273
return rc;
72217274
}
72227275

@@ -8190,6 +8243,7 @@ int vec0_write_metadata_value(vec0_vtab *p, int metadata_column_idx, i64 rowid,
81908243
}
81918244
sqlite3_stmt * stmt;
81928245
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
8246+
sqlite3_free((void *)zSql);
81938247
if(rc != SQLITE_OK) {
81948248
goto done;
81958249
}
@@ -8211,6 +8265,7 @@ int vec0_write_metadata_value(vec0_vtab *p, int metadata_column_idx, i64 rowid,
82118265
}
82128266
sqlite3_stmt * stmt;
82138267
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
8268+
sqlite3_free((void *)zSql);
82148269
if(rc != SQLITE_OK) {
82158270
goto done;
82168271
}
@@ -8409,6 +8464,7 @@ int vec0Update_Insert(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv,
84098464
goto cleanup;
84108465
}
84118466
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
8467+
sqlite3_free(zSql);
84128468
if(rc != SQLITE_OK) {
84138469
goto cleanup;
84148470
}
@@ -8651,12 +8707,14 @@ int vec0Update_Delete_ClearMetadata(vec0_vtab *p, int metadata_idx, i64 rowid, i
86518707
}
86528708
sqlite3_stmt * stmt;
86538709
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
8710+
sqlite3_free((void *)zSql);
86548711
if(rc != SQLITE_OK) {
86558712
goto done;
86568713
}
86578714
sqlite3_bind_int64(stmt, 1, rowid);
86588715
rc = sqlite3_step(stmt);
86598716
if(rc != SQLITE_DONE) {
8717+
sqlite3_finalize(stmt);
86608718
rc = SQLITE_ERROR;
86618719
goto done;
86628720
}
@@ -8743,6 +8801,7 @@ int vec0Update_UpdateAuxColumn(vec0_vtab *p, int auxiliary_column_idx, sqlite3_v
87438801
return SQLITE_NOMEM;
87448802
}
87458803
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
8804+
sqlite3_free((void *)zSql);
87468805
if(rc != SQLITE_OK) {
87478806
return rc;
87488807
}
@@ -9529,52 +9588,67 @@ static int vec_static_blob_entriesFilter(sqlite3_vtab_cursor *pVtabCursor,
95299588
if (idxNum == VEC_SBE__QUERYPLAN_KNN) {
95309589
assert(argc == 2);
95319590
pCur->query_plan = VEC_SBE__QUERYPLAN_KNN;
9532-
struct sbe_query_knn_data *knn_data;
9591+
struct sbe_query_knn_data *knn_data = NULL;
9592+
void *queryVector = NULL;
9593+
vector_cleanup cleanup = vector_cleanup_noop;
9594+
i32 *topk_rowids = NULL;
9595+
f32 *distances = NULL;
9596+
u8 *candidates = NULL;
9597+
u8 *taken = NULL;
9598+
int rc = SQLITE_OK;
9599+
95339600
knn_data = sqlite3_malloc(sizeof(*knn_data));
95349601
if (!knn_data) {
9535-
return SQLITE_NOMEM;
9602+
rc = SQLITE_NOMEM;
9603+
goto knn_cleanup;
95369604
}
95379605
memset(knn_data, 0, sizeof(*knn_data));
95389606

9539-
void *queryVector;
95409607
size_t dimensions;
95419608
enum VectorElementType elementType;
9542-
vector_cleanup cleanup;
95439609
char *err;
9544-
int rc = vector_from_value(argv[0], &queryVector, &dimensions, &elementType,
9610+
rc = vector_from_value(argv[0], &queryVector, &dimensions, &elementType,
95459611
&cleanup, &err);
95469612
if (rc != SQLITE_OK) {
9547-
return SQLITE_ERROR;
9613+
sqlite3_free(err);
9614+
rc = SQLITE_ERROR;
9615+
goto knn_cleanup;
95489616
}
95499617
if (elementType != p->blob->element_type) {
9550-
return SQLITE_ERROR;
9618+
rc = SQLITE_ERROR;
9619+
goto knn_cleanup;
95519620
}
95529621
if (dimensions != p->blob->dimensions) {
9553-
return SQLITE_ERROR;
9622+
rc = SQLITE_ERROR;
9623+
goto knn_cleanup;
95549624
}
95559625

95569626
i64 k = min(sqlite3_value_int64(argv[1]), (i64)p->blob->nvectors);
95579627
if (k < 0) {
95589628
// HANDLE https://github.com/asg017/sqlite-vec/issues/55
9559-
return SQLITE_ERROR;
9629+
rc = SQLITE_ERROR;
9630+
goto knn_cleanup;
95609631
}
95619632
if (k == 0) {
95629633
knn_data->k = 0;
95639634
pCur->knn_data = knn_data;
9635+
cleanup(queryVector);
95649636
return SQLITE_OK;
95659637
}
95669638

95679639
size_t bsize = (p->blob->nvectors + 7) & ~7;
95689640

9569-
i32 *topk_rowids = sqlite3_malloc(k * sizeof(i32));
9641+
topk_rowids = sqlite3_malloc(k * sizeof(i32));
95709642
if (!topk_rowids) {
95719643
// HANDLE https://github.com/asg017/sqlite-vec/issues/55
9572-
return SQLITE_ERROR;
9644+
rc = SQLITE_ERROR;
9645+
goto knn_cleanup;
95739646
}
9574-
f32 *distances = sqlite3_malloc(bsize * sizeof(f32));
9647+
distances = sqlite3_malloc(bsize * sizeof(f32));
95759648
if (!distances) {
95769649
// HANDLE https://github.com/asg017/sqlite-vec/issues/55
9577-
return SQLITE_ERROR;
9650+
rc = SQLITE_ERROR;
9651+
goto knn_cleanup;
95789652
}
95799653

95809654
for (size_t i = 0; i < p->blob->nvectors; i++) {
@@ -9583,11 +9657,17 @@ static int vec_static_blob_entriesFilter(sqlite3_vtab_cursor *pVtabCursor,
95839657
distances[i] =
95849658
distance_l2_sqr_float(v, (float *)queryVector, &p->blob->dimensions);
95859659
}
9586-
u8 *candidates = bitmap_new(bsize);
9587-
assert(candidates);
9660+
candidates = bitmap_new(bsize);
9661+
if (!candidates) {
9662+
rc = SQLITE_NOMEM;
9663+
goto knn_cleanup;
9664+
}
95889665

9589-
u8 *taken = bitmap_new(bsize);
9590-
assert(taken);
9666+
taken = bitmap_new(bsize);
9667+
if (!taken) {
9668+
rc = SQLITE_NOMEM;
9669+
goto knn_cleanup;
9670+
}
95919671

95929672
bitmap_fill(candidates, bsize);
95939673
for (size_t i = bsize; i >= p->blob->nvectors; i--) {
@@ -9601,6 +9681,21 @@ static int vec_static_blob_entriesFilter(sqlite3_vtab_cursor *pVtabCursor,
96019681
knn_data->rowids = topk_rowids;
96029682

96039683
pCur->knn_data = knn_data;
9684+
9685+
// Cleanup temporary allocations (not owned by knn_data)
9686+
sqlite3_free(candidates);
9687+
sqlite3_free(taken);
9688+
cleanup(queryVector);
9689+
return SQLITE_OK;
9690+
9691+
knn_cleanup:
9692+
sqlite3_free(knn_data);
9693+
sqlite3_free(topk_rowids);
9694+
sqlite3_free(distances);
9695+
sqlite3_free(candidates);
9696+
sqlite3_free(taken);
9697+
cleanup(queryVector);
9698+
return rc;
96049699
} else {
96059700
pCur->query_plan = VEC_SBE__QUERYPLAN_FULLSCAN;
96069701
pCur->iRowid = 0;

test_memory_fix.sql

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
-- Test case for memory leak fixes in sqlite-vec
2+
-- This tests the fixes from PR #258 addressing issues #245, #259, #265
3+
4+
-- Test 1: Basic vec0 table creation and insertion
5+
CREATE VIRTUAL TABLE test_vectors USING vec0(
6+
id TEXT PRIMARY KEY,
7+
embedding FLOAT[4]
8+
);
9+
10+
-- Insert several vectors (this used to cause segfault on 5th+ insertion in some cases)
11+
INSERT INTO test_vectors(id, embedding) VALUES ('vec1', '[1.0, 2.0, 3.0, 4.0]');
12+
INSERT INTO test_vectors(id, embedding) VALUES ('vec2', '[5.0, 6.0, 7.0, 8.0]');
13+
INSERT INTO test_vectors(id, embedding) VALUES ('vec3', '[9.0, 10.0, 11.0, 12.0]');
14+
INSERT INTO test_vectors(id, embedding) VALUES ('vec4', '[13.0, 14.0, 15.0, 16.0]');
15+
INSERT INTO test_vectors(id, embedding) VALUES ('vec5', '[17.0, 18.0, 19.0, 20.0]');
16+
INSERT INTO test_vectors(id, embedding) VALUES ('vec6', '[21.0, 22.0, 23.0, 24.0]');
17+
INSERT INTO test_vectors(id, embedding) VALUES ('vec7', '[25.0, 26.0, 27.0, 28.0]');
18+
INSERT INTO test_vectors(id, embedding) VALUES ('vec8', '[29.0, 30.0, 31.0, 32.0]');
19+
INSERT INTO test_vectors(id, embedding) VALUES ('vec9', '[33.0, 34.0, 35.0, 36.0]');
20+
INSERT INTO test_vectors(id, embedding) VALUES ('vec10', '[37.0, 38.0, 39.0, 40.0]');
21+
22+
SELECT 'Inserted 10 vectors successfully' AS status;
23+
24+
-- Test 2: KNN query (tests memory handling in vec0Filter_knn)
25+
SELECT id, distance
26+
FROM test_vectors
27+
WHERE embedding MATCH '[1.0, 2.0, 3.0, 4.0]'
28+
ORDER BY distance
29+
LIMIT 3;
30+
31+
-- Test 3: Delete operation (tests memory handling in vec0Update_Delete)
32+
DELETE FROM test_vectors WHERE id = 'vec5';
33+
SELECT 'Deleted vec5 successfully' AS status;
34+
35+
-- Test 4: Verify integrity after operations
36+
SELECT COUNT(*) AS remaining_count FROM test_vectors;
37+
38+
-- Test 5: Multiple insertions and deletions (stress test for memory leaks)
39+
INSERT INTO test_vectors(id, embedding) VALUES ('stress1', '[1.0, 1.0, 1.0, 1.0]');
40+
INSERT INTO test_vectors(id, embedding) VALUES ('stress2', '[2.0, 2.0, 2.0, 2.0]');
41+
DELETE FROM test_vectors WHERE id = 'stress1';
42+
INSERT INTO test_vectors(id, embedding) VALUES ('stress3', '[3.0, 3.0, 3.0, 3.0]');
43+
DELETE FROM test_vectors WHERE id = 'stress2';
44+
INSERT INTO test_vectors(id, embedding) VALUES ('stress4', '[4.0, 4.0, 4.0, 4.0]');
45+
DELETE FROM test_vectors WHERE id = 'stress3';
46+
47+
SELECT 'Stress test passed' AS status;
48+
49+
-- Clean up
50+
DROP TABLE test_vectors;
51+
SELECT 'All tests completed successfully' AS status;

0 commit comments

Comments
 (0)