Skip to content

Commit fb7da53

Browse files
committed
fix(sqlite): format size_t error details with %llu in sqlite3_mprintf
SQLite's printf has no z length modifier: "%zu" parses as its %z (free-after-use string) conversion, so every error path that formatted a size_t dereferenced the integer as a char* and crashed. That turned invalid input into a segfault instead of an error, e.g. CREATE VIRTUAL TABLE ... vec0(embedding float[0]) and loading an HNSW index with a dangling entry point. Use %llu with explicit casts; the existing test_overflow and test_persistence_fuzz cases now pass instead of crashing.
1 parent d4ce0cc commit fb7da53

2 files changed

Lines changed: 20 additions & 16 deletions

File tree

‎include/sqlite-vec-cpp/index/hnsw_persistence.hpp‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ int save_hnsw_index(sqlite3* db, const char* schema, const char* table,
433433
sqlite3_finalize(stmt);
434434
sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr);
435435
if (pzErr)
436-
*pzErr = sqlite3_mprintf("Failed to save HNSW node %zu", failedNodeId);
436+
*pzErr = sqlite3_mprintf("Failed to save HNSW node %llu",
437+
static_cast<unsigned long long>(failedNodeId));
437438
return rc;
438439
}
439440

@@ -530,7 +531,8 @@ HNSWIndex<T, Metric> load_hnsw_index(sqlite3* db, const char* schema, const char
530531
if (node.id != node_id) {
531532
sqlite3_finalize(stmt);
532533
if (pzErr)
533-
*pzErr = sqlite3_mprintf("HNSW node id mismatch for rowid %zu", node_id);
534+
*pzErr = sqlite3_mprintf("HNSW node id mismatch for rowid %llu",
535+
static_cast<unsigned long long>(node_id));
534536
throw std::runtime_error("HNSW node id mismatch");
535537
}
536538
nodes.emplace(node_id, std::move(node));
@@ -546,14 +548,15 @@ HNSWIndex<T, Metric> load_hnsw_index(sqlite3* db, const char* schema, const char
546548

547549
if (!nodes.empty() && nodes.find(entry_point_id) == nodes.end()) {
548550
if (pzErr)
549-
*pzErr = sqlite3_mprintf("HNSW entry point %zu missing from nodes", entry_point_id);
551+
*pzErr = sqlite3_mprintf("HNSW entry point %llu missing from nodes",
552+
static_cast<unsigned long long>(entry_point_id));
550553
throw std::runtime_error("HNSW entry point missing from nodes");
551554
}
552555

553556
for (auto& [id, node] : nodes) {
554557
for (auto& layer : node.edges) {
555-
std::erase_if(layer,
556-
[&](size_t neighbor_id) { return nodes.find(neighbor_id) == nodes.end(); });
558+
std::erase_if(
559+
layer, [&](size_t neighbor_id) { return nodes.find(neighbor_id) == nodes.end(); });
557560
}
558561
}
559562

@@ -606,7 +609,8 @@ int save_hnsw_node_incremental(sqlite3* db, const char* schema, const char* tabl
606609
sqlite3_finalize(stmt);
607610

608611
if (rc != SQLITE_DONE && pzErr) {
609-
*pzErr = sqlite3_mprintf("Failed to save HNSW node %zu", node.id);
612+
*pzErr = sqlite3_mprintf("Failed to save HNSW node %llu",
613+
static_cast<unsigned long long>(node.id));
610614
}
611615

612616
return rc;
@@ -657,7 +661,8 @@ int save_hnsw_nodes_incremental(sqlite3* db, const char* schema, const char* tab
657661
sqlite3_finalize(stmt);
658662
sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr);
659663
if (pzErr)
660-
*pzErr = sqlite3_mprintf("Failed to save HNSW node %zu", node.id);
664+
*pzErr = sqlite3_mprintf("Failed to save HNSW node %llu",
665+
static_cast<unsigned long long>(node.id));
661666
return rc;
662667
}
663668
}

‎include/sqlite-vec-cpp/sqlite/vec0_module.hpp‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
#include <vector>
1717
#include "../distances/l2.hpp"
1818
#include "../index/hnsw.hpp"
19+
#include "../index/hnsw_persistence.hpp"
1920
#include "../utils/error.hpp"
2021
#include "parsers.hpp"
21-
#include "../index/hnsw_persistence.hpp"
2222

2323
#include <unordered_map>
2424
#include "value.hpp"
@@ -94,9 +94,8 @@ inline void vec0_registry_remove(sqlite3* db, std::string_view schema_name,
9494
vec0_table_registry().erase(vec0_registry_key(db, schema_name, table_name));
9595
}
9696
template <typename Fn>
97-
inline auto vec0_with_table(sqlite3* db, std::string_view schema_name,
98-
std::string_view table_name, Fn&& fn)
99-
-> decltype(fn(static_cast<Vec0Table*>(nullptr))) {
97+
inline auto vec0_with_table(sqlite3* db, std::string_view schema_name, std::string_view table_name,
98+
Fn&& fn) -> decltype(fn(static_cast<Vec0Table*>(nullptr))) {
10099
std::lock_guard<std::mutex> lk(vec0_registry_mutex());
101100
auto& reg = vec0_table_registry();
102101
auto it = reg.find(vec0_registry_key(db, schema_name, table_name));
@@ -341,9 +340,8 @@ vec0_run_ann_query(Vec0Table* table, const Value& query_value, size_t k, size_t
341340
const size_t entry_count = std::min(kMaxRouteEntryPoints, ordered_rowids.size());
342341
route_entry_points.reserve(entry_count);
343342
for (size_t i = 0; i < entry_count; ++i) {
344-
const size_t index = entry_count == 1
345-
? 0
346-
: i * (ordered_rowids.size() - 1) / (entry_count - 1);
343+
const size_t index =
344+
entry_count == 1 ? 0 : i * (ordered_rowids.size() - 1) / (entry_count - 1);
347345
route_entry_points.push_back(static_cast<size_t>(ordered_rowids[index]));
348346
}
349347
}
@@ -605,8 +603,9 @@ inline int vec0Create(sqlite3* db, void* pAux, int argc, const char* const* argv
605603
parse_vec0_schema(argc, argv, embedding_col, dims);
606604

607605
if (dims == 0 || dims > kMaxVec0Dimensions) {
608-
*pzErr = sqlite3_mprintf("vec0: dimensions must be in [1, %zu], got %zu",
609-
kMaxVec0Dimensions, dims);
606+
*pzErr = sqlite3_mprintf("vec0: dimensions must be in [1, %llu], got %llu",
607+
static_cast<unsigned long long>(kMaxVec0Dimensions),
608+
static_cast<unsigned long long>(dims));
610609
return SQLITE_ERROR;
611610
}
612611
if (embedding_col.empty() || embedding_col.find('"') != std::string::npos) {

0 commit comments

Comments
 (0)