Skip to content

fix(extract): drop cgo's import "C" pseudo-package - #1931

Open
ilyabrykau-orca wants to merge 1 commit into
DeusData:mainfrom
ilyabrykau-orca:fix/go-cgo-pseudo-import
Open

fix(extract): drop cgo's import "C" pseudo-package#1931
ilyabrykau-orca wants to merge 1 commit into
DeusData:mainfrom
ilyabrykau-orca:fix/go-cgo-pseudo-import

Conversation

@ilyabrykau-orca

Copy link
Copy Markdown

Fixes #1926

Problem

import "C" is not an import. "C" is reserved by go/build; no package with that
import path can exist. The clause only tells the Go toolchain to compile the
immediately preceding comment block as C.

The Go import parser treated it as an ordinary import spec, so it reached
cbm_pipeline_resolve_import_node(), fell through Strategy 1 (no module resolves) and
Strategy 2 (no namespace map), and landed in the Strategy 3 symbol-name fallback
(src/pipeline/pass_pkgmap.c:1904-1998). That looks up every project node named C and
returns the targetable candidate with the lexicographically smallest qualified name.

Measured on a real Go+C repository (~1150 Go files): all 27 files carrying
import "C" pointed their IMPORTS edge at the same unrelated C member of a test
helper — 27 false edges, and 27 files whose import map named a package that cannot
exist. Which symbol gets hijacked is decided by a lexicographic tie-break, so it moves
as the repo grows.

Change

internal/cbm/extract_imports.c, parse_go_import_spec() — skip the spec when the
import path is exactly "C", before the import is pushed. Go-specific knowledge belongs
in the Go import parser rather than in the language-agnostic resolver, and dropping it at
extraction also keeps the pseudo-package out of the file's import map used by call
resolution.

Effect

Nodes are unchanged and the file's real imports are untouched; only the false edge
disappears.

nodes edges IMPORTS with local_name = 'C'
3-file fixture, before 14 22 1
3-file fixture, after 14 21 0
real repo, before 27
real repo, after 0

Test

tests/test_extraction.cgo_cgo_pseudo_import_dropped.

Reproduce-first: RED without the extractor change —

go_cgo_pseudo_import_dropped   FAIL tests/test_extraction.c:2881:
  ASSERT(strcmp(r->imports.items[i].module_path, "C") != 0)
  318 passed, 1 failed

GREEN with it — 319 passed.

The test also asserts the same file's real fmt import survives, so the fix cannot
regress into dropping the whole import block.

Checks

  • scripts/test.sh --suites extraction — 319 passed, 0 failed.
  • clang-format clean on both edited hunks.
  • scripts/lint.sh could not complete locally: clang-tidy and cppcheck are not
    installed on this machine. Relying on CI for those two legs.

`import "C"` is not an import: "C" is reserved by go/build and no package
with that path can exist — it only tells the toolchain to compile the
preceding comment as C. cbm emitted it as an ordinary import, so the
import resolver fell through to its symbol-name fallback
(cbm_pipeline_resolve_import_node Strategy 3, pass_pkgmap.c), matched the
literal name "C" against every project node called C, and picked the
lexicographically smallest one.

Measured on a real Go+C repo: all 27 files carrying `import "C"` pointed
their IMPORTS edge at the same unrelated `C` member of a test helper —
27 false edges, and 27 files whose import map named a package that does
not exist.

Skip the spec in parse_go_import_spec. Node counts are unchanged and the
file's real imports are untouched; only the false edge disappears (a
3-file fixture goes 22 edges -> 21, 14 nodes -> 14; the real repo goes
27 -> 0 such edges).

Reproduce-first: go_cgo_pseudo_import_dropped is RED without the
extractor change (the "C" import is still in imports[]) and GREEN with
it, while still asserting the file's real `fmt` import survives.

Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
@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 added a commit to ilyabrykau-orca/codebase-memory-mcp that referenced this pull request Aug 30, 2026
A Go import path names a package - never a function, method, field or
another language's build target. Strategy 1 resolves every correct Go
import (module path -> the package's Folder node); when it misses the
import is external and the correct result is NO edge. Two name-guess
fallbacks violated that:

- Strategy 3's language-agnostic symbol-name fallback matched the
  path's last segment against any project definition of the same
  simple name and returned the lexicographically smallest survivor:
  import "os/exec" bound a test harness's exec() method (52 edges on
  the measured repo), two imports bound a Function extracted from a
  Makefile. 89 of 2295 Go IMPORTS edges (3.9%) were false; 27 of those
  are the import "C" case (DeusData#1926/DeusData#1931), the remaining 62 this.
- Strategy 1b's sibling-file resolution admits symbol labels through
  import_targetable_label, re-creating the same bug one directory
  closer: with Strategy 3 gated, the field census still bound one
  os/exec import to a same-package exec() method.

Gate both on the importing file's language through a new pure
predicate, cbm_import_symbol_fallback_allowed(): false for Go, true
for everything else - member-importing languages (Python, Java, Rust
use crate::ops::helper) legitimately need the symbol fallback, and
build/markup grammars (SCSS partials, Meson subdir, Pony use) the
sibling one. Strategies 1, 2 and 4 are untouched.

Reproduce-first test ei_go_import_never_binds_symbol asserts the
IMPORTS relation EXACTLY (an internal package import stays; neither
the cross-package nor the same-package decoy may be bound): RED
count=2 expected==1 before each gate, GREEN after. Adds
ei_edge_count_is, the exact-count sibling of ei_edge_present, since a
floor cannot catch a fabricated extra edge.

Field-validated on a real ~1150-file Go+C repo: Go IMPORTS by target
label went Folder 2206 / Method 85 / Function 4 on main to
Folder 2206 / Method 0 / Function 0 with the fix; all 2206 correct
package-Folder edges survive.

Fixes DeusData#1934

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.

fix(extract): Go — import "C" (cgo pseudo-package) resolves to an arbitrary project symbol named C

1 participant