fix(parallel): destroy thread parser before slab reclaim in LSP-cross loop

The LSP-cross loop in parallel_resolve called cbm_slab_reclaim() without
first destroying the thread-local tree-sitter parser, violating the
documented slab contract. The parser's lexer holds slab-allocated state
(notably lexer.included_ranges, a 24-byte allocation that lands in the
≤64B slab bucket); reclaiming the slab leaves that pointer dangling.

Workers don't trip on it because they don't reparse afterward, but the
main thread participates in cbm_parallel_for and any later cbm_extract_file
on the main thread — including the sequential pass_definitions in the
incremental pipeline — hits a heap-use-after-free in ts_lexer_goto on
macOS-ASan.

Two-part fix:
- pass_parallel.c: destroy the thread parser before cbm_slab_reclaim() in
  the per-file LSP-cross loop, matching the worker extract loop.
- pass_definitions.c: defensively drop the thread parser at pass entry so
  the incremental sequential path cannot inherit stale state from any
  prior run.
This commit is contained in:
Martin Vogel committed 2026-05-29 23:11:52 +02:00
1 parent 297003aab4
commit 82d5c841e6
2 files changed
+11

No files matched your search

+5
View File
@@ -333,6 +333,11 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
/* Ensure extraction library is initialized */
cbm_init();
/* Defensive: a prior pipeline run may have left a thread-local parser whose
* lexer holds pointers into a slab that has since been reclaimed. Drop it
* here so the first cbm_extract_file below recreates a fresh parser. */
cbm_destroy_thread_parser();
int total_defs = 0;
int total_calls = 0;
int total_imports = 0;
+6
View File
@@ -2128,6 +2128,12 @@ static void resolve_worker(int worker_id, void *ctx_ptr) {
}
}
free(filtered);
/* Contract: cbm_slab_reclaim() requires the thread parser to be
* destroyed first; otherwise its lexer holds slab pointers
* (lexer.included_ranges) that get freed underneath it, causing
* a heap-use-after-free on the next ts_lexer_goto. The next
* cbm_extract_file on this thread will recreate the parser. */
cbm_destroy_thread_parser();
cbm_slab_reclaim();
uint64_t lsp_elapsed_ns = extract_now_ns() - lsp_t0;
atomic_fetch_add_explicit(&rc->time_ns_cross_lsp, lsp_elapsed_ns,