Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/main/java/com/ibm/cldk/L3WalaOverlays.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
import com.github.javaparser.ast.stmt.BlockStmt;
import com.ibm.cldk.schema.CanId;
import com.ibm.cldk.schema.JCallable;
import com.ibm.cldk.schema.JDdgEdge;
import com.ibm.cldk.schema.JModule;
import com.ibm.cldk.schema.JType;
import com.ibm.cldk.syntactic_analysis.L1BuildContext;
import com.ibm.cldk.syntactic_analysis.controlflow.BodyNodeBuilder;
import com.ibm.cldk.syntactic_analysis.controlflow.ControlFlowGraph;
import com.ibm.cldk.syntactic_analysis.dataflow.DdgEdges;
import com.ibm.cldk.utils.Log;
import com.ibm.cldk.wala.InstructionToNode;
import com.ibm.cldk.wala.WalaAnalysis;
Expand Down Expand Up @@ -87,6 +89,7 @@ public static void apply(
int matched = 0;
int skippedNoMatch = 0;
int totalOverApprox = 0;
int droppedDangling = 0;

for (MethodIr m : wala.applicationMethods()) {
Optional<Joined> joinedOpt = join(m, typeIndex, parseCache, modules);
Expand Down Expand Up @@ -125,15 +128,20 @@ public static void apply(
callable.setBody(cfg.nodes());
callable.setCfg(cfg.toCfgEdges());
callable.setCdg(pdg.cdg);
callable.setDdg(pdg.ddg);
// An edge whose endpoint is an unmapped sentinel names no body node, so no consumer can
// follow it (issue #228). The body is already set above, so the guard sees the final map.
List<JDdgEdge> resolved = DdgEdges.dropDangling(callable, pdg.ddg);
droppedDangling += pdg.ddg.size() - resolved.size();
callable.setDdg(resolved);

matched++;
totalOverApprox += mapper.overApproximationCount();
}

Log.info("L3 WALA overlays applied: " + matched + " callable(s) covered, "
+ skippedNoMatch + " skipped (no source match), "
+ totalOverApprox + " sentinel over-approximation(s)");
+ totalOverApprox + " sentinel over-approximation(s), "
+ droppedDangling + " unresolved ddg edge(s) dropped");
}

// ----- the WALA-method → JCallable join -----------------------------------------------------
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/ibm/cldk/L4WalaOverlays.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.ibm.cldk.schema.JCallable;
import com.ibm.cldk.schema.JDdgEdge;
import com.ibm.cldk.schema.JModule;
import com.ibm.cldk.syntactic_analysis.dataflow.DdgEdges;
import com.ibm.cldk.utils.Log;
import com.ibm.cldk.wala.InstructionToNode;
import com.ibm.cldk.wala.WalaAnalysis;
Expand Down Expand Up @@ -261,7 +262,9 @@ private static int merge(JCallable callable, List<JDdgEdge> produced) {
}

int added = 0;
for (JDdgEdge edge : produced) {
// An unmapped WALA instruction maps to the "<line>:0" sentinel, which names no body node;
// an edge on one is unfollowable in JSON and unwritable in Neo4j, so it never lands (#228).
for (JDdgEdge edge : DdgEdges.dropDangling(callable, produced)) {
if (!edge.getProv().contains("points-to")) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.ibm.cldk.syntactic_analysis.dataflow;

import com.ibm.cldk.schema.JCallable;
import com.ibm.cldk.schema.JDdgEdge;
import java.util.ArrayList;
import java.util.List;

/** Shared guards over a callable's {@code ddg}, applied by every producer that attaches edges to it. */
public final class DdgEdges {

private DdgEdges() {}

/**
* Drops the edges of {@code edges} whose {@code src} or {@code dst} is not a body node of
* {@code callable}.
*
* <p>A WALA instruction whose covering source statement cannot be identified maps to the
* sentinel {@code "<line>:0"} (see {@link com.ibm.cldk.wala.InstructionToNode}), which is a
* well-formed {@code localId} but names no body node. An edge on that sentinel is unusable:
* the Neo4j emitter materialises body nodes from {@code body{}}, so it silently cannot write
* the edge, and a JSON consumer resolving endpoints finds nothing on the other end. Rather
* than emit a dependence a reader cannot follow, the edge is dropped here — the same
* information the graph already carried, now stated identically in both artefacts.
*
* <p>Dropping is sound in the direction that matters: the sentinel marks an
* over-approximation, so the edge is a <em>may</em>-dependence whose exact endpoint was never
* established. The alternative — emitting a synthetic {@code <line>:0} vertex — would need a
* body-node kind no schema vocabulary has.
*
* @return the edges that resolve on both ends, in input order
*/
public static List<JDdgEdge> dropDangling(JCallable callable, List<JDdgEdge> edges) {
if (edges == null || edges.isEmpty() || callable.getBody() == null) {
return edges;
}
List<JDdgEdge> kept = new ArrayList<>(edges.size());
for (JDdgEdge e : edges) {
if (callable.getBody().containsKey(e.getSrc()) && callable.getBody().containsKey(e.getDst())) {
kept.add(e);
}
}
return kept;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.ibm.cldk.syntactic_analysis.dataflow;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;

import com.ibm.cldk.schema.JBodyNode;
import com.ibm.cldk.schema.JCallable;
import com.ibm.cldk.schema.JDdgEdge;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;

class DdgEdgesTest {

private static JCallable callableWithBody(String... keys) {
JCallable c = new JCallable();
Map<String, JBodyNode> body = new LinkedHashMap<>();
for (String k : keys) {
JBodyNode n = new JBodyNode();
n.setKind("statement");
body.put(k, n);
}
c.setBody(body);
return c;
}

private static JDdgEdge edge(String src, String dst) {
JDdgEdge e = new JDdgEdge();
e.setSrc(src);
e.setDst(dst);
e.setVar("v");
e.getProv().add("points-to");
return e;
}

@Test
void dropsEdgesWhoseEndpointIsNotABodyNode() {
JCallable c = callableWithBody("10:9", "12:9");
List<JDdgEdge> kept = DdgEdges.dropDangling(
c,
Arrays.asList(
edge("10:9", "12:9"), // both resolve
edge("11:0", "12:9"), // src is the unmapped sentinel
edge("10:9", "11:0"))); // dst is the unmapped sentinel

assertEquals(1, kept.size(), "only the edge that resolves on both ends survives");
assertEquals("10:9", kept.get(0).getSrc());
assertEquals("12:9", kept.get(0).getDst());
}

@Test
void keepsEveryEdgeWhenAllEndpointsResolve() {
JCallable c = callableWithBody("@entry", "10:9", "10:9/actual_in:0");
List<JDdgEdge> edges = Arrays.asList(edge("@entry", "10:9"), edge("10:9", "10:9/actual_in:0"));
assertEquals(edges, DdgEdges.dropDangling(c, edges));
}

@Test
void passesEmptyAndBodylessInputStraightThrough() {
List<JDdgEdge> empty = new ArrayList<>();
assertSame(empty, DdgEdges.dropDangling(callableWithBody("10:9"), empty));

JCallable noBody = new JCallable();
noBody.setBody(null);
List<JDdgEdge> edges = Arrays.asList(edge("10:9", "11:0"));
assertSame(edges, DdgEdges.dropDangling(noBody, edges), "no body map ⇒ nothing to check against");
}
}