diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index 779758c84..246be5072 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -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; diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 52075d31e..3ce678931 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -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,