Skip to content

fix(pipeline): suppress weak short-name matches for Go selector calls - #1907

Open
ilyabrykau-orca wants to merge 1 commit into
DeusData:mainfrom
ilyabrykau-orca:fix/go-weak-selector-calls
Open

fix(pipeline): suppress weak short-name matches for Go selector calls#1907
ilyabrykau-orca wants to merge 1 commit into
DeusData:mainfrom
ilyabrykau-orca:fix/go-weak-selector-calls

Conversation

@ilyabrykau-orca

Copy link
Copy Markdown

What does this PR do?

Fixes #1906.

Extends the TS/JS receiver-aware weak-match guard (#592/#606) to Go: a selector call x.foo() whose receiver the Go LSP cannot type must not be bound by a receiver-blind short-name strategy to an arbitrary same-named project symbol (f.Close() on an *os.File → a project Close; on the repo measured in #1906 these strategies were 36% of all CALLS edges, and one 14-line stdlib-only helper had 3/3 false outbound edges).

Mirrors how TS/JS joined the Perl guard (#476):

  1. internal/cbm/extract_calls.c — flag Go call_expression with a selector_expression callee as is_method, same block as the TS/JS member_expression flag (Go has no this/super analog to exempt).
  2. src/pipeline/registry.c + pipeline.h — new cbm_go_suppress_weak_method_match(is_go, is_method, strategy, confidence). Drops suffix_match/fuzzy always; drops unique_name only when its confidence carries the import-unreachability penalty (< CONF_UNIQUE_NAME) — the stdlib-hijack shape. field_type_hint is deliberately kept: Go struct fields carry declared types, so the hint is receiver-aware there (lrp_go_s8_field_type_hint stays GREEN).
  3. src/pipeline/pass_calls.c / pass_parallel.c — feed the Go gate next to the TS/JS one. The drop defers to the emit path exactly like TS/JS, so service/route/HTTP/CONFIG edges stay main-identical. same_module, import_map, qualified_suffix, callee_suffix and all lsp_* strategies are untouched: typed receivers, same-package calls, and import-qualified calls resolve exactly as before.
  4. Tests (reproduce-first):
    • pipeline_go_receiver_suppresses_weak_method_edge — RED without the extractor flag (FAIL: ASSERT(!(cross_file_call_exists(s, project, "FileLen", "Close")))), GREEN with it. Asserts the stdlib f.Close() edge is gone while the typed same-package s.Close(), a bare local call, and an import-qualified cross-package call keep resolving. The fixture carries a go.mod — import reachability (the unique_name penalty) depends on it, like every real Go repo.
    • extract_go_selector_call_flags_is_method pins the extractor flag; the old flag-exempt contract test keeps its guard using Python as the exempt language.
    • go_suppress_drops_weak_selector_matches / go_suppress_keeps_typed_and_import_aware_matches unit-pin the drop-list, the 0.75-vs-0.375 unique_name split, and the lsp_* keeps.

Perf/memory: one ts_node_child_by_field_name + two strcmp per Go call site at extraction, one short-circuited predicate per resolved Go call, zero allocations, no new passes.

Known residual (documented in #1906, out of scope here): a Go file whose import table maps zero project packages has no penalty signal, so a lone same-named project symbol can still capture a stdlib method call there; same_module can still mis-bind interface-typed calls inside one package. Both need receiver typing, not name heuristics.

Checklist

  • Every commit is signed off (git commit -s) — required, CI rejects
    unsigned commits (DCO, see CONTRIBUTING.md)
  • Tests pass locally (scripts/test.sh — full leg, ASan+UBSan, "All tests passed")
  • Lint passes (git clang-format --diff HEAD clean on changed lines; clang-tidy/cppcheck via CI)
  • New behavior is covered by a test (reproduce-first for bug fixes)

@github-actions

Copy link
Copy Markdown

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. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

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.

@ilyabrykau-orca

Copy link
Copy Markdown
Author

Rebased onto current main (post-#1903). #1903 generalized the TS/JS guard into cbm_suppress_weak_member_match with per-site language gates — exactly the shape this PR anticipated — so the Go integration now composes with that helper at both call sites via its own predicate (cbm_go_suppress_weak_method_match; different drop-list: field_type_hint kept, unique_name only when import-unreachability-penalized). The flag-exempt extraction contract test moved from Go to Rust, since Python joined the flagged set in #1903 and Go joins here. Full scripts/test.sh leg green on the rebased stack.

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>
@ilyabrykau-orca

Copy link
Copy Markdown
Author

Retriggered CI with a no-op amend (identical tree, new SHA — I lack rerun rights on this repo). The two failures on the previous run look environmental, not change-related: test_watcher.c:2526 index_call_count == 0, expected 1 on macos-15-intel (a watcher-timing probe; this PR touches no watcher code), and windows-guards dying in its own harness setup (SETUP FAIL: ASCII baseline did not index) before reaching any guard. No semantic changes; the local full venue leg is green.

@ilyabrykau-orca
ilyabrykau-orca force-pushed the fix/go-weak-selector-calls branch from 7f4f68c to f76971f Compare August 30, 2026 13:23
ilyabrykau-orca added a commit to ilyabrykau-orca/codebase-memory-mcp that referenced this pull request Aug 30, 2026
The READS/WRITES resolvers and the USAGE registry fallback hand bare
reference text to the short-name registry, which contains Field nodes -
so once Go struct fields exist (DeusData#1935), every Go local err := ... binds
whichever struct field is named err, project-wide: 21308 USAGE and 5191
WRITES onto Go fields on the measured repo, top target a test struct's
field T collecting 3013 edges. In Go that binding is impossible by
construction: a field is only reachable through a selector expression
(x.f), and selector references resolve on the LSP path - every
Field-targeted reference edge in the census carried dot-less text.

Add cbm_go_suppress_bare_field_ref() next to the DeusData#1928 predicate and
consult it at the same four sites (both READS/WRITES resolvers, both
USAGE registry fallbacks): drop the bind when the file is Go, the
target label is Field, and the reference text has no '.'. Go-gated
because C#/Java/C++/Python method bodies legitimately reference their
own members bare (cp_reads_writes_cs_static_field pins that shape).

Field-validated on the DeusData#1940 stack: USAGE onto Go fields 21308 -> 0,
WRITES 5191 -> 0; the only remaining field-targeted edges are 2466
CALLS, which are DeusData#1906/DeusData#1907's selector-guard territory. Reproduce-
first pipeline probes (sequential + parallel twins) were RED on the
stack without this commit.

Fixes DeusData#1942

Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
ilyabrykau-orca added a commit to ilyabrykau-orca/codebase-memory-mcp that referenced this pull request Aug 30, 2026
The READS/WRITES resolvers and the USAGE registry fallback hand bare
reference text to the short-name registry, which contains Field nodes -
so once Go struct fields exist (DeusData#1935), every Go local err := ... binds
whichever struct field is named err, project-wide: 21308 USAGE and 5191
WRITES onto Go fields on the measured repo, top target a test struct's
field T collecting 3013 edges. In Go that binding is impossible by
construction: a field is only reachable through a selector expression
(x.f), and selector references resolve on the LSP path - every
Field-targeted reference edge in the census carried dot-less text.

Add cbm_go_suppress_bare_field_ref() next to the DeusData#1928 predicate and
consult it at the same four sites (both READS/WRITES resolvers, both
USAGE registry fallbacks): drop the bind when the file is Go, the
target label is Field, and the reference text has no '.'. Go-gated
because C#/Java/C++/Python method bodies legitimately reference their
own members bare (cp_reads_writes_cs_static_field pins that shape).

Field-validated on the DeusData#1940 stack: USAGE onto Go fields 21308 -> 0,
WRITES 5191 -> 0; the only remaining field-targeted edges are 2466
CALLS, which are DeusData#1906/DeusData#1907's selector-guard territory. Reproduce-
first pipeline probes (sequential + parallel twins) were RED on the
stack without this commit.

Fixes DeusData#1942

Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go: weak short-name strategies fabricate CALLS edges for selector calls (stdlib receivers hijack project symbols)

1 participant