Repository navigation
Description
Is your feature request related to a problem? Please describe.
The four interprocedural value verbs are refused on a Java graph, behind a probe for whether any dependence edge touches an SDG port. The refusal was correct: codeanalyzer-java emitted the port vertices and the param_in/param_out/summary lattice between them, but no ddg edge ever touched a port, so slice_forward from a parameter returned the seed alone, paths_between returned no paths, and flows_to_call/flows_to_argument returned False for every input — an empty answer indistinguishable from "no flow exists".
codeanalyzer-java 3.0.3 fixes it (codeanalyzer-java#227, PR codeanalyzer-java#230). The analyzer now emits @formal_in:k → use, return → @formal_out, statement → <call>/actual_in:i and <call>/actual_out → statement, so the port layer is joined to the statement graph in both directions. The same release also drops ddg edges whose endpoint is not a body node (codeanalyzer-java#228), which removes the only measured disagreement between analysis.json and the Neo4j projection.
Measured on daytrader8 at -a 4 with 3.0.3: 5,108 ddg edges touch a port (0 before), formal_in ports with out-degree zero fall from 1,176 to 140 (all 140 are parameters no statement reads), 0 dangling endpoints, and JSON and graph both report 10,493 ddg edges.
Describe the solution you'd like
- Pin at
codeanalyzer-java==3.0.3; the probe passes without a special case -
slice_forwardfrom a parameter,paths_between,flows_to_callandflows_to_argumentanswer on a Java graph, over both the JSON and the Neo4j backend - A test that would have failed before: an end-to-end witness that a caller's argument reaches a callee's parameter across at least two frames
Describe alternatives you've considered
Not stated in the original issue.
Additional context
Scope boundary
In scope: move the codeanalyzer-java pin to 3.0.3, confirm the port-edge probe passes on its own, remove the refusal so the four verbs answer on Java, and regenerate any committed Java -a 4 fixtures (ddg roughly doubles).
Out of scope: the analyzer-side work (shipped); the wheel/JVM uptake (#339); the pin-vs-releases/latest release mechanism (#336).
Caveats and known risks
- The attachment of
actual_in/actual_outis deliberately coarse: every actual of a call site is fed by the one statement containing it, not by the reaching definition of that particular argument. Paths are complete; per-argument precision needs the statement's own incoming edges. Do not assert a tighter shape than the analyzer promises. - Under
--l3-engine walathere are no@entryedges to mirror, soformal_inports stay unattached on that engine. If the SDK ever selects that engine, the probe must keep gating there. - 140
formal_inports still have out-degree zero on daytrader8 — unread parameters. A probe asserting "every port is attached" would fail on correct output.
Definition of done
- The four verbs return non-empty, correct answers on a Java fixture where a value crosses two call boundaries, and the same query over Neo4j agrees with the JSON backend
- The gate/probe code path that refused them is gone or narrowed to the wala-engine case, with a test covering the reason it stays
Activity
rahlk commentedon Sep 7, 2026
Verified against real 3.0.3 output ahead of the work. Three things to record, one of which changes a line in the definition of done.
The probe lifts on its own — it should be kept, not removed. Ran the four verbs through
JCodeanalyzerover a whole-project-a 4daytrader8 payload produced by 3.0.3, with no SDK change of any kind:_ports_carry_dependenceFalseTrueslice_forward('orderID')completeflows_to_call→completeOrderTrueflows_to_argument('orderID')Truepaths_betweenacross the twoThe shortest cross-callable path is 21 hops and alternates the four vocabularies the surface promises —
data,argument,return,control— so the witness the third goal asks for exists in the fixture already.The probe asks the data whether ports carry dependence, never the analyzer version. That is what makes it lift by itself, and it is also why deleting it would be a regression rather than a cleanup: the Neo4j floor is 3.0.1, so a graph emitted by 3.0.1 or 3.0.2 is still attachable and still needs the refusal, and the
--l3-engine walacase in the caveats needs it too. Suggest the last DoD line becomes: the probe is kept, a test pins that it refuses on a 3.0.2-shaped graph and answers on a 3.0.3-shaped one, and no version literal appears in the decision.A narrow guard is worth adding while here. The local backend computes the probe as "some
formal_inhas an outgoing edge" without requiring that edge's target to exist as a body node; the Neo4j backend requires a materialised node at both ends. Those two definitions can disagree on output carrying dangling endpoints — which 3.0.2 had and 3.0.3 fixed, so the divergence is unobservable today and was unobservable before only because the out-degree was uniformly zero. Onedst in nodesclause closes it.The committed fixtures move much less than "roughly doubles". That figure is the whole-project number; on the two committed files the change is surgical:
ddgcdg/cfg/summary/call_graph/param_in/param_out/ artifacts / callables / unitsEvery one of a4's 867 new edges is an
ssaport crossing;points-tostays at 320, self-loops stay at 20, and dangling endpoints are 0 in both releases on this fixture — so #228's fix is not observable here, only on the whole project. Regenerated copies of both are ready.