From 43e861e36a9a05f8fd71b97d2480c8cbb09c65ea Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sun, 2 Aug 2026 22:40:01 +0200 Subject: [PATCH] fix(pipeline): release db_path/db_dir on the manifest abort paths; free the test harness cross arena Second LSan round from the Linux leg, both verified green in the container (532 passed, 0 failed under LSan): - dump_and_persist_hashes' two semantic-manifest abort returns leaked BOTH of the function's strdups (db_path and db_dir, the latter otherwise freed only further down). Same ownership rule as the previous fix: every exit releases what the function allocated. - test_parallel's sequential harness drives the passes directly and never destroyed ctx->seq_cross_arena, which the cross pass fills with the shared per-language registries (stdlib registrations included -- ~20MB per test). Production's run_sequential_pipeline destroys it after all passes; the harness now does the same. Signed-off-by: Martin Vogel --- src/pipeline/pipeline.c | 7 +++++++ tests/test_parallel.c | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/src/pipeline/pipeline.c b/src/pipeline/pipeline.c index e1eaf124f..824cd72a9 100644 --- a/src/pipeline/pipeline.c +++ b/src/pipeline/pipeline.c @@ -1734,12 +1734,19 @@ static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_hash_t *bas if (cbm_pipeline_build_fresh_semantic_manifest(p->project_name, p->repo_path, p->mode, &manifest, &manifest_count) != 0) { cbm_log_error("pipeline.err", "phase", "semantic_manifest"); + /* db_path and db_dir are this function's strdups; the success tail and + * the publish-failure return release them, and these two aborts must + * too -- LSan caught exactly these paths leaking both strings. */ + free(db_dir); + free(db_path); return CBM_PIPELINE_ABORT_PRESERVE_DB; } if (!cbm_pipeline_semantic_manifests_equal(baseline_manifest, baseline_count, manifest, manifest_count)) { cbm_log_warn("pipeline.abort", "reason", "semantic_inputs_changed"); cbm_pipeline_free_semantic_manifest(manifest, manifest_count); + free(db_dir); + free(db_path); return CBM_PIPELINE_ABORT_PRESERVE_DB; } diff --git a/tests/test_parallel.c b/tests/test_parallel.c index 042b41971..1df6d9276 100644 --- a/tests/test_parallel.c +++ b/tests/test_parallel.c @@ -244,6 +244,15 @@ static cbm_gbuf_t *run_sequential_with_lsp_cross_and_mutator( } free(cache); cbm_registry_free(reg); + /* The cross pass builds its shared registries in the caller-owned + * ctx->seq_cross_arena precisely so they outlive pass_calls; production's + * run_sequential_pipeline destroys that arena after all passes, and this + * harness must too -- LSan flagged the whole registry arena (stdlib + * registrations included) leaking per test. */ + if (ctx.seq_cross_arena_live) { + cbm_arena_destroy(&ctx.seq_cross_arena); + ctx.seq_cross_arena_live = false; + } return gbuf; }