Skip to content

feat(extract): cgo is invisible — the C preamble inside a .go file is never parsed, and C.f() calls produce no edges #1929

Description

@ilyabrykau-orca

feat(extract): cgo is invisible — the C preamble inside a .go file is never parsed, and C.f() calls produce no edges

Status: draft implementation-proposal open — PR #1947 for the three bounded pieces (//export linkage as export_linkage contracts, C. pseudo-namespace veto, preamble flagged in parse coverage); marked ready on maintainer design ack. The design-sized fourth piece — parsing the preamble with the C grammar — remains open here. (The adjacent import "C" hijack is #1926, fixed by #1931.)

What is missing

A cgo file is one file in two languages: the comment block above import "C" is C, the rest is Go. cbm parses it with the Go grammar only, so:

  1. Preamble-defined C functions/types get no node — and no parse_partial entry either: the Go grammar sees a comment, not an error, so coverage reports the file as fully indexed.
  2. C.f(...) calls produce no CALLS edge — nothing to bind to, and no notion of a C pseudo-namespace.
  3. //export Name linkage is unread — inbound C→Go edges appear only by short-name luck.

No occurrence of the string cgo exists under src/.

Damage measured (~1150-file Go+C repo)

source graph
files with import "C" 27
C.<ident> references / call-shaped C.f( 141 / 39 0 CALLS edges
preamble-defined C functions (one file alone) 12 0 nodes
//export directives 22 22 inbound edges, all unique_name @ 0.38–0.75
cgo files flagged in parse_partial 0 (silent)

The //export row matters most: those 22 C→Go edges are right by accident — name guesses standing in for a declared ABI contract. Any name collision moves them silently. (Pre-#1906 there was exactly 1 C.* CALLS edge, itself a lucky unique_name guess; the Go guard correctly removed it.)

Minimal reproduction

// fx/cgo_use.go
package fx

/*
static int helper(int a) { return a + 1; }
static const int kAnswer = 42;
*/
import "C"

func Run(a int) int { return int(C.helper(C.int(a))) }

//export GoCallback
func GoCallback(v C.int) C.int { return v }

Result: nodes for Run and GoCallback only; no helper/kAnswer nodes; no CALLS out of Run; no linkage marker on GoCallback.

Detection recipe

SELECT count(*) FROM edges
WHERE type='CALLS' AND json_extract(properties,'$.callee') GLOB 'C.*';
-- compare: grep -rhoE '\bC\.[A-Za-z_][A-Za-z0-9_]*\(' --include='*.go' . | wc -l

Fix sketch (three separable pieces, increasing cost)

  1. //export linkage (cheapest, best value/effort): record the exported C symbol name on the Go def; C-side calls bind by declared contract instead of luck — strictly upgrades 22 existing edges.
  2. C. as a known pseudo-namespace: resolve C.f() to a synthetic external node or suppress explicitly — never let it enter the general registry (a standing Go: weak short-name strategies fabricate CALLS edges for selector calls (stdlib receivers hijack project symbols) #1906-shaped hijack surface).
  3. Parse the preamble (design-sized): run the C grammar over the comment range with line offsets. First case of two grammars over one file; interacts with parse_partial accounting and file attribution (Nondeterministic Java USAGE-edge misattribution since #667 — edges attach to wrong source files on every fresh index #787) — this is the piece needing maintainer input. Even flagging the preamble range in parse_partial alone would beat today's silence.

Related: #1926/#1931, #787, #725, #1906; tracked in #1932.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions