Skip to content

Commit 6cb4f2e

Browse files
committed
Merge branch 'main' into fix/java-enum-extraction
2 parents 40868a0 + 078de1c commit 6cb4f2e

63 files changed

Lines changed: 4132 additions & 266 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/_build.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ on:
88
description: 'Version string (e.g. v0.8.0)'
99
type: string
1010
default: ''
11+
attest:
12+
description: 'Generate build provenance attestations for release artifacts'
13+
type: boolean
14+
default: false
1115

1216
permissions:
1317
contents: read
18+
id-token: write
19+
attestations: write
1420

1521
jobs:
1622
build-unix:
@@ -83,6 +89,12 @@ jobs:
8389
tar -czf "codebase-memory-mcp-${GOOS}-${GOARCH}.tar.gz" \
8490
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
8591
92+
- name: Attest standard binary provenance
93+
if: ${{ inputs.attest }}
94+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
95+
with:
96+
subject-path: codebase-memory-mcp-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
97+
8698
- name: Build UI binary
8799
env:
88100
VERSION: ${{ inputs.version }}
@@ -113,6 +125,12 @@ jobs:
113125
tar -czf "codebase-memory-mcp-ui-${GOOS}-${GOARCH}.tar.gz" \
114126
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
115127
128+
- name: Attest UI binary provenance
129+
if: ${{ inputs.attest }}
130+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
131+
with:
132+
subject-path: codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
133+
116134
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
117135
with:
118136
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
@@ -158,6 +176,12 @@ jobs:
158176
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
159177
zip codebase-memory-mcp-windows-amd64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
160178
179+
- name: Attest standard binary provenance
180+
if: ${{ inputs.attest }}
181+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
182+
with:
183+
subject-path: codebase-memory-mcp-windows-amd64.zip
184+
161185
- name: Build UI binary
162186
shell: msys2 {0}
163187
env:
@@ -178,6 +202,12 @@ jobs:
178202
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
179203
zip codebase-memory-mcp-ui-windows-amd64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
180204
205+
- name: Attest UI binary provenance
206+
if: ${{ inputs.attest }}
207+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
208+
with:
209+
subject-path: codebase-memory-mcp-ui-windows-amd64.zip
210+
181211
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
182212
with:
183213
name: binaries-windows-amd64
@@ -227,6 +257,12 @@ jobs:
227257
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
228258
zip codebase-memory-mcp-windows-arm64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
229259
260+
- name: Attest standard binary provenance
261+
if: ${{ inputs.attest }}
262+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
263+
with:
264+
subject-path: codebase-memory-mcp-windows-arm64.zip
265+
230266
- name: Build UI binary
231267
shell: msys2 {0}
232268
env:
@@ -247,6 +283,12 @@ jobs:
247283
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
248284
zip codebase-memory-mcp-ui-windows-arm64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
249285
286+
- name: Attest UI binary provenance
287+
if: ${{ inputs.attest }}
288+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
289+
with:
290+
subject-path: codebase-memory-mcp-ui-windows-arm64.zip
291+
250292
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
251293
with:
252294
name: binaries-windows-arm64
@@ -299,6 +341,12 @@ jobs:
299341
tar -czf "codebase-memory-mcp-linux-${ARCH}-portable.tar.gz" \
300342
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
301343
344+
- name: Attest standard binary provenance
345+
if: ${{ inputs.attest }}
346+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
347+
with:
348+
subject-path: codebase-memory-mcp-linux-${{ matrix.arch }}-portable.tar.gz
349+
302350
- name: Build UI binary (static)
303351
env:
304352
VERSION: ${{ inputs.version }}
@@ -318,6 +366,12 @@ jobs:
318366
tar -czf "codebase-memory-mcp-ui-linux-${ARCH}-portable.tar.gz" \
319367
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
320368
369+
- name: Attest UI binary provenance
370+
if: ${{ inputs.attest }}
371+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
372+
with:
373+
subject-path: codebase-memory-mcp-ui-linux-${{ matrix.arch }}-portable.tar.gz
374+
321375
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
322376
with:
323377
name: binaries-linux-${{ matrix.arch }}-portable

.github/workflows/_smoke.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,20 @@ jobs:
248248
run: |
249249
& "C:\Program Files\Windows Defender\MpCmdRun.exe" -SignatureUpdate 2>$null
250250
$result = & "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File "$PWD\codebase-memory-mcp.exe" -DisableRemediation
251+
$code = $LASTEXITCODE
251252
Write-Host $result
252-
if ($LASTEXITCODE -ne 0) { Write-Host "BLOCKED: Windows Defender flagged binary!"; exit 1 }
253-
Write-Host "=== Windows Defender: clean ==="
253+
# MpCmdRun -Scan exit codes: 0 = clean, 2 = threat found. Any OTHER non-zero
254+
# means the scan engine could not run at all (e.g. hr=0x800106ba: the Defender
255+
# antimalware service is unavailable on the runner) — that is NOT a detection.
256+
# Fail soft on an engine failure so a transient runner-side AV outage can't
257+
# false-fail a release; only a real detection (exit 2) hard-blocks.
258+
if ($code -eq 2) {
259+
Write-Host "BLOCKED: Windows Defender flagged binary!"; exit 1
260+
} elseif ($code -ne 0) {
261+
Write-Host "::warning::Windows Defender scan could not run (exit $code) - skipping AV gate on this runner"
262+
} else {
263+
Write-Host "=== Windows Defender: clean ==="
264+
}
254265
255266
smoke-linux-portable:
256267
needs: setup-matrix

.github/workflows/_test.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ jobs:
8888
env:
8989
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
9090

91+
test-tsan:
92+
runs-on: ubuntu-latest
93+
timeout-minutes: 120
94+
steps:
95+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
96+
97+
- name: Install deps (Ubuntu)
98+
run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev
99+
100+
- name: ThreadSanitizer tests
101+
run: make -f Makefile.cbm test-tsan CC=clang CXX=clang++
102+
91103
test-windows:
92104
needs: setup-matrix
93105
strategy:

.github/workflows/dry-run.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ jobs:
5656
build:
5757
if: ${{ inputs.skip_builds != true && !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
5858
needs: [test]
59+
permissions:
60+
contents: read
61+
id-token: write
62+
attestations: write
5963
uses: ./.github/workflows/_build.yml
64+
with:
65+
attest: false
6066

6167
# ── Smoke test every binary ────────────────────────────────────
6268
# Run unless builds were skipped or a build leg FAILED. Every build leg

.github/workflows/release.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,14 @@ jobs:
5656
# ── 3. Build all platforms ──────────────────────────────────────
5757
build:
5858
needs: [test]
59+
permissions:
60+
contents: read
61+
id-token: write
62+
attestations: write
5963
uses: ./.github/workflows/_build.yml
6064
with:
6165
version: ${{ inputs.version }}
66+
attest: true
6267

6368
# ── 4. Smoke test every binary ──────────────────────────────────
6469
smoke:
@@ -99,10 +104,10 @@ jobs:
99104
- name: Generate checksums
100105
run: sha256sum *.tar.gz *.zip > checksums.txt
101106

102-
- name: Attest build provenance
107+
- name: Attest checksum provenance
103108
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
104109
with:
105-
subject-path: '*.tar.gz,*.zip,checksums.txt'
110+
subject-path: checksums.txt
106111

107112
- name: Generate SBOM
108113
run: |

Makefile.cbm

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ CFLAGS_TEST = $(CFLAGS_COMMON) -g -O1 $(SANITIZE)
6767
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) -g -O1 $(SANITIZE)
6868

6969
# TSan (can't combine with ASan)
70+
TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer
7071
CFLAGS_TSAN = $(CFLAGS_COMMON) -g -O1 \
71-
-fsanitize=thread -fno-omit-frame-pointer
72+
$(TSAN_SANITIZE)
7273
CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -g -O1 \
73-
-fsanitize=thread -fno-omit-frame-pointer
74+
$(TSAN_SANITIZE)
7475

7576
# Windows needs ws2_32 (Winsock), psapi (GetProcessMemoryInfo), shell32
7677
# (CommandLineToArgvW — the UTF-8 argv path in main.c, #423/#20), and
@@ -89,7 +90,7 @@ endif
8990

9091
LDFLAGS = -lm -lstdc++ -lpthread -lz $(WIN32_LIBS) $(STATIC_FLAGS)
9192
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(WIN32_LIBS)
92-
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(WIN32_LIBS)
93+
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz $(TSAN_SANITIZE) $(WIN32_LIBS)
9394

9495
# ── Source files ─────────────────────────────────────────────────
9596

@@ -317,6 +318,7 @@ TEST_EXTRACTION_SRCS = \
317318
tests/test_extraction.c \
318319
tests/test_extraction_inheritance.c \
319320
tests/test_extraction_imports.c \
321+
tests/test_parse_coverage.c \
320322
tests/test_grammar_regression.c \
321323
tests/test_grammar_labels.c \
322324
tests/test_grammar_imports.c \
@@ -445,6 +447,7 @@ TEST_REPRO_SRCS = \
445447
tests/repro/repro_issue363.c \
446448
tests/repro/repro_issue581.c \
447449
tests/repro/repro_issue787.c \
450+
tests/repro/repro_issue842.c \
448451
tests/repro/repro_invariant_calls.c \
449452
tests/repro/repro_invariant_graph.c \
450453
tests/repro/repro_invariant_breadth.c \
@@ -483,13 +486,17 @@ GRAMMAR_CFLAGS = -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR) -I$(TS_INCLUDE)
483486
GRAMMAR_CFLAGS_TEST = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \
484487
$(SANITIZE)
485488
GRAMMAR_CFLAGS_TSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \
486-
-fsanitize=thread -fno-omit-frame-pointer
489+
$(TSAN_SANITIZE)
487490

488491
# Object files for grammars + ts_runtime + lsp_all + preprocessor
489492
GRAMMAR_OBJS_TEST = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/%.o,$(GRAMMAR_SRCS))
490493
TS_RUNTIME_OBJ_TEST = $(BUILD_DIR)/ts_runtime.o
491494
LSP_OBJ_TEST = $(BUILD_DIR)/lsp_all.o
492495
PP_OBJ_TEST = $(BUILD_DIR)/preprocessor.o
496+
GRAMMAR_OBJS_TSAN = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/tsan_%.o,$(GRAMMAR_SRCS))
497+
TS_RUNTIME_OBJ_TSAN = $(BUILD_DIR)/tsan_ts_runtime.o
498+
LSP_OBJ_TSAN = $(BUILD_DIR)/tsan_lsp_all.o
499+
PP_OBJ_TSAN = $(BUILD_DIR)/tsan_preprocessor.o
493500

494501
# ── Targets ──────────────────────────────────────────────────────
495502

@@ -520,60 +527,94 @@ $(BUILD_DIR)/lsp_all.o: $(LSP_UNITY_DEPS) | $(BUILD_DIR)
520527
$(BUILD_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR)
521528
$(CXX) $(CXXFLAGS_TEST) -w -I$(CBM_DIR)/vendored -c -o $@ $<
522529

530+
$(BUILD_DIR)/tsan_%.o: $(CBM_DIR)/%.c | $(BUILD_DIR)
531+
$(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $<
532+
533+
$(BUILD_DIR)/tsan_ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(BUILD_DIR)
534+
$(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $<
535+
536+
$(BUILD_DIR)/tsan_lsp_all.o: $(LSP_UNITY_DEPS) | $(BUILD_DIR)
537+
$(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $<
538+
539+
$(BUILD_DIR)/tsan_preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR)
540+
$(CXX) $(CXXFLAGS_TSAN) -w -I$(CBM_DIR)/vendored -c -o $@ $<
541+
523542
# ── Full test binary ─────────────────────────────────────────────
524543

525544
# mimalloc object files
526545
MIMALLOC_OBJ_TEST = $(BUILD_DIR)/mimalloc.o
546+
MIMALLOC_OBJ_TSAN = $(BUILD_DIR)/tsan_mimalloc.o
527547
MIMALLOC_OBJ_PROD = $(BUILD_DIR)/prod_mimalloc.o
528548

529549
$(BUILD_DIR)/mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR)
530550
$(CC) $(MIMALLOC_CFLAGS_TEST) -c -o $@ $<
531551

552+
$(BUILD_DIR)/tsan_mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR)
553+
$(CC) $(MIMALLOC_CFLAGS_TEST) $(TSAN_SANITIZE) -c -o $@ $<
554+
532555
$(BUILD_DIR)/prod_mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR)
533556
$(CC) $(MIMALLOC_CFLAGS) -c -o $@ $<
534557

535558
# sqlite3 object files (vendored amalgamation)
536559
SQLITE3_OBJ_TEST = $(BUILD_DIR)/sqlite3.o
560+
SQLITE3_OBJ_TSAN = $(BUILD_DIR)/tsan_sqlite3.o
537561
SQLITE3_OBJ_PROD = $(BUILD_DIR)/prod_sqlite3.o
538562

539563
$(BUILD_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR)
540564
$(CC) $(SQLITE3_CFLAGS_TEST) $(SANITIZE) -c -o $@ $<
541565

566+
$(BUILD_DIR)/tsan_sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR)
567+
$(CC) $(SQLITE3_CFLAGS_TEST) $(TSAN_SANITIZE) -c -o $@ $<
568+
542569
$(BUILD_DIR)/prod_sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR)
543570
$(CC) $(SQLITE3_CFLAGS) -c -o $@ $<
544571

545572
# TRE regex (only compiled on Windows — POSIX uses system <regex.h>)
546573
TRE_OBJ_TEST :=
574+
TRE_OBJ_TSAN :=
547575
TRE_OBJ_PROD :=
548576
ifeq ($(IS_MINGW),yes)
549577
TRE_OBJ_TEST = $(BUILD_DIR)/tre.o
578+
TRE_OBJ_TSAN = $(BUILD_DIR)/tsan_tre.o
550579
TRE_OBJ_PROD = $(BUILD_DIR)/prod_tre.o
551580

552581
$(BUILD_DIR)/tre.o: $(TRE_SRC) | $(BUILD_DIR)
553582
$(CC) $(TRE_CFLAGS) $(SANITIZE) -fno-sanitize=alignment -c -o $@ $<
554583

584+
$(BUILD_DIR)/tsan_tre.o: $(TRE_SRC) | $(BUILD_DIR)
585+
$(CC) $(TRE_CFLAGS) $(TSAN_SANITIZE) -c -o $@ $<
586+
555587
$(BUILD_DIR)/prod_tre.o: $(TRE_SRC) | $(BUILD_DIR)
556588
$(CC) $(TRE_CFLAGS) -O2 -c -o $@ $<
557589
endif
558590

559591
# Vendored LZ4 (test build)
560592
LZ4_OBJ_TEST = $(BUILD_DIR)/test_lz4.o $(BUILD_DIR)/test_lz4hc.o
593+
LZ4_OBJ_TSAN = $(BUILD_DIR)/tsan_lz4.o $(BUILD_DIR)/tsan_lz4hc.o
561594
$(BUILD_DIR)/test_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR)
562595
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR) -c -o $@ $<
563596
$(BUILD_DIR)/test_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR)
564597
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $<
598+
$(BUILD_DIR)/tsan_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR)
599+
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR) -c -o $@ $<
600+
$(BUILD_DIR)/tsan_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR)
601+
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $<
565602

566603
# Vendored zstd (test build)
567604
ZSTD_OBJ_TEST = $(BUILD_DIR)/test_zstd.o
605+
ZSTD_OBJ_TSAN = $(BUILD_DIR)/tsan_zstd.o
568606
$(BUILD_DIR)/test_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR)
569607
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $<
608+
$(BUILD_DIR)/tsan_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR)
609+
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $<
570610

571611
# nomic-embed-code pretrained vector blob
572612
UNIXCODER_OBJ = $(BUILD_DIR)/unixcoder_blob.o
573613
$(UNIXCODER_OBJ): $(UNIXCODER_BLOB_SRC) vendored/nomic/code_vectors.bin | $(BUILD_DIR)
574614
$(CC) -c -o $@ $<
575615

576616
OBJS_VENDORED_TEST = $(MIMALLOC_OBJ_TEST) $(SQLITE3_OBJ_TEST) $(TRE_OBJ_TEST) $(GRAMMAR_OBJS_TEST) $(TS_RUNTIME_OBJ_TEST) $(LSP_OBJ_TEST) $(PP_OBJ_TEST) $(LZ4_OBJ_TEST) $(ZSTD_OBJ_TEST) $(UNIXCODER_OBJ)
617+
OBJS_VENDORED_TSAN = $(MIMALLOC_OBJ_TSAN) $(SQLITE3_OBJ_TSAN) $(TRE_OBJ_TSAN) $(GRAMMAR_OBJS_TSAN) $(TS_RUNTIME_OBJ_TSAN) $(LSP_OBJ_TSAN) $(PP_OBJ_TSAN) $(LZ4_OBJ_TSAN) $(ZSTD_OBJ_TSAN) $(UNIXCODER_OBJ)
577618

578619
$(BUILD_DIR)/test-runner: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR)
579620
$(CC) $(CFLAGS_TEST) -Itests -Itests/repro -o $@ \
@@ -601,8 +642,18 @@ test-repro: $(BUILD_DIR)/test-repro-runner
601642

602643
# ── TSan full test ───────────────────────────────────────────────
603644

604-
test-tsan:
605-
@echo "TSan not yet wired for full extraction tests"
645+
TEST_TSAN_SUITES ?= mem slab_alloc parallel
646+
TSAN_OPTIONS ?= halt_on_error=1
647+
648+
$(BUILD_DIR)/test-runner-tsan: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TSAN) | $(BUILD_DIR)
649+
$(CC) $(CFLAGS_TSAN) -Itests -Itests/repro -o $@ \
650+
$(ALL_TEST_SRCS) $(PROD_SRCS) \
651+
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
652+
$(OBJS_VENDORED_TSAN) \
653+
$(LDFLAGS_TSAN)
654+
655+
test-tsan: $(BUILD_DIR)/test-runner-tsan
656+
cd $(CURDIR) && TSAN_OPTIONS="$(TSAN_OPTIONS)" $(BUILD_DIR)/test-runner-tsan $(TEST_TSAN_SUITES)
606657

607658
# ── Production binary ────────────────────────────────────────────
608659

0 commit comments

Comments
 (0)