Skip to content

Commit 56bcc47

Browse files
mstrathmanclaude
andcommitted
fix: opt predict_version into SQLITE_RESULT_SUBTYPE; harden CI subtype check
The Windows (MinGW) CI job surfaced a real latent bug, not a platform quirk: predict_version() calls sqlite3_result_subtype() to tag its JSON result, but SQLite 3.45+ refuses that call unless the function was registered with SQLITE_RESULT_SUBTYPE. macOS shipped an older SQLite that never enforced it, so local tests passed; MSYS2's newer sqlite3 CLI returned 'misuse of sqlite3_result_subtype()'. - Register predict_version with SQLITE_RESULT_SUBTYPE (guarded #ifdef so a header bump either direction stays safe; scoped to the one function that sets a subtype). - Compile the static test builds (soak, asan, wasm, fuzz) with -DSQLITE_STRICT_SUBTYPE=1 and exercise predict_version()/predict_debug() in the soak, so this bug class now fails under our own ASan/valgrind/ wasm runs instead of only on Windows CI. - wasm job: install emscripten directly from emscripten-core/emsdk instead of an unresolvable third-party action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: mstrathman <matthew.strathman@gmail.com>
1 parent 07c8f17 commit 56bcc47

4 files changed

Lines changed: 33 additions & 7 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ jobs:
6161
run: |
6262
make vendor/sqlite3ext.h sqlite-predict.h
6363
clang -std=c99 -g -O1 -fsanitize=fuzzer,address \
64-
-DSQLITE_CORE -DSQLITE_PREDICT_STATIC -Ivendor/ -I./ \
64+
-DSQLITE_CORE -DSQLITE_PREDICT_STATIC -DSQLITE_STRICT_SUBTYPE=1 \
65+
-Ivendor/ -I./ \
6566
fuzz/fuzz_predict.c sqlite-predict.c predict-forecast.c \
6667
predict-tabular.c predict-receipts.c vendor/sha256.c \
6768
vendor/sqlite3.c -o fuzz_predict -lm -lpthread -ldl
@@ -94,8 +95,14 @@ jobs:
9495
runs-on: ubuntu-latest
9596
steps:
9697
- uses: actions/checkout@v4
97-
- uses: mymindstorm/setup-emsdk@v14
98+
- name: Install emscripten
99+
run: |
100+
git clone --depth 1 https://github.com/emscripten-core/emsdk.git
101+
./emsdk/emsdk install latest
102+
./emsdk/emsdk activate latest
98103
- name: Compile soak to WebAssembly
99-
run: make soak-wasm CC=emcc
104+
run: |
105+
source ./emsdk/emsdk_env.sh
106+
make soak-wasm
100107
- name: Run under node
101108
run: node dist/soak.js

‎Makefile‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,17 @@ test-asan: vendor/sqlite3ext.h sqlite-predict.h
7373
mkdir -p $(prefix)
7474
clang -std=c99 -g -O1 -fsanitize=address,undefined \
7575
-fno-omit-frame-pointer -fno-sanitize-recover=undefined \
76-
-DSQLITE_CORE -DSQLITE_PREDICT_STATIC -Ivendor/ -I./ \
76+
-DSQLITE_CORE -DSQLITE_PREDICT_STATIC -DSQLITE_STRICT_SUBTYPE=1 \
77+
-Ivendor/ -I./ \
7778
tests/soak.c $(OBJS) vendor/sqlite3.c -o $(prefix)/soak-asan
7879
UBSAN_OPTIONS=print_stacktrace=1 ./$(prefix)/soak-asan
7980

8081
# libFuzzer harness (statically links sqlite3.c; SQLITE_CORE build)
8182
fuzz-build: vendor/sqlite3ext.h sqlite-predict.h
8283
mkdir -p $(prefix)
8384
clang -std=c99 -g -O1 -fsanitize=fuzzer,address,undefined \
84-
-DSQLITE_CORE -DSQLITE_PREDICT_STATIC -Ivendor/ -I./ \
85+
-DSQLITE_CORE -DSQLITE_PREDICT_STATIC -DSQLITE_STRICT_SUBTYPE=1 \
86+
-Ivendor/ -I./ \
8587
fuzz/fuzz_predict.c $(OBJS) vendor/sqlite3.c \
8688
-o $(prefix)/fuzz_predict
8789

@@ -115,12 +117,14 @@ test-valgrind: vendor/sqlite3ext.h sqlite-predict.h
115117
# standalone soak binary (used by CI valgrind + Windows, runnable directly)
116118
soak: $(prefix) vendor/sqlite3ext.h sqlite-predict.h
117119
$(CC) -std=c99 -g -O0 -Ivendor/ -I./ -DSQLITE_CORE -DSQLITE_PREDICT_STATIC \
120+
-DSQLITE_STRICT_SUBTYPE=1 \
118121
tests/soak.c $(OBJS) vendor/sqlite3.c -o $(prefix)/soak $(LDFLAGS)
119122

120123
# WebAssembly soak (emscripten): compiles the static soak to wasm. CI runs
121124
# it under node to prove the code is wasm-portable and correct there.
122125
soak-wasm: $(prefix) vendor/sqlite3ext.h sqlite-predict.h
123126
emcc -std=c99 -O1 -Ivendor/ -I./ -DSQLITE_CORE -DSQLITE_PREDICT_STATIC \
127+
-DSQLITE_STRICT_SUBTYPE=1 \
124128
-sALLOW_MEMORY_GROWTH -sEXIT_RUNTIME=1 -sSTACK_SIZE=1048576 \
125129
tests/soak.c $(OBJS) vendor/sqlite3.c -o $(prefix)/soak.js
126130

‎sqlite-predict.c‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,18 @@ __declspec(dllexport)
338338
#endif
339339
int rc = SQLITE_OK;
340340
const int flags = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
341+
/* predict_version tags its result with the JSON subtype. SQLite 3.45+
342+
* refuses sqlite3_result_subtype() unless the function opted in with
343+
* this flag (older SQLite ignores the unknown flag and never enforced
344+
* the check). Scoped to the one function that sets a subtype. */
345+
#ifdef SQLITE_RESULT_SUBTYPE
346+
const int subtype_flags = flags | SQLITE_RESULT_SUBTYPE;
347+
#else
348+
const int subtype_flags = flags;
349+
#endif
341350

342-
rc = sqlite3_create_function_v2(db, "predict_version", 0, flags, NULL,
343-
predict_version_fn, NULL, NULL, NULL);
351+
rc = sqlite3_create_function_v2(db, "predict_version", 0, subtype_flags,
352+
NULL, predict_version_fn, NULL, NULL, NULL);
344353
if (rc != SQLITE_OK)
345354
return rc;
346355
rc = sqlite3_create_function_v2(db, "predict_debug", 0, flags, NULL,

‎tests/soak.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ int main(void) {
3232
if (sqlite3_predict_init(db, NULL, NULL) != SQLITE_OK)
3333
return fail("init", db);
3434

35+
/* meta functions — predict_version() sets the JSON result subtype, so
36+
* this exercises that path under -DSQLITE_STRICT_SUBTYPE (see Makefile) */
37+
if (run_discard(db, "SELECT predict_version()", 1) ||
38+
run_discard(db, "SELECT predict_debug()", 1))
39+
goto done_fail;
40+
3541
if (run_discard(
3642
db,
3743
"CREATE TABLE series(ts TEXT, value REAL, grp TEXT);", 1) ||

0 commit comments

Comments
 (0)