fix(pipeline): a bare Go reference can bind a struct Field — READS/WRITES/USAGE attach every local err to whichever Field is named err
Status: fix open — PR #1944 (cbm_go_suppress_bare_field_ref() at the same four resolver sites as #1937's guard; stacked on #1937 → #1940). Measured: USAGE onto Go fields 21308 → 0, WRITES 5191 → 0; the combined stack ends below main's totals (USAGE 29198 → 21035, WRITES 2681 → 774) while adding 4533 Field nodes. Found while field-validating #1940 — the third leg of its ordering constraint.
What happens
The READS/WRITES resolvers (pass_usages.c:resolve_rw_edges, pass_parallel.c:resolve_file_rw) and the USAGE registry fallback (resolve_usage_edges / resolve_file_usages) hand bare reference text to cbm_registry_resolve(). The registry contains Field nodes, so a dot-less local —
— resolves by simple name to any Field named err, project-wide.
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, never through this fallback. Unlike C#/Java/C++/Python, where a method body legitimately references its own members bare (_count++ — cp_reads_writes_cs_static_field pins that as required), so the veto must be Go-gated, not global.
Damage measured (#1940 stack, ~1150-file Go+C repo)
onto Go Field nodes |
count |
USAGE |
21308 (top: a test struct's field T 3013, events 977, err 759, Path 242, config 221) |
WRITES |
5191 |
100% of Field-targeted USAGE edges carried dot-less reference text — no legitimate selector-shaped population is mixed in. (The 612/468 refs onto C/C++ fields surviving #1937 are the legitimate own-member-in-method shape plus C-side noise; untouched by this fix.) The remaining field-targeted class is 2466 CALLS — #1906/#1907's selector-guard territory.
Minimal reproduction (requires #1935's extraction)
// state/state.go
package state
type Tracker struct {
err error
}
// app/app.go
package app
import "errors"
func Run() error {
err := errors.New("x")
return err
}
Expected: no edge from Run into Tracker.err. Actual (with #1940 alone): WRITES Run → state.Tracker.err.
Detection recipe
SELECT e.type, count(*) FROM edges e
JOIN nodes t ON t.id = e.target_id
WHERE t.label = 'Field' AND t.file_path LIKE '%.go'
AND e.type IN ('READS','WRITES','USAGE')
GROUP BY 1;
Any nonzero row is this bug.
Fix (as landed)
Pure predicate next to #1937's: drop the bind when the file is Go, the target label is Field, and the reference has no .. Wired at both sequential resolvers and both parallel twins; reproduce-first probes on both paths (RED on the #1940 stack), plus unit tests (selector-shaped refs may bind; non-Field targets and non-Go languages untouched).
Related: #1927 (same "nothing checks what a name can denote" class on CALLS), #1928/#1937, #1935/#1940; tracked in #1932.
fix(pipeline): a bare Go reference can bind a struct Field — READS/WRITES/USAGE attach every local
errto whichever Field is namederrStatus: fix open — PR #1944 (
cbm_go_suppress_bare_field_ref()at the same four resolver sites as #1937's guard; stacked on #1937 → #1940). Measured: USAGE onto Go fields 21308 → 0, WRITES 5191 → 0; the combined stack ends below main's totals (USAGE 29198 → 21035, WRITES 2681 → 774) while adding 4533Fieldnodes. Found while field-validating #1940 — the third leg of its ordering constraint.What happens
The READS/WRITES resolvers (
pass_usages.c:resolve_rw_edges,pass_parallel.c:resolve_file_rw) and the USAGE registry fallback (resolve_usage_edges/resolve_file_usages) hand bare reference text tocbm_registry_resolve(). The registry containsFieldnodes, so a dot-less local —— resolves by simple name to any
Fieldnamederr, project-wide.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, never through this fallback. Unlike C#/Java/C++/Python, where a method body legitimately references its own members bare (_count++—cp_reads_writes_cs_static_fieldpins that as required), so the veto must be Go-gated, not global.Damage measured (#1940 stack, ~1150-file Go+C repo)
FieldnodesUSAGET3013,events977,err759,Path242,config221)WRITES100% of Field-targeted USAGE edges carried dot-less reference text — no legitimate selector-shaped population is mixed in. (The 612/468 refs onto C/C++ fields surviving #1937 are the legitimate own-member-in-method shape plus C-side noise; untouched by this fix.) The remaining field-targeted class is 2466
CALLS— #1906/#1907's selector-guard territory.Minimal reproduction (requires #1935's extraction)
Expected: no edge from
RunintoTracker.err. Actual (with #1940 alone): WRITESRun→state.Tracker.err.Detection recipe
Any nonzero row is this bug.
Fix (as landed)
Pure predicate next to #1937's: drop the bind when the file is Go, the target label is
Field, and the reference has no.. Wired at both sequential resolvers and both parallel twins; reproduce-first probes on both paths (RED on the #1940 stack), plus unit tests (selector-shaped refs may bind; non-Field targets and non-Go languages untouched).Related: #1927 (same "nothing checks what a name can denote" class on CALLS), #1928/#1937, #1935/#1940; tracked in #1932.