diff --git a/pom.xml b/pom.xml
index ebed3bf8b38659dec4eb6d51d2c217077142e64d..0a27175b3de9ff9c3c59b0e2d1edac151fedb7b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,6 +6,7 @@
tfm
tfm
+ pom
1.0-SNAPSHOT
@@ -16,5 +17,6 @@
sdg-core
sdg-cli
+ sdg-gui
diff --git a/sdg-cli/META-INF/MANIFEST.MF b/sdg-cli/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000000000000000000000000000000000..653a67f3e9fb02b8d19bf0f718ed1d62e523204a
--- /dev/null
+++ b/sdg-cli/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: tfm.cli.Slicer
+
diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml
index 09842784ad5ed95b0c83a4ca5d0b39f06f7bc49a..dd4e67936d56d79c6674414b0c4257cf2fe88ce0 100644
--- a/sdg-cli/pom.xml
+++ b/sdg-cli/pom.xml
@@ -6,7 +6,7 @@
tfm
sdg-cli
- 1.0-SNAPSHOT
+ 1.0.1
11
@@ -27,7 +27,7 @@
tfm
sdg-core
- 1.0-SNAPSHOT
+ 1.1.2
compile
diff --git a/sdg-cli/src/main/java/tfm/cli/GraphLog.java b/sdg-cli/src/main/java/tfm/cli/GraphLog.java
index baaa4c84415528067dfb4dfaea5a1616dd36a1f9..1becbce8a8359dfa2ceb0ef4eef17bd48de402be 100644
--- a/sdg-cli/src/main/java/tfm/cli/GraphLog.java
+++ b/sdg-cli/src/main/java/tfm/cli/GraphLog.java
@@ -1,6 +1,9 @@
package tfm.cli;
+import org.jgrapht.io.DOTExporter;
+import tfm.arcs.Arc;
import tfm.graphs.Graph;
+import tfm.nodes.GraphNode;
import tfm.utils.FileUtil;
import tfm.utils.Logger;
@@ -21,15 +24,12 @@ public abstract class GraphLog {
}
}
- static final String CFG = "cfg";
- static final String PDG = "pdg";
- static final String SDG = "sdg";
-
- G graph;
+ protected G graph;
protected String imageName;
- protected Format format;
+ protected String format;
protected boolean generated = false;
+ protected File outputDir = new File("./out/");
public GraphLog() {
this(null);
@@ -39,6 +39,10 @@ public abstract class GraphLog {
this.graph = graph;
}
+ public void setDirectory(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
public void log() throws IOException {
Logger.log(
"****************************\n" +
@@ -52,7 +56,7 @@ public abstract class GraphLog {
"****************************"
);
try (StringWriter stringWriter = new StringWriter()) {
- graph.getDOTExporter().exportGraph(graph, stringWriter);
+ getDOTExporter(graph).exportGraph(graph, stringWriter);
stringWriter.append('\n');
Logger.log(stringWriter.toString());
}
@@ -63,22 +67,24 @@ public abstract class GraphLog {
}
public void generateImages(String imageName) throws IOException {
- generateImages(imageName, Format.PNG);
+ generateImages(imageName, "pdf");
}
- public void generateImages(String imageName, Format format) throws IOException {
- this.imageName = imageName + "-" + graph.getClass().getName();
+ public void generateImages(String imageName, String format) throws IOException {
+ this.imageName = imageName + "-" + graph.getClass().getSimpleName();
this.format = format;
generated = true;
File tmpDot = File.createTempFile("graph-source-", ".dot");
+ tmpDot.getParentFile().mkdirs();
+ getImageFile().getParentFile().mkdirs();
// Graph -> DOT -> file
try (Writer w = new FileWriter(tmpDot)) {
- graph.getDOTExporter().exportGraph(graph, w);
+ getDOTExporter(graph).exportGraph(graph, w);
}
// Execute dot
ProcessBuilder pb = new ProcessBuilder("dot",
- tmpDot.getAbsolutePath(), "-T" + format.getExt(),
+ tmpDot.getAbsolutePath(), "-T" + format,
"-o", getImageFile().getAbsolutePath());
try {
int result = pb.start().waitFor();
@@ -96,7 +102,11 @@ public abstract class GraphLog {
FileUtil.open(getImageFile());
}
- protected File getImageFile() {
- return new File("./out/" + imageName + "." + format.getExt());
+ public File getImageFile() {
+ return new File(outputDir, imageName + "." + format);
+ }
+
+ protected DOTExporter, Arc> getDOTExporter(G graph) {
+ return graph.getDOTExporter();
}
}
diff --git a/sdg-cli/src/main/java/tfm/cli/PDGLog.java b/sdg-cli/src/main/java/tfm/cli/PDGLog.java
index a50a51ca7d0ef9f24d604de32a9b06ecf951b77c..f8eed98048661ea6d7cd8366b1e1fdc0bd4ee26e 100644
--- a/sdg-cli/src/main/java/tfm/cli/PDGLog.java
+++ b/sdg-cli/src/main/java/tfm/cli/PDGLog.java
@@ -31,18 +31,16 @@ public class PDGLog extends GraphLog {
Logger.log(graph.vertexSet().stream()
.sorted(Comparator.comparingLong(GraphNode::getId))
.map(node ->
- String.format("GraphNode { id: %s, instruction: %s, declared: %s, defined: %s, used: %s }",
+ String.format("GraphNode { id: %s, instruction: %s, variables: %s}",
node.getId(),
node.getInstruction(),
- node.getDeclaredVariables(),
- node.getDefinedVariables(),
- node.getUsedVariables())
+ node.getVariableActions())
).collect(Collectors.joining(System.lineSeparator()))
);
}
@Override
- public void generateImages(String imageName, Format format) throws IOException {
+ public void generateImages(String imageName, String format) throws IOException {
super.generateImages(imageName, format);
if (cfgLog != null)
cfgLog.generateImages(imageName, format);
diff --git a/sdg-cli/src/main/java/tfm/cli/PHPSlice.java b/sdg-cli/src/main/java/tfm/cli/PHPSlice.java
new file mode 100644
index 0000000000000000000000000000000000000000..6390f99798aac88945ee37c9a058a340641855dc
--- /dev/null
+++ b/sdg-cli/src/main/java/tfm/cli/PHPSlice.java
@@ -0,0 +1,148 @@
+package tfm.cli;
+
+import com.github.javaparser.JavaParser;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.comments.BlockComment;
+import com.github.javaparser.symbolsolver.JavaSymbolSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
+import org.apache.commons.cli.*;
+import tfm.graphs.cfg.CFG;
+import tfm.graphs.exceptionsensitive.ESSDG;
+import tfm.graphs.sdg.SDG;
+import tfm.nodes.GraphNode;
+import tfm.slicing.NodeIdSlicingCriterion;
+import tfm.slicing.Slice;
+import tfm.slicing.SlicingCriterion;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class PHPSlice {
+ protected static final Options OPTIONS = new Options();
+
+ static {
+ OPTIONS.addOption(Option
+ .builder("f")
+ .hasArg().argName("CriterionFile.java").type(File.class)
+ .required()
+ .desc("The file that contains the slicing criterion.")
+ .build());
+ OPTIONS.addOption(Option
+ .builder("i")
+ .hasArg().argName("node_id")
+ .required()
+ .desc("The slicing criterion, in the form of a node id (a positive integer).")
+ .build());
+ OPTIONS.addOption(Option
+ .builder("o")
+ .hasArg().argName("output_file")
+ .required()
+ .desc("The folder where the slice and the graphs should be placed")
+ .build());
+ OPTIONS.addOption("es", "exception-sensitive", false, "Enable exception-sensitive analysis");
+ OPTIONS.addOption(Option
+ .builder("h").longOpt("help")
+ .desc("Shows this text")
+ .build());
+ }
+
+ private final File outputDir;
+ private File scFile;
+ private int scId;
+ private final CommandLine cliOpts;
+
+ public PHPSlice(String... cliArgs) throws ParseException {
+ cliOpts = new DefaultParser().parse(OPTIONS, cliArgs);
+ if (cliOpts.hasOption('h'))
+ throw new ParseException(OPTIONS.toString());
+ setScId(Integer.parseInt(cliOpts.getOptionValue("i")));
+ setScFile(cliOpts.getOptionValue("f"));
+ outputDir = new File(cliOpts.getOptionValue("o"));
+ if (!outputDir.isDirectory())
+ throw new ParseException("The output directory is not a directory or not readable by us!");
+ }
+
+ private void setScFile(String fileName) throws ParseException {
+ File file = new File(fileName);
+ if (!(file.exists() && file.isFile()))
+ throw new ParseException("Slicing criterion file is not an existing file.");
+ scFile = file;
+ }
+
+ private void setScId(int line) throws ParseException {
+ if (line < 0)
+ throw new ParseException("The line of the slicing criterion must be strictly greater than zero.");
+ scId = line;
+ }
+
+ public void slice() throws ParseException, IOException {
+ // Configure JavaParser
+ CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
+ combinedTypeSolver.add(new ReflectionTypeSolver(true));
+ JavaParser.getStaticConfiguration().setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver));
+ JavaParser.getStaticConfiguration().setAttributeComments(false);
+
+ // Build the SDG
+ NodeList units = new NodeList<>();
+ try {
+ units.add(JavaParser.parse(scFile));
+ } catch (FileNotFoundException e) {
+ throw new ParseException(e.getMessage());
+ }
+
+ SDG sdg = cliOpts.hasOption("exception-sensitive") ? new ESSDG() : new SDG();
+ sdg.build(units);
+
+ SlicingCriterion sc = new NodeIdSlicingCriterion(0, "");
+ Slice slice = new Slice();
+ if (scId != 0) {
+ // Slice the SDG
+ sc = new NodeIdSlicingCriterion(scId, "");
+ slice = sdg.slice(sc);
+
+ // Convert the slice to code and output the result to `outputDir`
+ for (CompilationUnit cu : slice.toAst()) {
+ if (cu.getStorage().isEmpty())
+ throw new IllegalStateException("A synthetic CompilationUnit was discovered, with no file associated to it.");
+ File javaFile = new File(outputDir, cu.getStorage().get().getFileName());
+ try (PrintWriter pw = new PrintWriter(javaFile)) {
+ pw.print(new BlockComment(getDisclaimer(cu.getStorage().get())));
+ pw.print(cu);
+ } catch (FileNotFoundException e) {
+ System.err.println("Could not write file " + javaFile);
+ }
+ }
+ }
+
+ File imageDir = new File(outputDir, "images");
+ imageDir.mkdir();
+ // Output the sliced graph to the output directory
+ SDGLog sdgLog = new SlicedSDGLog(sdg, slice, sc);
+ sdgLog.setDirectory(outputDir);
+ sdgLog.generateImages("graph", "svg");
+ for (CFG cfg : sdg.getCFGs()) {
+ CFGLog log = new CFGLog(cfg);
+ log.setDirectory(imageDir);
+ log.generateImages("root" + cfg.getRootNode().map(GraphNode::getId).orElse(-1L), "svg");
+ }
+ }
+
+ protected String getDisclaimer(CompilationUnit.Storage s) {
+ return String.format("\n\tThis file was automatically generated as part of a slice with criterion" +
+ "\n\tnode id: %d\n\tOriginal file: %s\n", scId, s.getPath());
+ }
+
+ public static void main(String... args) {
+ try {
+ new PHPSlice(args).slice();
+ } catch (Exception e) {
+ System.err.println("Error!\n" + e.getMessage());
+ e.printStackTrace(System.err);
+ }
+ }
+
+}
diff --git a/sdg-cli/src/main/java/tfm/cli/SlicedSDGLog.java b/sdg-cli/src/main/java/tfm/cli/SlicedSDGLog.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b14693f20e46f588e71d8873d86ca2341d07a94
--- /dev/null
+++ b/sdg-cli/src/main/java/tfm/cli/SlicedSDGLog.java
@@ -0,0 +1,55 @@
+package tfm.cli;
+
+import org.jgrapht.io.Attribute;
+import org.jgrapht.io.DOTExporter;
+import org.jgrapht.io.DefaultAttribute;
+import tfm.arcs.Arc;
+import tfm.graphs.sdg.SDG;
+import tfm.nodes.GraphNode;
+import tfm.slicing.Slice;
+import tfm.slicing.SlicingCriterion;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Utility to export a sliced SDG in dot and show the slices and slicing criterion. */
+public class SlicedSDGLog extends SDGLog {
+ protected final Slice slice;
+ protected final GraphNode> sc;
+
+ public SlicedSDGLog(SDG graph, Slice slice) {
+ this(graph, slice, null);
+ }
+
+ public SlicedSDGLog(SDG graph, Slice slice, SlicingCriterion sc) {
+ super(graph);
+ this.slice = slice;
+ this.sc = sc == null ? null : sc.findNode(graph).orElse(null);
+ }
+
+ @Override
+ protected DOTExporter, Arc> getDOTExporter(SDG graph) {
+ return new DOTExporter<>(
+ n -> String.valueOf(n.getId()),
+ n -> {
+ String s = n.getId() + ": " + n.getInstruction();
+ if (!n.getVariableActions().isEmpty())
+ s += "\n" + n.getVariableActions().stream().map(Object::toString).reduce((a, b) -> a + "," + b).orElse("--");
+ return s;
+ },
+ Arc::getLabel,
+ this::vertexAttributes,
+ Arc::getDotAttributes);
+ }
+
+ protected Map vertexAttributes(GraphNode> node) {
+ Map map = new HashMap<>();
+ if (slice.contains(node) && node.equals(sc))
+ map.put("style", DefaultAttribute.createAttribute("filled,bold"));
+ else if (slice.contains(node))
+ map.put("style", DefaultAttribute.createAttribute("filled"));
+ else if (node.equals(sc))
+ map.put("style", DefaultAttribute.createAttribute("bold"));
+ return map;
+ }
+}
diff --git a/sdg-cli/src/main/java/tfm/cli/Slicer.java b/sdg-cli/src/main/java/tfm/cli/Slicer.java
index f6c46c9a1a30fc42bd801be041cf412d7e6bb071..268c631db789749362d5bc5af00e914857ca7d28 100644
--- a/sdg-cli/src/main/java/tfm/cli/Slicer.java
+++ b/sdg-cli/src/main/java/tfm/cli/Slicer.java
@@ -10,6 +10,7 @@ import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSol
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import org.apache.commons.cli.*;
+import tfm.graphs.exceptionsensitive.ESSDG;
import tfm.graphs.sdg.SDG;
import tfm.slicing.FileLineSlicingCriterion;
import tfm.slicing.Slice;
@@ -82,6 +83,7 @@ public class Slicer {
.desc("The directory where the sliced source code should be placed. By default, it is placed at " +
DEFAULT_OUTPUT_DIR)
.build());
+ OPTIONS.addOption("es", "exception-sensitive", false, "Enable exception-sensitive analysis");
OPTIONS.addOption(Option
.builder("h").longOpt("help")
.desc("Shows this text")
@@ -94,13 +96,14 @@ public class Slicer {
private int scLine;
private final List scVars = new ArrayList<>();
private final List scVarOccurrences = new ArrayList<>();
+ private final CommandLine cliOpts;
public Slicer(String... cliArgs) throws ParseException {
- CommandLine cl = new DefaultParser().parse(OPTIONS, cliArgs);
- if (cl.hasOption('h'))
+ cliOpts = new DefaultParser().parse(OPTIONS, cliArgs);
+ if (cliOpts.hasOption('h'))
throw new ParseException(OPTIONS.toString());
- if (cl.hasOption('c')) {
- Matcher matcher = SC_PATTERN.matcher(cl.getOptionValue("criterion"));
+ if (cliOpts.hasOption('c')) {
+ Matcher matcher = SC_PATTERN.matcher(cliOpts.getOptionValue("criterion"));
if (!matcher.matches())
throw new ParseException("Invalid format for slicing criterion, see --help for more details");
setScFile(matcher.group("file"));
@@ -113,24 +116,24 @@ public class Slicer {
else
setScVars(vars.split(","));
}
- } else if (cl.hasOption('f') && cl.hasOption('l')) {
- setScFile(cl.getOptionValue('f'));
- setScLine((Integer) cl.getParsedOptionValue("l"));
- if (cl.hasOption('v')) {
- if (cl.hasOption('n'))
- setScVars(cl.getOptionValues('v'), cl.getOptionValues('n'));
+ } else if (cliOpts.hasOption('f') && cliOpts.hasOption('l')) {
+ setScFile(cliOpts.getOptionValue('f'));
+ setScLine((Integer) cliOpts.getParsedOptionValue("l"));
+ if (cliOpts.hasOption('v')) {
+ if (cliOpts.hasOption('n'))
+ setScVars(cliOpts.getOptionValues('v'), cliOpts.getOptionValues('n'));
else
- setScVars(cl.getOptionValues('v'));
+ setScVars(cliOpts.getOptionValues('v'));
}
} else {
throw new ParseException("Slicing criterion not specified: either use \"-c\" or \"-f\" and \"l\".");
}
- if (cl.hasOption('o'))
- outputDir = (File) cl.getParsedOptionValue("o");
+ if (cliOpts.hasOption('o'))
+ outputDir = (File) cliOpts.getParsedOptionValue("o");
- if (cl.hasOption('i')) {
- for (String str : cl.getOptionValues('i')) {
+ if (cliOpts.hasOption('i')) {
+ for (String str : cliOpts.getOptionValues('i')) {
File dir = new File(str);
if (!dir.isDirectory())
throw new ParseException("One of the include directories is not a directory or isn't accesible: " + str);
@@ -218,7 +221,8 @@ public class Slicer {
} catch (FileNotFoundException e) {
throw new ParseException(e.getMessage());
}
- SDG sdg = new SDG();
+
+ SDG sdg = cliOpts.hasOption("exception-sensitive") ? new ESSDG() : new SDG();
sdg.build(units);
// Slice the SDG
diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml
index 88d56ba6630f6db693fb2fe869b28477da5ec9bc..56174fbdf865dbcdf7fd9fa4e20e3925a3723855 100644
--- a/sdg-core/pom.xml
+++ b/sdg-core/pom.xml
@@ -6,7 +6,7 @@
tfm
sdg-core
- 1.0-SNAPSHOT
+ 1.1.2
11
diff --git a/sdg-core/src/main/java/tfm/arcs/Arc.java b/sdg-core/src/main/java/tfm/arcs/Arc.java
index e9ecd300639910508c20d6e3cb6ea8d61e87e23b..a21fd933ff81ebc26fb88e6cb0f6df011ecdaacc 100644
--- a/sdg-core/src/main/java/tfm/arcs/Arc.java
+++ b/sdg-core/src/main/java/tfm/arcs/Arc.java
@@ -3,6 +3,7 @@ package tfm.arcs;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.io.Attribute;
import tfm.arcs.cfg.ControlFlowArc;
+import tfm.arcs.pdg.ConditionalControlDependencyArc;
import tfm.arcs.pdg.ControlDependencyArc;
import tfm.arcs.pdg.DataDependencyArc;
import tfm.arcs.sdg.CallArc;
@@ -25,7 +26,7 @@ public abstract class Arc extends DefaultEdge {
this.label = label;
}
- /** @see tfm.arcs.cfg.ControlFlowArc */
+ /** @see ControlFlowArc */
public final boolean isControlFlowArc() {
return this instanceof ControlFlowArc;
}
@@ -36,13 +37,17 @@ public abstract class Arc extends DefaultEdge {
throw new UnsupportedOperationException("Not a ControlFlowArc");
}
- /** @see tfm.arcs.cfg.ControlFlowArc.NonExecutable */
+ /** @see ControlFlowArc.NonExecutable */
public final boolean isExecutableControlFlowArc() {
- return this instanceof ControlFlowArc &&
- !(this instanceof ControlFlowArc.NonExecutable);
+ return isControlFlowArc() && !isNonExecutableControlFlowArc();
}
- /** @see tfm.arcs.pdg.ControlDependencyArc */
+ /** @see ControlFlowArc.NonExecutable */
+ public final boolean isNonExecutableControlFlowArc() {
+ return this instanceof ControlFlowArc.NonExecutable;
+ }
+
+ /** @see ControlDependencyArc */
public final boolean isControlDependencyArc() {
return this instanceof ControlDependencyArc;
}
@@ -53,7 +58,7 @@ public abstract class Arc extends DefaultEdge {
throw new UnsupportedOperationException("Not a ControlDependencyArc");
}
- /** @see tfm.arcs.pdg.DataDependencyArc */
+ /** @see DataDependencyArc */
public final boolean isDataDependencyArc() {
return this instanceof DataDependencyArc;
}
@@ -105,6 +110,21 @@ public abstract class Arc extends DefaultEdge {
throw new UnsupportedOperationException("Not a SummaryArc");
}
+ /** @see ConditionalControlDependencyArc */
+ public final boolean isConditionalControlDependencyArc() {
+ return this instanceof ConditionalControlDependencyArc;
+ }
+
+ public final boolean isUnconditionalControlDependencyArc() {
+ return isControlDependencyArc() && !isConditionalControlDependencyArc();
+ }
+
+ public final ControlDependencyArc asConditionalControlDependencyArc() {
+ if (isConditionalControlDependencyArc())
+ return (ConditionalControlDependencyArc) this;
+ throw new UnsupportedOperationException("Not a ConditionalControlDependencyArc");
+ }
+
@Override
public String toString() {
return String.format("%s{%d -> %d}", getClass().getName(),
diff --git a/sdg-core/src/main/java/tfm/arcs/cfg/ControlFlowArc.java b/sdg-core/src/main/java/tfm/arcs/cfg/ControlFlowArc.java
index 4a5f23f385692648c4fe10fb444c23f9146246d2..ede205df99192a8f5655be67ced26cc5236c2169 100644
--- a/sdg-core/src/main/java/tfm/arcs/cfg/ControlFlowArc.java
+++ b/sdg-core/src/main/java/tfm/arcs/cfg/ControlFlowArc.java
@@ -3,7 +3,6 @@ package tfm.arcs.cfg;
import org.jgrapht.io.Attribute;
import org.jgrapht.io.DefaultAttribute;
import tfm.arcs.Arc;
-import tfm.graphs.augmented.ACFG;
import tfm.graphs.cfg.CFG;
import java.util.Map;
@@ -15,12 +14,14 @@ import java.util.Map;
*/
public class ControlFlowArc extends Arc {
/**
- * Represents a non-executable control flow arc, used within the {@link ACFG ACFG}.
+ * Represents a non-executable control flow arc, used within the {@link tfm.graphs.augmented.ACFG ACFG}.
* Initially it had the following meaning: connecting a statement with
* the following one as if the source was a {@code nop} command (no operation).
*
* It is used to improve control dependence, and it should be skipped when
* computing data dependence and other analyses.
+ * @see tfm.graphs.augmented.ACFG
+ * @see ControlFlowArc
*/
public static final class NonExecutable extends ControlFlowArc {
@Override
diff --git a/sdg-core/src/main/java/tfm/arcs/pdg/ConditionalControlDependencyArc.java b/sdg-core/src/main/java/tfm/arcs/pdg/ConditionalControlDependencyArc.java
new file mode 100644
index 0000000000000000000000000000000000000000..39623a10223241cb083aeb56eefc95de33addab4
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/arcs/pdg/ConditionalControlDependencyArc.java
@@ -0,0 +1,47 @@
+package tfm.arcs.pdg;
+
+import org.jgrapht.io.Attribute;
+import org.jgrapht.io.DefaultAttribute;
+
+import java.util.Map;
+
+/**
+ * An arc that represents conditional control dependency (CCD): a node {@code a}
+ * is conditionally control dependent on a pair of nodes {@code b}, {@code c},
+ * if the presence of {@code a} allows the execution of {@code c} when {@code b}
+ * is executed and the absence of {@code a} prevents it.
+ * The representation is done by connecting {@code a} to {@code c} with a {@link CC2 CC2}
+ * arc, and {@code a} to {@code b} with a {@link CC1 CC1} arc.
+ * @see tfm.graphs.exceptionsensitive.ConditionalControlDependencyBuilder
+ */
+public abstract class ConditionalControlDependencyArc extends ControlDependencyArc {
+ /**
+ * Currently only used in exception handling, connecting a
+ * {@link com.github.javaparser.ast.stmt.CatchClause CatchClause} to statements
+ * that execute after its {@link com.github.javaparser.ast.stmt.TryStmt TryStmt}.
+ * @see ConditionalControlDependencyArc
+ */
+ public static class CC1 extends ConditionalControlDependencyArc {
+ @Override
+ public Map getDotAttributes() {
+ Map map = super.getDotAttributes();
+ map.put("color", DefaultAttribute.createAttribute("orange"));
+ return map;
+ }
+ }
+
+ /**
+ * Currently only used in exception handling, connecting a
+ * {@link com.github.javaparser.ast.stmt.CatchClause CatchClause} to statements
+ * that may throw exceptions inside its corresponding {@link com.github.javaparser.ast.stmt.TryStmt TryStmt}.
+ * @see ConditionalControlDependencyArc
+ */
+ public static class CC2 extends ConditionalControlDependencyArc {
+ @Override
+ public Map getDotAttributes() {
+ Map map = super.getDotAttributes();
+ map.put("color", DefaultAttribute.createAttribute("green"));
+ return map;
+ }
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/arcs/pdg/DataDependencyArc.java b/sdg-core/src/main/java/tfm/arcs/pdg/DataDependencyArc.java
index a0c7725cf42669b15fe24240ab4f6f24a82772fb..27dc5296b0cc4e85a69d82182f146b9663e98dfa 100644
--- a/sdg-core/src/main/java/tfm/arcs/pdg/DataDependencyArc.java
+++ b/sdg-core/src/main/java/tfm/arcs/pdg/DataDependencyArc.java
@@ -5,8 +5,10 @@ import org.jgrapht.io.DefaultAttribute;
import tfm.arcs.Arc;
import tfm.graphs.pdg.PDG;
import tfm.graphs.sdg.SDG;
+import tfm.nodes.VariableAction;
import java.util.Map;
+import java.util.Objects;
/**
* An arc used in the {@link PDG} and {@link SDG},
@@ -16,20 +18,46 @@ import java.util.Map;
* path between the nodes where the variable is not redefined.
*/
public class DataDependencyArc extends Arc {
+ protected final VariableAction source;
+ protected final VariableAction target;
- public DataDependencyArc(String variable) {
- super(variable);
+ public DataDependencyArc(VariableAction.Definition source, VariableAction.Usage target) {
+ super(source.getVariable());
+ this.source = source;
+ this.target = target;
}
- public DataDependencyArc() {
- super();
+ public DataDependencyArc(VariableAction.Declaration source, VariableAction.Definition target) {
+ super(source.getVariable());
+ this.source = source;
+ this.target = target;
+ }
+
+ public VariableAction getSource() {
+ return source;
+ }
+
+ public VariableAction getTarget() {
+ return target;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o)
+ && o instanceof DataDependencyArc
+ && Objects.equals(source, ((DataDependencyArc) o).source)
+ && Objects.equals(target, ((DataDependencyArc) o).target);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), source, target);
}
@Override
public Map getDotAttributes() {
Map map = super.getDotAttributes();
- map.put("style", DefaultAttribute.createAttribute("dashed"));
- map.put("color", DefaultAttribute.createAttribute("red"));
+ map.put("color", DefaultAttribute.createAttribute(target.isDefinition() ? "pink" : "red"));
return map;
}
}
diff --git a/sdg-core/src/main/java/tfm/arcs/sdg/InterproceduralArc.java b/sdg-core/src/main/java/tfm/arcs/sdg/InterproceduralArc.java
index 13c74714bd81bc7f0fc03551eb5210e71bc0f7fc..21a0139c173b77a27b24e981541f5fac6434bf08 100644
--- a/sdg-core/src/main/java/tfm/arcs/sdg/InterproceduralArc.java
+++ b/sdg-core/src/main/java/tfm/arcs/sdg/InterproceduralArc.java
@@ -1,12 +1,29 @@
package tfm.arcs.sdg;
+import org.jgrapht.io.Attribute;
+import org.jgrapht.io.DefaultAttribute;
import tfm.arcs.Arc;
+import java.util.Map;
+
public abstract class InterproceduralArc extends Arc {
protected InterproceduralArc() {
super();
}
+ @Override
+ public Map getDotAttributes() {
+ var map = super.getDotAttributes();
+ map.put("penwidth", DefaultAttribute.createAttribute("3"));
+ if (isInterproceduralInputArc())
+ map.put("color", DefaultAttribute.createAttribute("orange"));
+ else if (isInterproceduralOutputArc())
+ map.put("color", DefaultAttribute.createAttribute("blue"));
+ else
+ map.put("color", DefaultAttribute.createAttribute("green"));
+ return map;
+ }
+
@Override
public abstract boolean isInterproceduralInputArc();
diff --git a/sdg-core/src/main/java/tfm/arcs/sdg/ParameterInOutArc.java b/sdg-core/src/main/java/tfm/arcs/sdg/ParameterInOutArc.java
index 4f377d7537ca22d66f7030f6d42efebfe90ed860..fa78470a4c28bae3f5feeb2d4c0554aa16c341f1 100644
--- a/sdg-core/src/main/java/tfm/arcs/sdg/ParameterInOutArc.java
+++ b/sdg-core/src/main/java/tfm/arcs/sdg/ParameterInOutArc.java
@@ -23,7 +23,9 @@ public class ParameterInOutArc extends InterproceduralArc {
@Override
public boolean isInterproceduralOutputArc() {
- return ((GraphNode>) getSource()).getNodeType() == NodeType.FORMAL_OUT &&
- ((GraphNode>) getTarget()).getNodeType() == NodeType.ACTUAL_OUT;
+ return (((GraphNode>) getSource()).getNodeType() == NodeType.FORMAL_OUT &&
+ ((GraphNode>) getTarget()).getNodeType() == NodeType.ACTUAL_OUT) ||
+ (((GraphNode>) getSource()).getNodeType() == NodeType.METHOD_OUTPUT &&
+ ((GraphNode>) getTarget()).getNodeType() == NodeType.METHOD_CALL_RETURN);
}
}
diff --git a/sdg-core/src/main/java/tfm/arcs/sdg/ReturnArc.java b/sdg-core/src/main/java/tfm/arcs/sdg/ReturnArc.java
new file mode 100644
index 0000000000000000000000000000000000000000..040c1033cbda2fef195ad59cd739c294ce6dbb94
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/arcs/sdg/ReturnArc.java
@@ -0,0 +1,13 @@
+package tfm.arcs.sdg;
+
+public class ReturnArc extends InterproceduralArc {
+ @Override
+ public boolean isInterproceduralInputArc() {
+ return false;
+ }
+
+ @Override
+ public boolean isInterproceduralOutputArc() {
+ return true;
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/Graph.java b/sdg-core/src/main/java/tfm/graphs/Graph.java
index bb0fab6aa0e13f29f882138d5900e16f3e66ee4b..5c7c3ac3f209aadbff1aa8ceb71adc6291289dff 100644
--- a/sdg-core/src/main/java/tfm/graphs/Graph.java
+++ b/sdg-core/src/main/java/tfm/graphs/Graph.java
@@ -1,21 +1,19 @@
package tfm.graphs;
import com.github.javaparser.ast.Node;
-import org.jetbrains.annotations.NotNull;
import org.jgrapht.graph.DirectedPseudograph;
import org.jgrapht.io.DOTExporter;
import tfm.arcs.Arc;
import tfm.nodes.GraphNode;
import tfm.nodes.NodeFactory;
+import tfm.nodes.SyntheticNode;
import tfm.utils.ASTUtils;
-import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
-/**
- *
- * */
public abstract class Graph extends DirectedPseudograph, Arc> {
protected Graph() {
@@ -27,15 +25,15 @@ public abstract class Graph extends DirectedPseudograph, Arc> {
*
* @param node the node to add to the graph
*/
- public void addNode(@NotNull GraphNode node) {
+ public void addNode(GraphNode node) {
this.addVertex(node);
}
- public GraphNode addNode(@NotNull String instruction, @NotNull ASTNode node) {
+ public GraphNode addNode(String instruction, ASTNode node) {
return this.addNode(instruction, node, GraphNode.DEFAULT_FACTORY);
}
- public GraphNode addNode(@NotNull String instruction, @NotNull ASTNode node, @NotNull NodeFactory nodeFactory) {
+ public GraphNode addNode(String instruction, ASTNode node, NodeFactory nodeFactory) {
GraphNode newNode = nodeFactory.graphNode(instruction, node);
this.addNode(newNode);
@@ -45,16 +43,29 @@ public abstract class Graph extends DirectedPseudograph, Arc> {
@SuppressWarnings("unchecked")
public Optional> findNodeByASTNode(ASTNode astNode) {
- return vertexSet().stream()
- .filter(node -> ASTUtils.equalsWithRangeInCU(node.getAstNode(), astNode))
- .findFirst()
- .map(node -> (GraphNode) node);
+ Set> set = findAllNodes(n -> ASTUtils.equalsWithRangeInCU(n.getAstNode(), astNode));
+ if (set.isEmpty())
+ return Optional.empty();
+ if (set.size() == 1)
+ return Optional.of((GraphNode) set.iterator().next());
+ set.removeIf(SyntheticNode.class::isInstance);
+ if (set.isEmpty())
+ return Optional.empty();
+ if (set.size() == 1)
+ return Optional.of((GraphNode) set.iterator().next());
+ throw new IllegalStateException("There may only be one real node representing each AST node in the graph!");
}
public Optional> findNodeById(long id) {
- return vertexSet().stream()
- .filter(node -> Objects.equals(node.getId(), id))
- .findFirst();
+ return findNodeBy(n -> n.getId() == id);
+ }
+
+ public Optional> findNodeBy(Predicate> p) {
+ return vertexSet().stream().filter(p).findFirst();
+ }
+
+ public Set> findAllNodes(Predicate> p) {
+ return vertexSet().stream().filter(p).collect(Collectors.toSet());
}
@Override
@@ -71,7 +82,7 @@ public abstract class Graph extends DirectedPseudograph, Arc> {
public DOTExporter, Arc> getDOTExporter() {
return new DOTExporter<>(
graphNode -> String.valueOf(graphNode.getId()),
- GraphNode::getInstruction,
+ n -> n.getId() + ": " + n.getInstruction(),
Arc::getLabel,
null,
Arc::getDotAttributes);
diff --git a/sdg-core/src/main/java/tfm/graphs/GraphNodeContentVisitor.java b/sdg-core/src/main/java/tfm/graphs/GraphNodeContentVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..94d2136ed87381b5e8d918799a47f6cb9a6e08e2
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/GraphNodeContentVisitor.java
@@ -0,0 +1,147 @@
+package tfm.graphs;
+
+import com.github.javaparser.ast.body.*;
+import com.github.javaparser.ast.stmt.*;
+import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import tfm.nodes.GraphNode;
+
+/** A generic visitor that can be use as a basis to traverse the nodes inside
+ * a given {@link GraphNode}. */
+public class GraphNodeContentVisitor extends VoidVisitorAdapter {
+ protected GraphNode> graphNode = null;
+
+ public final void startVisit(GraphNode> graphNode, A arg) {
+ this.graphNode = graphNode;
+ graphNode.getAstNode().accept(this, arg);
+ this.graphNode = null;
+ }
+
+ public void startVisit(GraphNode> graphNode) {
+ startVisit(graphNode, null);
+ }
+
+ @Override
+ public void visit(AssertStmt n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(BlockStmt n, A arg) {}
+
+ @Override
+ public void visit(BreakStmt n, A arg) {
+ n.getLabel().ifPresent(l -> l.accept(this, arg));
+ }
+
+ @Override
+ public void visit(CatchClause n, A arg) {
+ n.getParameter().accept(this, arg);
+ }
+
+ @Override
+ public void visit(ConstructorDeclaration n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(ContinueStmt n, A arg) {
+ n.getLabel().ifPresent(l -> l.accept(this, arg));
+ }
+
+ @Override
+ public void visit(DoStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ }
+
+ // TODO: this should not be part of any node, but in practice there are still
+ // synthetic nodes that rely on it instead of extending SyntheticNode.
+ @Override
+ public void visit(EmptyStmt n, A arg) {}
+
+ @Override
+ public void visit(EnumConstantDeclaration n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(EnumDeclaration n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(ExplicitConstructorInvocationStmt n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(ExpressionStmt n, A arg) {
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(FieldDeclaration n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(ForEachStmt n, A arg) {
+ n.getIterable().accept(this, arg);
+ n.getVariable().accept(this, arg);
+ }
+
+ @Override
+ public void visit(ForStmt n, A arg) {
+ n.getCompare().ifPresent(c -> c.accept(this, arg));
+ }
+
+ @Override
+ public void visit(IfStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ }
+
+ @Override
+ public void visit(LabeledStmt n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(MethodDeclaration n, A arg) {}
+
+ @Override
+ public void visit(ReturnStmt n, A arg) {
+ n.getExpression().ifPresent(e -> e.accept(this, arg));
+ }
+
+ @Override
+ public void visit(SwitchEntryStmt n, A arg) {
+ n.getLabel().ifPresent(l -> l.accept(this, arg));
+ }
+
+ @Override
+ public void visit(SwitchStmt n, A arg) {
+ n.getSelector().accept(this, arg);
+ }
+
+ @Override
+ public void visit(SynchronizedStmt n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(ThrowStmt n, A arg) {
+ n.getExpression().accept(this, arg);
+ }
+
+ @Override
+ public void visit(TryStmt n, A arg) {}
+
+ @Override
+ public void visit(LocalClassDeclarationStmt n, A arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(WhileStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/GraphWithRootNode.java b/sdg-core/src/main/java/tfm/graphs/GraphWithRootNode.java
index d7a3442dd9d4ebbc23e0a787c63ef8d2af4a60b3..0bf2932a94fa7497a13d148650012d259e2090e3 100644
--- a/sdg-core/src/main/java/tfm/graphs/GraphWithRootNode.java
+++ b/sdg-core/src/main/java/tfm/graphs/GraphWithRootNode.java
@@ -2,7 +2,6 @@ package tfm.graphs;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.MethodDeclaration;
-import org.jetbrains.annotations.NotNull;
import tfm.nodes.GraphNode;
import tfm.nodes.NodeFactory;
@@ -10,7 +9,7 @@ import java.util.Objects;
import java.util.Optional;
public abstract class GraphWithRootNode extends Graph implements Buildable {
-
+ protected boolean built = false;
protected GraphNode rootNode;
protected GraphWithRootNode() {
@@ -19,22 +18,16 @@ public abstract class GraphWithRootNode extends Graph
/**
* Builds the root node with the given instruction and AST node.
- * If the root node already exists, just returns false
- *
+ * If the root node already exists, nothing happens.
* @param instruction the instruction string
* @param rootNodeAst the AST node
- * @return true if the root node is created, false otherwise
*/
- public boolean buildRootNode(@NotNull String instruction, @NotNull ASTRootNode rootNodeAst, @NotNull NodeFactory nodeFactory) {
- if (rootNode != null) {
- return false;
- }
-
+ public void buildRootNode(String instruction, ASTRootNode rootNodeAst, NodeFactory nodeFactory) {
+ if (rootNode != null)
+ return;
GraphNode root = nodeFactory.graphNode(instruction, rootNodeAst);
this.rootNode = root;
this.addVertex(root);
-
- return true;
}
public Optional> getRootNode() {
@@ -57,4 +50,9 @@ public abstract class GraphWithRootNode extends Graph
return super.removeVertex(graphNode);
}
+
+ @Override
+ public boolean isBuilt() {
+ return built;
+ }
}
diff --git a/sdg-core/src/main/java/tfm/graphs/augmented/ACFG.java b/sdg-core/src/main/java/tfm/graphs/augmented/ACFG.java
index b05e44e043bee72cbad1a46c8b7170fc6aea065e..315de53f635a8018b4b91ff87c3011a1442ad1f8 100644
--- a/sdg-core/src/main/java/tfm/graphs/augmented/ACFG.java
+++ b/sdg-core/src/main/java/tfm/graphs/augmented/ACFG.java
@@ -1,10 +1,22 @@
package tfm.graphs.augmented;
+import tfm.arcs.Arc;
import tfm.arcs.cfg.ControlFlowArc;
import tfm.graphs.cfg.CFG;
import tfm.graphs.cfg.CFGBuilder;
import tfm.nodes.GraphNode;
+/**
+ * An augmented version of the {@link CFG}. Its corresponding builder is the
+ * {@link ACFGBuilder}, and the main difference is the ability to properly handle
+ * unconditional jumps ({@link com.github.javaparser.ast.stmt.SwitchStmt switch},
+ * {@link com.github.javaparser.ast.stmt.BreakStmt break}, {@link com.github.javaparser.ast.stmt.ContinueStmt continue},
+ * etc.) by using {@link tfm.arcs.cfg.ControlFlowArc.NonExecutable non-executable
+ * control flow arcs}. Any dependence graph built on top of this one should use the
+ * {@link PPDG} as its program dependence graph; otherwise more instructions will
+ * be included than necessary.
+ * @see tfm.arcs.cfg.ControlFlowArc.NonExecutable
+ */
public class ACFG extends CFG {
public void addNonExecutableControlFlowEdge(GraphNode> from, GraphNode> to) {
addControlFlowEdge(from, to, new ControlFlowArc.NonExecutable());
@@ -14,4 +26,12 @@ public class ACFG extends CFG {
protected CFGBuilder newCFGBuilder() {
return new ACFGBuilder(this);
}
+
+ /**
+ * Discerns whether a node contained in this graph is a pseudo-predicate or not.
+ * Pseudo-predicates have one (and only one) outgoing non-executable control flow arc.
+ */
+ public boolean isPseudoPredicate(GraphNode> node) {
+ return outgoingEdgesOf(node).stream().filter(Arc::isNonExecutableControlFlowArc).count() == 1;
+ }
}
diff --git a/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java b/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java
index 37bb23f6016a0eee41e287be7e57246fe042b00f..2bf6d32c203a7239ecd60ead587e4804bc790c00 100644
--- a/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java
+++ b/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java
@@ -5,6 +5,7 @@ import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.visitor.VoidVisitor;
import tfm.graphs.cfg.CFGBuilder;
@@ -34,7 +35,7 @@ import java.util.List;
*/
public class ACFGBuilder extends CFGBuilder {
/** Same as {@link ACFGBuilder#hangingNodes}, but to be connected as non-executable edges. */
- private final List> nonExecHangingNodes = new LinkedList<>();
+ protected final List> nonExecHangingNodes = new LinkedList<>();
protected ACFGBuilder(ACFG graph) {
super(graph);
@@ -57,6 +58,7 @@ public class ACFGBuilder extends CFGBuilder {
public void visit(IfStmt ifStmt, Void arg) {
// *if* -> {then else} -> after
GraphNode> cond = connectTo(ifStmt, String.format("if (%s)", ifStmt.getCondition()));
+ ifStmt.getCondition().accept(this, arg);
// if -> {*then* else} -> after
ifStmt.getThenStmt().accept(this, arg);
@@ -80,6 +82,7 @@ public class ACFGBuilder extends CFGBuilder {
@Override
public void visit(WhileStmt whileStmt, Void arg) {
GraphNode> cond = connectTo(whileStmt, String.format("while (%s)", whileStmt.getCondition()));
+ whileStmt.getCondition().accept(this, arg);
breakStack.push(new LinkedList<>());
continueStack.push(new LinkedList<>());
@@ -101,6 +104,7 @@ public class ACFGBuilder extends CFGBuilder {
continueStack.push(new LinkedList<>());
GraphNode> cond = connectTo(doStmt, String.format("while (%s)", doStmt.getCondition()));
+ doStmt.getCondition().accept(this, arg);
doStmt.getBody().accept(this, arg);
@@ -120,15 +124,22 @@ public class ACFGBuilder extends CFGBuilder {
continueStack.push(new LinkedList<>());
// Initialization
- forStmt.getInitialization().forEach(this::connectTo);
+ forStmt.getInitialization().forEach(n -> {
+ connectTo(n);
+ n.accept(this, arg);
+ });
// Condition
Expression condition = forStmt.getCompare().orElse(new BooleanLiteralExpr(true));
GraphNode> cond = connectTo(forStmt, String.format("for (;%s;)", condition));
+ condition.accept(this, arg);
// Body and update expressions
forStmt.getBody().accept(this, arg);
- forStmt.getUpdate().forEach(this::connectTo);
+ forStmt.getUpdate().forEach(n -> {
+ connectTo(n);
+ n.accept(this, arg);
+ });
// Condition if body contained anything
hangingNodes.addAll(continueStack.pop());
@@ -147,6 +158,7 @@ public class ACFGBuilder extends CFGBuilder {
GraphNode> cond = connectTo(forEachStmt,
String.format("for (%s : %s)", forEachStmt.getVariable(), forEachStmt.getIterable()));
+ forEachStmt.getIterable().accept(this, arg);
forEachStmt.getBody().accept(this, arg);
@@ -165,6 +177,7 @@ public class ACFGBuilder extends CFGBuilder {
switchEntriesStack.push(new LinkedList<>());
breakStack.push(new LinkedList<>());
GraphNode> cond = connectTo(switchStmt, String.format("switch (%s)", switchStmt.getSelector()));
+ switchStmt.getSelector().accept(this, arg);
// expr --> each case (fallthrough by default, so case --> case too)
for (SwitchEntryStmt entry : switchStmt.getEntries()) {
entry.accept(this, arg); // expr && prev case --> case --> next case
@@ -228,6 +241,8 @@ public class ACFGBuilder extends CFGBuilder {
@Override
public void visit(ReturnStmt returnStmt, Void arg) {
GraphNode node = connectTo(returnStmt);
+ node.addDefinedVariable(new NameExpr(VARIABLE_NAME_OUTPUT));
+ returnStmt.getExpression().ifPresent(n -> n.accept(this, arg));
returnList.add(node);
clearHanging();
nonExecHangingNodes.add(node);
@@ -240,15 +255,16 @@ public class ACFGBuilder extends CFGBuilder {
if (!methodDeclaration.getBody().isPresent())
throw new IllegalStateException("The method must have a body!");
- graph.buildRootNode("ENTER " + methodDeclaration.getNameAsString(), methodDeclaration, TypeNodeFactory.fromType(NodeType.METHOD_ENTER));
+ graph.buildRootNode("ENTER " + methodDeclaration.getDeclarationAsString(false, false, false),
+ methodDeclaration, TypeNodeFactory.fromType(NodeType.METHOD_ENTER));
hangingNodes.add(graph.getRootNode().get());
for (Parameter param : methodDeclaration.getParameters())
- connectTo(addFormalInGraphNode(param));
+ connectTo(addFormalInGraphNode(methodDeclaration, param));
methodDeclaration.getBody().get().accept(this, arg);
returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add);
for (Parameter param : methodDeclaration.getParameters())
- connectTo(addFormalOutGraphNode(param));
+ connectTo(addFormalOutGraphNode(methodDeclaration, param));
nonExecHangingNodes.add(graph.getRootNode().get());
connectTo(graph.addNode("Exit", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_EXIT)));
}
diff --git a/sdg-core/src/main/java/tfm/graphs/augmented/ControlDependencyBuilder.java b/sdg-core/src/main/java/tfm/graphs/augmented/ControlDependencyBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d5fa9d4ded37b02cb68b0ee955bb86e302a7406
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/augmented/ControlDependencyBuilder.java
@@ -0,0 +1,32 @@
+package tfm.graphs.augmented;
+
+import tfm.arcs.Arc;
+import tfm.nodes.GraphNode;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class ControlDependencyBuilder extends tfm.graphs.pdg.ControlDependencyBuilder {
+ public ControlDependencyBuilder(ACFG cfg, PPDG pdg) {
+ super(cfg, pdg);
+ }
+
+ protected boolean postdominates(GraphNode> a, GraphNode> b, Set> visited) {
+ // Stop w/ success if a == b or a has already been visited
+ if (a.equals(b) || visited.contains(a))
+ return true;
+ Set outgoing = cfg.outgoingEdgesOf(a);
+ // Limit the traversal if it is a PPDG
+ outgoing = outgoing.stream().filter(Arc::isExecutableControlFlowArc).collect(Collectors.toSet());
+ // Stop w/ failure if there are no edges to traverse from a
+ if (outgoing.isEmpty())
+ return false;
+ // Find all possible paths starting from a, if ALL find b, then true, else false
+ visited.add(a);
+ for (Arc out : outgoing) {
+ if (!postdominates(cfg.getEdgeTarget(out), b, visited))
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/augmented/PPDG.java b/sdg-core/src/main/java/tfm/graphs/augmented/PPDG.java
index 6dd57cfd565ccf66745eb2e0049e05351d0ba08e..1e1988927e1a9be5cf75ffe7569491ae4a709f75 100644
--- a/sdg-core/src/main/java/tfm/graphs/augmented/PPDG.java
+++ b/sdg-core/src/main/java/tfm/graphs/augmented/PPDG.java
@@ -1,12 +1,6 @@
package tfm.graphs.augmented;
-import tfm.nodes.GraphNode;
-import tfm.slicing.PseudoPredicateSlicingAlgorithm;
-import tfm.slicing.Slice;
-import tfm.slicing.SlicingCriterion;
-import tfm.utils.NodeNotFoundException;
-
-import java.util.Optional;
+import tfm.graphs.pdg.PDG;
public class PPDG extends APDG {
public PPDG() {
@@ -18,10 +12,18 @@ public class PPDG extends APDG {
}
@Override
- public Slice slice(SlicingCriterion slicingCriterion) {
- Optional> node = slicingCriterion.findNode(this);
- if (node.isEmpty())
- throw new NodeNotFoundException(slicingCriterion);
- return new PseudoPredicateSlicingAlgorithm(this).traverse(node.get());
+ protected PDG.Builder createBuilder() {
+ return new Builder();
+ }
+
+ public class Builder extends APDG.Builder {
+ protected Builder() {
+ super();
+ }
+
+ @Override
+ protected void buildControlDependency() {
+ new ControlDependencyBuilder((ACFG) cfg, PPDG.this).build();
+ }
}
}
diff --git a/sdg-core/src/main/java/tfm/graphs/cfg/CFG.java b/sdg-core/src/main/java/tfm/graphs/cfg/CFG.java
index ddf50c34817674f88a1976793ca66e09a2266852..52d6b4fde0c8a08845fcd7550f851ef4607fa2c1 100644
--- a/sdg-core/src/main/java/tfm/graphs/cfg/CFG.java
+++ b/sdg-core/src/main/java/tfm/graphs/cfg/CFG.java
@@ -5,12 +5,17 @@ import tfm.arcs.Arc;
import tfm.arcs.cfg.ControlFlowArc;
import tfm.graphs.GraphWithRootNode;
import tfm.nodes.GraphNode;
+import tfm.nodes.VariableAction;
import tfm.nodes.type.NodeType;
import tfm.utils.NodeNotFoundException;
import java.util.HashSet;
-import java.util.Objects;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* The Control Flow Graph represents the statements of a method in
@@ -20,8 +25,6 @@ import java.util.Set;
* @see ControlFlowArc
*/
public class CFG extends GraphWithRootNode {
- private boolean built = false;
-
public CFG() {
super();
}
@@ -34,33 +37,50 @@ public class CFG extends GraphWithRootNode {
super.addEdge(from, to, arc);
}
- public Set> findLastDefinitionsFrom(GraphNode> startNode, String variable) {
+ public List findLastDefinitionsFrom(GraphNode> startNode, VariableAction.Usage variable) {
if (!this.containsVertex(startNode))
throw new NodeNotFoundException(startNode, this);
- return findLastDefinitionsFrom(new HashSet<>(), startNode.getId(), startNode, variable);
+ return findLastVarActionsFrom(startNode, variable, VariableAction::isDefinition);
}
- private Set> findLastDefinitionsFrom(Set visited, long startNode, GraphNode> currentNode, String variable) {
- visited.add(currentNode.getId());
-
- Set> res = new HashSet<>();
-
- for (Arc arc : incomingEdgesOf(currentNode)) {
- if (!arc.isExecutableControlFlowArc())
- continue;
- GraphNode> from = getEdgeSource(arc);
+ public List findLastDeclarationsFrom(GraphNode> startNode, VariableAction.Definition variable) {
+ if (!this.containsVertex(startNode))
+ throw new NodeNotFoundException(startNode, this);
+ return findLastVarActionsFrom(startNode, variable, VariableAction::isDeclaration);
+ }
- if (!Objects.equals(startNode, from.getId()) && visited.contains(from.getId())) {
- continue;
- }
+ protected List findLastVarActionsFrom(GraphNode> startNode, VariableAction variable, Predicate actionFilter) {
+ return findLastVarActionsFrom(new HashSet<>(), new LinkedList<>(), startNode, startNode, variable, actionFilter);
+ }
- if (from.getDefinedVariables().contains(variable)) {
- res.add(from);
- } else {
- res.addAll(findLastDefinitionsFrom(visited, startNode, from, variable));
+ @SuppressWarnings("unchecked")
+ protected List findLastVarActionsFrom(Set> visited, List result,
+ GraphNode> start, GraphNode> currentNode, VariableAction var,
+ Predicate filter) {
+ // Base case
+ if (visited.contains(currentNode))
+ return result;
+ visited.add(currentNode);
+
+ Stream stream = currentNode.getVariableActions().stream();
+ if (start.equals(currentNode))
+ stream = stream.takeWhile(Predicate.not(var::equals));
+ List list = stream.filter(var::matches).filter(filter).collect(Collectors.toList());
+ if (!list.isEmpty()) {
+ for (int i = list.size() - 1; i >= 0; i--) {
+ result.add((E) list.get(i));
+ if (!list.get(i).isOptional())
+ break;
}
+ if (!list.get(0).isOptional())
+ return result;
}
- return res;
+
+ // Not found: traverse backwards!
+ for (Arc arc : incomingEdgesOf(currentNode))
+ if (arc.isExecutableControlFlowArc())
+ findLastVarActionsFrom(visited, result, start, getEdgeSource(arc), var, filter);
+ return result;
}
@Override
@@ -78,11 +98,6 @@ public class CFG extends GraphWithRootNode {
if (vertexSet().stream().noneMatch(n -> n.getNodeType() == NodeType.METHOD_EXIT))
throw new IllegalStateException("There is no exit node after building the graph");
built = true;
- }/**/
-
- @Override
- public boolean isBuilt() {
- return built;
}
protected CFGBuilder newCFGBuilder() {
diff --git a/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java b/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java
index 6fbfa9f44675ebbf0424924341af066095c10cc0..b7113f7528c0b2fc06a76fe8b2ed800e188ed6d2 100644
--- a/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java
+++ b/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java
@@ -3,11 +3,15 @@ package tfm.graphs.cfg;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
-import com.github.javaparser.ast.body.VariableDeclarator;
-import com.github.javaparser.ast.expr.*;
+import com.github.javaparser.ast.expr.BooleanLiteralExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.SimpleName;
import com.github.javaparser.ast.stmt.*;
+import com.github.javaparser.ast.type.VoidType;
import com.github.javaparser.ast.visitor.VoidVisitor;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import tfm.nodes.FormalIONode;
import tfm.nodes.GraphNode;
import tfm.nodes.TypeNodeFactory;
import tfm.nodes.type.NodeType;
@@ -15,9 +19,6 @@ import tfm.utils.ASTUtils;
import java.util.*;
-import static tfm.nodes.type.NodeType.FORMAL_IN;
-import static tfm.nodes.type.NodeType.FORMAL_OUT;
-
/**
* Populates a {@link CFG}, given one and an AST root node.
* For now it only accepts {@link MethodDeclaration} as roots, as it disallows
@@ -35,6 +36,8 @@ import static tfm.nodes.type.NodeType.FORMAL_OUT;
*
*/
public class CFGBuilder extends VoidVisitorAdapter {
+ public static final String VARIABLE_NAME_OUTPUT = "-output-";
+
/**
* Stores the CFG representing the method analyzed.
*/
@@ -97,12 +100,14 @@ public class CFGBuilder extends VoidVisitorAdapter {
@Override
public void visit(ExpressionStmt expressionStmt, Void arg) {
connectTo(expressionStmt);
+ expressionStmt.getExpression().accept(this, arg);
}
@Override
public void visit(IfStmt ifStmt, Void arg) {
// *if* -> {then else} -> after
GraphNode> cond = connectTo(ifStmt, String.format("if (%s)", ifStmt.getCondition()));
+ ifStmt.getCondition().accept(this, arg);
// if -> {*then* else} -> after
ifStmt.getThenStmt().accept(this, arg);
@@ -147,6 +152,7 @@ public class CFGBuilder extends VoidVisitorAdapter {
@Override
public void visit(WhileStmt whileStmt, Void arg) {
GraphNode> cond = connectTo(whileStmt, String.format("while (%s)", whileStmt.getCondition()));
+ whileStmt.getCondition().accept(this, arg);
breakStack.push(new LinkedList<>());
continueStack.push(new LinkedList<>());
@@ -166,6 +172,7 @@ public class CFGBuilder extends VoidVisitorAdapter {
continueStack.push(new LinkedList<>());
GraphNode> cond = connectTo(doStmt, String.format("while (%s)", doStmt.getCondition()));
+ doStmt.getCondition().accept(this, arg);
doStmt.getBody().accept(this, arg);
@@ -183,15 +190,22 @@ public class CFGBuilder extends VoidVisitorAdapter {
continueStack.push(new LinkedList<>());
// Initialization
- forStmt.getInitialization().forEach(this::connectTo);
+ forStmt.getInitialization().forEach(n -> {
+ connectTo(n);
+ n.accept(this, arg);
+ });
// Condition
Expression condition = forStmt.getCompare().orElse(new BooleanLiteralExpr(true));
GraphNode> cond = connectTo(forStmt, String.format("for (;%s;)", condition));
+ condition.accept(this, arg);
// Body and update expressions
forStmt.getBody().accept(this, arg);
- forStmt.getUpdate().forEach(this::connectTo);
+ forStmt.getUpdate().forEach(n -> {
+ connectTo(n);
+ n.accept(this, arg);
+ });
// Condition if body contained anything
hangingNodes.addAll(continueStack.pop());
@@ -208,6 +222,7 @@ public class CFGBuilder extends VoidVisitorAdapter {
GraphNode> cond = connectTo(forEachStmt,
String.format("for (%s : %s)", forEachStmt.getVariable(), forEachStmt.getIterable()));
+ forEachStmt.getIterable().accept(this, arg);
forEachStmt.getBody().accept(this, arg);
@@ -224,12 +239,8 @@ public class CFGBuilder extends VoidVisitorAdapter {
@Override
public void visit(SwitchEntryStmt entryStmt, Void arg) {
// Case header (prev -> case EXPR)
- GraphNode node;
- if (entryStmt.getLabel().isPresent()) {
- node = connectTo(entryStmt, "case " + entryStmt.getLabel().get());
- } else {
- node = connectTo(entryStmt, "default");
- }
+ GraphNode node = connectTo(entryStmt, entryStmt.getLabel().isPresent() ?
+ "case " + entryStmt.getLabel().get() : "default");
switchEntriesStack.peek().add(node);
// Case body (case EXPR --> body)
entryStmt.getStatements().accept(this, arg);
@@ -242,6 +253,7 @@ public class CFGBuilder extends VoidVisitorAdapter {
switchEntriesStack.push(new LinkedList<>());
breakStack.push(new LinkedList<>());
GraphNode> cond = connectTo(switchStmt, String.format("switch (%s)", switchStmt.getSelector()));
+ switchStmt.getSelector().accept(this, arg);
// expr --> each case (fallthrough by default, so case --> case too)
for (SwitchEntryStmt entry : switchStmt.getEntries()) {
entry.accept(this, arg); // expr && prev case --> case --> next case
@@ -282,6 +294,10 @@ public class CFGBuilder extends VoidVisitorAdapter {
@Override
public void visit(ReturnStmt returnStmt, Void arg) {
GraphNode node = connectTo(returnStmt);
+ returnStmt.getExpression().ifPresent(n -> {
+ n.accept(this, arg);
+ node.addDefinedVariable(new NameExpr(VARIABLE_NAME_OUTPUT));
+ });
returnList.add(node);
clearHanging();
}
@@ -291,49 +307,53 @@ public class CFGBuilder extends VoidVisitorAdapter {
// Sanity checks
if (graph.getRootNode().isPresent())
throw new IllegalStateException("CFG is only allowed for one method, not multiple!");
- if (!methodDeclaration.getBody().isPresent())
+ if (methodDeclaration.getBody().isEmpty())
throw new IllegalStateException("The method must have a body! Abstract methods have no CFG");
// Create the root node
graph.buildRootNode(
- "ENTER " + methodDeclaration.getNameAsString(),
+ "ENTER " + methodDeclaration.getDeclarationAsString(false, false, false),
methodDeclaration,
TypeNodeFactory.fromType(NodeType.METHOD_ENTER));
hangingNodes.add(graph.getRootNode().get());
// Create and connect formal-in nodes sequentially
for (Parameter param : methodDeclaration.getParameters())
- connectTo(addFormalInGraphNode(param));
+ connectTo(addFormalInGraphNode(methodDeclaration, param));
// Visit the body of the method
methodDeclaration.getBody().get().accept(this, arg);
// Append all return statements (without repetition)
returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add);
- // Create and connect formal-out nodes sequentially
- for (Parameter param : methodDeclaration.getParameters()) {
- // Do not generate out for primitives
- if (param.getType().isPrimitiveType()) {
- continue;
- }
+ createAndConnectFormalOutNodes(methodDeclaration);
- connectTo(addFormalOutGraphNode(param));
- }
// Create and connect the exit node
connectTo(graph.addNode("Exit", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_EXIT)));
}
- protected GraphNode addFormalInGraphNode(Parameter param) {
- ExpressionStmt exprStmt = new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(
- param.getType(),
- param.getNameAsString(),
- new NameExpr(param.getNameAsString() + "_in"))));
- return graph.addNode(exprStmt.toString(), exprStmt, TypeNodeFactory.fromType(FORMAL_IN));
+ protected void createAndConnectFormalOutNodes(MethodDeclaration methodDeclaration) {
+ createAndConnectFormalOutNodes(methodDeclaration, true);
+ }
+
+ /** If the method declaration has a return type, create an "OUTPUT" node and connect it. */
+ protected void createAndConnectFormalOutNodes(MethodDeclaration methodDeclaration, boolean createOutput) {
+ for (Parameter param : methodDeclaration.getParameters())
+ connectTo(addFormalOutGraphNode(methodDeclaration, param));
+ if (createOutput && !methodDeclaration.getType().equals(new VoidType())) {
+ GraphNode> outputNode = graph.addNode("output", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_OUTPUT));
+ outputNode.addUsedVariable(new NameExpr(VARIABLE_NAME_OUTPUT));
+ connectTo(outputNode);
+ }
+ }
+
+ protected FormalIONode addFormalInGraphNode(MethodDeclaration methodDeclaration, Parameter param) {
+ FormalIONode node = FormalIONode.createFormalIn(methodDeclaration, param);
+ graph.addNode(node);
+ return node;
}
- protected GraphNode addFormalOutGraphNode(Parameter param) {
- ExpressionStmt exprStmt = new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(
- param.getType(),
- param.getNameAsString() + "_out",
- new NameExpr(param.getNameAsString()))));
- return graph.addNode(exprStmt.toString(), exprStmt, TypeNodeFactory.fromType(FORMAL_OUT));
+ protected FormalIONode addFormalOutGraphNode(MethodDeclaration methodDeclaration, Parameter param) {
+ FormalIONode node = FormalIONode.createFormalOut(methodDeclaration, param);
+ graph.addNode(node);
+ return node;
}
}
diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ConditionalControlDependencyBuilder.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ConditionalControlDependencyBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..94112d46754468dc7a21460d5e237c6f93ae4a97
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ConditionalControlDependencyBuilder.java
@@ -0,0 +1,155 @@
+package tfm.graphs.exceptionsensitive;
+
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.stmt.CatchClause;
+import tfm.arcs.Arc;
+import tfm.nodes.ExceptionReturnNode;
+import tfm.nodes.GraphNode;
+import tfm.nodes.NormalReturnNode;
+import tfm.nodes.ReturnNode;
+import tfm.utils.ASTUtils;
+import tfm.utils.Utils;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Adds {@link tfm.arcs.pdg.ConditionalControlDependencyArc CCD arcs} to a
+ * {@link tfm.graphs.exceptionsensitive.ESPDG ES-PDG}, according to the algorithm
+ * outlined in Algorithm I of [SAS2020]. All auxiliary functions are implemented
+ * here ({@link #getBlockInstructs(CatchClause) getBlockInstructs/1}, {@link
+ * #getTryBlockInstructs(CatchClause) getTryBlockInstructs/1}, {@link
+ * #isExceptionSource(GraphNode) isExceptionSource/1}), except {@link
+ * ESSDG#isPseudoPredicate(GraphNode) isPseudoPredicate/1}.
+ *
+ *
+ * [SAS2020]: dinsa://Areas/Program Slicing/Trabajos/Slicing Exceptions/Papers/SAS 2020
+ * @see tfm.arcs.pdg.ConditionalControlDependencyArc
+ */
+public class ConditionalControlDependencyBuilder {
+ protected ESCFG cfg;
+ protected ESPDG pdg;
+
+ public ConditionalControlDependencyBuilder(ESCFG cfg, ESPDG pdg) {
+ this.cfg = Objects.requireNonNull(cfg);
+ this.pdg = Objects.requireNonNull(pdg);
+ }
+
+ /**
+ * Adds the {@link tfm.arcs.pdg.ControlDependencyArc CCD arcs}. This method should only be called
+ * once per {@link ESPDG}, as multiple executions may create duplicate arcs.
+ */
+ @SuppressWarnings("unchecked")
+ public void build() {
+ for (GraphNode> node : pdg.vertexSet()) {
+ if (node.getAstNode() instanceof CatchClause) {
+ buildCC1((GraphNode) node);
+ buildCC2((GraphNode) node);
+ }
+ }
+ }
+
+ /** Create the {@link tfm.arcs.pdg.ConditionalControlDependencyArc.CC1 CC1} arcs associated to a given {@link
+ * CatchClause}. This process removes some {@link tfm.arcs.pdg.ControlDependencyArc CD arcs} in the process. */
+ protected void buildCC1(GraphNode cc) {
+ Set blockInstructs = getBlockInstructs(cc.getAstNode());
+ Set cdArcs = new HashSet<>();
+ for (Arc arc : pdg.outgoingEdgesOf(cc))
+ if (arc.isControlDependencyArc() && !blockInstructs.contains(pdg.getEdgeTarget(arc).getAstNode()))
+ cdArcs.add(arc);
+ for (Arc arc : cdArcs) {
+ pdg.addCC1Arc(pdg.getEdgeSource(arc), pdg.getEdgeTarget(arc));
+ pdg.removeEdge(arc);
+ }
+ }
+
+ /** Create the {@link tfm.arcs.pdg.ConditionalControlDependencyArc.CC2 CC2}
+ * arcs associated to a given {@link CatchClause}. */
+ protected void buildCC2(GraphNode cc) {
+ Set tryBlockInstructs = getTryBlockInstructs(cc.getAstNode());
+ for (Node node : tryBlockInstructs)
+ for (GraphNode> dst : pdg.findAllNodes(n -> ASTUtils.equalsWithRangeInCU(n.getAstNode(), node)))
+ if (isExceptionSource(dst) && hasControlDependencePath(dst, cc, tryBlockInstructs))
+ pdg.addCC2Arc(cc, dst);
+ }
+
+ /** Obtains the set of AST nodes found within a given {@link CatchClause}. */
+ protected static Set getBlockInstructs(CatchClause cc) {
+ return childNodesOf(cc);
+ }
+
+ /** Obtains the set of AST nodes found within the {@link com.github.javaparser.ast.stmt.TryStmt try}
+ * associated with the given {@link CatchClause}. */
+ protected static Set getTryBlockInstructs(CatchClause cc) {
+ Optional parent = cc.getParentNode();
+ assert parent.isPresent();
+ return childNodesOf(parent.get());
+ }
+
+ /** Checks whether the argument is or contains an exception source. */
+ protected static boolean isExceptionSource(GraphNode> node) {
+ if (node instanceof ReturnNode) {
+ if (node instanceof ExceptionReturnNode)
+ return true;
+ if (node instanceof NormalReturnNode)
+ return false;
+ }
+ return !new ExceptionSourceSearcher().search(node.getAstNode()).isEmpty();
+ }
+
+ /**
+ * Checks whether there is a control dependence path from {@code a} to {@code b},
+ * where all nodes in the path are in the given {@code universe}. The path is found
+ * following the rules of the PPDG traversal.
+ * @see tfm.slicing.PseudoPredicateSlicingAlgorithm
+ */
+ protected boolean hasControlDependencePath(GraphNode> a, GraphNode> b, Set universe) {
+ Set> visited = new HashSet<>();
+ Set> pending = new HashSet<>();
+ pending.add(b);
+ boolean first = true;
+
+ // First step: consider any path
+ // Rest of steps: do not keep traversing backwards from a pseudo-predicate
+ while (!pending.isEmpty()) {
+ GraphNode> node = Utils.setPop(pending);
+ if (node.equals(a))
+ return true;
+ // PPDG rule: Skip if already visited or if it is a pseudo-predicate; do not check first time (criterion)
+ if (!first && (cfg.isPseudoPredicate(node) || visited.contains(node)))
+ continue;
+ pdg.incomingEdgesOf(node).stream()
+ .filter(Arc::isControlDependencyArc)
+ .map(pdg::getEdgeSource)
+ .filter(gn -> universe.contains(gn.getAstNode()))
+ .forEach(pending::add);
+ visited.add(node);
+ first = false;
+ }
+ return false;
+ }
+
+ /** Internal method to find all possible AST nodes that descend from the given argument. */
+ protected static Set childNodesOf(Node parent) {
+ Set result = new HashSet<>();
+ Set pending = new HashSet<>();
+ pending.add(parent);
+
+ while (!pending.isEmpty()) {
+ Set newPending = new HashSet<>();
+ for (Node n : pending) {
+ newPending.addAll(n.getChildNodes());
+ result.add(n);
+ }
+ pending.clear();
+ pending.addAll(newPending);
+ }
+
+ // Some elements are never going to match nodes: remove Expression except MethodCallExpr
+ result.removeIf(n -> n instanceof Expression && !((Expression) n).isMethodCallExpr());
+ return result;
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ab945e87f300d10efad08866fd4ba3de642e308
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java
@@ -0,0 +1,369 @@
+package tfm.graphs.exceptionsensitive;
+
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.stmt.*;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
+import com.github.javaparser.resolution.types.ResolvedReferenceType;
+import com.github.javaparser.resolution.types.ResolvedType;
+import tfm.arcs.cfg.ControlFlowArc;
+import tfm.graphs.augmented.ACFG;
+import tfm.graphs.augmented.ACFGBuilder;
+import tfm.graphs.augmented.ControlDependencyBuilder;
+import tfm.graphs.cfg.CFGBuilder;
+import tfm.nodes.*;
+import tfm.nodes.type.NodeType;
+import tfm.utils.Logger;
+
+import java.util.*;
+
+public class ESCFG extends ACFG {
+ protected final static String ACTIVE_EXCEPTION_VARIABLE = "-activeException-";
+
+ @Override
+ protected CFGBuilder newCFGBuilder() {
+ return new Builder();
+ }
+
+ protected ExceptionExitNode addExceptionExitNode(MethodDeclaration method, ResolvedType type) {
+ ExceptionExitNode node = new ExceptionExitNode(method, type);
+ addNode(node);
+ return node;
+ }
+
+ protected ExceptionReturnNode addExceptionReturnNode(MethodCallExpr call, ResolvedType type) {
+ ExceptionReturnNode node = new ExceptionReturnNode(call, type);
+ addNode(node);
+ return node;
+ }
+
+ /**
+ * An instruction which may the source of an exception.
+ * The exception's types are cached in this object.
+ */
+ protected static class ExceptionSource {
+ private final GraphNode> source;
+ private final Map exceptions = new HashMap<>();
+
+ protected ExceptionSource(GraphNode> source) {
+ this.source = Objects.requireNonNull(source);
+ }
+
+ public ExceptionSource(GraphNode> source, ResolvedType... exceptionTypes) {
+ this(source);
+ if (exceptionTypes.length == 0)
+ throw new IllegalArgumentException("There must be at least one exception type");
+ for (ResolvedType t : exceptionTypes)
+ exceptions.put(t, true);
+ }
+
+ public void deactivateTypes(ResolvedReferenceType type) {
+ exceptions.keySet().stream().filter(type::isAssignableBy).forEach(t -> exceptions.put(t, false));
+ }
+
+ public boolean isActive() {
+ return exceptions.containsValue(true);
+ }
+
+ /** Creates a single ExceptionSource from a list of sources.
+ * Each type is marked active if it was active in any of the sources. */
+ public static ExceptionSource merge(GraphNode> summary, Collection sources) {
+ ExceptionSource result = new ExceptionSource(summary);
+ for (ExceptionSource es : sources)
+ for (ResolvedType rt : es.exceptions.keySet())
+ result.exceptions.merge(rt, es.exceptions.get(rt), Boolean::logicalOr);
+ return result;
+ }
+ }
+
+ public class Builder extends ACFGBuilder {
+ /** Map of the currently relevant exception sources, mapped by type. */
+ protected Map> exceptionSourceMap = new HashMap<>();
+ /** Stack the 'try's that surround the element we're visiting now. */
+ protected Deque tryStack = new LinkedList<>();
+ /** Stack of statements that surround the element we're visiting now. */
+ protected Deque stmtStack = new LinkedList<>();
+ /** Stack of hanging nodes that need to be connected by
+ * non-executable edges at the end of the current try statement. */
+ protected Deque>> tryNonExecHangingStack = new LinkedList<>();
+ /** Nodes that need to be connected by non-executable edges to the 'Exit' node. */
+ protected List> exitNonExecHangingNodes = new LinkedList<>();
+ /** Map of return nodes from each method call, mapped by the normal return node of said call. */
+ protected Map> pendingNormalReturnNodes = new HashMap<>();
+
+ protected Builder() {
+ super(ESCFG.this);
+ }
+
+ @Override
+ public void visit(MethodDeclaration methodDeclaration, Void arg) {
+ if (getRootNode().isPresent())
+ throw new IllegalStateException("CFG is only allowed for one method, not multiple!");
+ if (methodDeclaration.getBody().isEmpty())
+ throw new IllegalStateException("The method must have a body!");
+
+ buildRootNode("ENTER " + methodDeclaration.getDeclarationAsString(false, false, false),
+ methodDeclaration, TypeNodeFactory.fromType(NodeType.METHOD_ENTER));
+
+ hangingNodes.add(getRootNode().get());
+ for (Parameter param : methodDeclaration.getParameters())
+ connectTo(addFormalInGraphNode(methodDeclaration, param));
+ methodDeclaration.getBody().get().accept(this, arg);
+ returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add);
+ if (exceptionSourceMap.isEmpty()) {
+ createAndConnectFormalOutNodes(methodDeclaration);
+ } else {
+ // Normal exit
+ NormalExitNode normalExit = new NormalExitNode(methodDeclaration);
+ addNode(normalExit);
+ connectTo(normalExit);
+ createAndConnectFormalOutNodes(methodDeclaration);
+ List> lastNodes = new LinkedList<>(hangingNodes);
+ clearHanging();
+ // Exception exit
+ Collection exceptionExits = processExceptionSources(methodDeclaration);
+ for (ExceptionExitNode node : exceptionExits) {
+ node.addUsedVariable(new NameExpr(ACTIVE_EXCEPTION_VARIABLE));
+ hangingNodes.add(node);
+ createAndConnectFormalOutNodes(methodDeclaration, false);
+ lastNodes.addAll(hangingNodes);
+ clearHanging();
+ }
+ hangingNodes.addAll(lastNodes);
+ }
+ ExitNode exit = new ExitNode(methodDeclaration);
+ addNode(exit);
+ nonExecHangingNodes.addAll(exitNonExecHangingNodes);
+ nonExecHangingNodes.add(getRootNode().get());
+ connectTo(exit);
+
+ processPendingNormalResultNodes();
+ }
+
+ protected Collection processExceptionSources(MethodDeclaration method) {
+ if (!tryStack.isEmpty())
+ throw new IllegalStateException("Can't process exception sources inside a Try statement.");
+ Map exceptionExitMap = new HashMap<>();
+ for (ResolvedType type : exceptionSourceMap.keySet()) {
+ // 1. Create "T exit" if it does not exist
+ if (!exceptionExitMap.containsKey(type))
+ exceptionExitMap.put(type, addExceptionExitNode(method, type));
+ ExceptionExitNode ee = exceptionExitMap.get(type);
+ for (ExceptionSource es : exceptionSourceMap.get(type))
+ // 2. Connect exception source to "T exit"
+ addEdge(es.source, ee, es.isActive() ? new ControlFlowArc() : new ControlFlowArc.NonExecutable());
+ }
+ return exceptionExitMap.values();
+ }
+
+ /**
+ * Creates the non-executable edges from "normal return" nodes to their target.
+ * (the first node shared by every path from all normal and exceptional return nodes of
+ * this method call -- which is equivalent to say the first node that post-dominates the call
+ * or that post-dominates all return nodes).
+ */
+ protected void processPendingNormalResultNodes() {
+ for (Map.Entry> entry : pendingNormalReturnNodes.entrySet())
+ createNonExecArcFor(entry.getKey(), entry.getValue());
+ }
+
+ protected void createNonExecArcFor(NormalReturnNode node, Set returnNodes) {
+ ControlDependencyBuilder cdBuilder = new ControlDependencyBuilder(ESCFG.this, null);
+ vertexSet().stream()
+ .sorted(Comparator.comparingLong(GraphNode::getId))
+ .filter(candidate -> {
+ for (ReturnNode retNode : returnNodes)
+ if (!cdBuilder.postdominates(retNode, candidate))
+ return false;
+ return true;
+ })
+ .findFirst()
+ .ifPresentOrElse(
+ n -> addNonExecutableControlFlowEdge(node, n),
+ () -> {throw new IllegalStateException("A common post-dominator cannot be found for a normal exit!");});
+ }
+
+ @Override
+ public void visit(TryStmt n, Void arg) {
+ if (n.getFinallyBlock().isPresent())
+ Logger.log("ES-CFG Builder", "try statement with unsupported finally block");
+ stmtStack.push(n);
+ tryStack.push(n);
+ tryNonExecHangingStack.push(new HashSet<>());
+ if (n.getResources().isNonEmpty())
+ throw new IllegalStateException("try-with-resources is not supported");
+ if (n.getFinallyBlock().isPresent())
+ throw new IllegalStateException("try-finally is not supported");
+ GraphNode node = connectTo(n, "try");
+ n.getTryBlock().accept(this, arg);
+ List> hanging = new LinkedList<>(hangingNodes);
+ List> nonExecHanging = new LinkedList<>(nonExecHangingNodes);
+ clearHanging();
+ for (CatchClause cc : n.getCatchClauses()) {
+ cc.accept(this, arg);
+ hanging.addAll(hangingNodes);
+ nonExecHanging.addAll(nonExecHangingNodes);
+ clearHanging();
+ }
+ hangingNodes.addAll(hanging);
+ nonExecHangingNodes.addAll(nonExecHanging);
+ nonExecHangingNodes.add(node);
+ nonExecHangingNodes.addAll(tryNonExecHangingStack.pop());
+ tryStack.pop();
+ stmtStack.pop();
+ }
+
+ // =====================================================
+ // ================= Exception sources =================
+ // =====================================================
+
+ protected void populateExceptionSourceMap(ExceptionSource source) {
+ for (ResolvedType type : source.exceptions.keySet()) {
+ exceptionSourceMap.computeIfAbsent(type, (t) -> new LinkedList<>());
+ exceptionSourceMap.get(type).add(source);
+ }
+ }
+
+ @Override
+ public void visit(ThrowStmt n, Void arg) {
+ stmtStack.push(n);
+ GraphNode stmt = connectTo(n);
+ n.getExpression().accept(this, arg);
+ stmt.addDefinedVariable(new NameExpr(ACTIVE_EXCEPTION_VARIABLE));
+ populateExceptionSourceMap(new ExceptionSource(stmt, n.getExpression().calculateResolvedType()));
+ clearHanging();
+ nonExecHangingNodes.add(stmt);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(MethodCallExpr n, Void arg) {
+ ResolvedMethodDeclaration resolved = n.resolve();
+ if (resolved.getNumberOfSpecifiedExceptions() == 0)
+ return;
+
+ Set returnNodes = new HashSet<>();
+
+ // Normal return
+ NormalReturnNode normalReturn = new NormalReturnNode(n);
+ addNode(normalReturn);
+ GraphNode> stmtNode = findNodeByASTNode(stmtStack.peek()).orElseThrow();
+ assert hangingNodes.size() == 1 && hangingNodes.get(0) == stmtNode;
+ assert nonExecHangingNodes.size() == 0;
+ returnNodes.add(normalReturn);
+ connectTo(normalReturn);
+ clearHanging();
+
+ // Exception return
+ for (ResolvedType type : resolved.getSpecifiedExceptions()) {
+ hangingNodes.add(stmtNode);
+ ExceptionReturnNode exceptionReturn = addExceptionReturnNode(n, type);
+ exceptionReturn.addDefinedVariable(new NameExpr(ACTIVE_EXCEPTION_VARIABLE));
+ populateExceptionSourceMap(new ExceptionSource(exceptionReturn, type));
+ returnNodes.add(exceptionReturn);
+ connectTo(exceptionReturn);
+ if (tryNonExecHangingStack.isEmpty())
+ exitNonExecHangingNodes.add(exceptionReturn);
+ else
+ tryNonExecHangingStack.peek().add(exceptionReturn);
+ clearHanging();
+ }
+
+ // Register set of return nodes
+ pendingNormalReturnNodes.put(normalReturn, returnNodes);
+
+ // Ready for next instruction
+ hangingNodes.add(normalReturn);
+ }
+
+ @Override
+ public void visit(CatchClause n, Void arg) {
+ // 1. Connect all available exception sources here
+ Set sources = new HashSet<>();
+ for (List list : exceptionSourceMap.values())
+ sources.addAll(list);
+ for (ExceptionSource src : sources)
+ (src.isActive() ? hangingNodes : nonExecHangingNodes).add(src.source);
+ GraphNode> node = connectTo(n, "catch (" + n.getParameter().toString() + ")");
+ node.addUsedVariable(new NameExpr(ACTIVE_EXCEPTION_VARIABLE));
+ exceptionSourceMap.clear();
+ // 2. Set up as exception source
+ ExceptionSource catchES = ExceptionSource.merge(node, sources);
+ Type type = n.getParameter().getType();
+ if (type.isUnionType())
+ type.asUnionType().getElements().forEach(t -> catchES.deactivateTypes(t.resolve().asReferenceType()));
+ else if (type.isReferenceType())
+ catchES.deactivateTypes(type.resolve().asReferenceType());
+ else
+ throw new IllegalStateException("catch node with type different to union/reference type");
+ populateExceptionSourceMap(catchES);
+
+ // 3. Connect and visit body
+ n.getBody().accept(this, arg);
+ }
+
+ // =====================================================================================
+ // ================= Statements that may directly contain method calls =================
+ // =====================================================================================
+ // Note: expressions are visited as part of super.visit(Node, Void), see ACFG and CFG.
+
+ @Override
+ public void visit(IfStmt ifStmt, Void arg) {
+ stmtStack.push(ifStmt);
+ super.visit(ifStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(WhileStmt whileStmt, Void arg) {
+ stmtStack.push(whileStmt);
+ super.visit(whileStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(DoStmt doStmt, Void arg) {
+ stmtStack.push(doStmt);
+ super.visit(doStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(ForStmt forStmt, Void arg) {
+ stmtStack.push(forStmt);
+ super.visit(forStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(ForEachStmt forEachStmt, Void arg) {
+ stmtStack.push(forEachStmt);
+ super.visit(forEachStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(SwitchStmt switchStmt, Void arg) {
+ stmtStack.push(switchStmt);
+ super.visit(switchStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(ReturnStmt returnStmt, Void arg) {
+ stmtStack.push(returnStmt);
+ super.visit(returnStmt, arg);
+ stmtStack.pop();
+ }
+
+ @Override
+ public void visit(ExpressionStmt expressionStmt, Void arg) {
+ stmtStack.push(expressionStmt);
+ super.visit(expressionStmt, arg);
+ stmtStack.pop();
+ }
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESPDG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESPDG.java
new file mode 100644
index 0000000000000000000000000000000000000000..626c938a47958cef43da71d76545650d09f0af02
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESPDG.java
@@ -0,0 +1,41 @@
+package tfm.graphs.exceptionsensitive;
+
+import tfm.arcs.pdg.ConditionalControlDependencyArc;
+import tfm.graphs.augmented.PPDG;
+import tfm.graphs.pdg.PDG;
+import tfm.nodes.GraphNode;
+
+public class ESPDG extends PPDG {
+ public ESPDG() {
+ this(new ESCFG());
+ }
+
+ public ESPDG(ESCFG escfg) {
+ super(escfg);
+ }
+
+ public void addCC1Arc(GraphNode> src, GraphNode> dst) {
+ addEdge(src, dst, new ConditionalControlDependencyArc.CC1());
+ }
+
+ public void addCC2Arc(GraphNode> src, GraphNode> dst) {
+ addEdge(src, dst, new ConditionalControlDependencyArc.CC2());
+ }
+
+ @Override
+ protected PDG.Builder createBuilder() {
+ return new Builder();
+ }
+
+ public class Builder extends PPDG.Builder {
+ protected Builder() {
+ super();
+ }
+
+ @Override
+ protected void buildControlDependency() {
+ super.buildControlDependency();
+ new ConditionalControlDependencyBuilder((ESCFG) cfg, ESPDG.this).build();
+ }
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a4f5ece394e58ffca21b01f55666e6f7744069d
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java
@@ -0,0 +1,74 @@
+package tfm.graphs.exceptionsensitive;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.NodeList;
+import tfm.arcs.sdg.ReturnArc;
+import tfm.graphs.augmented.ACFG;
+import tfm.graphs.cfg.CFG;
+import tfm.graphs.pdg.PDG;
+import tfm.graphs.sdg.SDG;
+import tfm.graphs.sdg.SDGBuilder;
+import tfm.graphs.sdg.sumarcs.NaiveSummaryArcsBuilder;
+import tfm.nodes.ExitNode;
+import tfm.nodes.GraphNode;
+import tfm.nodes.ReturnNode;
+import tfm.nodes.SyntheticNode;
+import tfm.nodes.type.NodeType;
+import tfm.slicing.ExceptionSensitiveSlicingAlgorithm;
+import tfm.slicing.Slice;
+import tfm.slicing.SlicingCriterion;
+import tfm.utils.Context;
+
+import java.util.Optional;
+import java.util.Set;
+
+public class ESSDG extends SDG {
+ protected static final Set NOT_PP_TYPES = Set.of(NodeType.METHOD_CALL, NodeType.METHOD_OUTPUT, NodeType.METHOD_CALL_RETURN);
+
+ @Override
+ protected SDGBuilder createBuilder() {
+ return new Builder();
+ }
+
+ @Override
+ public Slice slice(SlicingCriterion slicingCriterion) {
+ Optional> optSlicingNode = slicingCriterion.findNode(this);
+ if (optSlicingNode.isEmpty())
+ throw new IllegalArgumentException("Could not locate the slicing criterion in the SDG");
+ return new ExceptionSensitiveSlicingAlgorithm(ESSDG.this).traverse(optSlicingNode.get());
+ }
+
+ @Override
+ public void build(NodeList nodeList) {
+ nodeList.accept(createBuilder(), new Context());
+ Set> vertices = Set.copyOf(vertexSet());
+ vertices.forEach(n -> new ExceptionSensitiveMethodCallReplacerVisitor(this).startVisit(n));
+ new NaiveSummaryArcsBuilder(this).visit();
+ compilationUnits = nodeList;
+ built = true;
+ }
+
+ public boolean isPseudoPredicate(GraphNode> node) {
+ if (NOT_PP_TYPES.contains(node.getNodeType()) || node instanceof SyntheticNode)
+ return false;
+ for (CFG cfg : cfgs)
+ if (cfg.containsVertex(node))
+ return ((ACFG) cfg).isPseudoPredicate(node);
+ throw new IllegalArgumentException("Node " + node.getId() + "'s associated CFG cannot be found!");
+ }
+
+ public void addReturnArc(ExitNode source, ReturnNode target) {
+ addEdge(source, target, new ReturnArc());
+ }
+
+ class Builder extends SDGBuilder {
+ public Builder() {
+ super(ESSDG.this);
+ }
+
+ @Override
+ protected PDG createPDG() {
+ return new ESPDG();
+ }
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff808804a6899c80f6e7fc870b28f27f7f5e73aa
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java
@@ -0,0 +1,128 @@
+package tfm.graphs.exceptionsensitive;
+
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.type.ReferenceType;
+import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
+import com.github.javaparser.resolution.types.ResolvedReferenceType;
+import com.github.javaparser.resolution.types.ResolvedType;
+import tfm.graphs.sdg.MethodCallReplacerVisitor;
+import tfm.nodes.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallReplacerVisitor {
+ public ExceptionSensitiveMethodCallReplacerVisitor(ESSDG sdg) {
+ super(sdg);
+ }
+
+ @Override
+ public void visit(MethodCallExpr methodCallExpr, Void arg) {
+ if (methodCallExpr.resolve().getNumberOfSpecifiedExceptions() > 0)
+ handleExceptionReturnArcs(methodCallExpr);
+ super.visit(methodCallExpr, arg);
+ }
+
+ /** Creates the following connections:
+ *
+ * - {@link ExceptionExitNode} to {@link ExceptionReturnNode} with a ratio of (* to 1), n per method
+ * - {@link NormalExitNode} to {@link NormalReturnNode} with a ratio of (1 to 1), 1 per method
+ *
+ * @param call The method call to be connected to its method declaration.
+ */
+ protected void handleExceptionReturnArcs(MethodCallExpr call) {
+ Set> synthNodes = sdg.vertexSet().stream()
+ .filter(SyntheticNode.class::isInstance)
+ .map(n -> (SyntheticNode>) n)
+ .collect(Collectors.toSet());
+ ResolvedMethodDeclaration resolvedDecl = call.resolve();
+ MethodDeclaration decl = resolvedDecl.toAst().orElseThrow();
+
+ connectNormalNodes(synthNodes, call, decl);
+ connectExceptionNodes(synthNodes, call, decl);
+ }
+
+ /** Connects normal exit nodes to their corresponding return node. */
+ protected void connectNormalNodes(Set> synthNodes, MethodCallExpr call, MethodDeclaration decl) {
+ ReturnNode normalReturn = (ReturnNode) synthNodes.stream()
+ .filter(NormalReturnNode.class::isInstance)
+ .filter(n -> n.getAstNode() == call)
+ .findAny().orElseThrow();
+ ExitNode normalExit = (ExitNode) synthNodes.stream()
+ .filter(NormalExitNode.class::isInstance)
+ .filter(n -> n.getAstNode() == decl)
+ .findAny().orElseThrow();
+ ((ESSDG) sdg).addReturnArc(normalExit, normalReturn);
+ }
+
+ /**
+ * Connects exception exit nodes to their corresponding return node,
+ * taking into account that return nodes are generated from the 'throws' list
+ * in the method declaration and exit nodes are generated from the exception sources
+ * that appear in the method. This creates a mismatch that is solved in {@link #connectRemainingExceptionNodes(Map, Set)}
+ */
+ protected void connectExceptionNodes(Set> synthNodes, MethodCallExpr call, MethodDeclaration decl) {
+ Map exceptionReturnMap = new HashMap<>();
+ Set eeNodes = synthNodes.stream()
+ .filter(ExceptionExitNode.class::isInstance)
+ .map(ExceptionExitNode.class::cast)
+ .filter(n -> n.getAstNode() == decl)
+ .collect(Collectors.toSet());
+ for (ReferenceType rType : decl.getThrownExceptions()) {
+ ResolvedType type = rType.resolve();
+ ExceptionReturnNode exceptionReturn = synthNodes.stream()
+ .filter(ExceptionReturnNode.class::isInstance)
+ .map(ExceptionReturnNode.class::cast)
+ .filter(n -> n.getAstNode() == call)
+ .filter(n -> n.getExceptionType().equals(type))
+ .findAny().orElseThrow();
+ ExceptionExitNode exceptionExit = eeNodes.stream()
+ .filter(n -> n.getExceptionType().equals(type))
+ .findAny().orElseThrow();
+ eeNodes.remove(exceptionExit);
+ exceptionReturnMap.put(type, exceptionReturn);
+ ((ESSDG) sdg).addReturnArc(exceptionExit, exceptionReturn);
+ }
+
+ connectRemainingExceptionNodes(exceptionReturnMap, eeNodes);
+ }
+
+ /** Connects the remaining exception exit nodes to their closest exception return node. */
+ protected void connectRemainingExceptionNodes(Map exceptionReturnMap, Set eeNodes) {
+ boolean hasThrowable = resolvedTypeSetContains(exceptionReturnMap.keySet(), "java.lang.Throwable");
+ boolean hasException = resolvedTypeSetContains(exceptionReturnMap.keySet(), "java.lang.Exception");
+
+ eeFor: for (ExceptionExitNode ee : eeNodes) {
+ List typeList = List.of(ee.getExceptionType().asReferenceType());
+ while (!typeList.isEmpty()) {
+ List newTypeList = new LinkedList<>();
+ for (ResolvedReferenceType type : typeList) {
+ if (exceptionReturnMap.containsKey(type)) {
+ ((ESSDG) sdg).addReturnArc(ee, exceptionReturnMap.get(type));
+ continue eeFor;
+ }
+ // Skip RuntimeException, unless Throwable or Exception are present as ER nodes
+ if (type.getQualifiedName().equals("java.lang.RuntimeException") && !hasThrowable && !hasException)
+ continue;
+ // Skip Error, unless Throwable is present as EE node
+ if (type.getQualifiedName().equals("java.lang.Error") && !hasThrowable)
+ continue;
+ // Object has no ancestors, the startVisit has ended
+ if (!type.getQualifiedName().equals("java.lang.Object"))
+ newTypeList.addAll(type.asReferenceType().getDirectAncestors());
+ }
+ typeList = newTypeList;
+ }
+ }
+ }
+
+ /** Utility method. Finds whether or not a type can be found in a set of resolved types.
+ * The full qualified name should be used (e.g. 'java.lang.Object' for Object). */
+ protected static boolean resolvedTypeSetContains(Set set, String qualifiedType) {
+ return set.stream()
+ .map(ResolvedType::asReferenceType)
+ .map(ResolvedReferenceType::getQualifiedName)
+ .anyMatch(qualifiedType::equals);
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..a61c877ab2bd0e351f7fa6d513bd1f91199cd6ab
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java
@@ -0,0 +1,45 @@
+package tfm.graphs.exceptionsensitive;
+
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.stmt.ThrowStmt;
+import com.github.javaparser.resolution.types.ResolvedType;
+import tfm.graphs.GraphNodeContentVisitor;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class ExceptionSourceSearcher extends GraphNodeContentVisitor {
+ public Collection search(Node node) {
+ try {
+ node.accept(this, null);
+ return Collections.emptySet();
+ } catch (FoundException e) {
+ return e.types;
+ }
+ }
+
+ @Override
+ public void visit(ThrowStmt n, Void arg) {
+ throw new FoundException(n.getExpression().calculateResolvedType());
+ }
+
+ @Override
+ public void visit(MethodCallExpr n, Void arg) {
+ if (n.resolve().getNumberOfSpecifiedExceptions() > 0)
+ throw new FoundException(n.resolve().getSpecifiedExceptions());
+ else super.visit(n, arg);
+ }
+
+ static class FoundException extends RuntimeException {
+ protected final Collection types;
+
+ public FoundException(ResolvedType type) {
+ this(Collections.singleton(type));
+ }
+
+ public FoundException(Collection types) {
+ this.types = types;
+ }
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/graphs/pdg/ControlDependencyBuilder.java b/sdg-core/src/main/java/tfm/graphs/pdg/ControlDependencyBuilder.java
index fd4cd4ceede4c3bed52ed32d4b7aedf78e2dcfd0..29aaa16fbb66a7a0acf53c4cb148e9d584d82473 100644
--- a/sdg-core/src/main/java/tfm/graphs/pdg/ControlDependencyBuilder.java
+++ b/sdg-core/src/main/java/tfm/graphs/pdg/ControlDependencyBuilder.java
@@ -1,14 +1,13 @@
package tfm.graphs.pdg;
import tfm.arcs.Arc;
-import tfm.graphs.augmented.PPDG;
+import tfm.arcs.cfg.ControlFlowArc;
import tfm.graphs.cfg.CFG;
import tfm.nodes.GraphNode;
import tfm.nodes.type.NodeType;
import java.util.HashSet;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* A simple but slow finder of control dependencies.
@@ -23,62 +22,52 @@ import java.util.stream.Collectors;
* from a to the "Exit" node.
*
* There exist better, cheaper approaches that have linear complexity w.r.t. the number of edges in the CFG.
- * Usage: pass an empty {@link PDG} and a filled {@link CFG} and then run {@link #analyze()}.
+ * Usage: pass an empty {@link PDG} and a filled {@link CFG} and then run {@link #build()}.
* This builder should only be used once, and then discarded.
*/
-class ControlDependencyBuilder {
- private final PDG pdg;
- private final CFG cfg;
+public class ControlDependencyBuilder {
+ protected final CFG cfg;
+ protected final PDG pdg;
- public ControlDependencyBuilder(PDG pdg, CFG cfg) {
- this.pdg = pdg;
+ public ControlDependencyBuilder(CFG cfg, PDG pdg) {
this.cfg = cfg;
+ this.pdg = pdg;
}
- public void analyze() {
- assert cfg.getRootNode().isPresent();
- assert pdg.getRootNode().isPresent();
-
- Set> roots = new HashSet<>(cfg.vertexSet());
- roots.remove(cfg.getRootNode().get());
-
- Set> cfgNodes = new HashSet<>(cfg.vertexSet());
- cfgNodes.removeIf(node -> node.getNodeType() == NodeType.METHOD_EXIT);
+ public void build() {
+ assert cfg.isBuilt();
+ GraphNode> enterNode = cfg.getRootNode().orElseThrow();
+ GraphNode> exitNode = cfg.findNodeBy(n -> n.getNodeType() == NodeType.METHOD_EXIT).orElseThrow();
- for (GraphNode> node : cfgNodes)
- registerNode(node);
+ Arc enterExitArc = null;
+ if (!cfg.containsEdge(enterNode, exitNode)) {
+ enterExitArc = new ControlFlowArc();
+ cfg.addEdge(enterNode, exitNode, enterExitArc);
+ }
- for (GraphNode> src : cfgNodes) {
- for (GraphNode> dest : cfgNodes) {
- if (src == dest) continue;
- if (hasControlDependence(src, dest)) {
- pdg.addControlDependencyArc(src, dest);
- roots.remove(dest);
- }
+ Set> nodes = pdg.vertexSet();
+ for (GraphNode> a : nodes) {
+ for (GraphNode> b : nodes) {
+ if (a == b) continue;
+ if (hasControlDependence(a, b))
+ pdg.addControlDependencyArc(a, b);
}
}
- // In the original definition, nodes were dependent by default on the Enter/Start node
- for (GraphNode> node : roots)
- if (!node.getInstruction().equals("Exit"))
- pdg.addControlDependencyArc(pdg.getRootNode().get(), node);
- }
- public void registerNode(GraphNode> node) {
- pdg.addVertex(node);
+ if (enterExitArc != null)
+ cfg.removeEdge(enterExitArc);
}
public boolean hasControlDependence(GraphNode> a, GraphNode> b) {
int yes = 0;
- Set list = cfg.outgoingEdgesOf(a);
+ Set arcs = cfg.outgoingEdgesOf(a);
// Nodes with less than 1 outgoing arc cannot control another node.
- if (cfg.outDegreeOf(a) < 2)
+ if (arcs.size() < 2)
return false;
- for (Arc arc : cfg.outgoingEdgesOf(a)) {
- GraphNode> successor = cfg.getEdgeTarget(arc);
- if (postdominates(successor, b))
+ for (Arc arc : arcs)
+ if (postdominates(cfg.getEdgeTarget(arc), b))
yes++;
- }
- int no = list.size() - yes;
+ int no = arcs.size() - yes;
return yes > 0 && no > 0;
}
@@ -86,14 +75,11 @@ class ControlDependencyBuilder {
return postdominates(a, b, new HashSet<>());
}
- private boolean postdominates(GraphNode> a, GraphNode> b, Set> visited) {
+ protected boolean postdominates(GraphNode> a, GraphNode> b, Set> visited) {
// Stop w/ success if a == b or a has already been visited
if (a.equals(b) || visited.contains(a))
return true;
Set outgoing = cfg.outgoingEdgesOf(a);
- // Limit the traversal if it is a PPDG
- if (pdg instanceof PPDG)
- outgoing = outgoing.stream().filter(Arc::isExecutableControlFlowArc).collect(Collectors.toSet());
// Stop w/ failure if there are no edges to traverse from a
if (outgoing.isEmpty())
return false;
diff --git a/sdg-core/src/main/java/tfm/graphs/pdg/DataDependencyBuilder.java b/sdg-core/src/main/java/tfm/graphs/pdg/DataDependencyBuilder.java
deleted file mode 100644
index f8d11d57ddb7401ade9c5bcdd0cb06e3047f8db3..0000000000000000000000000000000000000000
--- a/sdg-core/src/main/java/tfm/graphs/pdg/DataDependencyBuilder.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package tfm.graphs.pdg;
-
-import com.github.javaparser.ast.Node;
-import com.github.javaparser.ast.stmt.*;
-import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
-import tfm.graphs.cfg.CFG;
-import tfm.nodes.GraphNode;
-
-import java.util.Optional;
-
-class DataDependencyBuilder extends VoidVisitorAdapter {
-
- private CFG cfg;
- private PDG pdg;
-
- public DataDependencyBuilder(PDG pdg, CFG cfg) {
- this.pdg = pdg;
- this.cfg = cfg;
- }
-
- @Override
- public void visit(ExpressionStmt expressionStmt, Void ignored) {
- buildDataDependency(expressionStmt);
- }
-
- @Override
- public void visit(IfStmt ifStmt, Void ignored) {
- buildDataDependency(ifStmt);
-
- ifStmt.getThenStmt().accept(this, null);
-
- ifStmt.getElseStmt().ifPresent(statement -> statement.accept(this, null));
- }
-
- @Override
- public void visit(WhileStmt whileStmt, Void ignored) {
- buildDataDependency(whileStmt);
-
- whileStmt.getBody().accept(this, null);
- }
-
- @Override
- public void visit(ForStmt forStmt, Void ignored) {
- forStmt.getInitialization()
- .forEach(this::buildDataDependency);
-
- buildDataDependency(forStmt); // Only for comparison
-
- forStmt.getUpdate()
- .forEach(this::buildDataDependency);
-
- forStmt.getBody().accept(this, null);
- }
-
- @Override
- public void visit(ForEachStmt forEachStmt, Void ignored) {
- buildDataDependency(forEachStmt);
-
- forEachStmt.getBody().accept(this, null);
- }
-
- @Override
- public void visit(SwitchStmt switchStmt, Void ignored) {
- buildDataDependency(switchStmt);
-
- switchStmt.getEntries().accept(this, null);
- }
-
- @Override
- public void visit(SwitchEntryStmt switchEntryStmt, Void ignored) {
- buildDataDependency(switchEntryStmt);
-
- switchEntryStmt.getStatements().accept(this, null);
- }
-
- @Override
- public void visit(ReturnStmt n, Void arg) {
- buildDataDependency(n);
- }
-
- private void buildDataDependency(Node node) {
- Optional> optionalGraphNode = pdg.findNodeByASTNode(node);
- assert optionalGraphNode.isPresent();
-
- buildDataDependency(optionalGraphNode.get());
- }
-
- private void buildDataDependency(GraphNode> node) {
- for (String usedVariable : node.getUsedVariables()) {
- cfg.findLastDefinitionsFrom(node, usedVariable)
- .forEach(definitionNode -> pdg.addDataDependencyArc(definitionNode, node, usedVariable));
- }
- }
-}
diff --git a/sdg-core/src/main/java/tfm/graphs/pdg/PDG.java b/sdg-core/src/main/java/tfm/graphs/pdg/PDG.java
index 976ee6490a444d197e585db6785e49a2bfd58003..f7e219ac2a779b4da6135e3c550669f00faec78a 100644
--- a/sdg-core/src/main/java/tfm/graphs/pdg/PDG.java
+++ b/sdg-core/src/main/java/tfm/graphs/pdg/PDG.java
@@ -4,28 +4,20 @@ import com.github.javaparser.ast.body.MethodDeclaration;
import tfm.arcs.pdg.ControlDependencyArc;
import tfm.arcs.pdg.DataDependencyArc;
import tfm.graphs.GraphWithRootNode;
-import tfm.graphs.Sliceable;
import tfm.graphs.cfg.CFG;
import tfm.nodes.GraphNode;
-import tfm.slicing.ClassicSlicingAlgorithm;
-import tfm.slicing.Slice;
-import tfm.slicing.SlicingCriterion;
-import tfm.utils.NodeNotFoundException;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
+import tfm.nodes.VariableAction;
+import tfm.nodes.type.NodeType;
/**
* The Program Dependence Graph represents the statements of a method in
* a graph, connecting statements according to their {@link ControlDependencyArc control}
* and {@link DataDependencyArc data} relationships. You can build one manually or use
- * the {@link PDGBuilder PDGBuilder}.
+ * the {@link Builder PDGBuilder}.
* The variations of the PDG are represented as child types.
*/
-public class PDG extends GraphWithRootNode implements Sliceable {
- private boolean built = false;
- private CFG cfg;
+public class PDG extends GraphWithRootNode {
+ protected CFG cfg;
public PDG() {
this(new CFG());
@@ -40,16 +32,15 @@ public class PDG extends GraphWithRootNode implements Sliceab
this.addEdge(from, to, new ControlDependencyArc());
}
- public void addDataDependencyArc(GraphNode> from, GraphNode> to, String variable) {
- this.addEdge(from, to, new DataDependencyArc(variable));
- }
-
- @Override
- public Slice slice(SlicingCriterion slicingCriterion) {
- Optional> node = slicingCriterion.findNode(this);
- if (node.isEmpty())
- throw new NodeNotFoundException(slicingCriterion);
- return new ClassicSlicingAlgorithm(this).traverse(node.get());
+ public void addDataDependencyArc(VariableAction src, VariableAction tgt) {
+ DataDependencyArc arc;
+ if (src instanceof VariableAction.Definition && tgt instanceof VariableAction.Usage)
+ arc = new DataDependencyArc((VariableAction.Definition) src, (VariableAction.Usage) tgt);
+ else if (src instanceof VariableAction.Declaration && tgt instanceof VariableAction.Definition)
+ arc = new DataDependencyArc((VariableAction.Declaration) src, (VariableAction.Definition) tgt);
+ else
+ throw new UnsupportedOperationException("Unsupported combination of VariableActions");
+ addEdge(src.getGraphNode(), tgt.getGraphNode(), arc);
}
public CFG getCfg() {
@@ -58,18 +49,73 @@ public class PDG extends GraphWithRootNode implements Sliceab
@Override
public void build(MethodDeclaration method) {
- new PDGBuilder(this).createFrom(method);
+ createBuilder().build(method);
built = true;
}
- @Override
- public boolean isBuilt() {
- return built;
+ protected Builder createBuilder() {
+ return new Builder();
}
- public List> findDeclarationsOfVariable(String variable) {
- return vertexSet().stream()
- .filter(node -> node.getDeclaredVariables().contains(variable))
- .collect(Collectors.toList());
+ /**
+ * Populates a {@link PDG}, given a complete {@link CFG}, an empty {@link PDG} and an AST root node.
+ * For now it only accepts {@link MethodDeclaration} as root, as it can only receive a single CFG.
+ *
+ * Usage:
+ *
+ * - Create an empty {@link CFG}.
+ * - Create an empty {@link PDG} (optionally passing the {@link CFG} as argument).
+ * - Create a new {@link Builder}, passing both graphs as arguments.
+ * - Accept the builder as a visitor of the {@link MethodDeclaration} you want to analyse using
+ * {@link com.github.javaparser.ast.Node#accept(com.github.javaparser.ast.visitor.VoidVisitor, Object) Node#accept(VoidVisitor, Object)}:
+ * {@code methodDecl.accept(builder, null)}
+ * - Once the previous step is finished, the complete PDG is saved in
+ * the object created in the second step. The builder should be discarded
+ * and not reused.
+ *
+ */
+ public class Builder {
+ protected Builder() {
+ assert PDG.this.getCfg() != null;
+ }
+
+ public void build(MethodDeclaration methodDeclaration) {
+ if (methodDeclaration.getBody().isEmpty())
+ throw new IllegalStateException("Method needs to have a body");
+ buildAndCopyCFG(methodDeclaration);
+ buildControlDependency();
+ buildDataDependency();
+ }
+
+ protected void buildAndCopyCFG(MethodDeclaration methodDeclaration) {
+ if (!cfg.isBuilt())
+ cfg.build(methodDeclaration);
+ cfg.vertexSet().stream()
+ .filter(node -> node.getNodeType() != NodeType.METHOD_EXIT)
+ .forEach(PDG.this::addVertex);
+ assert cfg.getRootNode().isPresent();
+ PDG.this.setRootNode(cfg.getRootNode().get());
+ }
+
+ protected void buildControlDependency() {
+ new ControlDependencyBuilder(cfg, PDG.this).build();
+ }
+
+ protected void buildDataDependency() {
+ for (GraphNode> node : vertexSet()) {
+ for (VariableAction varAct : node.getVariableActions()) {
+ if (varAct.isUsage()) {
+ VariableAction.Usage use = (VariableAction.Usage) varAct;
+ for (VariableAction.Definition def : cfg.findLastDefinitionsFrom(node, use))
+ addDataDependencyArc(def, use);
+ } else if (varAct.isDefinition()) {
+ VariableAction.Definition def = (VariableAction.Definition) varAct;
+ for (VariableAction.Declaration dec : cfg.findLastDeclarationsFrom(node, def))
+ if (def.getGraphNode() != dec.getGraphNode())
+ addDataDependencyArc(dec, def);
+ }
+ }
+ }
+ }
}
}
diff --git a/sdg-core/src/main/java/tfm/graphs/pdg/PDGBuilder.java b/sdg-core/src/main/java/tfm/graphs/pdg/PDGBuilder.java
deleted file mode 100644
index 321edd034970429b6807ebe1bc5c8a21f8903e1c..0000000000000000000000000000000000000000
--- a/sdg-core/src/main/java/tfm/graphs/pdg/PDGBuilder.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package tfm.graphs.pdg;
-
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.body.VariableDeclarator;
-import com.github.javaparser.ast.expr.Expression;
-import com.github.javaparser.ast.expr.VariableDeclarationExpr;
-import com.github.javaparser.ast.stmt.BlockStmt;
-import com.github.javaparser.ast.stmt.ExpressionStmt;
-import tfm.graphs.cfg.CFG;
-import tfm.nodes.type.NodeType;
-
-/**
- * Populates a {@link PDG}, given a complete {@link CFG}, an empty {@link PDG} and an AST root node.
- * For now it only accepts {@link MethodDeclaration} as root, as it can only receive a single CFG.
- *
- * Usage:
- *
- * - Create an empty {@link CFG}.
- * - Create an empty {@link PDG} (optionally passing the {@link CFG} as argument).
- * - Create a new {@link PDGBuilder}, passing both graphs as arguments.
- * - Accept the builder as a visitor of the {@link MethodDeclaration} you want to analyse using
- * {@link com.github.javaparser.ast.Node#accept(com.github.javaparser.ast.visitor.VoidVisitor, Object) Node#accept(VoidVisitor, Object)}:
- * {@code methodDecl.accept(builder, null)}
- * - Once the previous step is finished, the complete PDG is saved in
- * the object created in the second step. The builder should be discarded
- * and not reused.
- *
- */
-public class PDGBuilder {
- private PDG pdg;
- private CFG cfg;
-
- protected PDGBuilder(PDG pdg) {
- assert pdg.getCfg() != null;
- this.pdg = pdg;
- this.cfg = pdg.getCfg();
- }
-
- public void createFrom(MethodDeclaration methodDeclaration) {
- if (!methodDeclaration.getBody().isPresent())
- throw new IllegalStateException("Method needs to have a body");
-
- BlockStmt methodBody = methodDeclaration.getBody().get();
-
- // build CFG
- if (!cfg.isBuilt())
- cfg.build(methodDeclaration);
-
- // Copy nodes from CFG to PDG
- cfg.vertexSet().stream()
- .filter(node -> node.getNodeType() != NodeType.METHOD_EXIT)
- .forEach(node -> pdg.addVertex(node));
-
- assert this.cfg.getRootNode().isPresent();
-
- pdg.setRootNode(cfg.getRootNode().get());
-
- // Build control dependency
- ControlDependencyBuilder controlDependencyBuilder = new ControlDependencyBuilder(pdg, cfg);
- controlDependencyBuilder.analyze();
-
- // Build data dependency
- DataDependencyBuilder dataDependencyBuilder = new DataDependencyBuilder(pdg, cfg);
- methodBody.accept(dataDependencyBuilder, null);
-
- // Build data dependency of "out" variables
- pdg.vertexSet().stream()
- .filter(node -> node.getNodeType() == NodeType.FORMAL_OUT)
- .forEach(node -> {
- assert node.getAstNode() instanceof ExpressionStmt;
-
- Expression expression = ((ExpressionStmt) node.getAstNode()).getExpression();
-
- assert expression.isVariableDeclarationExpr();
-
- VariableDeclarationExpr variableDeclarationExpr = expression.asVariableDeclarationExpr();
-
- // There should be only 1 variableDeclarator
- assert variableDeclarationExpr.getVariables().size() == 1;
-
- VariableDeclarator variableDeclarator = variableDeclarationExpr.getVariables().get(0);
-
- assert variableDeclarator.getInitializer().isPresent();
- assert variableDeclarator.getInitializer().get().isNameExpr();
-
- String variable = variableDeclarator.getInitializer().get().asNameExpr().getNameAsString();
-
- cfg.findLastDefinitionsFrom(node, variable)
- .forEach(variableDefinitionNode -> pdg.addDataDependencyArc(variableDefinitionNode, node, variable));
- });
- }
-}
diff --git a/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacer.java b/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacer.java
deleted file mode 100644
index c3bc8affac2b29ed1af653d2e9d85f37f697887e..0000000000000000000000000000000000000000
--- a/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package tfm.graphs.sdg;
-
-import com.github.javaparser.ast.body.MethodDeclaration;
-import tfm.utils.Context;
-
-class MethodCallReplacer {
-
- private SDG sdg;
-
- public MethodCallReplacer(SDG sdg) {
- this.sdg = sdg;
- }
-
- public void replace(Context context) {
- for (MethodDeclaration methodDeclaration : this.sdg.getMethodDeclarations()) {
- methodDeclaration.accept(new MethodCallReplacerVisitor(sdg), context);
- }
- }
-}
diff --git a/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java b/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java
index 9b991a55a1fcb905cc950eddc41ac6b1c941f29e..ea37112c4e29d05d8d826491545ffbe3762fdaaa 100644
--- a/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java
+++ b/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java
@@ -1,98 +1,41 @@
package tfm.graphs.sdg;
import com.github.javaparser.ast.ArrayCreationLevel;
-import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
-import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.*;
-import com.github.javaparser.ast.stmt.*;
-import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import com.github.javaparser.ast.stmt.EmptyStmt;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import tfm.arcs.Arc;
-import tfm.graphs.cfg.CFG;
-import tfm.nodes.GraphNode;
-import tfm.nodes.TypeNodeFactory;
+import tfm.arcs.pdg.DataDependencyArc;
+import tfm.graphs.GraphNodeContentVisitor;
+import tfm.graphs.cfg.CFGBuilder;
+import tfm.nodes.*;
import tfm.nodes.type.NodeType;
-import tfm.utils.Context;
import tfm.utils.Logger;
-import java.util.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
-class MethodCallReplacerVisitor extends VoidVisitorAdapter {
-
- private SDG sdg;
- private GraphNode> originalMethodCallNode;
+public class MethodCallReplacerVisitor extends GraphNodeContentVisitor {
+ protected final SDG sdg;
public MethodCallReplacerVisitor(SDG sdg) {
this.sdg = sdg;
}
- private void searchAndSetMethodCallNode(Node node) {
- Optional> optionalNode = sdg.findNodeByASTNode(node);
- assert optionalNode.isPresent();
- originalMethodCallNode = optionalNode.get();
-
- }
-
- @Override
- public void visit(DoStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
- @Override
- public void visit(ForEachStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
- @Override
- public void visit(ForStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
- @Override
- public void visit(IfStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
- @Override
- public void visit(SwitchStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
- @Override
- public void visit(WhileStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
@Override
- public void visit(ReturnStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
+ public void startVisit(GraphNode> graphNode) {
+ if (!(graphNode.getAstNode() instanceof MethodCallExpr) && !(graphNode instanceof SyntheticNode))
+ super.startVisit(graphNode);
}
@Override
- public void visit(ExpressionStmt n, Context arg) {
- searchAndSetMethodCallNode(n);
- super.visit(n, arg);
- }
-
- @Override
- public void visit(MethodCallExpr methodCallExpr, Context context) {
- NodeList arguments = methodCallExpr.getArguments();
-
- // Parse first method call expressions as arguments
-// arguments.stream()
-// .filter(Expression::isMethodCallExpr)
-// .forEach(expression -> expression.accept(this, context));
-
+ public void visit(MethodCallExpr methodCallExpr, Void arg) {
GraphNode methodDeclarationNode;
try {
methodDeclarationNode = methodCallExpr.resolve().toAst()
@@ -103,14 +46,15 @@ class MethodCallReplacerVisitor extends VoidVisitorAdapter {
return;
}
- MethodDeclaration methodDeclaration = methodDeclarationNode.getAstNode();
-
- GraphNode methodCallNode = sdg.addNode("CALL " + methodCallExpr.toString(), methodCallExpr, TypeNodeFactory.fromType(NodeType.METHOD_CALL));
+ NodeList arguments = methodCallExpr.getArguments();
+ NodeList parameters = methodDeclarationNode.getAstNode().getParameters();
- sdg.addControlDependencyArc(originalMethodCallNode, methodCallNode);
+ // Create and connect the CALL node
+ CallNode methodCallNode = new CallNode(methodCallExpr);
+ sdg.addNode(methodCallNode);
+ sdg.addControlDependencyArc(graphNode, methodCallNode);
sdg.addCallArc(methodCallNode, methodDeclarationNode);
- NodeList parameters = methodDeclarationNode.getAstNode().getParameters();
for (int i = 0; i < parameters.size(); i++) {
Parameter parameter = parameters.get(i);
@@ -126,123 +70,119 @@ class MethodCallReplacerVisitor extends VoidVisitorAdapter {
new NodeList<>(new ArrayCreationLevel(varArgs.size())),
new ArrayInitializerExpr(varArgs)
);
+ i = parameters.size();
}
- // In expression
- VariableDeclarationExpr inVariableDeclarationExpr = new VariableDeclarationExpr(
- new VariableDeclarator(
- parameter.getType(),
- parameter.getNameAsString() + "_in",
- new NameExpr(argument.toString())
- )
- );
-
- ExpressionStmt inExprStmt = new ExpressionStmt(inVariableDeclarationExpr);
-
- GraphNode argumentInNode = sdg.addNode(inExprStmt.toString(), inExprStmt, TypeNodeFactory.fromType(NodeType.ACTUAL_IN));
-
- sdg.addControlDependencyArc(methodCallNode, argumentInNode);
-
- // Handle data dependency: Remove arc from method call node and add it to IN node
-
- sdg.incomingEdgesOf(originalMethodCallNode).stream()
- .filter(Arc::isDataDependencyArc)
- .filter(arc -> Objects.equals(arc.getLabel(), argument.toString()))
- .forEach(arc -> sdg.addDataDependencyArc(sdg.getEdgeSource(arc), argumentInNode, argument.toString()));
-
- // Now, find the corresponding method declaration's in node and link argument node with it
-
- Optional> optionalParameterInNode = sdg.outgoingEdgesOf(methodDeclarationNode).stream()
- .map(arc -> (GraphNode) sdg.getEdgeTarget(arc))
- .filter(node -> node.getNodeType() == NodeType.FORMAL_IN)
- .filter(node -> node.getInstruction().contains(parameter.getNameAsString() + "_in"))
- .findFirst();
-
- if (optionalParameterInNode.isPresent()) {
- sdg.addParameterInOutArc(argumentInNode, optionalParameterInNode.get());
- } else {
- Logger.log("MethodCallReplacerVisitor", "WARNING: IN declaration node for argument " + argument + " not found.");
- Logger.log("MethodCallReplacerVisitor", String.format("Context: %s, Method: %s, Call: %s", context.getCurrentMethod().get().getNameAsString(), methodDeclaration.getSignature().asString(), methodCallExpr));
- }
-
- // Out expression
-
- // If argument is primitive or not a variable, do not generate out nodes
- if (parameter.getType().isPrimitiveType() || !argument.isNameExpr()) {
- continue;
- }
-
- AssignExpr outVariableAssignExpr = new AssignExpr(
- argument,
- new NameExpr(parameter.getNameAsString() + "_out"),
- AssignExpr.Operator.ASSIGN
- );
+ createActualIn(methodDeclarationNode, methodCallNode, parameter, argument);
+ createActualOut(methodDeclarationNode, methodCallNode, parameter, argument);
+ }
- ExpressionStmt outExprStmt = new ExpressionStmt(outVariableAssignExpr);
+ // Add the 'output' node to the call and connect to the METHOD_OUTPUT node (there should be only one -- if any)
+ sdg.outgoingEdgesOf(methodDeclarationNode).stream()
+ .filter(arc -> sdg.getEdgeTarget(arc).getNodeType() == NodeType.METHOD_OUTPUT)
+ .map(sdg::getEdgeTarget)
+ .forEach(node -> processMethodOutputNode(node, methodCallNode));
+ }
- GraphNode argumentOutNode = sdg.addNode(outExprStmt.toString(), outExprStmt, TypeNodeFactory.fromType(NodeType.ACTUAL_OUT));
+ protected void createActualIn(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument) {
+ ActualIONode argumentInNode = ActualIONode.createActualIn(call.getAstNode(), parameter, argument);
+ sdg.addNode(argumentInNode);
+ sdg.addControlDependencyArc(call, argumentInNode);
+
+ // Handle data dependency: Remove arc from method call node and add it to IN node
+ List arcsToRemove = sdg.incomingEdgesOf(graphNode).stream()
+ .filter(Arc::isDataDependencyArc)
+ .map(Arc::asDataDependencyArc)
+ .filter(arc -> arc.getTarget().isContainedIn(argument))
+ .collect(Collectors.toList());
+ arcsToRemove.forEach(arc -> moveArc(arc, argumentInNode, true));
+
+ // Now, find the corresponding method declaration's in node and link argument node with it
+ Optional optFormalInNode = sdg.outgoingEdgesOf(declaration).stream()
+ .map(sdg::getEdgeTarget)
+ .filter(FormalIONode.class::isInstance)
+ .map(FormalIONode.class::cast)
+ .filter(argumentInNode::matchesFormalIO)
+ .findFirst();
- sdg.addControlDependencyArc(methodCallNode, argumentOutNode);
+ if (optFormalInNode.isPresent())
+ sdg.addParameterInOutArc(argumentInNode, optFormalInNode.get());
+ else
+ Logger.log(getClass().getSimpleName(), "WARNING: FORMAL-IN node for argument " + argument + " of call " + call + " not found.");
+ }
- // Now, find the corresponding method call's out node and link argument node with it
+ protected void createActualOut(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument) {
+ Set variablesForOutNode = new HashSet<>();
+ argument.accept(new OutNodeVariableVisitor(), variablesForOutNode);
- Optional> optionalParameterOutNode = sdg.outgoingEdgesOf(methodDeclarationNode).stream()
- .map(arc -> (GraphNode) sdg.getEdgeTarget(arc))
- .filter(node -> node.getNodeType() == NodeType.FORMAL_OUT)
- .filter(node -> node.getInstruction().contains(parameter.getNameAsString() + "_out"))
- .findFirst();
+ // Here, variablesForOutNode may have 1 variable or more depending on the expression
+ if (variablesForOutNode.isEmpty()) {
+ // If the argument is not a variable or it is not declared in the scope, then there is no OUT node
+ Logger.log("MethodCallReplacerVisitor", String.format("Expression '%s' should not have out node", argument.toString()));
+ return;
+ } else if (variablesForOutNode.size() == 1) {
+ String variable = variablesForOutNode.iterator().next();
- // Handle data dependency: remove arc from method call node and add it to OUT node
+ List> declarations = sdg.findDeclarationsOfVariable(variable, graphNode);
- sdg.outgoingEdgesOf(originalMethodCallNode).stream()
- .filter(Arc::isDataDependencyArc)
- .filter(arc -> Objects.equals(arc.getLabel(), argument.toString()))
- .forEach(arc -> sdg.addDataDependencyArc(argumentOutNode, sdg.getEdgeTarget(arc), argument.toString()));
+ Logger.log("MethodCallReplacerVisitor", String.format("Declarations of variable: '%s': %s", variable, declarations));
- if (optionalParameterOutNode.isPresent()) {
- sdg.addParameterInOutArc(optionalParameterOutNode.get(), argumentOutNode);
- } else {
- Logger.log("MethodCallReplacerVisitor", "WARNING: OUT declaration node for argument " + argument + " not found.");
- Logger.log("MethodCallReplacerVisitor", String.format("Context: %s, Method: %s, Call: %s", context.getCurrentMethod().get().getNameAsString(), methodDeclaration.getSignature().asString(), methodCallExpr));
+ if (declarations.isEmpty()) {
+ Logger.log("MethodCallReplacerVisitor", String.format("Expression '%s' should not have out node", argument.toString()));
+ return;
}
- }
-
- // Add 'output' node of the call
-
- // First, check if method has an output node
-
- if (methodDeclaration.getType().isVoidType()) {
+ } else {
+ // Multiple variables (varargs, array) not considered!
return;
}
- // If not void, find the output node
+ ActualIONode argumentOutNode = ActualIONode.createActualOut(call.getAstNode(), parameter, argument);
+ sdg.addNode(argumentOutNode);
+ sdg.addControlDependencyArc(call, argumentOutNode);
- Optional> optionalDeclarationOutputNode = sdg.outgoingEdgesOf(methodDeclarationNode).stream()
- .filter(arc -> sdg.getEdgeTarget(arc).getNodeType() == NodeType.METHOD_OUTPUT)
- .map(arc -> (GraphNode) sdg.getEdgeTarget(arc))
+ // Now, find the corresponding method call's out node and link argument node with it
+ Optional optionalParameterOutNode = sdg.outgoingEdgesOf(declaration).stream()
+ .map(sdg::getEdgeTarget)
+ .filter(FormalIONode.class::isInstance)
+ .map(FormalIONode.class::cast)
+ .filter(argumentOutNode::matchesFormalIO)
.findFirst();
- if (!optionalDeclarationOutputNode.isPresent()) {
- // Method return type is void, do nothing
- return;
- }
-
- GraphNode declarationOutputNode = optionalDeclarationOutputNode.get();
-
- // If method has output node, then create output call node and link them
- GraphNode callReturnNode = sdg.addNode("output", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_CALL_RETURN));
-
- sdg.addControlDependencyArc(methodCallNode, callReturnNode);
- sdg.addDataDependencyArc(callReturnNode, originalMethodCallNode);
- sdg.addParameterInOutArc(declarationOutputNode, callReturnNode);
+ // Handle data dependency: copy arc from method call node and add it to OUT node
+ List arcsToRemove = sdg.outgoingEdgesOf(graphNode).stream()
+ .filter(Arc::isDataDependencyArc)
+ .map(DataDependencyArc.class::cast)
+ .filter(arc -> arc.getSource().isContainedIn(argument))
+ .collect(Collectors.toList());
+ arcsToRemove.forEach(arc -> moveArc(arc, argumentOutNode, false));
+
+ if (optionalParameterOutNode.isPresent())
+ sdg.addParameterInOutArc(optionalParameterOutNode.get(), argumentOutNode);
+ else
+ Logger.log(getClass().getSimpleName(), "WARNING: FORMAL-OUT node for argument " + argument + " of call " + call + " not found.");
+ }
- Logger.log("MethodCallReplacerVisitor", String.format("%s | Method '%s' called", methodCallExpr, methodDeclaration.getNameAsString()));
+ /**
+ * @param moveTarget If true, the target will be changed to 'node', otherwise the source will be.
+ */
+ protected void moveArc(DataDependencyArc arc, ActualIONode node, boolean moveTarget) {
+ VariableAction sourceAction = arc.getSource();
+ VariableAction targetAction = arc.getTarget();
+ if (moveTarget)
+ sdg.addDataDependencyArc(sourceAction, targetAction.moveTo(node));
+ else
+ sdg.addDataDependencyArc(sourceAction.moveTo(node), targetAction);
+ sdg.removeEdge(arc);
}
- @Override
- public void visit(MethodDeclaration n, Context arg) {
- arg.setCurrentMethod(n);
+ protected void processMethodOutputNode(GraphNode> methodOutputNode, GraphNode methodCallNode) {
+ GraphNode callReturnNode = sdg.addNode("call output", new EmptyStmt(),
+ TypeNodeFactory.fromType(NodeType.METHOD_CALL_RETURN));
+ VariableAction.Usage usage = graphNode.addUsedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT));
+ VariableAction.Definition definition = callReturnNode.addDefinedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT));
- super.visit(n, arg);
+ sdg.addControlDependencyArc(methodCallNode, callReturnNode);
+ sdg.addDataDependencyArc(definition, usage);
+ sdg.addParameterInOutArc(methodOutputNode, callReturnNode);
}
}
diff --git a/sdg-core/src/main/java/tfm/graphs/sdg/SDG.java b/sdg-core/src/main/java/tfm/graphs/sdg/SDG.java
index 05ea563c61238780ceddfdfe097745079db36623..ba0187fa02466ca33c662ccad91857b9598e3330 100644
--- a/sdg-core/src/main/java/tfm/graphs/sdg/SDG.java
+++ b/sdg-core/src/main/java/tfm/graphs/sdg/SDG.java
@@ -3,8 +3,8 @@ package tfm.graphs.sdg;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.stmt.ExpressionStmt;
-import tfm.arcs.Arc;
import tfm.arcs.pdg.ControlDependencyArc;
import tfm.arcs.pdg.DataDependencyArc;
import tfm.arcs.sdg.CallArc;
@@ -13,25 +13,23 @@ import tfm.arcs.sdg.SummaryArc;
import tfm.graphs.Buildable;
import tfm.graphs.Graph;
import tfm.graphs.cfg.CFG;
+import tfm.graphs.sdg.sumarcs.NaiveSummaryArcsBuilder;
import tfm.nodes.GraphNode;
+import tfm.nodes.VariableAction;
import tfm.slicing.ClassicSlicingAlgorithm;
import tfm.slicing.Slice;
import tfm.slicing.Sliceable;
import tfm.slicing.SlicingCriterion;
import tfm.utils.Context;
-import tfm.utils.Utils;
import java.util.*;
+import java.util.stream.Collectors;
public class SDG extends Graph implements Sliceable, Buildable> {
- private boolean built = false;
+ protected final List cfgs = new LinkedList<>();
- private Map methodCFGMap;
- private NodeList compilationUnits;
-
- public SDG() {
- this.methodCFGMap = new HashMap<>();
- }
+ protected boolean built = false;
+ protected NodeList compilationUnits;
public NodeList getCompilationUnits() {
return compilationUnits;
@@ -47,42 +45,44 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) {
- nodeList.accept(new SDGBuilder(this), new Context());
+ nodeList.accept(createBuilder(), new Context());
+ Set> vertices = Set.copyOf(vertexSet());
+ vertices.forEach(n -> new MethodCallReplacerVisitor(this).startVisit(n));
+ new NaiveSummaryArcsBuilder(this).visit();
compilationUnits = nodeList;
built = true;
}
+ protected SDGBuilder createBuilder() {
+ return new SDGBuilder(this);
+ }
+
@Override
public boolean isBuilt() {
return built;
}
- public Set getMethodDeclarations() {
- return this.methodCFGMap.keySet();
+ public void setMethodCFG(CFG cfg) {
+ this.cfgs.add(cfg);
}
- public void setMethodCFG(MethodDeclaration methodDeclaration, CFG cfg) {
- this.methodCFGMap.put(methodDeclaration, cfg);
- }
-
- public Optional getMethodCFG(MethodDeclaration methodDeclaration) {
- if (!this.methodCFGMap.containsKey(methodDeclaration)) {
- return Optional.empty();
- }
-
- return Optional.of(this.methodCFGMap.get(methodDeclaration));
+ public Collection getCFGs() {
+ return cfgs;
}
public void addControlDependencyArc(GraphNode> from, GraphNode> to) {
this.addEdge(from, to, new ControlDependencyArc());
}
- public void addDataDependencyArc(GraphNode> from, GraphNode> to) {
- this.addDataDependencyArc(from, to, null);
- }
-
- public void addDataDependencyArc(GraphNode> from, GraphNode> to, String variable) {
- this.addEdge(from, to, new DataDependencyArc(variable));
+ public void addDataDependencyArc(VariableAction src, VariableAction tgt) {
+ DataDependencyArc arc;
+ if (src instanceof VariableAction.Definition && tgt instanceof VariableAction.Usage)
+ arc = new DataDependencyArc((VariableAction.Definition) src, (VariableAction.Usage) tgt);
+ else if (src instanceof VariableAction.Declaration && tgt instanceof VariableAction.Definition)
+ arc = new DataDependencyArc((VariableAction.Declaration) src, (VariableAction.Definition) tgt);
+ else
+ throw new UnsupportedOperationException("Unsupported combination of VariableActions");
+ addEdge(src.getGraphNode(), tgt.getGraphNode(), arc);
}
public void addCallArc(GraphNode> from, GraphNode to) {
@@ -98,26 +98,13 @@ public class SDG extends Graph implements Sliceable, Buildable> findDeclarationsOfVariable(String variable, GraphNode> root) {
- return this.methodCFGMap.values().stream()
+ return this.cfgs.stream()
.filter(cfg -> cfg.containsVertex(root))
.findFirst()
- .map(cfg -> doFindDeclarationsOfVariable(variable, root, cfg, Utils.emptyList()))
- .orElse(Utils.emptyList());
- }
-
- private List> doFindDeclarationsOfVariable(String variable, GraphNode> root, CFG cfg, List> res) {
- Set controlDependencies = cfg.incomingEdgesOf(root);
-
- for (Arc arc : controlDependencies) {
- GraphNode> source = cfg.getEdgeSource(arc);
-
- if (source.getDeclaredVariables().contains(variable)) {
- res.add(root);
- } else {
- res.addAll(doFindDeclarationsOfVariable(variable, source, cfg, res));
- }
- }
-
- return res;
+ .map(cfg -> cfg.findLastDeclarationsFrom(root, new VariableAction.Definition(new NameExpr(variable), root)))
+ .orElseThrow()
+ .stream()
+ .map(VariableAction::getGraphNode)
+ .collect(Collectors.toList());
}
}
diff --git a/sdg-core/src/main/java/tfm/graphs/sdg/SDGBuilder.java b/sdg-core/src/main/java/tfm/graphs/sdg/SDGBuilder.java
index 33a13fd9a8d6663cd855b2393babfc56b080e2b5..9d860f7175f2088a30d8fc157d0eeedfc7af5461 100644
--- a/sdg-core/src/main/java/tfm/graphs/sdg/SDGBuilder.java
+++ b/sdg-core/src/main/java/tfm/graphs/sdg/SDGBuilder.java
@@ -3,19 +3,11 @@ package tfm.graphs.sdg;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.stmt.EmptyStmt;
-import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
-import tfm.arcs.Arc;
import tfm.graphs.pdg.PDG;
-import tfm.graphs.sdg.sumarcs.NaiveSummaryArcsBuilder;
-import tfm.graphs.sdg.sumarcs.SummaryArcsBuilder;
-import tfm.nodes.GraphNode;
-import tfm.nodes.TypeNodeFactory;
-import tfm.nodes.type.NodeType;
import tfm.utils.Context;
-class SDGBuilder extends VoidVisitorAdapter {
+public class SDGBuilder extends VoidVisitorAdapter {
SDG sdg;
@@ -25,85 +17,35 @@ class SDGBuilder extends VoidVisitorAdapter {
@Override
public void visit(MethodDeclaration methodDeclaration, Context context) {
- if (!methodDeclaration.getBody().isPresent()) {
+ if (methodDeclaration.getBody().isEmpty())
return;
- }
-
context.setCurrentMethod(methodDeclaration);
+ buildAndCopyPDG(methodDeclaration);
+ }
- // Build PDG and add to SDGGraph
- PDG pdg = new PDG();
- pdg.build(methodDeclaration);
-
- assert pdg.isBuilt();
- assert pdg.getRootNode().isPresent();
-
- // Add all nodes from PDG to SDG
- for (GraphNode> node : pdg.vertexSet()) {
- sdg.addNode(node);
- }
-
- // Add all arcs from PDG to SDG
- for (Arc arc : pdg.edgeSet()) {
- if (arc.isControlDependencyArc()) {
- sdg.addControlDependencyArc(pdg.getEdgeSource(arc), pdg.getEdgeTarget(arc));
- } else {
- sdg.addDataDependencyArc(pdg.getEdgeSource(arc), pdg.getEdgeTarget(arc), arc.getLabel());
- }
- }
-
- GraphNode methodDeclarationNode = pdg.getRootNode().get();
-
- // Add CFG
- sdg.setMethodCFG(methodDeclaration, pdg.getCfg());
-
- // Add output node
- if (methodDeclaration.getType().isVoidType()) {
- // If method return type is void, do nothing
- return;
- }
-
- GraphNode outputNode = sdg.addNode("output", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_OUTPUT));
-
- sdg.addControlDependencyArc(methodDeclarationNode, outputNode);
+ protected PDG createPDG() {
+ return new PDG();
+ }
- // Add return arc from all return statements to the output node
- pdg.getCfg().vertexSet().stream()
- .filter(node -> node.getAstNode() instanceof ReturnStmt)
- .map(node -> (GraphNode) node)
- .forEach(node -> sdg.addDataDependencyArc(node, outputNode));
+ protected void buildAndCopyPDG(MethodDeclaration methodDeclaration) {
+ PDG pdg = createPDG();
+ pdg.build(methodDeclaration);
+ pdg.vertexSet().forEach(sdg::addNode);
+ pdg.edgeSet().forEach(arc -> sdg.addEdge(pdg.getEdgeSource(arc), pdg.getEdgeTarget(arc), arc));
+ sdg.setMethodCFG(pdg.getCfg());
}
@Override
public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, Context context) {
-// if (sdgGraph.getRootNode() != null) {
-// throw new IllegalStateException("¡Solo podemos procesar una clase por el momento!");
-// }
-
- if (classOrInterfaceDeclaration.isInterface()) {
+ if (classOrInterfaceDeclaration.isInterface())
throw new IllegalArgumentException("¡Las interfaces no estan permitidas!");
- }
-
context.setCurrentClass(classOrInterfaceDeclaration);
-
- classOrInterfaceDeclaration.getMembers().accept(this, context);
-
- // Once every PDG is built, expand method call nodes of each one
- // and link them to the corresponding method declaration node
- MethodCallReplacer methodCallReplacer = new MethodCallReplacer(sdg);
- methodCallReplacer.replace(context);
-
-
-
- // 3. Build summary arcs
- SummaryArcsBuilder summaryArcsBuilder = new NaiveSummaryArcsBuilder(sdg);
- summaryArcsBuilder.visit();
+ super.visit(classOrInterfaceDeclaration, context);
}
@Override
public void visit(CompilationUnit compilationUnit, Context context) {
context.setCurrentCU(compilationUnit);
-
super.visit(compilationUnit, context);
}
}
diff --git a/sdg-core/src/main/java/tfm/graphs/sdg/sumarcs/NaiveSummaryArcsBuilder.java b/sdg-core/src/main/java/tfm/graphs/sdg/sumarcs/NaiveSummaryArcsBuilder.java
index f2eb7846bfc34f28c2c5e0f94796b964e6ca4bf0..60e03c102e82981a592f2f177f0036c642bf1a7a 100644
--- a/sdg-core/src/main/java/tfm/graphs/sdg/sumarcs/NaiveSummaryArcsBuilder.java
+++ b/sdg-core/src/main/java/tfm/graphs/sdg/sumarcs/NaiveSummaryArcsBuilder.java
@@ -4,11 +4,14 @@ import com.github.javaparser.ast.body.MethodDeclaration;
import tfm.arcs.Arc;
import tfm.graphs.sdg.SDG;
import tfm.nodes.GraphNode;
+import tfm.nodes.SyntheticNode;
import tfm.nodes.type.NodeType;
import tfm.utils.Utils;
+import java.util.Collection;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -18,14 +21,18 @@ public class NaiveSummaryArcsBuilder extends SummaryArcsBuilder {
super(sdg);
}
+ @SuppressWarnings("unchecked")
+ protected Collection> findAllMethodDeclarations() {
+ return sdg.vertexSet().stream()
+ .filter(Predicate.not((SyntheticNode.class::isInstance)))
+ .filter(n -> n.getAstNode() instanceof MethodDeclaration)
+ .map(n -> (GraphNode) n)
+ .collect(Collectors.toSet());
+ }
+
@Override
public void visit() {
- for (MethodDeclaration methodDeclaration : sdg.getMethodDeclarations()) {
- Optional> optionalMethodDeclarationNode = sdg.findNodeByASTNode(methodDeclaration);
- assert optionalMethodDeclarationNode.isPresent();
-
- GraphNode methodDeclarationNode = optionalMethodDeclarationNode.get();
-
+ for (GraphNode methodDeclarationNode : findAllMethodDeclarations()) {
Set> formalOutNodes = sdg.outgoingEdgesOf(methodDeclarationNode).stream()
.filter(arc -> sdg.getEdgeTarget(arc).getNodeType().is(NodeType.FORMAL_OUT))
.map(arc -> (GraphNode>) sdg.getEdgeTarget(arc))
diff --git a/sdg-core/src/main/java/tfm/nodes/ActualIONode.java b/sdg-core/src/main/java/tfm/nodes/ActualIONode.java
new file mode 100644
index 0000000000000000000000000000000000000000..9bfaa53448c9002c5df15cf44ad7fa18014f4096
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/nodes/ActualIONode.java
@@ -0,0 +1,70 @@
+package tfm.nodes;
+
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import tfm.nodes.type.NodeType;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static tfm.nodes.type.NodeType.*;
+
+public class ActualIONode extends IONode {
+ protected final static Set VALID_NODE_TYPES = Set.of(ACTUAL_IN, ACTUAL_OUT);
+ protected Expression argument;
+
+ protected ActualIONode(NodeType type, MethodCallExpr astNode, Parameter parameter, Expression argument) {
+ super(type, createLabel(type, parameter, argument), astNode, parameter);
+ if (!VALID_NODE_TYPES.contains(type))
+ throw new IllegalArgumentException("Illegal type for actual-in/out node");
+ this.argument = Objects.requireNonNull(argument);
+ }
+
+ public Expression getArgument() {
+ return argument;
+ }
+
+ public boolean matchesFormalIO(FormalIONode o) {
+ // 1. We must be an ActualIONode, o must be a FormalIONode
+ return getClass().equals(ActualIONode.class) && o.getClass().equals(FormalIONode.class)
+ // 2. Our variables must match (type + name)
+ && parameter.equals(o.parameter)
+ // 3a. If ACTUAL_IN, the arg must be FORMAL_IN
+ && ((nodeType.equals(ACTUAL_IN) && o.nodeType.equals(FORMAL_IN))
+ // 3b. same for ACTUAL_OUT--FORMAL_OUT
+ || (nodeType.equals(ACTUAL_OUT) && o.nodeType.equals(FORMAL_OUT)))
+ // 4. The method call must resolve to the method declaration of the argument.
+ && Objects.equals(o.astNode, astNode.resolve().toAst().orElse(null));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && o instanceof ActualIONode
+ && Objects.equals(((ActualIONode) o).argument, argument);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), argument);
+ }
+
+ protected static String createLabel(NodeType type, Parameter param, Expression arg) {
+ switch (type) {
+ case ACTUAL_IN:
+ return String.format("%s %s_in = %s", param.getTypeAsString(), param.getNameAsString(), arg.toString());
+ case ACTUAL_OUT:
+ return String.format("%s = %s_out", arg, param.getNameAsString());
+ default:
+ throw new IllegalStateException("Invalid NodeType for actual-in/out node: " + type);
+ }
+ }
+
+ public static ActualIONode createActualIn(MethodCallExpr methodCallExpr, Parameter parameter, Expression argument) {
+ return new ActualIONode(ACTUAL_IN, methodCallExpr, parameter, argument);
+ }
+
+ public static ActualIONode createActualOut(MethodCallExpr methodCallExpr, Parameter parameter, Expression argument) {
+ return new ActualIONode(ACTUAL_OUT, methodCallExpr, parameter, argument);
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/nodes/CallNode.java b/sdg-core/src/main/java/tfm/nodes/CallNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2aad606b25661b1e729b2713918eafc66840ca3
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/nodes/CallNode.java
@@ -0,0 +1,12 @@
+package tfm.nodes;
+
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import tfm.nodes.type.NodeType;
+
+import java.util.LinkedList;
+
+public class CallNode extends SyntheticNode {
+ public CallNode(MethodCallExpr astNode) {
+ super(NodeType.METHOD_CALL, "CALL " + astNode.toString(), astNode, new LinkedList<>());
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/nodes/ExceptionExitNode.java b/sdg-core/src/main/java/tfm/nodes/ExceptionExitNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..92483326808b316c0186d7c2033ae5da7fe9623c
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/nodes/ExceptionExitNode.java
@@ -0,0 +1,31 @@
+package tfm.nodes;
+
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.resolution.types.ResolvedType;
+import tfm.nodes.type.NodeType;
+
+import java.util.Objects;
+
+public class ExceptionExitNode extends ExitNode {
+ protected final ResolvedType exceptionType;
+
+ public ExceptionExitNode(MethodDeclaration astNode, ResolvedType exceptionType) {
+ super(NodeType.METHOD_EXCEPTION_EXIT, exceptionType.describe() + " exit", astNode);
+ this.exceptionType = Objects.requireNonNull(exceptionType);
+ }
+
+ public ResolvedType getExceptionType() {
+ return exceptionType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && o instanceof ExceptionExitNode &&
+ exceptionType.equals(((ExceptionExitNode) o).exceptionType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), exceptionType);
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/nodes/ExceptionReturnNode.java b/sdg-core/src/main/java/tfm/nodes/ExceptionReturnNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..fef594f1bb71acdb7f50ce53ec32e3d6a58e7967
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/nodes/ExceptionReturnNode.java
@@ -0,0 +1,31 @@
+package tfm.nodes;
+
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.resolution.types.ResolvedType;
+import tfm.nodes.type.NodeType;
+
+import java.util.Objects;
+
+public class ExceptionReturnNode extends ReturnNode {
+ protected ResolvedType exceptionType;
+
+ public ExceptionReturnNode(MethodCallExpr astNode, ResolvedType exceptionType) {
+ super(NodeType.METHOD_CALL_EXCEPTION_RETURN, exceptionType.describe() + " return", astNode);
+ this.exceptionType = Objects.requireNonNull(exceptionType);
+ }
+
+ public ResolvedType getExceptionType() {
+ return exceptionType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && o instanceof ExceptionReturnNode &&
+ exceptionType.equals(((ExceptionReturnNode) o).exceptionType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), exceptionType);
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/nodes/ExitNode.java b/sdg-core/src/main/java/tfm/nodes/ExitNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..daeadae919af7db0036bec924f2024b29ecadf4a
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/nodes/ExitNode.java
@@ -0,0 +1,16 @@
+package tfm.nodes;
+
+import com.github.javaparser.ast.body.MethodDeclaration;
+import tfm.nodes.type.NodeType;
+
+import java.util.LinkedList;
+
+public class ExitNode extends SyntheticNode {
+ public ExitNode(MethodDeclaration astNode) {
+ super(NodeType.METHOD_EXIT, "Exit", astNode, new LinkedList<>());
+ }
+
+ protected ExitNode(NodeType type, String instruction, MethodDeclaration astNode) {
+ super(type, instruction, astNode, new LinkedList<>());
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/nodes/FormalIONode.java b/sdg-core/src/main/java/tfm/nodes/FormalIONode.java
new file mode 100644
index 0000000000000000000000000000000000000000..91f0fc2d0af3191f5ffbe3497399ec304eb56934
--- /dev/null
+++ b/sdg-core/src/main/java/tfm/nodes/FormalIONode.java
@@ -0,0 +1,41 @@
+package tfm.nodes;
+
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import tfm.nodes.type.NodeType;
+
+import java.util.Set;
+
+public class FormalIONode extends IONode {
+ protected static final Set VALID_NODE_TYPES = Set.of(NodeType.FORMAL_IN, NodeType.FORMAL_OUT);
+
+ protected FormalIONode(NodeType type, MethodDeclaration astNode, Parameter parameter) {
+ super(type, createLabel(type, parameter), astNode, parameter);
+ if (!VALID_NODE_TYPES.contains(type))
+ throw new IllegalArgumentException("Illegal type for formal-in/out node");
+ }
+
+ protected static String createLabel(NodeType type, Parameter param) {
+ switch (type) {
+ case FORMAL_IN:
+ return String.format("%s %s = %2$s_in", param.getTypeAsString(), param.getNameAsString());
+ case FORMAL_OUT:
+ return String.format("%s %s_out = %2$s", param.getTypeAsString(), param.getNameAsString());
+ default:
+ throw new IllegalStateException("Invalid NodeType for formal-in/out node: " + type);
+ }
+ }
+
+ public static FormalIONode createFormalIn(MethodDeclaration methodDeclaration, Parameter parameter) {
+ FormalIONode node = new FormalIONode(NodeType.FORMAL_IN, methodDeclaration, parameter);
+ node.addDeclaredVariable(parameter.getNameAsExpression());
+ node.addDefinedVariable(parameter.getNameAsExpression());
+ return node;
+ }
+
+ public static FormalIONode createFormalOut(MethodDeclaration methodDeclaration, Parameter parameter) {
+ FormalIONode node = new FormalIONode(NodeType.FORMAL_OUT, methodDeclaration, parameter);
+ node.addUsedVariable(parameter.getNameAsExpression());
+ return node;
+ }
+}
diff --git a/sdg-core/src/main/java/tfm/nodes/GraphNode.java b/sdg-core/src/main/java/tfm/nodes/GraphNode.java
index 0a7295bd42abea92c9facccc115f1f403dd6f364..f9d07a27df093ee726819d05484ba248ede77c74 100644
--- a/sdg-core/src/main/java/tfm/nodes/GraphNode.java
+++ b/sdg-core/src/main/java/tfm/nodes/GraphNode.java
@@ -1,18 +1,17 @@
package tfm.nodes;
import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.expr.NameExpr;
import org.jetbrains.annotations.NotNull;
import tfm.graphs.cfg.CFG;
import tfm.graphs.pdg.PDG;
import tfm.graphs.sdg.SDG;
import tfm.nodes.type.NodeType;
-import tfm.utils.Utils;
-import tfm.variables.VariableExtractor;
-import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* Represents a node in the various graphs ({@link CFG CFG},
@@ -25,58 +24,38 @@ import java.util.Set;
* @param The type of the AST represented by this node.
*/
public class GraphNode implements Comparable> {
-
public static final NodeFactory DEFAULT_FACTORY = TypeNodeFactory.fromType(NodeType.STATEMENT);
- private final NodeType nodeType;
-
- private final long id;
- private final String instruction;
- private final N astNode;
-
- private final Set declaredVariables;
- private final Set definedVariables;
- private final Set usedVariables;
-
- GraphNode(long id, NodeType type, String instruction, @NotNull N astNode) {
- this(
- id,
- type,
- instruction,
- astNode,
- Utils.emptySet(),
- Utils.emptySet(),
- Utils.emptySet()
- );
+ protected final NodeType nodeType;
+
+ protected final long id;
+ protected final String instruction;
+ protected final N astNode;
+ protected final List variableActions;
+
+ protected GraphNode(NodeType type, String instruction, @NotNull N astNode) {
+ this(IdHelper.getInstance().getNextId(), type, instruction, astNode);
+ }
+
+ protected GraphNode(long id, NodeType type, String instruction, @NotNull N astNode) {
+ this(id, type, instruction, astNode, new LinkedList<>());
+ extractVariables();
+ }
- extractVariables(astNode);
+ protected GraphNode(NodeType type, String instruction, @NotNull N astNode, List variableActions) {
+ this(IdHelper.getInstance().getNextId(), type, instruction, astNode, variableActions);
}
- GraphNode(
- long id,
- NodeType type,
- String instruction,
- @NotNull N astNode,
- Collection declaredVariables,
- Collection definedVariables,
- Collection usedVariables
- ) {
+ protected GraphNode(long id, NodeType type, String instruction, @NotNull N astNode, List variableActions) {
this.id = id;
this.nodeType = type;
this.instruction = instruction;
this.astNode = astNode;
-
- this.declaredVariables = new HashSet<>(declaredVariables);
- this.definedVariables = new HashSet<>(definedVariables);
- this.usedVariables = new HashSet<>(usedVariables);
+ this.variableActions = variableActions;
}
- private void extractVariables(@NotNull Node node) {
- new VariableExtractor()
- .setOnVariableDeclarationListener(this.declaredVariables::add)
- .setOnVariableDefinitionListener(this.definedVariables::add)
- .setOnVariableUseListener(this.usedVariables::add)
- .visit(node);
+ protected void extractVariables() {
+ new VariableVisitor().startVisit(this);
}
public long getId() {
@@ -96,16 +75,20 @@ public class GraphNode implements Comparable> {
return astNode;
}
- public void addDeclaredVariable(String variable) {
- declaredVariables.add(variable);
+ public void addDeclaredVariable(NameExpr variable) {
+ variableActions.add(new VariableAction.Declaration(variable, this));
}
- public void addDefinedVariable(String variable) {
- definedVariables.add(variable);
+ public VariableAction.Definition addDefinedVariable(NameExpr variable) {
+ VariableAction.Definition def = new VariableAction.Definition(variable, this);
+ variableActions.add(def);
+ return def;
}
- public void addUsedVariable(String variable) {
- usedVariables.add(variable);
+ public VariableAction.Usage addUsedVariable(NameExpr variable) {
+ VariableAction.Usage use = new VariableAction.Usage(variable, this);
+ variableActions.add(use);
+ return use;
}
@Override
@@ -129,16 +112,8 @@ public class GraphNode implements Comparable