Skip to content

fix(pipeline): guard USAGE/WRITES/READS against cross-language binds - #1937

Open
ilyabrykau-orca wants to merge 1 commit into
DeusData:mainfrom
ilyabrykau-orca:fix/go-usage-writes-cross-language
Open

fix(pipeline): guard USAGE/WRITES/READS against cross-language binds#1937
ilyabrykau-orca wants to merge 1 commit into
DeusData:mainfrom
ilyabrykau-orca:fix/go-usage-writes-cross-language

Conversation

@ilyabrykau-orca

Copy link
Copy Markdown

Fixes #1928.

What

USAGE, WRITES and READS edges resolve through the same short-name registry as CALLS but never consulted the #725 cross-language guard. On a Go tree with eBPF C probes, every Go identifier spelled like a C one produced a reference edge into the C file: 31.5% of all WRITES on the measured repo (866 of 2793) crossed the Go→C boundary — dozens of Go test locals named event "writing" a C probe's automatic variable. Go code cannot touch a C translation unit's identifiers; every edge in that class is false.

How

New pure predicate cbm_suppress_cross_language_ref(caller_lang, target_file_path) in registry.c, next to (and reusing the language machinery of) cbm_suppress_cross_language_suffix_match, consulted on:

  • the registry-fallback branch of resolve_usage_edges (USAGE), and
  • resolve_rw_edges (WRITES/READS), which now receives the file's language.

Two deliberate differences from the CALLS guard:

Measured expectation

From the issue: Go→C/C++ WRITES 866 → ~0, USAGE 1596 → ~0 (plus the C→Go reverse direction, 15 + 90). This also unblocks #1935 — landing ~4600 Go Field nodes without this guard would mint ~5700 WRITES / ~22000 USAGE name-collision edges onto err/ctx/_ fields.

Tests

  • Reproduce-first pipeline_go_rw_usage_never_cross_into_c (Go + C fixture). Both paths proven RED independently on main: first FAIL … ASSERT(!(cross_file_edge_exists(s, project, "UsesHandle", "handle", "USAGE"))), then — with only the USAGE branch guarded — FAIL … ASSERT(!(cross_file_edge_exists(s, project, "WriteTotal", "total_events", "WRITES"))). GREEN with both wired. Same-language control (BumpCounter WRITES) survives.
  • Unit test cross_language_ref_drops_go_vs_c next to the get_architecture hotspots report inflated fan_in that contradicts actual graph in-degree (same-name collision across languages) #725 unit test: Go↔C/C++/header drops both directions, same-language keeps, C↔.h keeps, JS↔TS keeps, unknown-language/NULL/empty keeps.
  • Generalized the CALLS-only test helper into cross_file_edge_exists(…, edge_type); cross_file_call_exists is now a one-line wrapper.
  • Full scripts/test.sh venue leg (ASan+UBSan, all suites + contract steps): green. git clang-format --diff: clean.

Base: upstream main directly (independent of the #1907/#1913/#1915 stack). #1932 tracks the family.

@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.

USAGE, WRITES and READS edges resolve through the same short-name
registry as CALLS but never consulted the DeusData#725 cross-language guard.
On a Go tree with eBPF C probes every Go identifier spelled like a C
one produced a reference edge into the C file: 31.5% of all WRITES on
the originally measured repo crossed the Go->C boundary, led by dozens
of Go test locals named event writing a C probe's automatic variable.

Add cbm_suppress_cross_language_ref() - the reference-edge analog of
cbm_suppress_cross_language_suffix_match - and consult it on BOTH
resolvers of each edge type: the sequential pass (pass_usages.c
resolve_usage_edges registry-fallback branch, resolve_rw_edges) and
their parallel twins (pass_parallel.c resolve_file_usages,
resolve_file_rw). The sequential-only version of this change left 344
Go->C WRITES alive on a ~1150-file repo because large repos resolve
through pass_parallel.c - the field census caught it, and the
parallel-twin test now pins it.

Unlike the CALLS guard the predicate takes no strategy parameter: a
reference edge carries no import-closure evidence, so every registry
strategy is a bare-name guess across a boundary. LSP-backed semantic
references resolve before the fallback and are unaffected. JS/TS stay
one family, and C/C++ count as one family too (.h maps to
CBM_LANG_CPP, so a .c file referencing its own header is not a
boundary).

Field-validated on the ~1150-file Go+C repo: Go->C/C++ WRITES 835->0,
USAGE 1545->0; C->Go 15/90->0; 6432 reference edges dropped in total,
every one cross-language (Go->.json 3034, Go->.hpp/.h 1612, Go->.c
746, Go->.sh/.yaml/.yml 443, ...) and none same-language. CALLS and
IMPORTS totals are byte-identical to main.

Fixes DeusData#1928

Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
@ilyabrykau-orca

Copy link
Copy Markdown
Author

Amended (force-push): the field census caught the sequential-only version leaving 344 Go→C WRITES / ~1200 USAGE alive on the ~1150-file repo — USAGE and WRITES/READS each have a second, independent resolver in pass_parallel.c (resolve_file_usages / resolve_file_rw), and large repos resolve through that path. Both parallel twins now consult the same guard, mirroring their sequential counterparts, and the test gained a parallel twin (52-filler fixture) that was RED against the sequential-only fix.

Fresh census (this branch vs main, same tree): Go→C/C++ WRITES 835 → 0, USAGE 1545 → 0, C→Go 15/90 → 0; 6432 reference edges dropped in total, every one cross-language (Go→.json 3034, Go→.hpp/.h 1612, Go→.c 746, Go→.sh/.yaml 443, …) and none same-language. CALLS and IMPORTS totals byte-identical to main. Full venue leg green.

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.

fix(pipeline): USAGE and WRITES edges have no cross-language guard — 31% of WRITES on a Go+C repo cross the Go/C boundary

1 participant