Repository navigation
Go L4 interprocedural SDG, VTA dispatch and library stand-ins - #36
Merged
Merged
Conversation
…VTA set construction
… passed to libraries
…ress-taken values
…fset 0, array-pointer slices
A closure that writes through a local alias of a captured pointer (`q := p; q.n = x`) writes no path of p, so it had no capture formal_out for p and the flow x -> p was refuted. Apply the shared by-ref predicate (MayHoldRef) to captures, as to params. Found by metamorphic relation MR5.
A literal writing through a local alias of a captured pointer (`q := p; q.n = x`) writes no path of p, so at -a 3 its host did not weak-define p.* and a later read of p.n had no edge from it. Add x.* for every capture x that may hold a reference, mirroring 078616f at L4.
CANGO_LEVELS="2 3 4" runs the -a 4 triple (-j 1, -j N, rerun) with a byte-identity check and records L4 counts, times, bytes and degraded codes; l3_metrics.py adds an l4 section.
With strconv/Atoi: [] the Atoi summary edges go and the flow stays; a cut on n within SanitizedFind is bypassed by err (cdg, return) and the receiver (coarse host wiring), all correct dependence (codellm-devkit/.github#103). The no-flow pair moves to a separate entry, Audit, that shares no host variable.
A method value of an interface or a struct embedding one makes VTA return a $bound or promotion wrapper whose own call of the concrete method sits at a synthetic site no call node joins; the wrapper's VTA callees are now targets of the original site.
With tests loaded the PkgPath-keyed package map keeps only the test variant, so the plain package other packages import had no SSA bodies and VTA missed the types flowing through it. L4 now builds SSA over every loaded variant.
A D7 interface-method target (or L2's static callee) at a site with no L2 edge was emitted with prov ["vta"]. It stays a dispatch target with its param edges; only VTA's own callees become call_graph edges.
formalIndex built a partially initialised join just to call it.
VTA sees no concrete type in library code, so ctx.Value(k).(Store).Find or a sync.Map round-trip kept only the D7 interface method. An invoke site whose receiver originates in a type assertion, a library call or a library load now also targets every project type implementing the interface. Also documents F1 and F7 in spec §4.8.
The documented code was never produced. It lists the project packages go/packages loaded with errors, sorted.
SET n += row.p keeps a property the row lacks, so a clean push over a graph a degraded run wrote kept its degraded_json. The application row now carries an explicit null. Also: Degraded.Code is an open vocabulary.
…pec; stale comments
A library-originated value returned by or passed into a project function is part of the remaining gap, not only one stored in project storage.
A library-originated value that reached an interface call through a project function's result or parameter, a closure capture, a field, a global or a container element kept only the D7 interface method. A whole-program monotone fixpoint now tracks it (storage by container type), and map lookups, index and channel receives count as origins. CHA runs under recover: a panic drops its targets and records no_cha.
callLO read the implementer-is-library-code memo before implementers filled it, and the memo is not a fact, so an invoke through a promoted library method got no CHA target when its caller was visited first. The memo is now filled with the implementer list it belongs to. runCHA clones the site slices it restores on a panic.
…s as L2 does, list range-over-func in package-level literals
…nd-trip; bind on go/types' method-expression kind
sinha108
marked this pull request as ready for review
October 28, 2026 21:21
pavuluri
approved these changes
Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds L4, the interprocedural system dependence graph (SDG), to codeanalyzer-go at
-a 4, and makes--emit neo4jproject at L4. It also closes L2 and L3 gaps that L4 depends on.Motivation and Context
L4 brings the Go analyzer to the same level as codeanalyzer-python and codeanalyzer-java. It is the graph that python-sdk's interprocedural slicing and taint walk over: each callable gets
formal_in/formal_outvertices and each resolved call siteactual_in/actual_out, wired byparam_in/param_outand per-call-sitesummaryedges, with VTA dispatch for interface and function-value calls and library stand-ins so sinks can name library functions.Design:
docs/design/specs/v2-l3-l4-dataflow.md§4.6–§4.16 and §6, amended through this work. Decisions are tagged[user]/[call]in §4.16.How Has This Been Tested?
Unit and gate tests (
go test ./..., CI on Go 1.26 and the latest stable Go):--library-models, the may-alias delta, and the library-origin CHA complementdatabase/sql.DB.Query'squerythrough an interface call that only VTA resolves, and an unrelated pair is unreachable-a 1 ⊆ -a 2 ⊆ -a 3 ⊆ -a 4monotonicity on the JSON, with a self-test of the checker itself-j 1vs-j Nbyte-identity, and an order-independence test that permutes the library-origin fixpoint's work ordergo/ssaMetamorphic tests (new,
internal/metamorphic): 15 relations with exact, reachability and edge-delta oracles, over the fixtures, chi and a focused examples module (go test), and memos by default underscripts/metamorphic.sh(--alladds nats-server). They found and fixed two soundness bugs in closure captures (L3 and L4).Live Neo4j 5.26 (
neo4j_it, throwaway Docker container), including a lazy L4 push over an older lower-level graph.Real-app evaluation, reproducible with
CANGO_LEVELS="2 3 4" scripts/eval_l3.sh --all(same 8 apps and pinned commits as the L3 PR); results indocs/design/notes/l3-dataflow-metrics.md§ L4 (go1.27.3, measured at the PR's final analyzer code,98ebfef). Every app completes-a 4with no crash, and-j 1/-j 16/ a second-j 16are byte-identical.k8s.io/api)-a 3→-a 4(-j 1)-a 4(×-a 3)analysis.json-a 3→-a 4param_in/param_outsummaryTakeaways:
-a 4in 38.4 s at 7.9 GB, 0.99–1.13× its-a 3peak (which varies by about 1 GB run to run); the package load still dominates memory. About 6 s of its L4 time is the CHA complement's interprocedural library-origin fixpoint.range_func, not modelled at L4, D8), so the SDK will withhold "no flow" answers on them;unmapped_ssacomes almost entirely from closures in package-levelvarinitializers.scripts/metamorphic.sh) passes on the same head with 0 violations in 14 relations.Bugs found by the reviews and metamorphic tests, fixed here with regression tests:
//linedirectives were empty (Call-site recording: empty spans under //line, and one walker shared by L1 and L3 #26)--skip-tests=falsesilently lost SSA for packages with testsctx.Value(k).(I),sync.Map.Load) had no concrete targetsanalysis.jsonignored the umask and was not fsynced before the renameBreaking Changes
-a 4is accepted;--strictmakes any degradation a non-zero exit;--library-models <path.json>(valid only at L4).--emit neo4jnow projects at L4. With--target-filesit falls back to L3 and prints a warning;-a 4 -tis an error (L4 is whole-program).-a 1..3output is a subset of-a 4):of; callablesummary; applicationparam_in/param_out;degraded[];external_symbols[].receiverand.body;ddgedges withprovreaching-defsandpoints-to;call_graphedges withprovvta.receiver.appendkeeping element defs, and value structs holding references are now modelled (L3 data-dependence completeness: decide L3 vs L4 for three gaps, and add the go/ssa check #22); a closure's host writes through every reference-holding capture.//linedirectives now use real file positions.GO_PARAM_IN/GO_PARAM_OUT(var) andGO_SUMMARYare live; stand-in vertices hang off:GoExternal;:GoApplication.degraded_json. A lazy push re-upserts every L4 row and forces a full push when the stored level is lower; removing stale L4 rows needs--eager.analysis.jsonis streamed module by module and written atomically (temp file plus rename); the bytes are unchanged.Types of changes
Checklist
Additional context
go/ssais built over the existinggo/packagesload, project packages only, and joined to the L3 units by AST pointer and callLparen.x/tools/go/callgraph/vta) resolves interface and function-value calls, with library targets in its function set and synthetic wrappers followed through; a whole-program library-origin fixpoint adds a CHA complement for receivers whose dynamic type came from library code.points-toedges.recover; failures degrade (no_ssa,partial_ssa,no_vta,no_cha, …) instead of crashing.sort.Slice,http.HandleFunc) are not edges.base_typesholds type strings, not ids, andinterfaces[]is never computed (GO_EMBEDS/GO_SATISFIESempty).taint()sanitizer calls do not cut flows assigned at the call site.s = append(s, …)killing element defs, inverted spans in malformed functions (End clamped to its file), the satellite-callparenttests, and the Go 1.23 range-over-int / range-over-func fixture. Small correctness and test-coverage gaps from the L3 reviews #28 stays open for the rest.