diff --git a/src/main/java/com/ibm/cldk/neo4j/V2GraphProjector.java b/src/main/java/com/ibm/cldk/neo4j/V2GraphProjector.java index f019b3a6..c8c06452 100644 --- a/src/main/java/com/ibm/cldk/neo4j/V2GraphProjector.java +++ b/src/main/java/com/ibm/cldk/neo4j/V2GraphProjector.java @@ -147,14 +147,14 @@ public static GraphRows project(Analysis analysis, String appName) { for (JIdEdge e : analysis.getApplication().getParamIn()) { b.edgeIfBothResolved("J_PARAM_IN", new NodeRef("JBodyNode", "id", e.getSrc()), - new NodeRef("JBodyNode", "id", e.getDst()), RowBuilder.props()); + new NodeRef("JBodyNode", "id", e.getDst()), paramEdgeProps(e)); } } if (analysis.getApplication().getParamOut() != null) { for (JIdEdge e : analysis.getApplication().getParamOut()) { b.edgeIfBothResolved("J_PARAM_OUT", new NodeRef("JBodyNode", "id", e.getSrc()), - new NodeRef("JBodyNode", "id", e.getDst()), RowBuilder.props()); + new NodeRef("JBodyNode", "id", e.getDst()), paramEdgeProps(e)); } } @@ -731,4 +731,14 @@ private static Map mapOf(String k, Object v) { m.put(k, v); return m; } + + /** + * {@code var} on a param edge: the callee-side formal's variable, which the catalog declares + * and every {@code param_in}/{@code param_out} edge carries (codeanalyzer-python#195). + */ + private static Map paramEdgeProps(JIdEdge e) { + Map p = RowBuilder.props(); + p.put("var", e.getVar()); + return RowBuilder.prune(p); + } } diff --git a/src/main/java/com/ibm/cldk/schema/JIdEdge.java b/src/main/java/com/ibm/cldk/schema/JIdEdge.java index a0fdaf0f..239de7eb 100644 --- a/src/main/java/com/ibm/cldk/schema/JIdEdge.java +++ b/src/main/java/com/ibm/cldk/schema/JIdEdge.java @@ -3,11 +3,18 @@ import lombok.Data; /** - * An id-to-id edge with no payload — the shape of {@code param_in}/{@code param_out} (application - * scope, global-ordinal endpoints) and {@code summary} (callable scope, local endpoints). + * An id-to-id edge — the shape of {@code param_in}/{@code param_out} (application scope, + * global-ordinal endpoints) and {@code summary} (callable scope, local endpoints). + * + *

{@code var} is the callee-side formal's variable on a {@code param_in}/{@code param_out} + * edge — the parameter name, or {@code $ret} for the return port — set on every such edge. The + * Neo4j catalog declared it on {@code J_PARAM_IN}/{@code J_PARAM_OUT} while nothing wrote it, so + * a Cypher predicate on {@code r.var} went three-valued across every call boundary + * (codeanalyzer-python#195). Absent (never null-serialized) on {@code summary} edges. */ @Data public class JIdEdge { private String src; private String dst; + private String var; } diff --git a/src/main/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVertices.java b/src/main/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVertices.java index 1227400a..a1ef5746 100644 --- a/src/main/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVertices.java +++ b/src/main/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVertices.java @@ -118,13 +118,14 @@ private static void addActualsAndEdges( for (int i = 0; i < nArgs; i++) { int formalIndex = Math.min(i, calleeParams - 1); paramIn.add(edge( - global(c, local + "/actual_in:" + i), global(callee, "@formal_in:" + formalIndex))); + global(c, local + "/actual_in:" + i), global(callee, "@formal_in:" + formalIndex), + callee.getParameters().get(formalIndex).getName())); } } // Recomputed rather than read off callee.getBody(): traversal order is not call order, so // the callee's own addFormals may not have run yet when the caller's site is processed. if (hasActualOut && returnsValue(callee)) { - paramOut.add(edge(global(callee, "@formal_out"), global(c, local + "/actual_out"))); + paramOut.add(edge(global(callee, "@formal_out"), global(c, local + "/actual_out"), "$ret")); } } } @@ -143,6 +144,13 @@ private static JBodyNode vertex(String kind, String of, String parent) { return n; } + /** A param edge names the callee-side formal it binds: the parameter, or {@code $ret}. */ + private static JIdEdge edge(String src, String dst, String var) { + JIdEdge e = edge(src, dst); + e.setVar(var); + return e; + } + private static JIdEdge edge(String src, String dst) { JIdEdge e = new JIdEdge(); e.setSrc(src); diff --git a/src/test/java/com/ibm/cldk/neo4j/V2Neo4jSchemaConformanceTest.java b/src/test/java/com/ibm/cldk/neo4j/V2Neo4jSchemaConformanceTest.java index a9b94333..0512d62c 100644 --- a/src/test/java/com/ibm/cldk/neo4j/V2Neo4jSchemaConformanceTest.java +++ b/src/test/java/com/ibm/cldk/neo4j/V2Neo4jSchemaConformanceTest.java @@ -40,6 +40,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -165,6 +166,30 @@ public void everyEmittedNodeLabelAndPropertyIsDeclared() { } } + /** + * codeanalyzer-python#195, same defect here: the catalog declared {@code var} on + * J_PARAM_IN/J_PARAM_OUT and the projection wrote no properties, so a predicate on + * {@code r.var} went three-valued across every call boundary. Every emitted edge of those + * types carries every declared property — on all of them, not some. + */ + @Test + public void paramEdgesCarryEveryDeclaredProperty() { + for (String rel : Arrays.asList("J_PARAM_IN", "J_PARAM_OUT")) { + int seen = 0; + for (EdgeRow e : rows.edges) { + if (!e.type.equals(rel)) { + continue; + } + seen++; + for (String key : REL_BY_TYPE.get(rel).properties.keySet()) { + assertTrue(e.props.containsKey(key) && e.props.get(key) != null, + rel + " " + e.from.value + " -> " + e.to.value + " lacks " + key); + } + } + assertTrue(seen > 0, "precondition: the L4 fixture must emit " + rel); + } + } + @Test public void everyEmittedRelationshipIsDeclared() { assertTrue(rows.edges.size() > 0, "fixture produced no edges"); diff --git a/src/test/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVerticesTest.java b/src/test/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVerticesTest.java index b706eba8..40628399 100644 --- a/src/test/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVerticesTest.java +++ b/src/test/java/com/ibm/cldk/syntactic_analysis/dataflow/SdgVerticesTest.java @@ -82,9 +82,11 @@ void buildsFormalActualVerticesAndParamEdges() { JIdEdge in = r.paramIn.get(0); assertEquals(CanId.ordinalId(A_ID, "3:16/actual_in:0"), in.getSrc()); assertEquals(CanId.ordinalId(B_ID, "formal_in:0"), in.getDst()); + assertEquals("y", in.getVar(), "param_in names the callee formal it binds (#195)"); assertEquals(1, r.paramOut.size()); assertEquals(CanId.ordinalId(B_ID, "formal_out"), r.paramOut.get(0).getSrc()); assertEquals(CanId.ordinalId(A_ID, "3:16/actual_out"), r.paramOut.get(0).getDst()); + assertEquals("$ret", r.paramOut.get(0).getVar(), "param_out names the return port"); } @Test