Skip to content

Commit a61d451

Browse files
asg017claude
andcommitted
Add comprehensive fuzz testing infrastructure with 6 new targets
- Fix numpy.c: tautology bug (|| → &&), infinite loop, and missing sqlite3_vec_numpy_init call - Replace tests/fuzz/Makefile: auto-detect clang, add UBSAN, macOS ld_classic workaround, generic build rules for all 10 targets - Add 6 new fuzz targets: shadow-corrupt (corrupted shadow tables), vec0-operations (INSERT/DELETE/query sequences), scalar-functions (all 18 SQL scalar functions), vec0-create-full (CREATE + lifecycle), metadata-columns (metadata/auxiliary columns), vec-each (vec_each TVF) - Add seed corpora for shadow-corrupt, vec0-operations, exec, and json - Add fuzz-build/fuzz-quick/fuzz-long targets to root Makefile All 10 targets verified building and running on macOS ARM (Apple Silicon). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9a6bf96 commit a61d451

24 files changed

Lines changed: 605 additions & 52 deletions

Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,39 @@ test-loadable-watch:
192192
test-unit:
193193
$(CC) -DSQLITE_CORE -DSQLITE_VEC_TEST tests/test-unit.c sqlite-vec.c vendor/sqlite3.c -I./ -Ivendor -o $(prefix)/test-unit && $(prefix)/test-unit
194194

195+
fuzz-build:
196+
$(MAKE) -C tests/fuzz all
197+
198+
fuzz-quick: fuzz-build
199+
@echo "Running all fuzz targets for 30 seconds each..."
200+
@for target in tests/fuzz/targets/*; do \
201+
[ -f "$$target" ] && [ -x "$$target" ] || continue; \
202+
name=$$(basename $$target); \
203+
echo "=== Fuzzing $$name ==="; \
204+
corpus="tests/fuzz/corpus/$$name"; \
205+
mkdir -p "$$corpus"; \
206+
dict="tests/fuzz/$${name//_/-}.dict"; \
207+
dict_flag=""; \
208+
[ -f "$$dict" ] && dict_flag="-dict=$$dict"; \
209+
"$$target" $$dict_flag \
210+
-max_total_time=30 "$$corpus" 2>&1 || true; \
211+
done
212+
213+
fuzz-long: fuzz-build
214+
@echo "Running all fuzz targets for 5 minutes each..."
215+
@for target in tests/fuzz/targets/*; do \
216+
[ -f "$$target" ] && [ -x "$$target" ] || continue; \
217+
name=$$(basename $$target); \
218+
echo "=== Fuzzing $$name ==="; \
219+
corpus="tests/fuzz/corpus/$$name"; \
220+
mkdir -p "$$corpus"; \
221+
dict="tests/fuzz/$${name//_/-}.dict"; \
222+
dict_flag=""; \
223+
[ -f "$$dict" ] && dict_flag="-dict=$$dict"; \
224+
"$$target" $$dict_flag \
225+
-max_total_time=300 "$$corpus" 2>&1 || true; \
226+
done
227+
195228
site-dev:
196229
npm --prefix site run dev
197230

tests/fuzz/Makefile

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,78 @@
1+
# Auto-detect clang with libFuzzer support.
2+
# Priority: Homebrew LLVM (macOS ARM) → Homebrew LLVM (macOS Intel) →
3+
# versioned clang (Linux) → system clang
4+
FUZZ_CC ?= $(shell \
5+
if [ -x /opt/homebrew/opt/llvm/bin/clang ]; then \
6+
echo "/opt/homebrew/opt/llvm/bin/clang"; \
7+
elif [ -x /usr/local/opt/llvm/bin/clang ]; then \
8+
echo "/usr/local/opt/llvm/bin/clang"; \
9+
elif command -v clang-18 >/dev/null 2>&1; then \
10+
echo "clang-18"; \
11+
elif command -v clang-17 >/dev/null 2>&1; then \
12+
echo "clang-17"; \
13+
elif command -v clang >/dev/null 2>&1; then \
14+
echo "clang"; \
15+
else \
16+
echo "FUZZ_CC_NOT_FOUND"; \
17+
fi)
118

2-
TARGET_DIR=./targets
19+
# AddressSanitizer + UndefinedBehaviorSanitizer + libFuzzer.
20+
# Override FUZZ_SANITIZERS to change (e.g., drop ubsan on Windows).
21+
FUZZ_SANITIZERS ?= -fsanitize=address,undefined,fuzzer
22+
23+
# On macOS, Homebrew LLVM may need -Wl,-ld_classic to work with the system linker.
24+
FUZZ_LDFLAGS ?= $(shell \
25+
if [ "$$(uname -s)" = "Darwin" ]; then \
26+
echo "-Wl,-ld_classic"; \
27+
fi)
28+
29+
FUZZ_CFLAGS = $(FUZZ_SANITIZERS) -I ../../ -I ../../vendor -DSQLITE_CORE -g $(FUZZ_LDFLAGS)
30+
FUZZ_SRCS = ../../vendor/sqlite3.c ../../sqlite-vec.c
31+
32+
TARGET_DIR = ./targets
333

434
$(TARGET_DIR):
535
mkdir -p $@
636

7-
# ASAN_OPTIONS=detect_leaks=1 ./fuzz_json -detect_leaks=1 '-trace_malloc=[12]' tmp
8-
$(TARGET_DIR)/json: json.c $(TARGET_DIR)
9-
/opt/homebrew/opt/llvm/bin/clang \
10-
-fsanitize=address,fuzzer \
11-
-I ../../ -I ../../vendor -DSQLITE_CORE -g \
12-
../../vendor/sqlite3.c \
13-
../../sqlite-vec.c \
14-
$< \
15-
-o $@
16-
17-
18-
$(TARGET_DIR)/vec0_create: vec0-create.c ../../sqlite-vec.c $(TARGET_DIR)
19-
/opt/homebrew/opt/llvm/bin/clang \
20-
-fsanitize=address,fuzzer \
21-
-I ../../ -I ../../vendor -DSQLITE_CORE -g \
22-
../../vendor/sqlite3.c \
23-
../../sqlite-vec.c \
24-
$< \
25-
-o $@
26-
27-
$(TARGET_DIR)/numpy: numpy.c ../../sqlite-vec.c $(TARGET_DIR)
28-
/opt/homebrew/opt/llvm/bin/clang \
29-
-fsanitize=address,fuzzer \
30-
-I ../../ -I ../../vendor -DSQLITE_CORE -g \
31-
../../vendor/sqlite3.c \
32-
../../sqlite-vec.c \
33-
$< \
34-
-o $@
35-
36-
$(TARGET_DIR)/exec: exec.c ../../sqlite-vec.c $(TARGET_DIR)
37-
/opt/homebrew/opt/llvm/bin/clang \
38-
-fsanitize=address,fuzzer \
39-
-I ../../ -I ../../vendor -DSQLITE_CORE -g \
40-
../../vendor/sqlite3.c \
41-
../../sqlite-vec.c \
42-
$< \
43-
-o $@
44-
45-
all: $(TARGET_DIR)/json $(TARGET_DIR)/numpy $(TARGET_DIR)/json $(TARGET_DIR)/exec
37+
# Existing targets (filename uses -, Makefile target uses _)
38+
$(TARGET_DIR)/vec0_create: vec0-create.c $(FUZZ_SRCS) | $(TARGET_DIR)
39+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
40+
41+
$(TARGET_DIR)/exec: exec.c $(FUZZ_SRCS) | $(TARGET_DIR)
42+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
43+
44+
$(TARGET_DIR)/json: json.c $(FUZZ_SRCS) | $(TARGET_DIR)
45+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
46+
47+
$(TARGET_DIR)/numpy: numpy.c $(FUZZ_SRCS) | $(TARGET_DIR)
48+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
49+
50+
# New targets
51+
$(TARGET_DIR)/shadow_corrupt: shadow-corrupt.c $(FUZZ_SRCS) | $(TARGET_DIR)
52+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
53+
54+
$(TARGET_DIR)/vec0_operations: vec0-operations.c $(FUZZ_SRCS) | $(TARGET_DIR)
55+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
56+
57+
$(TARGET_DIR)/scalar_functions: scalar-functions.c $(FUZZ_SRCS) | $(TARGET_DIR)
58+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
59+
60+
$(TARGET_DIR)/vec0_create_full: vec0-create-full.c $(FUZZ_SRCS) | $(TARGET_DIR)
61+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
62+
63+
$(TARGET_DIR)/metadata_columns: metadata-columns.c $(FUZZ_SRCS) | $(TARGET_DIR)
64+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
65+
66+
$(TARGET_DIR)/vec_each: vec-each.c $(FUZZ_SRCS) | $(TARGET_DIR)
67+
$(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SRCS) $< -o $@
68+
69+
FUZZ_TARGETS = vec0_create exec json numpy \
70+
shadow_corrupt vec0_operations scalar_functions \
71+
vec0_create_full metadata_columns vec_each
72+
73+
all: $(addprefix $(TARGET_DIR)/,$(FUZZ_TARGETS))
4674

4775
clean:
4876
rm -rf $(TARGET_DIR)/*
77+
78+
.PHONY: all clean

tests/fuzz/corpus/exec/select1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT 1

tests/fuzz/corpus/exec/vec_version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT vec_version()

tests/fuzz/corpus/json/empty

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

tests/fuzz/corpus/json/valid_2d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[0.5, -0.5]

tests/fuzz/corpus/json/valid_4d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[1.0, 2.0, 3.0, 4.0]
17 Bytes
Binary file not shown.
17 Bytes
Binary file not shown.
17 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)