Skip to content

Commit 6536737

Browse files
authored
fix(sdg): param_in/param_out carry the bound formal's var in JSON and on J_PARAM_IN/OUT (#250)
The catalog declared `var` on J_PARAM_IN and J_PARAM_OUT and the projector emitted both with no properties, so a Cypher predicate on `r.var` went three-valued across every call boundary and silently dropped the path. The variable was already known where the edges are built — each actual binds a named callee formal — but JIdEdge had nowhere to carry it. JIdEdge.var now holds it: the bound parameter's name on param_in, `$ret` on param_out. Summary edges keep the bare shape (Gson omits an unset var rather than writing null). The conformance test now asserts every emitted edge of both types carries every declared property, on all of them rather than some — a partially present property is the same trap one step along. Same defect and same direction as codeanalyzer-python#195. Closes #249
1 parent fdab050 commit 6536737

5 files changed

Lines changed: 58 additions & 6 deletions

File tree

‎src/main/java/com/ibm/cldk/neo4j/V2GraphProjector.java‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,14 @@ public static GraphRows project(Analysis analysis, String appName) {
147147
for (JIdEdge e : analysis.getApplication().getParamIn()) {
148148
b.edgeIfBothResolved("J_PARAM_IN",
149149
new NodeRef("JBodyNode", "id", e.getSrc()),
150-
new NodeRef("JBodyNode", "id", e.getDst()), RowBuilder.props());
150+
new NodeRef("JBodyNode", "id", e.getDst()), paramEdgeProps(e));
151151
}
152152
}
153153
if (analysis.getApplication().getParamOut() != null) {
154154
for (JIdEdge e : analysis.getApplication().getParamOut()) {
155155
b.edgeIfBothResolved("J_PARAM_OUT",
156156
new NodeRef("JBodyNode", "id", e.getSrc()),
157-
new NodeRef("JBodyNode", "id", e.getDst()), RowBuilder.props());
157+
new NodeRef("JBodyNode", "id", e.getDst()), paramEdgeProps(e));
158158
}
159159
}
160160

@@ -731,4 +731,14 @@ private static Map<String, Object> mapOf(String k, Object v) {
731731
m.put(k, v);
732732
return m;
733733
}
734+
735+
/**
736+
* {@code var} on a param edge: the callee-side formal's variable, which the catalog declares
737+
* and every {@code param_in}/{@code param_out} edge carries (codeanalyzer-python#195).
738+
*/
739+
private static Map<String, Object> paramEdgeProps(JIdEdge e) {
740+
Map<String, Object> p = RowBuilder.props();
741+
p.put("var", e.getVar());
742+
return RowBuilder.prune(p);
743+
}
734744
}

‎src/main/java/com/ibm/cldk/schema/JIdEdge.java‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
import lombok.Data;
44

55
/**
6-
* An id-to-id edge with no payload — the shape of {@code param_in}/{@code param_out} (application
7-
* scope, global-ordinal endpoints) and {@code summary} (callable scope, local endpoints).
6+
* An id-to-id edge — the shape of {@code param_in}/{@code param_out} (application scope,
7+
* global-ordinal endpoints) and {@code summary} (callable scope, local endpoints).
8+
*
9+
* <p>{@code var} is the callee-side formal's variable on a {@code param_in}/{@code param_out}
10+
* edge — the parameter name, or {@code $ret} for the return port — set on every such edge. The
11+
* Neo4j catalog declared it on {@code J_PARAM_IN}/{@code J_PARAM_OUT} while nothing wrote it, so
12+
* a Cypher predicate on {@code r.var} went three-valued across every call boundary
13+
* (codeanalyzer-python#195). Absent (never null-serialized) on {@code summary} edges.
814
*/
915
@Data
1016
public class JIdEdge {
1117
private String src;
1218
private String dst;
19+
private String var;
1320
}

‎src/main/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVertices.java‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ private static void addActualsAndEdges(
118118
for (int i = 0; i < nArgs; i++) {
119119
int formalIndex = Math.min(i, calleeParams - 1);
120120
paramIn.add(edge(
121-
global(c, local + "/actual_in:" + i), global(callee, "@formal_in:" + formalIndex)));
121+
global(c, local + "/actual_in:" + i), global(callee, "@formal_in:" + formalIndex),
122+
callee.getParameters().get(formalIndex).getName()));
122123
}
123124
}
124125
// Recomputed rather than read off callee.getBody(): traversal order is not call order, so
125126
// the callee's own addFormals may not have run yet when the caller's site is processed.
126127
if (hasActualOut && returnsValue(callee)) {
127-
paramOut.add(edge(global(callee, "@formal_out"), global(c, local + "/actual_out")));
128+
paramOut.add(edge(global(callee, "@formal_out"), global(c, local + "/actual_out"), "$ret"));
128129
}
129130
}
130131
}
@@ -143,6 +144,13 @@ private static JBodyNode vertex(String kind, String of, String parent) {
143144
return n;
144145
}
145146

147+
/** A param edge names the callee-side formal it binds: the parameter, or {@code $ret}. */
148+
private static JIdEdge edge(String src, String dst, String var) {
149+
JIdEdge e = edge(src, dst);
150+
e.setVar(var);
151+
return e;
152+
}
153+
146154
private static JIdEdge edge(String src, String dst) {
147155
JIdEdge e = new JIdEdge();
148156
e.setSrc(src);

‎src/test/java/com/ibm/cldk/neo4j/V2Neo4jSchemaConformanceTest.java‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.nio.file.Files;
4141
import java.nio.file.Path;
4242
import java.nio.file.Paths;
43+
import java.util.Arrays;
4344
import java.util.HashMap;
4445
import java.util.HashSet;
4546
import java.util.LinkedHashMap;
@@ -165,6 +166,30 @@ public void everyEmittedNodeLabelAndPropertyIsDeclared() {
165166
}
166167
}
167168

169+
/**
170+
* codeanalyzer-python#195, same defect here: the catalog declared {@code var} on
171+
* J_PARAM_IN/J_PARAM_OUT and the projection wrote no properties, so a predicate on
172+
* {@code r.var} went three-valued across every call boundary. Every emitted edge of those
173+
* types carries every declared property — on all of them, not some.
174+
*/
175+
@Test
176+
public void paramEdgesCarryEveryDeclaredProperty() {
177+
for (String rel : Arrays.asList("J_PARAM_IN", "J_PARAM_OUT")) {
178+
int seen = 0;
179+
for (EdgeRow e : rows.edges) {
180+
if (!e.type.equals(rel)) {
181+
continue;
182+
}
183+
seen++;
184+
for (String key : REL_BY_TYPE.get(rel).properties.keySet()) {
185+
assertTrue(e.props.containsKey(key) && e.props.get(key) != null,
186+
rel + " " + e.from.value + " -> " + e.to.value + " lacks " + key);
187+
}
188+
}
189+
assertTrue(seen > 0, "precondition: the L4 fixture must emit " + rel);
190+
}
191+
}
192+
168193
@Test
169194
public void everyEmittedRelationshipIsDeclared() {
170195
assertTrue(rows.edges.size() > 0, "fixture produced no edges");

‎src/test/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVerticesTest.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ void buildsFormalActualVerticesAndParamEdges() {
8282
JIdEdge in = r.paramIn.get(0);
8383
assertEquals(CanId.ordinalId(A_ID, "3:16/actual_in:0"), in.getSrc());
8484
assertEquals(CanId.ordinalId(B_ID, "formal_in:0"), in.getDst());
85+
assertEquals("y", in.getVar(), "param_in names the callee formal it binds (#195)");
8586
assertEquals(1, r.paramOut.size());
8687
assertEquals(CanId.ordinalId(B_ID, "formal_out"), r.paramOut.get(0).getSrc());
8788
assertEquals(CanId.ordinalId(A_ID, "3:16/actual_out"), r.paramOut.get(0).getDst());
89+
assertEquals("$ret", r.paramOut.get(0).getVar(), "param_out names the return port");
8890
}
8991

9092
@Test

0 commit comments

Comments
 (0)