fix(pipeline): bind field_type_hint by owning QN segment, not substring - #1936
fix(pipeline): bind field_type_hint by owning QN segment, not substring#1936ilyabrykau-orca wants to merge 4 commits into
Conversation
A Go selector call x.foo() whose receiver the Go LSP cannot type falls through to the generic registry resolver, which binds it by bare short name to an arbitrary same-named project symbol. Stdlib calls are the worst case: f.Close() on an *os.File gets a CALLS edge to whatever project Close wins candidate ranking (measured on a real Go repo: confidence 0.11, 15 candidates; suffix_match + unique_name were 36% of all CALLS edges, and one 14-line stdlib-only function got 3 out of 3 false outbound edges). Extend the TS/JS receiver-aware guard (DeusData#592/DeusData#606) to Go: - extract_calls.c: flag Go call_expression with a selector_expression callee as is_method, mirroring the TS/JS member_expression flag. - registry.c: add cbm_go_suppress_weak_method_match. Unlike the TS/JS drop-list, field_type_hint is KEPT (Go struct fields carry declared types, so the hint is receiver-aware — lrp_go_s8_field_type_hint), and unique_name is dropped only when its confidence carries the import-unreachability penalty (the stdlib-hijack shape); an unpenalized lone candidate inside the caller's import closure never enters the field-type-hint upgrade and must survive. - pass_calls.c / pass_parallel.c: feed the Go gate next to the TS/JS one; the drop still defers to the emit path so service/route/HTTP edges stay main-identical. Reproduce-first: pipeline_go_receiver_suppresses_weak_method_edge is RED without the extractor flag (the f.Close -> project Close edge exists) and GREEN with it; typed same-package calls, bare local calls and import-qualified cross-package calls still resolve. The old extraction contract test used Go as the flag-exempt language — Python takes that role, and extract_go_selector_call_flags_is_method pins the new behavior. Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
A Go method's QN was the flat package form (proj.pkg.method) — the receiver was ignored, so every same-name method in a package collided on one QN and the graph upsert kept exactly one node. Measured on a real Go repo: 20 Process/Name methods across 15 files kept 2 nodes; 9 different Task() methods fused into one chimera node carrying all nine bodies' call edges; 19 structs pointed DEFINES_METHOD at a single shared method node; a _test.go mock Close outranked the production Close in the dedupe tie-break. The upsert's own comment calls kind-disambiguated QNs 'the real cure'. Qualify the QN with the receiver type (proj.pkg.Recv.method), the same shape as Go interface members and the C++ out-of-line method path right below it in extract_func_def: - extract_defs.c: def.qualified_name = parent_class + name whenever the receiver type resolves; go_receiver_type_name becomes the shared cbm_go_receiver_type_name (exported via helpers.h) so both sides of the contract use one formula. - extract_unified.c (compute_func_qn): mirror branch for method_declaration, so method-body calls keep exact source attribution instead of degrading to File-node fallback (calls_find_source). - Consumers already agree: pxc_build_lsp_def passes the def QN and parent_class (receiver_type) verbatim into the Go LSP registries, and check_go_class_implements explicitly supports class-qualified method QNs (its path (b)). Side effect: resolve_same_module's exact module.name hash no longer matches concrete methods, which kills the conf-0.9 false edges where an interface-typed call bound to an unrelated same-package method. Fixes DeusData#1909 Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
Go allows any number of init() functions per package — even several in one file — and all of them run at start-up. On the flat QN (proj.pkg.init) they all collided and the graph upsert kept ONE node per package, silently dropping the rest: measured on a real Go repo, an event-source package registering each decoder in its own file's init() collapsed 21 init functions into one node (29 in source, 7 in the graph), erasing the whole registration pattern. Disambiguate with the DeusData#495 cfg-twin pattern: fold the file basename and line into the QN (proj.pkg.init#a.go:L5). Calling init explicitly is illegal in Go, so nothing ever joins on the plain QN; the call-scope side (compute_func_qn) mirrors the exact formula so init-body calls keep their source attribution instead of degrading to the calls_find_source File fallback. Reproduce-first: pipeline_go_multi_init_nodes_survive is RED without the def-side change (node count 1, expected 2) and GREEN with it; extract_go_multiple_init_disambiguated pins two same-file inits to distinct suffixed QNs. Fixes DeusData#1910 Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
|
CI retrigger (no-op amend, identical tree — no rerun rights): the failing job was |
ed20aab to
0f2024c
Compare
try_field_type_hint accepted any candidate whose qualified name merely CONTAINED the hinted type name. A single-letter receiver (f.Close(), hint "F") matched almost every same-named candidate - the method name itself included - and rebound a call whose resolution had already failed to an arbitrary project symbol at confidence 0.85, presented as the one weak strategy the Go selector guard deliberately trusts. Require the candidate's owning dot-segment - the segment immediately before the method - to EQUAL the hinted type (or its I-prefixed interface form). The intended variable-named-after-its-type case keeps resolving (lrp_go_s8_field_type_hint stays green); the fabricated bindings lose their only anchor and fall back to the weak short-name strategies the guards already handle. Measured on a real ~1150-file Go repo (with the DeusData#1906/DeusData#1909/DeusData#1910 fixes applied): field_type_hint 1484 -> 356 CALLS edges, the single-character-receiver class 376 -> 0, Go->C/C++ targets 34 -> 0, and no other strategy absorbed the removed edges. 63 edges moved to the interface that declares the method instead of an arbitrary implementation. Fixes DeusData#1927 Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
|
CI retrigger (no-op amend): |
0f2024c to
6aac10c
Compare
|
Heads-up: this went red-to-merge because #1937 ( Checked with Re-anchor your new test and its |
Fixes #1927.
What
try_field_type_hint()(parallel resolver) accepted any candidate whose qualified name merely contained the hinted type name as a substring. A single-letter receiver —f.Close(), hint"F"— matched almost every same-named candidate (the method name itself included) and rebound a call whose resolution had already failed to an arbitrary project symbol at confidence 0.85, presented asfield_type_hint, the one weak strategy the Go selector guard (#1906) deliberately trusts.This PR requires the candidate's owning dot-segment — the segment immediately before the method — to equal the hinted type (or its
I-prefixed interface form):fth_owner_segment_is()replaces the twostrstr()calls.Measured effect
On the real ~1150-file Go+C repo from #1927, measured with this exact change on the #1906/#1909/#1910 stack (the same table is in the issue body):
field_type_hintlsp_*Nothing relocated: the removed edges were fabricated on top of already-failed resolutions, so removing the substring anchor leaves nothing behind. 63
event.Get*edges moved to the interface that declares the method instead of an arbitrary implementation.Tests
pipeline_go_parallel_field_hint_requires_owner_segment(parallel-path fixture, ≥50 files). RED before the fix on this branch:FAIL tests/test_pipeline.c:4941: ASSERT(!(cross_file_call_exists(s, project, "Lookup", "Close")))— the stdlibf.Close()bound toFileStore.Closebecause"F"⊂ the QN. GREEN after.finder.Find()→Finder.Findstill hinted) in the same test.lrp_go_s8_field_type_hintstays green — itsrepo/Repofixture is segment-exact, the case the hint exists for.scripts/test.shvenue leg (ASan+UBSan, all suites + contract steps): green.git clang-format --diff: clean.Stacking
Stacked on #1907 → #1913 → #1915 (same pattern as those PRs): the test's absence assertions rely on #1907's Go selector guard downstream of the hint, and Go owner segments are receiver-qualified by #1913. The tip commit is the only new change; it applies to main independently.
#1932 tracks the family; #1927 is the direct consequence of #1907/#1913 moving weak-strategy mass into
field_type_hint.