fix(pipeline): key the Go Field guard on the recorded selector shape, not reference text - #2006
Conversation
… not reference text cbm_go_suppress_bare_field_ref dropped a Field-targeted reference when strchr(ref_name, '.') == NULL — but the extractor strips the receiver on every path that reaches the resolver (resolve_lhs_write_name records the trailing field name of a selector LHS; is_reference_node records the inner field_identifier), so the dot test could never be false for Go. The guard was a blanket veto: all ~4588 Go Field nodes recovered by one 61de19b were unreachable by USAGE/READS/WRITES, including genuine selector references (issue DeusData#1962, found in maintainer post-merge review of DeusData#1944). The selector-vs-bare distinction exists at extraction time and was discarded; record it and let the resolver consume it: - CBMUsage/CBMReadWrite gain is_member_access. The usage recorder sets it when the reference node is a field_identifier (the member half of a selector); resolve_lhs_write_name reports it through an out-param when it takes the field/member LHS branch. - cbm_go_suppress_bare_field_ref(is_go, is_member_access, target_label) refuses a Field bind only for references that were never the member half of a selector. All four resolver sites (sequential and parallel, USAGE and READS/WRITES) pass the recorded flag. Reproduce-first: with the veto restored, the extended DeusData#1942 fixtures go RED on both resolver paths at the new asserts (t.err = nil must WRITE the field; t.n must produce USAGE); with the fix both twins are GREEN and the original bare-local negatives still hold. The dead ASSERT_FALSE(..., "t.err", ...) unit case — an input Go's extractor cannot produce — is replaced by flag-based cases. Fixes DeusData#1962. Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
|
Approved. I verified the inertness claim against
So The dead test is the real lesson here
/* A selector-shaped reference may bind a field. */
ASSERT_FALSE(cbm_go_suppress_bare_field_ref(true, "t.err", "Field"));That assertion passes. It has always passed. And it is worthless, because That is why this survived #1942 and #1944. A test written against a hypothetical input gives exactly the confidence of a test, and none of the coverage. Replacing it with flag-based cases that pin reachable behaviour is the most valuable change in this PR, more than the edges it recovers. It is also the second time in two days this repo has produced this shape — the other was a suite whose On the fix itselfRecording the signal instead of reconstructing it is the right call. #1962 identified that selector shape is known at extraction time and thrown away; the alternative — reconstructing "was this a selector?" downstream from a name with the receiver already gone — is not recoverable at all. There is no clever resolver-side fix here, which is what makes threading the flag the honest answer rather than the lazy one. A distinct Still Go-gated, with all four sites updated. Sequential and parallel, I also confirmed Before mergeCI is still queued. Once it is green I will merge — this recovers ~10k edges that were being silently discarded, which is squarely graph-quality-first. Thank you for taking the post-merge finding and coming back with the falsification run first. Reporting the census on |
|
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. |
|
Merged as The finding I keep coming back to is not the edges, though, it is why this survived two prior PRs. The guard had a unit test for exactly this case: ASSERT_FALSE(cbm_go_suppress_bare_field_ref(true, "t.err", "Field"));Green, always — and worthless, because Replacing that assertion with flag-based cases that pin reachable behaviour is worth more than the edges, because it is what stops the next version of this bug. I have written the pattern down on our side: when a guard is provably inert, suspect the test's inputs before the guard's logic, and give any guard whose effect should be selective a census — Recording the signal at extraction time rather than reconstructing it downstream was the right call too: once the receiver is stripped, "was this a selector?" is not recoverable, so there was no clever resolver-side alternative to find. Before merging I confirmed the 29-commit gap to |
Summary
Fixes the defect you found in post-merge review of #1944:
cbm_go_suppress_bare_field_ref'sstrchr(ref_name, '.')test is inert because the extractor strips the receiver on every path that reaches the resolver —resolve_lhs_write_namerecords the trailing field name of a selector LHS, andis_reference_noderecords the innerfield_identifier. The guard was therefore a blanket veto: every GoFieldnode was unreachable byUSAGE/READS/WRITES, genuine selector references included.As #1962 says, the signal the guard wants is computed at extraction time and discarded. This PR records it and threads it through:
CBMUsage/CBMReadWritegainis_member_access. The usage recorder sets it when the reference node is afield_identifier(the member half of a selector);resolve_lhs_write_namereports it through an out-param when it takes the field/member LHS branch. The receiver is stripped either way — the flag is the only surviving record of selector shape.cbm_go_suppress_bare_field_ref(is_go, is_member_access, target_label)now refuses aFieldbind only for references that were never the member half of a selector. All four resolver sites (sequential + parallel, USAGE + READS/WRITES) pass the recorded flag. Still Go-gated — the C#/Java/C++/Python bare-member shapes stay untouched (cp_reads_writes_cs_static_fieldstill pins that).ASSERT_FALSE(..., "t.err", "Field")unit case — an input Go's extractor cannot produce — is replaced by flag-based cases, so the unit test now pins reachable behavior.RED / GREEN (reproduce-first)
Extended the #1942 fixtures with a sibling-file method doing genuine selector references (
t.err = nil,return t.n). With the blanket veto restored, both resolver twins go RED at the new asserts:With the fix, both twins are GREEN and the original bare-local negatives (
Run'serr := …must not bind the field) still hold. Fullscripts/test.sh(ASan+UBSan) green.Field census (same repo as the #1937/#1940/#1944 numbers)
Your cheap falsification, run on current
main: zero Field-targetedUSAGE/READS/WRITESedges (4497 GoFieldnodes, reachable only by CALLS/DEFINES/TESTS) — the prediction was exact.With this fix, same repo:
USAGE→ GoFieldWRITES→ GoFieldFieldnodes reachedFor scale: the pre-#1944 bare-local pollution was 21308 USAGE / 5191 WRITES. The recovered population is the genuine selector subset, not a return of the noise — the bare-local negatives in the fixtures and the flag's default-false both hold that line.
Fixes #1962.