From 76aaffbebca42217825feeeb874a717c4150fe18 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 21:22:44 +0100 Subject: [PATCH 1/6] fix(resolver): delete the cursor of the OCaml structure walk ocaml_collect_structure created a tree cursor and had no ts_tree_cursor_delete at all. Its one exit after the cursor is the fall-through at the end of the loop, so every struct of every OCaml file leaked one. LeakSanitizer on Linux reported it at adapter_ocaml.c:603 from resolver_source_langs. The other three cursors in the file are already paired. Signed-off-by: Martin Vogel --- internal/cbm/resolve/adapter_ocaml.c | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/cbm/resolve/adapter_ocaml.c b/internal/cbm/resolve/adapter_ocaml.c index 18f1be309..1ca9e2674 100644 --- a/internal/cbm/resolve/adapter_ocaml.c +++ b/internal/cbm/resolve/adapter_ocaml.c @@ -664,6 +664,7 @@ static void ocaml_collect_structure(cbm_source_walk_t *walk, const char *owner, } } } + ts_tree_cursor_delete(&cursor); } /* Where this file stands among the modules of its build. */ From e1ff392bb2dac2d6f0a44f358b7b2efd8c5dca75 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 21:22:47 +0100 Subject: [PATCH 2/6] test(resolver): promote the Scala operator and generic call rows The dynamic lane's Scala rules recovered these, and the matrix asks for promotion. Each row was read against its fixture and the index: - `a + b` with `a: Vec2`, `def +(other: Vec2): Vec2`: `a.+(b)`, Vec2.+. - `(a + b).dot(b)`: `+` declares Vec2 as its result, Vec2.dot. - `first(ns)` with `def first[T](xs: List[T])`: the top-level def. A generic def had no arity before, so its calls abstained. No false edge came with them: `x + other.x` on a Double field abstains as scala_value_of_unknown_type, and `Some(...)` as scala_name_not_in_project. Signed-off-by: Martin Vogel --- tests/test_matrix_known_classes.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_matrix_known_classes.c b/tests/test_matrix_known_classes.c index 61e5d0670..3b93dcc2e 100644 --- a/tests/test_matrix_known_classes.c +++ b/tests/test_matrix_known_classes.c @@ -184,9 +184,9 @@ static const MKC_Golden mkc_goldens[] = { {"mkc_c2_csharp_operator_plus", "operator+", "new Vec2(a.X + b.X, a.Y + b.Y)", "Vec2", " public Vec2(float x, float y) { X = x; Y = y; }", "Method", 4, 0, true}, {"mkc_c2_scala_symbolic_method", "run", "a + b", "+", - " def +(other: Vec2): Vec2 = Vec2(x + other.x, y + other.y)", "Method", 2, 0, false}, + " def +(other: Vec2): Vec2 = Vec2(x + other.x, y + other.y)", "Method", 2, 0, true}, {"mkc_c2_scala_symbolic_method", "run", "(a + b).dot(b)", "dot", - " def dot(other: Vec2): Double = x * other.x + y * other.y", "Method", 3, 0, false}, + " def dot(other: Vec2): Double = x * other.x + y * other.y", "Method", 3, 0, true}, {"mkc_c2_ruby_operator_plus", "+", "Vec2.new(@x + other.x, @y + other.y)", "initialize", " def initialize(x, y)", "Method", 3, 0, true}, {"mkc_c4_typescript_async_await", "getData", "fetch(url)", "fetch", @@ -211,7 +211,7 @@ static const MKC_Golden mkc_goldens[] = { {"mkc_c4_swift_async_await", "run", "getData(url: url)", "getData", "func getData(url: String) async -> String {", "Function", 5, 0, true}, {"mkc_c5_scala_generic_call", "run", "first(ns)", "first", - "def first[T](xs: List[T]): Option[T] =", "Function", 1, 0, false}, + "def first[T](xs: List[T]): Option[T] =", "Function", 1, 0, true}, {"mkc_c5_swift_generic_call", "run", "maxOf(a, b)", "maxOf", "func maxOf(_ a: T, _ b: T) -> T {", "Function", 1, 0, true}, {"mkc_c5_ruby_duck_typed_call", "run", "first(items)", "first", "def first(collection)", From ab04546fa282e615ddbc625f5ffbd5d1be0c5077 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 21:22:52 +0100 Subject: [PATCH 3/6] fix(resolver): a Perl code reference under an operator is USAGE The dynamic lane made resolve_usages call every admitted Perl carrier a CALL_REFERENCE, through a language clause beside the comment saying composite expressions stay USAGE. So `$flag ? \&handler : \&alternate` got two CALL_REFERENCE edges, which call_reference_language_complex_contract forbids for every language with callable references: the value is one of several. The carrier now says it, as in the other languages: perl_lsp.c leaves may_be_call_reference false under a conditional, a binary operator or a low-precedence logical operator, parentheses aside, and the clause is gone. `my $r = \&own`, a table entry and direct arguments stay CALL_REFERENCE. mojo has 37 such edges and none is composite. resolver_source_langs gets positive and negative rows for each form. Tests corrected, because the rule is right and the rows were not: - The registrar in both Perl fixtures was `sub accept`. accept is a builtin and only an import overrides one (perlsub), so the adapter was right to abstain and `CALLS refs.accept` was never genuine. `perl -c` on the old fixture says "Not enough arguments for accept"; with two arguments the builtin runs and the sub does not. It is now `sub submit`, same length, and those rows are required. builtin-not-overridden-by-a-sub keeps the decoy. - The conditional's USAGE rows named `handler` without `\&`. The carrier and every other Perl row use the `\&handler` span, and the rows were never found, so nothing had checked them. Corrected and required. - perl_exact_coderef_is_call_reference-1 is recovered and true: promoted. Signed-off-by: Martin Vogel --- internal/cbm/lsp/perl_lsp.c | 16 +++++++ internal/cbm/resolve/adapter_perl.c | 4 +- src/pipeline/pass_resolver.c | 3 +- tests/repro/repro_reference_precision.c | 61 ++++++++----------------- tests/test_resolver_source_langs.c | 23 ++++++++++ 5 files changed, 61 insertions(+), 46 deletions(-) diff --git a/internal/cbm/lsp/perl_lsp.c b/internal/cbm/lsp/perl_lsp.c index 6e8d70dea..4f247fdf6 100644 --- a/internal/cbm/lsp/perl_lsp.c +++ b/internal/cbm/lsp/perl_lsp.c @@ -754,6 +754,21 @@ static bool perl_is_direct_coderef_argument(TSNode refgen) { return !ts_node_is_null(args) && ts_node_eq(args, container); } +/* `$f ? \&a : \&b` and `$f || \&a` hand over one of several values, so each \&name there only + * mentions its sub. Parentheses change nothing. */ +static bool perl_coderef_in_composite(TSNode refgen) { + for (TSNode parent = ts_node_parent(refgen); !ts_node_is_null(parent); + parent = ts_node_parent(parent)) { + const char *kind = ts_node_type(parent); + if (strcmp(kind, "conditional_expression") == 0 || strcmp(kind, "binary_expression") == 0 || + strcmp(kind, "lowprec_logical_expression") == 0) + return true; + if (strcmp(kind, "list_expression") != 0) + return false; + } + return false; +} + static void perl_emit_non_direct_coderef_usage(PerlLSPContext *ctx, TSNode refgen) { if (!ctx || !ctx->usages || !ctx->enclosing_func_qn || perl_is_direct_coderef_argument(refgen)) { @@ -777,6 +792,7 @@ static void perl_emit_non_direct_coderef_usage(PerlLSPContext *ctx, TSNode refge usage.ref_name = name; usage.enclosing_func_qn = ctx->enclosing_func_qn; usage.kind = CBM_USAGE_VALUE; + usage.may_be_call_reference = !perl_coderef_in_composite(refgen); usage.site_start_byte = start; usage.site_end_byte = end; cbm_usages_push(ctx->usages, ctx->arena, usage); diff --git a/internal/cbm/resolve/adapter_perl.c b/internal/cbm/resolve/adapter_perl.c index 12e072e2a..5e42bb387 100644 --- a/internal/cbm/resolve/adapter_perl.c +++ b/internal/cbm/resolve/adapter_perl.c @@ -29,7 +29,9 @@ * has no sub import of its own, and lists the name in @EXPORT or @EXPORT_OK. What it * lists can be a name it imported itself, which is then that module's export. * - * `\&name` takes a reference to the sub the same lookup selects, without calling it. + * `\&name` takes a reference to the sub the same lookup selects, without calling it. Under a + * conditional or an operator the value is one of several, so the site only mentions the sub: + * the carrier from extraction says which. * * Literal glob assignment and lexical `my sub` replace a name, so that name * abstains. A glob assigned from a computed name can touch any package anywhere diff --git a/src/pipeline/pass_resolver.c b/src/pipeline/pass_resolver.c index 4c7c39d55..40088fb7d 100644 --- a/src/pipeline/pass_resolver.c +++ b/src/pipeline/pass_resolver.c @@ -4702,8 +4702,7 @@ static bool resolve_usages(file_resolver_t *resolver, CBMArena *arena) { /* Source binding can establish a direct callable argument without a * hybrid row. Composite expressions remain USAGE: the extraction * carrier deliberately leaves may_be_call_reference false for them. */ - if (!site && (usage->may_be_call_reference || resolver->file->language == CBM_LANG_PERL) && - cbm_resolve_admit(&outcome) && + if (!site && usage->may_be_call_reference && cbm_resolve_admit(&outcome) && callable_node(cbm_gbuf_find_by_qn(resolver->file->graph, outcome.target))) relation = "CALL_REFERENCE"; /* Reading a property hands over its result, not the function. */ diff --git a/tests/repro/repro_reference_precision.c b/tests/repro/repro_reference_precision.c index 98e54fcea..8a671bc89 100644 --- a/tests/repro/repro_reference_precision.c +++ b/tests/repro/repro_reference_precision.c @@ -315,27 +315,17 @@ static const RP_QualityGolden rp_quality_goldens[] = { .label = "Function", .declaration_line = 1, .declaration_text = "sub handler {}"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "perl_exact_coderef_is_call_reference-1", - .why = "Delegate or coderef argument values and callable-parameter applicability require an " - "audited proof bridge.", - .tried = "Original full-MCP fixture retained; exact source occurrence and target declaration " - "reviewed; raw and forbidden assertions retained."}}, + .mode = RQ_REQUIRED}}, {"repro_perl_exact_coderef_is_call_reference", {.case_id = "perl_exact_coderef_is_call_reference-2", .site = {.file = "refs.pl", .caller = "argument", .start_byte = 66, .end_byte = 83}, .relation = "CALLS", .target = {.file = "refs.pl", - .suffix = "refs.accept", + .suffix = "refs.submit", .label = "Function", .declaration_line = 2, - .declaration_text = "sub accept { my ($callback) = @_; }"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "perl_exact_coderef_is_call_reference-2", - .why = "Delegate or coderef argument values and callable-parameter applicability require an " - "audited proof bridge.", - .tried = "Original full-MCP fixture retained; exact source occurrence and target declaration " - "reviewed; raw and forbidden assertions retained."}}, + .declaration_text = "sub submit { my ($callback) = @_; }"}, + .mode = RQ_REQUIRED}}, {"repro_rust_direct_scoped_function_value_is_exact_reference", {.case_id = "rust_direct_scoped_function_value_is_exact_reference-1", .site = {.file = "main.rs", .caller = "scoped_argument", .start_byte = 83, .end_byte = 98}, @@ -890,49 +880,31 @@ static const RP_QualityGolden rp_quality_goldens[] = { .site = {.file = "complex.pl", .caller = "complex", .start_byte = 99, .end_byte = 138}, .relation = "CALLS", .target = {.file = "complex.pl", - .suffix = "complex.accept", + .suffix = "complex.submit", .label = "Function", .declaration_line = 3, - .declaration_text = "sub accept { my ($callback) = @_; }"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "perl_conditional_coderefs_stay_usage-1", - .why = "Conditional callable values retain genuine target mentions, but complete source " - "binding, callable-argument applicability and value-reference proof are not yet " - "audited for this language/form.", - .tried = "Original full-MCP fixture retained; exact source occurrence and target declaration " - "reviewed; raw and forbidden assertions retained."}}, + .declaration_text = "sub submit { my ($callback) = @_; }"}, + .mode = RQ_REQUIRED}}, {"repro_perl_conditional_coderefs_stay_usage", {.case_id = "perl_conditional_coderefs_stay_usage-2", - .site = {.file = "complex.pl", .caller = "complex", .start_byte = 116, .end_byte = 123}, + .site = {.file = "complex.pl", .caller = "complex", .start_byte = 114, .end_byte = 123}, .relation = "USAGE", .target = {.file = "complex.pl", .suffix = "complex.handler", .label = "Function", .declaration_line = 1, .declaration_text = "sub handler {}"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "perl_conditional_coderefs_stay_usage-2", - .why = "Conditional callable values retain genuine target mentions, but complete source " - "binding, callable-argument applicability and value-reference proof are not yet " - "audited for this language/form.", - .tried = "Original full-MCP fixture retained; exact source occurrence and target declaration " - "reviewed; raw and forbidden assertions retained."}}, + .mode = RQ_REQUIRED}}, {"repro_perl_conditional_coderefs_stay_usage", {.case_id = "perl_conditional_coderefs_stay_usage-3", - .site = {.file = "complex.pl", .caller = "complex", .start_byte = 128, .end_byte = 137}, + .site = {.file = "complex.pl", .caller = "complex", .start_byte = 126, .end_byte = 137}, .relation = "USAGE", .target = {.file = "complex.pl", .suffix = "complex.alternate", .label = "Function", .declaration_line = 2, .declaration_text = "sub alternate {}"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "perl_conditional_coderefs_stay_usage-3", - .why = "Conditional callable values retain genuine target mentions, but complete source " - "binding, callable-argument applicability and value-reference proof are not yet " - "audited for this language/form.", - .tried = "Original full-MCP fixture retained; exact source occurrence and target declaration " - "reviewed; raw and forbidden assertions retained."}}, + .mode = RQ_REQUIRED}}, {"repro_python_local_function_shadows_imported_callable", {.case_id = "repro_python_local_function_shadows_imported_callable-reference", .site = {.file = "use.py", .caller = "crossArgument", .start_byte = 121, .end_byte = 129}, @@ -2302,10 +2274,12 @@ TEST(repro_tsx_exact_bound_method_is_call_reference) { PASS(); } +/* The registrar is not called `accept`: that is a Perl builtin, only an import overrides it + * (perlsub), and `perl -c` rejects `accept(\&handler)` as too few arguments to the builtin. */ TEST(repro_perl_exact_coderef_is_call_reference) { static const char source[] = "sub handler {}\n" - "sub accept { my ($callback) = @_; }\n" - "sub argument { accept(\\&handler); }\n"; + "sub submit { my ($callback) = @_; }\n" + "sub argument { submit(\\&handler); }\n"; int rc = rp_assert_exact_function_value_reference_quality( "refs.pl", source, CBM_LANG_PERL, "Function", "repro_perl_exact_coderef_is_call_reference"); if (rc != 0) @@ -3564,12 +3538,13 @@ TEST(repro_php_complex_first_class_callable_stays_usage) { PASS(); } +/* The registrar is not called `accept`: see repro_perl_exact_coderef_is_call_reference. */ TEST(repro_perl_conditional_coderefs_stay_usage) { static const char source[] = "sub handler {}\n" "sub alternate {}\n" - "sub accept { my ($callback) = @_; }\n" + "sub submit { my ($callback) = @_; }\n" "sub complex { my ($flag) = @_; " - "accept($flag ? \\&handler : \\&alternate); }\n"; + "submit($flag ? \\&handler : \\&alternate); }\n"; int rc = rp_assert_complex_function_values_stay_usage_quality( "complex.pl", source, CBM_LANG_PERL, "Function", "repro_perl_conditional_coderefs_stay_usage"); diff --git a/tests/test_resolver_source_langs.c b/tests/test_resolver_source_langs.c index 3e5bd1b1b..20bea89ff 100644 --- a/tests/test_resolver_source_langs.c +++ b/tests/test_resolver_source_langs.c @@ -1941,6 +1941,12 @@ static const char PL2_THING[] = "sub takes_ref { my $r = \\&own; $r }\n" "sub takes_qualified_ref { keep(\\&My::Root::root_m) }\n" "sub no_absent_ref { my $r = \\&absent; $r }\n" + "sub also { return 5; }\n" + "sub picks_one { my $f = shift; keep($f ? \\&own : \\&also) }\n" + "sub falls_back { my $f = shift; my $r = $f || \\&also; $r }\n" + "sub low_or { my $f = shift; my $r = ($f or \\&also); $r }\n" + "sub in_parens { my $f = shift; my $r = $f ? (\\&own) : 0; $r }\n" + "sub table_entry { my %t = (a => \\&own); $t{a} }\n" "1;\n"; static const char PL3_THING[] = @@ -2011,6 +2017,18 @@ TEST(resolver_source_perl_invocant_reexports_and_code_references) { "sub own { return 4; }"), SL_REF_AT("code-reference-with-its-package", "lib/My/Thing.pm", "Thing.takes_qualified_ref", "\\&My::Root::root_m", 0, "lib/My/Root.pm", "Root.root_m", "Function", 2, "sub root_m { return 3; }"), + SL_USE_AT("conditional-branch-mentions-its-sub", "lib/My/Thing.pm", "Thing.picks_one", "\\&own", 1, "lib/My/Thing.pm", "Thing.own", "Function", 5, + "sub own { return 4; }"), + SL_USE_AT("other-conditional-branch-mentions-its-sub", "lib/My/Thing.pm", "Thing.picks_one", "\\&also", 0, "lib/My/Thing.pm", "Thing.also", "Function", 19, + "sub also { return 5; }"), + SL_USE_AT("logical-operand-mentions-its-sub", "lib/My/Thing.pm", "Thing.falls_back", "\\&also", 1, "lib/My/Thing.pm", "Thing.also", "Function", 19, + "sub also { return 5; }"), + SL_USE_AT("low-precedence-operand-mentions-its-sub", "lib/My/Thing.pm", "Thing.low_or", "\\&also", 2, "lib/My/Thing.pm", "Thing.also", "Function", 19, + "sub also { return 5; }"), + SL_USE_AT("parentheses-change-nothing", "lib/My/Thing.pm", "Thing.in_parens", "\\&own", 2, "lib/My/Thing.pm", "Thing.own", "Function", 5, + "sub own { return 4; }"), + SL_REF_AT("table-entry-names-one-sub", "lib/My/Thing.pm", "Thing.table_entry", "\\&own", 3, "lib/My/Thing.pm", "Thing.own", "Function", 5, + "sub own { return 4; }"), }; static const RQ_Forbidden forbidden[] = { SL_DENY_AT("plain-function-takes-whatever-it-is-given", "lib/My/Thing.pm", "Thing._select", "$_[0]->own", 0), @@ -2018,6 +2036,11 @@ TEST(resolver_source_perl_invocant_reexports_and_code_references) { SL_DENY_AT("second-shift-is-the-second-argument", "lib/My/Thing.pm", "Thing.second_shift", "shift->own", 1), SL_DENY_AT("anonymous-sub-has-its-own-arguments", "lib/My/Thing.pm", "Thing.anon_has_its_own_args", "shift->own", 2), SL_NO_REF_AT("code-reference-to-nothing", "lib/My/Thing.pm", "Thing.no_absent_ref", "\\&absent", 0), + SL_NO_REF_AT("conditional-branch-is-no-reference", "lib/My/Thing.pm", "Thing.picks_one", "\\&own", 1), + SL_NO_REF_AT("other-conditional-branch-is-no-reference", "lib/My/Thing.pm", "Thing.picks_one", "\\&also", 0), + SL_NO_REF_AT("logical-operand-is-no-reference", "lib/My/Thing.pm", "Thing.falls_back", "\\&also", 1), + SL_NO_REF_AT("low-precedence-operand-is-no-reference", "lib/My/Thing.pm", "Thing.low_or", "\\&also", 2), + SL_NO_REF_AT("parentheses-are-no-reference", "lib/My/Thing.pm", "Thing.in_parens", "\\&own", 2), }; ASSERT(sl_check("perl-invocant", ".pm", "package Filler;\nsub f { return 1; }\n1;\n", sources, sizeof(sources) / sizeof(sources[0]), expected, From e0d3eb6bf44c15becd7b04f2d6ed2ca7a7508bb4 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 21:23:06 +0100 Subject: [PATCH 4/6] test(pipeline): promote the Perl row of callback parity callback-parity failed on recovered_gaps=1. The log's QUALITY_MISS for javascript-script-shadow-control was only the last line before the summary: that row is an unchanged known gap, and the row that recovered is callback-perl-coderef. `perlReferenceAccept(\&perlReferenceTarget)` is a direct coderef argument to a sub of the file, and the Perl adapter now proves it, so the CALL_REFERENCE is true. With the row required, callback-parity reads genuine=10 found=7 missing=3 recovered_gaps=0 on both routes. The sequential floor assertion also covers this pair now, so the exclusion list goes from six to five. Signed-off-by: Martin Vogel --- tests/test_pipeline.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index ed4cd5d29..17e03dbfe 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -4305,12 +4305,7 @@ TEST(pipeline_call_reference_sequential_parallel_edge_set_parity) { .label = "Function", .declaration_line = 1, .declaration_text = "sub perlReferenceTarget {}"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "callback-perl-coderef", - .why = "Package/subroutine declaration identity and typeglob/redefinition barriers lack a " - "proof adapter.", - .tried = "Focused pipeline probe: direct coderef has a legacy carrier only; no legacy " - "confidence is admitted."}, + .mode = RQ_REQUIRED}, {.case_id = "javascript-script-shadow-control", .site = {.file = "shadow.js", .caller = "shadow.javascriptShadowSite", @@ -4691,9 +4686,9 @@ TEST(pipeline_call_reference_sequential_parallel_edge_set_parity) { parallel_reference[i]); } for (int i = 0; i < pair_count; i++) { - /* These six independent expectations are counted above as source-audited + /* These five independent expectations are counted above as source-audited * quality gaps; recovery must promote them to required in the oracle. */ - if (i != 4 && i != 5 && i != 7 && i != 10 && i != 12 && i != 13) + if (i != 4 && i != 5 && i != 7 && i != 10 && i != 12) ASSERT_GT(sequential_reference[i], 0); ASSERT_EQ(parallel_reference[i], sequential_reference[i]); ASSERT_EQ(sequential_usage[i], 0); From f0d2473343fb0320a912ef3bdda7bb18264a834a Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 21:23:18 +0100 Subject: [PATCH 5/6] test(pipeline): a Rust tuple constructor call targets its struct rust-std-receiver reported wrong=2 projection_errors=1 because its two constructor rows had no target identity, written when the graph had none for a constructor. The go-rust lane's rule now emits CALLS to the struct, and it is true: a tuple struct's name is its constructor function in the value namespace. `EvidenceTier(1)` and `EvidenceTier(2)` bind the one `pub struct EvidenceTier(pub u8)` in that module, and resolver_rust_tuple_struct_constructor_is_a_call pins the named, unit, value and shadowed cases that must not. The rows now name the Struct node and are required. Naming it brings real_caller's return type into the audit, so `-> EvidenceTier` is attested as USAGE and USES_TYPE, as the matrix does for class mentions. Signed-off-by: Martin Vogel --- tests/test_pipeline.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 17e03dbfe..46a70456e 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -15407,26 +15407,44 @@ static bool rust_std_receiver_quality(cbm_store_t *store, const char *project, c .site = {.file = "src/lib.rs", .caller = "lib.real_caller", .needle = "EvidenceTier(1)"}, .relation = "CALLS", .target = {.file = "src/lib.rs", + .suffix = "lib.EvidenceTier", + .label = "Struct", .declaration_line = 4, .declaration_text = "pub struct EvidenceTier(pub u8);"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "RUST-TUPLE-STRUCT-CONSTRUCTOR-IDENTITY", - .why = "The tuple-struct constructor is genuine but has no independent callable " - "declaration identity in the current graph.", - .tried = "Retained the exact source invocation and struct anchor; no synthesized method " - "or legacy confidence substitutes for a constructor proof."}, + .mode = RQ_REQUIRED}, {.case_id = "rust.tuple-constructor.argument", .site = {.file = "src/lib.rs", .caller = "lib.real_caller", .needle = "EvidenceTier(2)"}, .relation = "CALLS", .target = {.file = "src/lib.rs", + .suffix = "lib.EvidenceTier", + .label = "Struct", .declaration_line = 4, .declaration_text = "pub struct EvidenceTier(pub u8);"}, - .mode = RQ_KNOWN_GAP, - .gap_id = "RUST-TUPLE-STRUCT-CONSTRUCTOR-IDENTITY", - .why = "The argument independently invokes the tuple-struct constructor; its callable " - "identity is not represented.", - .tried = "Kept the second raw call site in the denominator rather than merging it with " - "the receiver constructor."}, + .mode = RQ_REQUIRED}, + {.case_id = "rust.return-type.usage", + .site = {.file = "src/lib.rs", + .caller = "lib.real_caller", + .needle = "EvidenceTier", + .occurrence_index = 5}, + .relation = "USAGE", + .target = {.file = "src/lib.rs", + .suffix = "lib.EvidenceTier", + .label = "Struct", + .declaration_line = 4, + .declaration_text = "pub struct EvidenceTier(pub u8);"}, + .mode = RQ_REQUIRED}, + {.case_id = "rust.return-type.uses-type", + .site = {.file = "src/lib.rs", + .caller = "lib.real_caller", + .needle = "EvidenceTier", + .occurrence_index = 5}, + .relation = "USES_TYPE", + .target = {.file = "src/lib.rs", + .suffix = "lib.EvidenceTier", + .label = "Struct", + .declaration_line = 4, + .declaration_text = "pub struct EvidenceTier(pub u8);"}, + .mode = RQ_REQUIRED}, {.case_id = "rust.iterator-closure.receiver", .site = {.file = "src/lib.rs", .caller = "lib.untyped_receiver", From 7e502898bea4c904d143b59977bf80812ceb0825 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 21:43:29 +0100 Subject: [PATCH 6/6] fix(resolver): free the kind cache of a resolve worker source_damage_scan runs on a resolve worker when a source adapter reads a file with parse damage, and it fills the thread-local bitset cache of cbm_kind_in_set. extract_worker frees that cache at thread exit; resolve_worker_impl did not, so each such worker leaked it. LeakSanitizer on Linux reported 152 bytes in 3 objects from ks_build in resolver_source_langs, once the much larger OCaml cursor leak was gone. The first run's report was cut before it. Signed-off-by: Martin Vogel --- src/pipeline/pass_parallel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 160536af1..c93285def 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -4790,6 +4790,7 @@ static void resolve_worker_impl(int worker_id, void *ctx_ptr) { cbm_pxc_set_rust_manifest(NULL); cbm_destroy_thread_parser(); cbm_slab_destroy_thread(); + cbm_kind_in_set_free_cache(); /* a damaged source file's scan fills it here too */ cbm_service_pattern_cache_end(); cbm_pxc_thread_scratch_end(); file_node_cache_clear();