From 8bf641193c6cf913153ee2b7a10a73eaaa412bc2 Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Wed, 22 Jul 2020 13:09:58 +0200 Subject: [PATCH 1/9] Add exception-sensitive slicing Other changes: corrections, improvements and API simplification --- pom.xml | 2 + sdg-cli/pom.xml | 4 +- sdg-cli/src/main/java/tfm/cli/GraphLog.java | 38 +- sdg-cli/src/main/java/tfm/cli/PDGLog.java | 8 +- sdg-cli/src/main/java/tfm/cli/PHPSlice.java | 143 +++++++ .../src/main/java/tfm/cli/SlicedSDGLog.java | 55 +++ sdg-cli/src/main/java/tfm/cli/Slicer.java | 36 +- sdg-core/pom.xml | 2 +- sdg-core/src/main/java/tfm/arcs/Arc.java | 32 +- .../java/tfm/arcs/cfg/ControlFlowArc.java | 5 +- .../pdg/ConditionalControlDependencyArc.java | 47 +++ .../java/tfm/arcs/pdg/DataDependencyArc.java | 37 +- .../java/tfm/arcs/sdg/InterproceduralArc.java | 17 + .../java/tfm/arcs/sdg/ParameterInOutArc.java | 6 +- .../src/main/java/tfm/arcs/sdg/ReturnArc.java | 13 + sdg-core/src/main/java/tfm/graphs/Graph.java | 43 +- .../java/tfm/graphs/GraphWithRootNode.java | 22 +- .../main/java/tfm/graphs/augmented/ACFG.java | 20 + .../tfm/graphs/augmented/ACFGBuilder.java | 25 +- .../augmented/ControlDependencyBuilder.java | 32 ++ .../main/java/tfm/graphs/augmented/PPDG.java | 26 +- .../src/main/java/tfm/graphs/cfg/CFG.java | 69 ++-- .../main/java/tfm/graphs/cfg/CFGBuilder.java | 85 ++-- .../ConditionalControlDependencyBuilder.java | 155 ++++++++ .../tfm/graphs/exceptionsensitive/ESCFG.java | 374 ++++++++++++++++++ .../tfm/graphs/exceptionsensitive/ESPDG.java | 41 ++ .../tfm/graphs/exceptionsensitive/ESSDG.java | 73 ++++ ...ionSensitiveMethodCallReplacerVisitor.java | 60 +++ .../ExceptionSourceSearcher.java | 93 +++++ .../graphs/pdg/ControlDependencyBuilder.java | 74 ++-- .../tfm/graphs/pdg/DataDependencyBuilder.java | 94 ----- .../src/main/java/tfm/graphs/pdg/PDG.java | 108 +++-- .../main/java/tfm/graphs/pdg/PDGBuilder.java | 92 ----- .../tfm/graphs/sdg/MethodCallReplacer.java | 19 - .../graphs/sdg/MethodCallReplacerVisitor.java | 308 +++++++-------- .../src/main/java/tfm/graphs/sdg/SDG.java | 85 ++-- .../main/java/tfm/graphs/sdg/SDGBuilder.java | 88 +---- .../sdg/sumarcs/NaiveSummaryArcsBuilder.java | 19 +- .../src/main/java/tfm/nodes/ActualIONode.java | 70 ++++ .../src/main/java/tfm/nodes/CallNode.java | 12 + .../java/tfm/nodes/ExceptionExitNode.java | 31 ++ .../java/tfm/nodes/ExceptionReturnNode.java | 31 ++ .../src/main/java/tfm/nodes/ExitNode.java | 14 + .../src/main/java/tfm/nodes/FormalIONode.java | 41 ++ .../src/main/java/tfm/nodes/GraphNode.java | 97 ++--- sdg-core/src/main/java/tfm/nodes/IONode.java | 41 ++ .../src/main/java/tfm/nodes/IdHelper.java | 7 +- .../src/main/java/tfm/nodes/NodeFactory.java | 10 +- .../main/java/tfm/nodes/NormalExitNode.java | 10 + .../main/java/tfm/nodes/NormalReturnNode.java | 10 + .../src/main/java/tfm/nodes/ReturnNode.java | 10 + .../main/java/tfm/nodes/SyntheticNode.java | 16 + .../main/java/tfm/nodes/TypeNodeFactory.java | 22 +- .../main/java/tfm/nodes/VariableAction.java | 108 +++++ .../main/java/tfm/nodes/VariableVisitor.java | 172 ++++++++ .../main/java/tfm/nodes/type/NodeType.java | 10 +- .../tfm/slicing/ClassicSlicingAlgorithm.java | 11 +- .../ExceptionSensitiveSlicingAlgorithm.java | 151 +++++++ .../tfm/slicing/NodeIdSlicingCriterion.java | 36 ++ .../PseudoPredicateSlicingAlgorithm.java | 9 +- sdg-core/src/main/java/tfm/slicing/Slice.java | 4 + .../src/main/java/tfm/utils/ASTUtils.java | 10 - sdg-core/src/main/java/tfm/utils/Context.java | 9 +- sdg-core/src/main/java/tfm/utils/Logger.java | 22 +- sdg-core/src/main/java/tfm/utils/Utils.java | 12 +- .../java/tfm/variables/VariableExtractor.java | 71 ---- .../java/tfm/variables/VariableVisitor.java | 191 --------- .../tfm/variables/actions/VariableAction.java | 49 --- .../actions/VariableDeclaration.java | 25 -- .../variables/actions/VariableDefinition.java | 25 -- .../tfm/variables/actions/VariableUse.java | 25 -- .../tfm/graphs/pdg/HandCraftedGraphs.java | 8 +- .../test/java/tfm/graphs/pdg/PDGTests.java | 48 +-- sdg-core/src/test/res/carlos/Classic.java | 39 ++ sdg-core/src/test/res/carlos/Problem3.java | 8 +- sdg-core/src/test/res/java-slicing-benchmarks | 1 - .../src/test/res/programs/sdg/Example1.java | 5 +- sdg-core/src/test/res/review-07-2020/P1.java | 68 ++++ sdg-core/src/test/res/review-07-2020/P2.java | 13 + sdg-core/src/test/res/review-07-2020/P3.java | 47 +++ sdg-core/src/test/res/review-07-2020/P4.java | 63 +++ sdg-core/src/test/res/review-07-2020/P5.java | 50 +++ 82 files changed, 2902 insertions(+), 1260 deletions(-) create mode 100644 sdg-cli/src/main/java/tfm/cli/PHPSlice.java create mode 100644 sdg-cli/src/main/java/tfm/cli/SlicedSDGLog.java create mode 100644 sdg-core/src/main/java/tfm/arcs/pdg/ConditionalControlDependencyArc.java create mode 100644 sdg-core/src/main/java/tfm/arcs/sdg/ReturnArc.java create mode 100644 sdg-core/src/main/java/tfm/graphs/augmented/ControlDependencyBuilder.java create mode 100644 sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ConditionalControlDependencyBuilder.java create mode 100644 sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java create mode 100644 sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESPDG.java create mode 100644 sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java create mode 100644 sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java create mode 100644 sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java delete mode 100644 sdg-core/src/main/java/tfm/graphs/pdg/DataDependencyBuilder.java delete mode 100644 sdg-core/src/main/java/tfm/graphs/pdg/PDGBuilder.java delete mode 100644 sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacer.java create mode 100644 sdg-core/src/main/java/tfm/nodes/ActualIONode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/CallNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/ExceptionExitNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/ExceptionReturnNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/ExitNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/FormalIONode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/IONode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/NormalExitNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/NormalReturnNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/ReturnNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/SyntheticNode.java create mode 100644 sdg-core/src/main/java/tfm/nodes/VariableAction.java create mode 100644 sdg-core/src/main/java/tfm/nodes/VariableVisitor.java create mode 100644 sdg-core/src/main/java/tfm/slicing/ExceptionSensitiveSlicingAlgorithm.java create mode 100644 sdg-core/src/main/java/tfm/slicing/NodeIdSlicingCriterion.java delete mode 100644 sdg-core/src/main/java/tfm/variables/VariableExtractor.java delete mode 100644 sdg-core/src/main/java/tfm/variables/VariableVisitor.java delete mode 100644 sdg-core/src/main/java/tfm/variables/actions/VariableAction.java delete mode 100644 sdg-core/src/main/java/tfm/variables/actions/VariableDeclaration.java delete mode 100644 sdg-core/src/main/java/tfm/variables/actions/VariableDefinition.java delete mode 100644 sdg-core/src/main/java/tfm/variables/actions/VariableUse.java create mode 100644 sdg-core/src/test/res/carlos/Classic.java delete mode 160000 sdg-core/src/test/res/java-slicing-benchmarks create mode 100644 sdg-core/src/test/res/review-07-2020/P1.java create mode 100644 sdg-core/src/test/res/review-07-2020/P2.java create mode 100644 sdg-core/src/test/res/review-07-2020/P3.java create mode 100644 sdg-core/src/test/res/review-07-2020/P4.java create mode 100644 sdg-core/src/test/res/review-07-2020/P5.java diff --git a/pom.xml b/pom.xml index ebed3bf..0a27175 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/pom.xml b/sdg-cli/pom.xml index 0984278..fffc0cd 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -6,7 +6,7 @@ tfm sdg-cli - 1.0-SNAPSHOT + 1.0.0 11 @@ -27,7 +27,7 @@ tfm sdg-core - 1.0-SNAPSHOT + 1.0.0 compile diff --git a/sdg-cli/src/main/java/tfm/cli/GraphLog.java b/sdg-cli/src/main/java/tfm/cli/GraphLog.java index baaa4c8..1becbce 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 a50a51c..f8eed98 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 0000000..6536dda --- /dev/null +++ b/sdg-cli/src/main/java/tfm/cli/PHPSlice.java @@ -0,0 +1,143 @@ +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); + + // Slice the SDG + SlicingCriterion sc = new NodeIdSlicingCriterion(scId, ""); + Slice 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()); + } + } + +} 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 0000000..7b14693 --- /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 f6c46c9..268c631 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 88d56ba..71cebbf 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -6,7 +6,7 @@ tfm sdg-core - 1.0-SNAPSHOT + 1.0.0 11 diff --git a/sdg-core/src/main/java/tfm/arcs/Arc.java b/sdg-core/src/main/java/tfm/arcs/Arc.java index e9ecd30..a21fd93 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 4a5f23f..ede205d 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 0000000..39623a1 --- /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 a0c7725..e133d70 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,13 +18,40 @@ 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 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 13c7471..21a0139 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 4f377d7..fa78470 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 0000000..040c103 --- /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 bb0fab6..5c7c3ac 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/GraphWithRootNode.java b/sdg-core/src/main/java/tfm/graphs/GraphWithRootNode.java index d7a3442..0bf2932 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 b05e44e..315de53 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 37bb23f..6ed0206 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); @@ -244,11 +259,11 @@ public class ACFGBuilder extends CFGBuilder { 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 0000000..0d5fa9d --- /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 6dd57cf..1e19889 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 ddf50c3..52d6b4f 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 0038c50..fba7ca9 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,7 +307,7 @@ 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 @@ -302,32 +318,41 @@ public class CFGBuilder extends VoidVisitorAdapter { 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()) - connectTo(addFormalOutGraphNode(param)); + createAndConnectFormalOutNodes(methodDeclaration); // 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 0000000..94112d4 --- /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 0000000..a9d8ed6 --- /dev/null +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java @@ -0,0 +1,374 @@ +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 ExceptionSource(GraphNode source, Collection exceptionTypes) { + this(source, exceptionTypes.toArray(ResolvedType[]::new)); + } + + 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.getNameAsString(), 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); + 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(); + } + + // Add an exception source for the call node + populateExceptionSourceMap(new ExceptionSource(stmtNode, resolved.getSpecifiedExceptions())); + + // 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 0000000..626c938 --- /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 0000000..e1be7f9 --- /dev/null +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java @@ -0,0 +1,73 @@ +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()); + nodeList.accept(new ExceptionSensitiveMethodCallReplacerVisitor(this), new Context()); + 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 0000000..ea983ea --- /dev/null +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java @@ -0,0 +1,60 @@ +package tfm.graphs.exceptionsensitive; + +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import tfm.graphs.sdg.MethodCallReplacerVisitor; +import tfm.nodes.*; +import tfm.utils.Context; + +import java.util.Set; +import java.util.stream.Collectors; + +public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallReplacerVisitor { + public ExceptionSensitiveMethodCallReplacerVisitor(ESSDG sdg) { + super(sdg); + } + + @Override + public void visit(MethodCallExpr methodCallExpr, Context context) { + if (methodCallExpr.resolve().getNumberOfSpecifiedExceptions() > 0) + connectExitToReturn(methodCallExpr); + super.visit(methodCallExpr, context); + } + + protected void connectExitToReturn(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(); + + 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); + + for (ResolvedType type : resolvedDecl.getSpecifiedExceptions()) { + ReturnNode exceptionReturn = synthNodes.stream() + .filter(ExceptionReturnNode.class::isInstance) + .map(ExceptionReturnNode.class::cast) + .filter(n -> n.getAstNode() == call) + .filter(n -> n.getExceptionType().equals(type)) + .findAny().orElseThrow(); + ExitNode exceptionExit = synthNodes.stream() + .filter(ExceptionExitNode.class::isInstance) + .map(ExceptionExitNode.class::cast) + .filter(n -> n.getAstNode() == decl) + .filter(n -> n.getExceptionType().equals(type)) + .findAny().orElseThrow(); + ((ESSDG) sdg).addReturnArc(exceptionExit, exceptionReturn); + } + } +} 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 0000000..a577015 --- /dev/null +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java @@ -0,0 +1,93 @@ +package tfm.graphs.exceptionsensitive; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.stmt.*; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.Collection; +import java.util.Collections; + +public class ExceptionSourceSearcher extends VoidVisitorAdapter { + 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); + } + + // The following are completely traversed (they contain no statements): + // AssertStmt, BreakStmt, ContinueStmt, EmptyStmt, ExplicitConstructorInvocationStmt, + // ReturnStmt, ExpressionStmt, LocalClassDeclarationStmt + + // The following are not traversed at all: they only contain statements + @Override + public void visit(BlockStmt n, Void arg) {} + + @Override + public void visit(SwitchEntryStmt n, Void arg) {} + + @Override + public void visit(TryStmt n, Void arg) {} + + @Override + public void visit(LabeledStmt n, Void arg) {} + + // The following are partially traversed (expressions are traversed, statements are not) + @Override + public void visit(DoStmt n, Void arg) { + n.getCondition().accept(this, arg); + } + + @Override + public void visit(ForEachStmt n, Void arg) { + n.getIterable().accept(this, arg); + } + + @Override + public void visit(ForStmt n, Void arg) { + n.getCompare().ifPresent(comp -> comp.accept(this, arg)); + } + + @Override + public void visit(IfStmt n, Void arg) { + n.getCondition().accept(this, arg); + } + + @Override + public void visit(SwitchStmt n, Void arg) { + n.getSelector().accept(this, arg); + } + + @Override + public void visit(WhileStmt n, Void arg) { + n.getCondition().accept(this, 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 fd4cd4c..29aaa16 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 f8d11d5..0000000 --- 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 976ee64..f7e219a 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: + *
    + *
  1. Create an empty {@link CFG}.
  2. + *
  3. Create an empty {@link PDG} (optionally passing the {@link CFG} as argument).
  4. + *
  5. Create a new {@link Builder}, passing both graphs as arguments.
  6. + *
  7. 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)}
  8. + *
  9. 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.
  10. + *
+ */ + 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 321edd0..0000000 --- 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: - *
    - *
  1. Create an empty {@link CFG}.
  2. - *
  3. Create an empty {@link PDG} (optionally passing the {@link CFG} as argument).
  4. - *
  5. Create a new {@link PDGBuilder}, passing both graphs as arguments.
  6. - *
  7. 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)}
  8. - *
  9. 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.
  10. - *
- */ -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 c3bc8af..0000000 --- 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 277f073..e9e43fa 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,95 @@ package tfm.graphs.sdg; import com.github.javaparser.ast.ArrayCreationLevel; -import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.ConstructorDeclaration; 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.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.cfg.CFGBuilder; +import tfm.nodes.*; import tfm.nodes.type.NodeType; import tfm.utils.Context; import tfm.utils.Logger; import java.util.*; +import java.util.stream.Collectors; -class MethodCallReplacerVisitor extends VoidVisitorAdapter { +public class MethodCallReplacerVisitor extends VoidVisitorAdapter { - private SDG sdg; - private GraphNode originalMethodCallNode; + protected final SDG sdg; + /** The current location, as a stack of statements (e.g. do -> if -> exprStmt). */ + protected Deque> stmtStack = new LinkedList<>(); 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); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(ForEachStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(ForStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(IfStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(SwitchStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(WhileStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(ReturnStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @Override public void visit(ExpressionStmt n, Context arg) { - searchAndSetMethodCallNode(n); + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); super.visit(n, arg); + stmtStack.pop(); } @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)); - GraphNode methodDeclarationNode; try { methodDeclarationNode = methodCallExpr.resolve().toAst() @@ -103,178 +100,177 @@ class MethodCallReplacerVisitor extends VoidVisitorAdapter { return; } - MethodDeclaration methodDeclaration = methodDeclarationNode.getAstNode(); - - Optional optionalCFG = sdg.getMethodCFG(methodDeclaration); - assert optionalCFG.isPresent(); - CFG methodCFG = optionalCFG.get(); - - 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(stmtStack.peek(), methodCallNode); sdg.addCallArc(methodCallNode, methodDeclarationNode); - NodeList parameters = methodDeclarationNode.getAstNode().getParameters(); for (int i = 0; i < parameters.size(); i++) { Parameter parameter = parameters.get(i); Expression argument; - if (!parameter.isVarArgs()) { argument = arguments.get(i); } else { NodeList varArgs = new NodeList<>(arguments.subList(i, arguments.size())); - argument = new ArrayCreationExpr( parameter.getType(), 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); + createActualIn(methodDeclarationNode, methodCallNode, parameter, argument, context); + createActualOut(methodDeclarationNode, methodCallNode, parameter, argument, context); + } - // Handle data dependency: Remove arc from method call node and add it to IN node + // 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)); + } - 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())); + protected void createActualIn(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument, Context context) { + 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(stmtStack.peek()).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 optionalParameterInNode = sdg.outgoingEdgesOf(declaration).stream() + .map(sdg::getEdgeTarget) + .filter(FormalIONode.class::isInstance) + .map(FormalIONode.class::cast) + .filter(argumentInNode::matchesFormalIO) + .findFirst(); - // Now, find the corresponding method declaration's in node and link argument node with it + if (optionalParameterInNode.isPresent()) { + sdg.addParameterInOutArc(argumentInNode, optionalParameterInNode.get()); + } else { + Logger.log(getClass().getSimpleName(), "WARNING: IN declaration node for argument " + argument + " not found."); + Logger.log(getClass().getSimpleName(), String.format("Context: %s, Method: %s, Call: %s", + context.getCurrentMethod().orElseThrow().getNameAsString(), + declaration.getAstNode().getSignature().asString(), + call.getAstNode())); + } + } - 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(); + protected void createActualOut(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument, Context context) { + Set variablesForOutNode = new HashSet<>(); + argument.accept(new OutNodeVariableVisitor(), variablesForOutNode); - 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 + // 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(); - OutNodeVariableVisitor shouldHaveOutNodeVisitor = new OutNodeVariableVisitor(); - Set variablesForOutNode = new HashSet<>(); - argument.accept(shouldHaveOutNodeVisitor, variablesForOutNode); + List> declarations = sdg.findDeclarationsOfVariable(variable, stmtStack.peek()); - // Here, variablesForOutNode may have 1 variable or more depending on the expression + Logger.log("MethodCallReplacerVisitor", String.format("Declarations of variable: '%s': %s", variable, declarations)); - Logger.log("MethodCallReplacerVisitor", String.format("Variables for out node: %s", variablesForOutNode)); - if (variablesForOutNode.isEmpty()) { - /* - If the argument is not a variable or it is not declared in the scope, - then there is no OUT node - */ + if (declarations.isEmpty()) { Logger.log("MethodCallReplacerVisitor", String.format("Expression '%s' should not have out node", argument.toString())); - continue; - } else if (variablesForOutNode.size() == 1) { - String variable = variablesForOutNode.iterator().next(); - - List> declarations = sdg.findDeclarationsOfVariable(variable, originalMethodCallNode); - - Logger.log("MethodCallReplacerVisitor", String.format("Declarations of variable: '%s': %s", variable, declarations)); - - if (declarations.isEmpty()) { - Logger.log("MethodCallReplacerVisitor", String.format("Expression '%s' should not have out node", argument.toString())); - continue; - } - } else { - continue; + return; } - - AssignExpr outVariableAssignExpr = new AssignExpr( - argument, - new NameExpr(parameter.getNameAsString() + "_out"), - AssignExpr.Operator.ASSIGN - ); - - ExpressionStmt outExprStmt = new ExpressionStmt(outVariableAssignExpr); - - GraphNode argumentOutNode = sdg.addNode(outExprStmt.toString(), outExprStmt, TypeNodeFactory.fromType(NodeType.ACTUAL_OUT)); - - sdg.addControlDependencyArc(methodCallNode, argumentOutNode); - - // Now, find the corresponding method call's out node and link argument node with it - - 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(); - - // Handle data dependency: remove arc from method call node and add it to OUT node - - 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())); - - 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)); - } - } - - // 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; + // Handle data dependency: copy arc from method call node and add it to OUT node + List arcsToRemove = sdg.outgoingEdgesOf(stmtStack.peek()).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: OUT declaration node for argument " + argument + " not found."); + Logger.log(getClass().getSimpleName(), String.format("Context: %s, Method: %s, Call: %s", + context.getCurrentMethod().orElseThrow().getNameAsString(), + declaration.getAstNode().getSignature().asString(), + call.getAstNode())); } + } - GraphNode declarationOutputNode = optionalDeclarationOutputNode.get(); + /** + * @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); + } - // 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)); + protected void processMethodOutputNode(GraphNode methodOutputNode, GraphNode methodCallNode) { + GraphNode callReturnNode = sdg.addNode("call output", new EmptyStmt(), + TypeNodeFactory.fromType(NodeType.METHOD_CALL_RETURN)); + VariableAction.Usage usage = stmtStack.peek().addUsedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT)); + VariableAction.Definition definition = callReturnNode.addDefinedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT)); sdg.addControlDependencyArc(methodCallNode, callReturnNode); - sdg.addDataDependencyArc(callReturnNode, originalMethodCallNode); - sdg.addParameterInOutArc(declarationOutputNode, callReturnNode); - - Logger.log("MethodCallReplacerVisitor", String.format("%s | Method '%s' called", methodCallExpr, methodDeclaration.getNameAsString())); + sdg.addDataDependencyArc(definition, usage); + sdg.addParameterInOutArc(methodOutputNode, callReturnNode); } - private void argumentAsNameExpr(GraphNode methodCallNode) { + @Override + public void visit(MethodDeclaration n, Context arg) { + arg.setCurrentMethod(n); + super.visit(n, arg); + arg.setCurrentMethod(null); + } + @Override + public void visit(ConstructorDeclaration n, Context arg) { +// super.visit(n, arg); SKIPPED BECAUSE IT WAS SKIPPED IN CFG CREATION AND COUNTLESS OTHER VISITORS } @Override - public void visit(MethodDeclaration n, Context arg) { - arg.setCurrentMethod(n); + public void visit(ClassOrInterfaceDeclaration n, Context arg) { + arg.setCurrentClass(n); + super.visit(n, arg); + arg.setCurrentClass(null); + } + @Override + public void visit(CompilationUnit n, Context arg) { + arg.setCurrentCU(n); super.visit(n, arg); + arg.setCurrentCU(null); } } 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 11bc80f..cff775b 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,26 @@ 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.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +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 +48,43 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) { - nodeList.accept(new SDGBuilder(this), new Context()); + nodeList.accept(createBuilder(), new Context()); + nodeList.accept(new MethodCallReplacerVisitor(this), new Context()); + 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 +100,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 33a13fd..9d860f7 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 8f11678..b2064d7 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 @@ -5,11 +5,14 @@ import com.github.javaparser.ast.stmt.ExpressionStmt; 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; @@ -19,14 +22,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() == 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 0000000..9bfaa53 --- /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 0000000..e2aad60 --- /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 0000000..9248332 --- /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 0000000..fef594f --- /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 0000000..6fbaf15 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/ExitNode.java @@ -0,0 +1,14 @@ +package tfm.nodes; + +import com.github.javaparser.ast.body.MethodDeclaration; +import tfm.nodes.type.NodeType; + +public class ExitNode extends SyntheticNode { + public ExitNode(MethodDeclaration astNode) { + super(NodeType.METHOD_EXIT, "Exit", astNode); + } + + protected ExitNode(NodeType type, String instruction, MethodDeclaration astNode) { + super(type, instruction, astNode); + } +} 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 0000000..91f0fc2 --- /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 0a7295b..9dd3fdf 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(astNode); } - GraphNode( - long id, - NodeType type, - String instruction, - @NotNull N astNode, - Collection declaredVariables, - Collection definedVariables, - Collection usedVariables - ) { + protected GraphNode(NodeType type, String instruction, @NotNull N astNode, List variableActions) { + this(IdHelper.getInstance().getNextId(), type, instruction, astNode, variableActions); + } + + 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(@NotNull Node node) { + new VariableVisitor(this::addUsedVariable, this::addDefinedVariable, this::addDeclaredVariable).search(node); } 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> { return Objects.hash(getId(), getNodeType(), getInstruction(), getAstNode()); } - public Set getDeclaredVariables() { - return declaredVariables; - } - - public Set getDefinedVariables() { - return definedVariables; - } - - public Set getUsedVariables() { - return usedVariables; + public List getVariableActions() { + return Collections.unmodifiableList(variableActions); } public String getInstruction() { diff --git a/sdg-core/src/main/java/tfm/nodes/IONode.java b/sdg-core/src/main/java/tfm/nodes/IONode.java new file mode 100644 index 0000000..48fa0c9 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/IONode.java @@ -0,0 +1,41 @@ +package tfm.nodes; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.type.Type; +import tfm.nodes.type.NodeType; + +import java.util.LinkedList; +import java.util.Objects; + +public abstract class IONode extends SyntheticNode { + protected Parameter parameter; + + protected IONode(NodeType type, String instruction, T astNode, Parameter parameter) { + super(type, instruction, astNode, new LinkedList<>()); + this.parameter = Objects.requireNonNull(parameter); + } + + public String getParameterName() { + return parameter.getNameAsString(); + } + + public Parameter getParameter() { + return parameter; + } + + public Type getType() { + return parameter.getType(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o) && o instanceof IONode && + ((IONode) o).parameter.equals(parameter); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), parameter); + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/IdHelper.java b/sdg-core/src/main/java/tfm/nodes/IdHelper.java index 94e267b..78f2637 100644 --- a/sdg-core/src/main/java/tfm/nodes/IdHelper.java +++ b/sdg-core/src/main/java/tfm/nodes/IdHelper.java @@ -1,6 +1,6 @@ package tfm.nodes; -class IdHelper { +public class IdHelper { private static final int START_ID = 0; @@ -19,4 +19,9 @@ class IdHelper { synchronized long getNextId() { return nextId++; } + + /** DO NOT USE!!! */ + public static synchronized void reset() { + getInstance().nextId = START_ID; + } } diff --git a/sdg-core/src/main/java/tfm/nodes/NodeFactory.java b/sdg-core/src/main/java/tfm/nodes/NodeFactory.java index b0c5889..32b8e7e 100644 --- a/sdg-core/src/main/java/tfm/nodes/NodeFactory.java +++ b/sdg-core/src/main/java/tfm/nodes/NodeFactory.java @@ -3,7 +3,7 @@ package tfm.nodes; import com.github.javaparser.ast.Node; import org.jetbrains.annotations.NotNull; -import java.util.Collection; +import java.util.List; public interface NodeFactory { @@ -13,18 +13,14 @@ public interface NodeFactory { * * @param instruction the instruction that represents * @param node the node of the AST that represents - * @param declaredVariables the set of declared variables - * @param definedVariables the set of defined variables - * @param usedVariables the set of used variables + * @param variableActions the list of variable actions performed in this node * @param the type of the AST node * @return a new GraphNode */ GraphNode computedGraphNode( @NotNull String instruction, @NotNull ASTNode node, - @NotNull Collection declaredVariables, - @NotNull Collection definedVariables, - @NotNull Collection usedVariables + @NotNull List variableActions ); diff --git a/sdg-core/src/main/java/tfm/nodes/NormalExitNode.java b/sdg-core/src/main/java/tfm/nodes/NormalExitNode.java new file mode 100644 index 0000000..1a83ad5 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/NormalExitNode.java @@ -0,0 +1,10 @@ +package tfm.nodes; + +import com.github.javaparser.ast.body.MethodDeclaration; +import tfm.nodes.type.NodeType; + +public class NormalExitNode extends ExitNode { + public NormalExitNode(MethodDeclaration astNode) { + super(NodeType.METHOD_NORMAL_EXIT, "normal exit", astNode); + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/NormalReturnNode.java b/sdg-core/src/main/java/tfm/nodes/NormalReturnNode.java new file mode 100644 index 0000000..b7cf340 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/NormalReturnNode.java @@ -0,0 +1,10 @@ +package tfm.nodes; + +import com.github.javaparser.ast.expr.MethodCallExpr; +import tfm.nodes.type.NodeType; + +public class NormalReturnNode extends ReturnNode { + public NormalReturnNode(MethodCallExpr astNode) { + super(NodeType.METHOD_CALL_NORMAL_RETURN, "normal return", astNode); + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/ReturnNode.java b/sdg-core/src/main/java/tfm/nodes/ReturnNode.java new file mode 100644 index 0000000..4d06548 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/ReturnNode.java @@ -0,0 +1,10 @@ +package tfm.nodes; + +import com.github.javaparser.ast.expr.MethodCallExpr; +import tfm.nodes.type.NodeType; + +public abstract class ReturnNode extends SyntheticNode { + protected ReturnNode(NodeType type, String instruction, MethodCallExpr astNode) { + super(type, instruction, astNode); + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/SyntheticNode.java b/sdg-core/src/main/java/tfm/nodes/SyntheticNode.java new file mode 100644 index 0000000..c38b9f9 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/SyntheticNode.java @@ -0,0 +1,16 @@ +package tfm.nodes; + +import com.github.javaparser.ast.Node; +import tfm.nodes.type.NodeType; + +import java.util.List; + +public abstract class SyntheticNode extends GraphNode { + protected SyntheticNode(NodeType type, String instruction, T astNode) { + super(type, instruction, astNode); + } + + protected SyntheticNode(NodeType type, String instruction, T astNode, List variableActions) { + super(type, instruction, astNode, variableActions); + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/TypeNodeFactory.java b/sdg-core/src/main/java/tfm/nodes/TypeNodeFactory.java index 56a2bb9..f6d15e2 100644 --- a/sdg-core/src/main/java/tfm/nodes/TypeNodeFactory.java +++ b/sdg-core/src/main/java/tfm/nodes/TypeNodeFactory.java @@ -4,7 +4,7 @@ import com.github.javaparser.ast.Node; import org.jetbrains.annotations.NotNull; import tfm.nodes.type.NodeType; -import java.util.Collection; +import java.util.List; import java.util.Objects; public abstract class TypeNodeFactory implements NodeFactory { @@ -21,25 +21,13 @@ public abstract class TypeNodeFactory implements NodeFactory { public GraphNode computedGraphNode( @NotNull String instruction, @NotNull ASTNode node, - @NotNull Collection declaredVariables, - @NotNull Collection definedVariables, - @NotNull Collection usedVariables + @NotNull List variableActions ) { Objects.requireNonNull(instruction, "Instruction cannot be null!"); Objects.requireNonNull(node, "AST Node cannot be null"); - Objects.requireNonNull(declaredVariables, "declared variables collection cannot be null!"); - Objects.requireNonNull(definedVariables, "defined variables collection cannot be null"); - Objects.requireNonNull(usedVariables, "Used variables collection cannot be null!"); - - return new GraphNode<>( - IdHelper.getInstance().getNextId(), - getSpecificType(), - instruction, - node, - declaredVariables, - definedVariables, - usedVariables - ); + Objects.requireNonNull(variableActions, "declared variables collection cannot be null!"); + + return new GraphNode<>(IdHelper.getInstance().getNextId(), getSpecificType(), instruction, node, variableActions); } public GraphNode graphNode( diff --git a/sdg-core/src/main/java/tfm/nodes/VariableAction.java b/sdg-core/src/main/java/tfm/nodes/VariableAction.java new file mode 100644 index 0000000..cad54c4 --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/VariableAction.java @@ -0,0 +1,108 @@ +package tfm.nodes; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.NameExpr; +import tfm.utils.ASTUtils; + +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +/** An action upon a variable (e.g. usage, definition) */ +public abstract class VariableAction { + protected final NameExpr variable; + protected final GraphNode graphNode; + + protected boolean optional = false; + + public VariableAction(NameExpr variable, GraphNode graphNode) { + this.variable = variable; + this.graphNode = graphNode; + } + + public VariableAction moveTo(GraphNode destination) { + getGraphNode().variableActions.remove(this); + try { + VariableAction a = getClass().getConstructor(NameExpr.class, GraphNode.class).newInstance(variable, destination); + destination.variableActions.add(a); + return a; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new UnsupportedOperationException("The VariableAction constructor has changed!", e); + } + } + + public String getVariable() { + return variable.getNameAsString(); + } + + public boolean isOptional() { + return optional; + } + + public GraphNode getGraphNode() { + return graphNode; + } + + public boolean matches(VariableAction action) { + return Objects.equals(action.variable, variable); + } + + public boolean isContainedIn(Node node) { + Node me = variable; + while (me != null) { + if (ASTUtils.equalsWithRangeInCU(node, me)) + return true; + me = me.getParentNode().orElse(null); + } + return false; + } + + public final boolean isUsage() { + return this instanceof Usage; + } + + public final boolean isDefinition() { + return this instanceof Definition; + } + + public final boolean isDeclaration() { + return this instanceof Declaration; + } + + @Override + public String toString() { + return "{" + variable + "}"; + } + + public static class Usage extends VariableAction { + public Usage(NameExpr variable, GraphNode graphNode) { + super(variable, graphNode); + } + + @Override + public String toString() { + return "USE" + super.toString(); + } + } + + public static class Definition extends VariableAction { + public Definition(NameExpr variable, GraphNode graphNode) { + super(variable, graphNode); + } + + @Override + public String toString() { + return "DEF" + super.toString(); + } + } + + public static class Declaration extends VariableAction { + public Declaration(NameExpr variable, GraphNode graphNode) { + super(variable, graphNode); + } + + @Override + public String toString() { + return "DEC" + super.toString(); + } + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java b/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java new file mode 100644 index 0000000..b36963b --- /dev/null +++ b/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java @@ -0,0 +1,172 @@ +package tfm.nodes; + +import com.github.javaparser.ast.Node; +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 java.util.Set; +import java.util.function.Consumer; + +public class VariableVisitor extends VoidVisitorAdapter { + enum Action { + DECLARATION, + DEFINITION, + USE; + + public Action or(Action action) { + if (action == DECLARATION || this == DECLARATION) + return DECLARATION; + if (action == DEFINITION || this == DEFINITION) + return DEFINITION; + return USE; + } + } + + protected static final Set PREFIX_CHANGE = Set.of( + UnaryExpr.Operator.PREFIX_DECREMENT, UnaryExpr.Operator.PREFIX_INCREMENT); + protected static final Set POSTFIX_CHANGE = Set.of( + UnaryExpr.Operator.POSTFIX_DECREMENT, UnaryExpr.Operator.POSTFIX_INCREMENT); + + protected final Consumer onUse; + protected final Consumer onDef; + protected final Consumer onDecl; + + public VariableVisitor(Consumer onUse, Consumer onDef, Consumer onDecl) { + this.onUse = onUse; + this.onDecl = onDecl; + this.onDef = onDef; + } + + public void search(Node node) { + node.accept(this, Action.USE); + } + + @Override + public void visit(NameExpr n, Action action) { + switch (action) { + case DECLARATION: + onDecl.accept(n); + break; + case DEFINITION: + onDef.accept(n); + break; + case USE: + onUse.accept(n); + break; + default: + throw new UnsupportedOperationException(); + } + } + + // Transparently traversed (they contain no statements): + // AssertStmt, BreakStmt, ContinueStmt, EmptyStmt, ExplicitConstructorInvocationStmt, + // ReturnStmt, ExpressionStmt, LocalClassDeclarationStmt + // Transparently traversed (they contain only USE expressions): + // ArrayAccessExpr, BinaryExpr, ConditionalExpr, EnclosedExpr + + // Not traversed at all (they only contain statements) + @Override + public void visit(BlockStmt n, Action arg) { + } + + @Override + public void visit(TryStmt n, Action arg) { + } + + // Partially traversed (only expressions that may contain variables are traversed) + @Override + public void visit(CastExpr n, Action action) { + n.getExpression().accept(this, action); + } + + @Override + public void visit(FieldAccessExpr n, Action action) { + n.getScope().accept(this, action); + } + + @Override + public void visit(DoStmt n, Action arg) { + n.getCondition().accept(this, Action.USE); + } + + @Override + public void visit(ForStmt n, Action arg) { + n.getCompare().ifPresent(expression -> expression.accept(this, Action.USE)); + } + + @Override + public void visit(WhileStmt n, Action arg) { + n.getCondition().accept(this, Action.USE); + } + + @Override + public void visit(IfStmt n, Action arg) { + n.getCondition().accept(this, Action.USE); + } + + @Override + public void visit(SwitchEntryStmt n, Action arg) { + n.getLabel().ifPresent(expression -> expression.accept(this, Action.USE)); + } + + @Override + public void visit(SwitchStmt n, Action arg) { + n.getSelector().accept(this, Action.USE); + } + + // Modified traversal (there may be variable definitions or declarations) + @Override + public void visit(ForEachStmt n, Action action) { + n.getIterable().accept(this, Action.USE); + for (VariableDeclarator variable : n.getVariable().getVariables()) { + variable.getNameAsExpression().accept(this, Action.DECLARATION); + variable.getNameAsExpression().accept(this, Action.DEFINITION); + } + } + + @Override + public void visit(AssignExpr n, Action action) { + // Target will be used if operator is not '=' + if (n.getOperator() != AssignExpr.Operator.ASSIGN) + n.getTarget().accept(this, action); + n.getTarget().accept(this, action.or(Action.DEFINITION)); + n.getValue().accept(this, action); + } + + @Override + public void visit(UnaryExpr n, Action action) { + // ++a -> USAGE (get value), DEFINITION (add 1), USAGE (get new value) + // a++ -> USAGE (get value), DEFINITION (add 1) + // any other UnaryExpr (~, !, -) -> USAGE + if (PREFIX_CHANGE.contains(n.getOperator())) { + n.getExpression().accept(this, action); + n.getExpression().accept(this, action.or(Action.DEFINITION)); + } + n.getExpression().accept(this, action); + if (POSTFIX_CHANGE.contains(n.getOperator())) + n.getExpression().accept(this, action.or(Action.DEFINITION)); + } + + @Override + public void visit(VariableDeclarationExpr n, Action action) { + for (VariableDeclarator v : n.getVariables()) { + v.getNameAsExpression().accept(this, action.or(Action.DECLARATION)); + if (v.getInitializer().isPresent()) { + v.getNameAsExpression().accept(this, Action.DEFINITION); + v.getInitializer().get().accept(this, action); + } + } + } + + @Override + public void visit(MethodCallExpr n, Action action) { + n.getScope().ifPresent(expression -> expression.accept(this, action)); + for (Expression e : n.getArguments()) { + e.accept(this, action); + if (e.isNameExpr() || e.isFieldAccessExpr()) + e.accept(this, action.or(Action.DEFINITION)); + } + } +} diff --git a/sdg-core/src/main/java/tfm/nodes/type/NodeType.java b/sdg-core/src/main/java/tfm/nodes/type/NodeType.java index c44bcaf..0014096 100644 --- a/sdg-core/src/main/java/tfm/nodes/type/NodeType.java +++ b/sdg-core/src/main/java/tfm/nodes/type/NodeType.java @@ -8,9 +8,13 @@ public enum NodeType { METHOD_ENTER, /** The Exit node of a method. */ METHOD_EXIT, + /** The collector for natural exits of a method. */ + METHOD_NORMAL_EXIT, + /** The collector for exceptional exits of a given type of a method. */ + METHOD_EXCEPTION_EXIT, /** A method call, that is contained in a {@link #STATEMENT} node. */ METHOD_CALL, - /** An argument or globally accesible variable that + /** An argument or globally accessible variable that * has been used in a method call. */ ACTUAL_IN, /** An argument or globally accessible variable that @@ -22,6 +26,10 @@ public enum NodeType { /** An argument or globally accessible variable that * has been modified in a method declaration. */ FORMAL_OUT, + /** The representation of all normal exits from a method. */ + METHOD_CALL_NORMAL_RETURN, + /** The representation of all exceptional exits from a method. It may be split by type. */ + METHOD_CALL_EXCEPTION_RETURN, /** A node representing the return value of a non-void method call. */ METHOD_CALL_RETURN, /** A node representing the return value of a non-void method declaration. */ diff --git a/sdg-core/src/main/java/tfm/slicing/ClassicSlicingAlgorithm.java b/sdg-core/src/main/java/tfm/slicing/ClassicSlicingAlgorithm.java index e608af4..aa33c23 100644 --- a/sdg-core/src/main/java/tfm/slicing/ClassicSlicingAlgorithm.java +++ b/sdg-core/src/main/java/tfm/slicing/ClassicSlicingAlgorithm.java @@ -3,9 +3,9 @@ package tfm.slicing; import tfm.arcs.Arc; import tfm.graphs.Graph; import tfm.nodes.GraphNode; +import tfm.utils.Utils; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Predicate; @@ -41,7 +41,7 @@ public class ClassicSlicingAlgorithm implements SlicingAlgorithm { Set> visited = new HashSet<>(); while (!toVisit.isEmpty()) { - GraphNode node = removeFirst(toVisit); + GraphNode node = Utils.setPop(toVisit); // Avoid duplicate traversal if (visited.contains(node)) continue; @@ -58,11 +58,4 @@ public class ClassicSlicingAlgorithm implements SlicingAlgorithm { visited.forEach(slice::add); } - - protected static E removeFirst(Set set) { - Iterator i = set.iterator(); - E e = i.next(); - i.remove(); - return e; - } } diff --git a/sdg-core/src/main/java/tfm/slicing/ExceptionSensitiveSlicingAlgorithm.java b/sdg-core/src/main/java/tfm/slicing/ExceptionSensitiveSlicingAlgorithm.java new file mode 100644 index 0000000..0a1208d --- /dev/null +++ b/sdg-core/src/main/java/tfm/slicing/ExceptionSensitiveSlicingAlgorithm.java @@ -0,0 +1,151 @@ +package tfm.slicing; + +import tfm.arcs.Arc; +import tfm.arcs.pdg.ConditionalControlDependencyArc.CC1; +import tfm.arcs.pdg.ConditionalControlDependencyArc.CC2; +import tfm.arcs.pdg.ControlDependencyArc; +import tfm.graphs.exceptionsensitive.ESSDG; +import tfm.nodes.GraphNode; +import tfm.utils.Utils; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +// It doesn't inherit from PPSlicingAlgorithm because it's more difficult that way, +// plus the PPDG is inherently wrong (see SAS2020 paper on exceptions). +public class ExceptionSensitiveSlicingAlgorithm implements SlicingAlgorithm { + /** Return values for handlers. A node can either be skipped, traversed or not handled. + * In the second case, the handler is responsible of modifying the state of the algorithm. + * In the last case, the following handler is called. */ + protected static final int SKIPPED = 0, TRAVERSED = 1, NOT_HANDLED = 2; + + protected final ESSDG graph; + protected GraphNode slicingCriterion; + + /** Nodes that already in the slice and whose arcs have all been traversed. */ + protected final Set> visited = new HashSet<>(); + /** Nodes already in the slice whose arcs have been partially visited. + * The value stored is the number of arcs remaining */ + protected final Map, Integer> partlyVisited = new HashMap<>(); + /** Arcs that have already been traversed. No arc must be traversed twice. */ + protected final Set traversedArcs = new HashSet<>(); + /** Nodes that have been reached via an unconditional arc. + * The next step is to traverse their arcs and move them to 'visited' */ + protected final Set> reached = new HashSet<>(); + /** Current SDG check (it changes depending on the pass). */ + protected Predicate sdgSkipCheck; + + /** Arc handlers, in the order they are executed. Each handler is a predicate, + * if the arc is handled and added or skipped it should return true, for the + * chain to be stopped. Otherwise, it should return false and the next handler + * will try to handle it. The last handler must always handle the arcs that + * reach it (i.e. return true). */ + protected final List> HANDLERS = List.of( + this::handleRepeats, + arc -> sdgSkipCheck.test(arc) ? SKIPPED : NOT_HANDLED, + this::ppdgSkipCheck, + this::handleExceptionSensitive, + this::handleDefault + ); + + public ExceptionSensitiveSlicingAlgorithm(ESSDG graph) { + this.graph = Objects.requireNonNull(graph); + } + + @Override + public Slice traverse(GraphNode slicingCriterion) { + this.slicingCriterion = slicingCriterion; + reached.add(slicingCriterion); + sdgSkipCheck = Arc::isInterproceduralOutputArc; + pass(); + reached.addAll(partlyVisited.keySet()); + sdgSkipCheck = Arc::isInterproceduralInputArc; + pass(); + return createSlice(); + } + + protected Slice createSlice() { + Slice slice = new Slice(); + // Removes nodes that have only been visited by one kind of conditional control dependence + Predicate> pred = n -> slicingCriterion.equals(n) || + (!hasOnlyBeenReachedBy(n, CC1.class) && !hasOnlyBeenReachedBy(n, CC2.class)); + visited.stream().filter(pred).forEach(slice::add); + partlyVisited.keySet().stream().filter(pred).forEach(slice::add); + return slice; + } + + protected void pass() { + while (!reached.isEmpty()) { + GraphNode node = Utils.setPop(reached); + // Avoid duplicate traversal + if (visited.contains(node)) + continue; + // Traverse all edges backwards + Set incoming = graph.incomingEdgesOf(node); + int remaining = partlyVisited.getOrDefault(node, incoming.size()); + arcLoop: for (Arc arc : incoming) { + for (Function handler : HANDLERS) + switch (handler.apply(arc)) { + case TRAVERSED: // the arc has been traversed, count down and stop applying handlers + remaining--; + case SKIPPED: // stop applying handlers, go to next arc + continue arcLoop; + case NOT_HANDLED: // try the next handler + break; + default: + throw new UnsupportedOperationException("Handler answer not considered in switch"); + } + throw new IllegalStateException("This arc has not been handled by any handler"); + } + if (remaining == 0) { + visited.add(node); + partlyVisited.remove(node); + } else { + partlyVisited.put(node, remaining); + } + } + } + + protected int handleDefault(Arc arc) { + GraphNode src = graph.getEdgeSource(arc); + traversedArcs.add(arc); + if (!visited.contains(src)) + reached.add(src); + return TRAVERSED; + } + + protected int handleRepeats(Arc arc) { + return traversedArcs.contains(arc) ? SKIPPED : NOT_HANDLED; + } + + protected int ppdgSkipCheck(Arc arc) { + GraphNode node = graph.getEdgeTarget(arc); + return !node.equals(slicingCriterion) + && graph.isPseudoPredicate(node) + && hasOnlyBeenReachedBy(node, ControlDependencyArc.class) + && arc.isUnconditionalControlDependencyArc() + ? SKIPPED : NOT_HANDLED; + } + + protected int handleExceptionSensitive(Arc arc) { + GraphNode node = graph.getEdgeTarget(arc); + // Visit only CC1 if only CC1 has visited it + if (hasOnlyBeenReachedBy(node, CC1.class) && arc instanceof CC1) + return NOT_HANDLED; + // Visit none if the node has only been reached by conditional arcs + if (!node.equals(slicingCriterion) && reachedStream(node).allMatch(Arc::isConditionalControlDependencyArc)) + return SKIPPED; + // Otherwise (has been visited by other arcs) continue as normal + return NOT_HANDLED; + } + + protected boolean hasOnlyBeenReachedBy(GraphNode node, Class type) { + return reachedStream(node).allMatch(a -> a.getClass().equals(type)); + } + + protected Stream reachedStream(GraphNode node) { + return traversedArcs.stream().filter(arc -> graph.getEdgeSource(arc).equals(node)); + } +} diff --git a/sdg-core/src/main/java/tfm/slicing/NodeIdSlicingCriterion.java b/sdg-core/src/main/java/tfm/slicing/NodeIdSlicingCriterion.java new file mode 100644 index 0000000..234e303 --- /dev/null +++ b/sdg-core/src/main/java/tfm/slicing/NodeIdSlicingCriterion.java @@ -0,0 +1,36 @@ +package tfm.slicing; + +import tfm.graphs.cfg.CFG; +import tfm.graphs.pdg.PDG; +import tfm.graphs.sdg.SDG; +import tfm.nodes.GraphNode; + +import java.util.Optional; + +public class NodeIdSlicingCriterion extends SlicingCriterion { + protected final long id; + + public NodeIdSlicingCriterion(long id, String variable) { + super(variable); + this.id = id; + } + + public long getId() { + return id; + } + + @Override + public Optional> findNode(CFG graph) { + return graph.findNodeById(id); + } + + @Override + public Optional> findNode(PDG graph) { + return graph.findNodeById(id); + } + + @Override + public Optional> findNode(SDG graph) { + return graph.findNodeById(id); + } +} diff --git a/sdg-core/src/main/java/tfm/slicing/PseudoPredicateSlicingAlgorithm.java b/sdg-core/src/main/java/tfm/slicing/PseudoPredicateSlicingAlgorithm.java index 8f66c43..92abffb 100644 --- a/sdg-core/src/main/java/tfm/slicing/PseudoPredicateSlicingAlgorithm.java +++ b/sdg-core/src/main/java/tfm/slicing/PseudoPredicateSlicingAlgorithm.java @@ -1,14 +1,13 @@ package tfm.slicing; import tfm.arcs.Arc; -import tfm.graphs.Graph; +import tfm.graphs.exceptionsensitive.ESSDG; import tfm.nodes.GraphNode; -import tfm.utils.ASTUtils; public class PseudoPredicateSlicingAlgorithm extends ClassicSlicingAlgorithm { protected GraphNode slicingCriterion; - public PseudoPredicateSlicingAlgorithm(Graph graph) { + public PseudoPredicateSlicingAlgorithm(ESSDG graph) { super(graph); } @@ -30,6 +29,8 @@ public class PseudoPredicateSlicingAlgorithm extends ClassicSlicingAlgorithm { protected boolean ignorePseudoPredicate(Arc arc) { GraphNode target = graph.getEdgeTarget(arc); - return ASTUtils.isPseudoPredicate(target.getAstNode()) && target != slicingCriterion; + return ((ESSDG) graph).isPseudoPredicate(target) + && arc.isControlDependencyArc() + && target != slicingCriterion; } } diff --git a/sdg-core/src/main/java/tfm/slicing/Slice.java b/sdg-core/src/main/java/tfm/slicing/Slice.java index 485a5da..d74ec12 100644 --- a/sdg-core/src/main/java/tfm/slicing/Slice.java +++ b/sdg-core/src/main/java/tfm/slicing/Slice.java @@ -23,6 +23,10 @@ public class Slice { nodes.add(node.getAstNode()); } + public void addAll(Collection> nodes) { + nodes.forEach(this::add); + } + public boolean contains(GraphNode node) { return map.containsKey(node.getId()); } diff --git a/sdg-core/src/main/java/tfm/utils/ASTUtils.java b/sdg-core/src/main/java/tfm/utils/ASTUtils.java index dc3de70..0838481 100644 --- a/sdg-core/src/main/java/tfm/utils/ASTUtils.java +++ b/sdg-core/src/main/java/tfm/utils/ASTUtils.java @@ -69,16 +69,6 @@ public class ASTUtils { return null; } - public static boolean isPseudoPredicate(Node node) { - return node instanceof BreakStmt - || node instanceof ContinueStmt - || node instanceof ReturnStmt - || node instanceof ThrowStmt - || node instanceof SwitchEntryStmt - || node instanceof TryStmt - || node instanceof CatchClause; - } - public static boolean equalsWithRange(Node n1, Node n2) { return Objects.equals(n1.getRange(), n2.getRange()) && Objects.equals(n1, n2); } diff --git a/sdg-core/src/main/java/tfm/utils/Context.java b/sdg-core/src/main/java/tfm/utils/Context.java index 7d4e5eb..0a09485 100644 --- a/sdg-core/src/main/java/tfm/utils/Context.java +++ b/sdg-core/src/main/java/tfm/utils/Context.java @@ -2,6 +2,7 @@ package tfm.utils; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.nodeTypes.NodeWithName; @@ -14,10 +15,10 @@ public class Context { private CompilationUnit currentCU; private ClassOrInterfaceDeclaration currentClass; - private MethodDeclaration currentMethod; + private CallableDeclaration currentMethod; public Context() { - + this(null, null, null); } public Context(CompilationUnit cu) { @@ -48,7 +49,7 @@ public class Context { return Optional.ofNullable(currentClass); } - public Optional getCurrentMethod() { + public Optional> getCurrentMethod() { return Optional.ofNullable(currentMethod); } @@ -60,7 +61,7 @@ public class Context { this.currentClass = currentClass; } - public void setCurrentMethod(MethodDeclaration currentMethod) { + public void setCurrentMethod(CallableDeclaration currentMethod) { this.currentMethod = currentMethod; } diff --git a/sdg-core/src/main/java/tfm/utils/Logger.java b/sdg-core/src/main/java/tfm/utils/Logger.java index e621e29..d02a6f5 100644 --- a/sdg-core/src/main/java/tfm/utils/Logger.java +++ b/sdg-core/src/main/java/tfm/utils/Logger.java @@ -1,6 +1,24 @@ package tfm.utils; +import java.io.PrintStream; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + public class Logger { + protected static final List printStreams = new LinkedList<>(); + + static { + printStreams.add(System.out); + } + + public static void registerPrintStream(PrintStream ps) { + printStreams.add(Objects.requireNonNull(ps)); + } + + public static void clearPrintStreams() { + printStreams.clear(); + } public static void log() { log(""); @@ -19,12 +37,12 @@ public class Logger { } public static void log(String context, String message) { - System.out.println( + printStreams.forEach(out -> out.println( String.format("%s%s", context.isEmpty() ? "" : String.format("[%s]: ", context), message ) - ); + )); } public static void format(String message, Object... args) { diff --git a/sdg-core/src/main/java/tfm/utils/Utils.java b/sdg-core/src/main/java/tfm/utils/Utils.java index de41fe3..fa88c27 100644 --- a/sdg-core/src/main/java/tfm/utils/Utils.java +++ b/sdg-core/src/main/java/tfm/utils/Utils.java @@ -1,9 +1,6 @@ package tfm.utils; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; public class Utils { @@ -16,4 +13,11 @@ public class Utils { public static Set emptySet() { return new HashSet<>(0); } + + public static E setPop(Set set) { + Iterator it = set.iterator(); + E e = it.next(); + it.remove(); + return e; + } } diff --git a/sdg-core/src/main/java/tfm/variables/VariableExtractor.java b/sdg-core/src/main/java/tfm/variables/VariableExtractor.java deleted file mode 100644 index d482849..0000000 --- a/sdg-core/src/main/java/tfm/variables/VariableExtractor.java +++ /dev/null @@ -1,71 +0,0 @@ -package tfm.variables; - -import com.github.javaparser.ast.Node; -import org.checkerframework.checker.nullness.qual.NonNull; -import tfm.variables.actions.VariableAction; - -public class VariableExtractor { - - private VariableVisitor visitor; - - private OnVariableDeclarationListener onVariableDeclarationListener; - private OnVariableDefinitionListener onVariableDefinitionListener; - private OnVariableUseListener onVariableUseListener; - - public VariableExtractor() { - this.visitor = new VariableVisitor() { - @Override - void onVariableUse(@NonNull String variable) { - if (onVariableUseListener != null) - onVariableUseListener.onVariableUse(variable); - } - - @Override - void onVariableDefinition(@NonNull String variable) { - if (onVariableDefinitionListener != null) - onVariableDefinitionListener.onVariableDefinition(variable); - } - - @Override - void onVariableDeclaration(@NonNull String variable) { - if (onVariableDeclarationListener != null) - onVariableDeclarationListener.onVariableDeclaration(variable); - } - }; - } - - public VariableExtractor setOnVariableDeclarationListener(OnVariableDeclarationListener listener) { - this.onVariableDeclarationListener = listener; - return this; - } - - public VariableExtractor setOnVariableDefinitionListener(OnVariableDefinitionListener listener) { - this.onVariableDefinitionListener = listener; - return this; - } - - public VariableExtractor setOnVariableUseListener(OnVariableUseListener listener) { - this.onVariableUseListener = listener; - return this; - } - - public void visit(@NonNull Node node) { - node.accept(this.visitor, VariableAction.Actions.USE); - } - - @FunctionalInterface - public interface OnVariableDeclarationListener { - void onVariableDeclaration(String variable); - } - - @FunctionalInterface - public interface OnVariableDefinitionListener { - void onVariableDefinition(String variable); - } - - @FunctionalInterface - public interface OnVariableUseListener { - void onVariableUse(String variable); - } -} - diff --git a/sdg-core/src/main/java/tfm/variables/VariableVisitor.java b/sdg-core/src/main/java/tfm/variables/VariableVisitor.java deleted file mode 100644 index fb62998..0000000 --- a/sdg-core/src/main/java/tfm/variables/VariableVisitor.java +++ /dev/null @@ -1,191 +0,0 @@ -package tfm.variables; - -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 org.checkerframework.checker.nullness.qual.NonNull; -import tfm.variables.actions.VariableAction; - -abstract class VariableVisitor extends VoidVisitorAdapter { - - @Override - public void visit(ArrayAccessExpr n, VariableAction.Actions action) { - // // Logger.log("On ArrayAccessExpr: [" + n + "]"); - n.getName().accept(this, action.or(VariableAction.Actions.USE)); - n.getIndex().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(AssignExpr n, VariableAction.Actions action) { -// // Logger.log("On AssignExpr: [" + n + "]"); - - if (n.getOperator() != AssignExpr.Operator.ASSIGN) { // if +=, *=, -= ... - n.getTarget().accept(this, action.or(VariableAction.Actions.USE)); - } - - n.getTarget().accept(this, action.or(VariableAction.Actions.DEFINITION)); - n.getValue().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(BinaryExpr n, VariableAction.Actions action) { - // // Logger.log("On BinaryExpr: [" + n + "]"); - n.getLeft().accept(this, action.or(VariableAction.Actions.USE)); - n.getRight().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(BlockStmt n, VariableAction.Actions arg) { - // Logger.log("On blockStmt: " + n); - } - - @Override - public void visit(CastExpr n, VariableAction.Actions action) { - // // Logger.log("On CastExpr: [" + n + "]"); - n.getExpression().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(ConditionalExpr n, VariableAction.Actions action) { - // // Logger.log("On ConditionalExpr: [" + n + "]"); - n.getCondition().accept(this, action.or(VariableAction.Actions.USE)); - n.getThenExpr().accept(this, action.or(VariableAction.Actions.USE)); - n.getElseExpr().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(EnclosedExpr n, VariableAction.Actions action) { - // // Logger.log("On EnclosedExpr: [" + n + "]"); - n.getInner().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(FieldAccessExpr n, VariableAction.Actions action) { - // // Logger.log("On FieldAccessExpr: [" + n + "]"); - n.getScope().accept(this, action.or(VariableAction.Actions.USE)); - } - - @Override - public void visit(ForEachStmt n, VariableAction.Actions action) { - // Logger.log("On forEachStmt: " + n); - n.getIterable().accept(this, VariableAction.Actions.USE); - - for (VariableDeclarator variable : n.getVariable().getVariables()) { - variable.getNameAsExpression().accept(this, VariableAction.Actions.DECLARATION); - variable.getNameAsExpression().accept(this, VariableAction.Actions.DEFINITION); - } - } - - @Override - public void visit(ForStmt n, VariableAction.Actions arg) { - // Logger.log("On forStmt: " + n); -// n.getInitialization().forEach(stmt -> stmt.accept(this, VariableAction.Actions.USE)); - n.getCompare().ifPresent(expression -> expression.accept(this, VariableAction.Actions.USE)); -// n.getUpdate().forEach(stmt -> stmt.accept(this, VariableAction.Actions.USE)); - } - - @Override - public void visit(IfStmt n, VariableAction.Actions arg) { - // Logger.log("On ifStmt: " + n); - n.getCondition().accept(this, VariableAction.Actions.USE); - } - -// @Override -// public void visit(InstanceOfExpr n, Actions action) { -// ?? -// } - - // ??? - @Override - public void visit(MethodCallExpr n, VariableAction.Actions action) { - // // Logger.log("On MethodCallExpr: [" + n + "]"); - n.getScope().ifPresent(expression -> expression.accept(this, action.or(VariableAction.Actions.USE))); - n.getArguments().forEach(expression -> { - expression.accept(this, action.or(VariableAction.Actions.USE)); - - - if (expression.isNameExpr() || expression.isFieldAccessExpr()) { - expression.accept(this, action.or(VariableAction.Actions.DEFINITION)); - } - }); - } - - @Override - public void visit(NameExpr n, VariableAction.Actions action) { - // // Logger.log("On NameExpr. Found variable " + n.getNameAsString() + " and action " + action); - - String variable = n.getNameAsString(); - - switch (action) { - case DECLARATION: - onVariableDeclaration(variable); - break; - case DEFINITION: - onVariableDefinition(variable); - break; - default: - onVariableUse(variable); - break; - } - } - - @Override - public void visit(SwitchEntryStmt n, VariableAction.Actions arg) { - // Logger.log("On switchEntryStmt: " + n); - n.getLabel().ifPresent(expression -> expression.accept(this, VariableAction.Actions.USE)); - } - - @Override - public void visit(SwitchStmt n, VariableAction.Actions arg) { - // Logger.log("On switchStmt: " + n); - n.getSelector().accept(this, VariableAction.Actions.USE); - } - -// @Override -// public void visit(NormalAnnotationExpr n, Actions action) { -// ?? -// } - -// @Override -// public void visit(SingleMemberAnnotationExpr n, Actions action) { -// ?? -// } - - @Override - public void visit(UnaryExpr n, VariableAction.Actions action) { - // // Logger.log("On UnaryExpr: [" + n + "]"); - n.getExpression().accept(this, action.or(VariableAction.Actions.USE)); - n.getExpression().accept(this, action.or(VariableAction.Actions.DEFINITION)); - } - - @Override - public void visit(VariableDeclarationExpr n, VariableAction.Actions action) { - // // Logger.log("On VariableDeclarationExpr: [" + n + "]"); - n.getVariables() - .forEach(variableDeclarator -> { - variableDeclarator.getNameAsExpression().accept(this, action.or(VariableAction.Actions.DECLARATION)); // Declaration of the variable - variableDeclarator.getInitializer() - .ifPresent(expression -> { - variableDeclarator.getNameAsExpression().accept(this, action.or(VariableAction.Actions.DEFINITION)); // Definition of the variable (it is initialized) - expression.accept(this, action.or(VariableAction.Actions.USE)); // Use of the variables in the initialization - }); - }); - } - - @Override - public void visit(WhileStmt n, VariableAction.Actions arg) { - // Logger.log("On whileStmt: " + n); - n.getCondition().accept(this, VariableAction.Actions.USE); - } - - @Override - public void visit(SwitchExpr n, VariableAction.Actions action) { - // // Logger.log("On SwitchExpr: [" + n + "]"); - n.getSelector().accept(this, action.or(VariableAction.Actions.USE)); - } - - abstract void onVariableUse(@NonNull String variable); - abstract void onVariableDefinition(@NonNull String variable); - abstract void onVariableDeclaration(@NonNull String variable); -} diff --git a/sdg-core/src/main/java/tfm/variables/actions/VariableAction.java b/sdg-core/src/main/java/tfm/variables/actions/VariableAction.java deleted file mode 100644 index 53bcd52..0000000 --- a/sdg-core/src/main/java/tfm/variables/actions/VariableAction.java +++ /dev/null @@ -1,49 +0,0 @@ -package tfm.variables.actions; - -import tfm.nodes.GraphNode; - -public abstract class VariableAction { - - public enum Actions { - DECLARATION, - DEFINITION, - USE; - - public Actions or(Actions action) { - if (action == DECLARATION || this == DECLARATION) - return DECLARATION; - - if (action == DEFINITION || this == DEFINITION) - return DEFINITION; - - return USE; - } - - public String toString() { - return this == DECLARATION ? "declaration" : - (this == USE ? "use" : "definition"); - } - - } - private N node; - private String variable; - - protected VariableAction(String variable, N node) { - this.variable = variable; - this.node = node; - } - - public String getVariable() { - return variable; - } - - public N getNode() { - return node; - } - - public abstract boolean isDeclaration(); - - public abstract boolean isWrite(); - - public abstract boolean isRead(); -} diff --git a/sdg-core/src/main/java/tfm/variables/actions/VariableDeclaration.java b/sdg-core/src/main/java/tfm/variables/actions/VariableDeclaration.java deleted file mode 100644 index 34ff0c7..0000000 --- a/sdg-core/src/main/java/tfm/variables/actions/VariableDeclaration.java +++ /dev/null @@ -1,25 +0,0 @@ -package tfm.variables.actions; - -import tfm.nodes.GraphNode; - -public class VariableDeclaration extends VariableAction { - - public VariableDeclaration(String variable, N node) { - super(variable, node); - } - - @Override - public boolean isDeclaration() { - return true; - } - - @Override - public boolean isWrite() { - return false; - } - - @Override - public boolean isRead() { - return false; - } -} diff --git a/sdg-core/src/main/java/tfm/variables/actions/VariableDefinition.java b/sdg-core/src/main/java/tfm/variables/actions/VariableDefinition.java deleted file mode 100644 index 613b2da..0000000 --- a/sdg-core/src/main/java/tfm/variables/actions/VariableDefinition.java +++ /dev/null @@ -1,25 +0,0 @@ -package tfm.variables.actions; - -import tfm.nodes.GraphNode; - -public class VariableDefinition extends VariableAction { - - public VariableDefinition(String variable, N node) { - super(variable, node); - } - - @Override - public boolean isDeclaration() { - return false; - } - - @Override - public boolean isWrite() { - return true; - } - - @Override - public boolean isRead() { - return false; - } -} diff --git a/sdg-core/src/main/java/tfm/variables/actions/VariableUse.java b/sdg-core/src/main/java/tfm/variables/actions/VariableUse.java deleted file mode 100644 index 09453b4..0000000 --- a/sdg-core/src/main/java/tfm/variables/actions/VariableUse.java +++ /dev/null @@ -1,25 +0,0 @@ -package tfm.variables.actions; - -import tfm.nodes.GraphNode; - -public class VariableUse extends VariableAction { - - public VariableUse(String variable, N node) { - super(variable, node); - } - - @Override - public boolean isDeclaration() { - return false; - } - - @Override - public boolean isWrite() { - return false; - } - - @Override - public boolean isRead() { - return true; - } -} diff --git a/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java b/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java index 1a67253..7c0d02d 100644 --- a/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java +++ b/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java @@ -50,8 +50,8 @@ public class HandCraftedGraphs { cfg.addNonExecutableControlFlowEdge(cfg.getRootNode().get(), end); PPDG pdg = new PPDG(cfg); - ControlDependencyBuilder gen = new ControlDependencyBuilder(pdg, cfg); - gen.analyze(); + ControlDependencyBuilder gen = new ControlDependencyBuilder(cfg, pdg); + gen.build(); return pdg; } @@ -92,8 +92,8 @@ public class HandCraftedGraphs { cfg.addNonExecutableControlFlowEdge(cfg.getRootNode().get(), end); PPDG pdg = new PPDG(cfg); - ControlDependencyBuilder gen = new ControlDependencyBuilder(pdg, cfg); - gen.analyze(); + ControlDependencyBuilder gen = new ControlDependencyBuilder(cfg, pdg); + gen.build(); return pdg; } } diff --git a/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java b/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java index 106858d..b7ee6c5 100644 --- a/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java +++ b/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java @@ -81,22 +81,22 @@ public class PDGTests { cfg.build(root); PDG pdg = new PDG(cfg); pdg.buildRootNode("ENTER " + methodName, root, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - ctrlDepBuilder = new ControlDependencyBuilder(pdg, cfg); - ctrlDepBuilder.analyze(); + ctrlDepBuilder = new ControlDependencyBuilder(cfg, pdg); + ctrlDepBuilder.build(); // Create APDG ACFG acfg = new ACFG(); acfg.build(root); APDG apdg = new APDG(acfg); apdg.buildRootNode("ENTER " + methodName, root, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - ctrlDepBuilder = new ControlDependencyBuilder(apdg, acfg); - ctrlDepBuilder.analyze(); + ctrlDepBuilder = new ControlDependencyBuilder(acfg, apdg); + ctrlDepBuilder.build(); // Create PPDG PPDG ppdg = new PPDG(acfg); ppdg.buildRootNode("ENTER " + methodName, root, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - ctrlDepBuilder = new ControlDependencyBuilder(ppdg, acfg); - ctrlDepBuilder.analyze(); + ctrlDepBuilder = new ControlDependencyBuilder(acfg, ppdg); + ctrlDepBuilder.build(); // Print graphs (commented to decrease the test's time) String filePathNoExt = file.getPath().substring(0, file.getPath().lastIndexOf('.')); @@ -141,35 +141,35 @@ public class PDGTests { // Perform slices SlicingCriterion sc = new GraphNodeCriterion(node, "x"); - Slice[] slices = Arrays.stream(pdgs).map(p -> p.slice(sc)).toArray(Slice[]::new); +// Slice[] slices = Arrays.stream(pdgs).map(p -> p.slice(sc)).toArray(Slice[]::new); // Compare slices boolean ok = true; - Slice referenceSlice = slices[0]; - for (Slice slice : slices) { - ok = referenceSlice.equals(slice); - error |= !ok; - if (!ok) break; - } +// Slice referenceSlice = slices[0]; +// for (Slice slice : slices) { +// ok = referenceSlice.equals(slice); +// error |= !ok; +// if (!ok) break; +// } // Display slice Logger.log("Slicing on " + node.getId()); if (!ok) Logger.log("FAILED!"); - printSlices(pdgs[0], slices); +// printSlices(pdgs[0], slices); // Save slices as MethodDeclaration int i = 0; - for (Slice s : slices) { - i++; - try { - MethodDeclaration m = ((MethodDeclaration) s.getAst()); - m.setName(m.getName() + "_slice" + node.getId() + "_pdg" + i); - slicedMethods.add(m); - } catch (RuntimeException e) { - Logger.log("Error: " + e.getMessage()); - } - } +// for (Slice s : slices) { +// i++; +// try { +// MethodDeclaration m = ((MethodDeclaration) s.getAst()); +// m.setName(m.getName() + "_slice" + node.getId() + "_pdg" + i); +// slicedMethods.add(m); +// } catch (RuntimeException e) { +// Logger.log("Error: " + e.getMessage()); +// } +// } } return slicedMethods; } diff --git a/sdg-core/src/test/res/carlos/Classic.java b/sdg-core/src/test/res/carlos/Classic.java new file mode 100644 index 0000000..4b062ad --- /dev/null +++ b/sdg-core/src/test/res/carlos/Classic.java @@ -0,0 +1,39 @@ +public class Classic { + public static void main(String[] args) { + int sum = 0; + int product = 1; + int w = 7; + for (int i = 1; i < N; i++) { + sum = sum + i + w; + product *= i; + } + System.out.println(sum); + System.out.println(product); + } + + public static void main2(String[] args) { + String text = new Scanner(System.in).nextLine(); + int n = new Scanner(System.in).nextInt(); + int lines = 1; + int chars = 1; + String subtext = ""; + int i = 0; + while (i < text.size()) { + char c = text.charAt(i); + if (c == '\n') { + lines += 1; + chars += 1; + } else { + chars += 1; + if (n != 0) { + subtext = subtext + c; + n -= 1; + } + } + i++; + } + System.out.println(lines); + System.out.println(chars); + System.out.println(subtext); + } +} diff --git a/sdg-core/src/test/res/carlos/Problem3.java b/sdg-core/src/test/res/carlos/Problem3.java index 955ea04..546685d 100644 --- a/sdg-core/src/test/res/carlos/Problem3.java +++ b/sdg-core/src/test/res/carlos/Problem3.java @@ -6,15 +6,15 @@ public class Problem3 { try { f(); } catch (Exception e) { - log("error"); + System.out.println("error"); } x = 1; f(); } - public static void f() { + public static void f() throws Exception { if (x % 2 == 0) throw new Exception("error!"); - log("x = " + x); + System.out.println("x = " + x); } -} \ No newline at end of file +} diff --git a/sdg-core/src/test/res/java-slicing-benchmarks b/sdg-core/src/test/res/java-slicing-benchmarks deleted file mode 160000 index cc21305..0000000 --- a/sdg-core/src/test/res/java-slicing-benchmarks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cc21305fd85556362c596aeed4155f1effaf181d diff --git a/sdg-core/src/test/res/programs/sdg/Example1.java b/sdg-core/src/test/res/programs/sdg/Example1.java index 63a06cc..bcdee6d 100644 --- a/sdg-core/src/test/res/programs/sdg/Example1.java +++ b/sdg-core/src/test/res/programs/sdg/Example1.java @@ -1,7 +1,5 @@ package tfm.programs.sdg; -import tfm.utils.Logger; - public class Example1 { public static void main(String[] args) { @@ -10,8 +8,7 @@ public class Example1 { int f = sum(n1, n2); - Logger.log(f); - Logger.log(z); + System.out.println(f); } private static int sum(int x, int y) { diff --git a/sdg-core/src/test/res/review-07-2020/P1.java b/sdg-core/src/test/res/review-07-2020/P1.java new file mode 100644 index 0000000..812a5db --- /dev/null +++ b/sdg-core/src/test/res/review-07-2020/P1.java @@ -0,0 +1,68 @@ +// Problem: both 'equals' methods were included +// Solution +// 1. The output --> output (method_output -> method_call_return) was not classified as output. +// 2. The second pass of the slicing algorithm was misbehaving. +/** + * class FiguresGroup. + * + * @author LTP + * @version 2018-19 + */ +class FiguresGroup { + private static final int NUM_FIGURES = 10; + private Figure[] figuresList = new Figure[NUM_FIGURES]; + private int numF = 0; + + public void add(Figure f) { figuresList[numF++] = f; } + + public String toString() { + String s = ""; + for (int i = 0; i < numF; i++) { + s += "\n" + figuresList[i]; + } + return s; + } + private boolean found(Figure f) { + for (int i = 0; i < numF; i++) { + if (figuresList[i].equals(f)) return true; + } + return false; + } + private boolean included(FiguresGroup g) { + for (int i = 0; i < g.numF; i++) { + if (!found(g.figuresList[i])) return false; + } + return true; + } + public boolean equals(Object o) { + if (!(o instanceof FiguresGroup)) return false; + FiguresGroup g = (FiguresGroup) o; + return this.included(g) && g.included(this); + } + + public double area() { + double a = 0.0; + for (int i = 0; i < numF; i++) { + a += figuresList[i].area(); + } + return a; + } + + public Figure greatestFigure() { + if (numF == 0) return null; + Figure f = figuresList[0]; + double a = figuresList[0].area(); + for (int i = 1; i < numF; i++) { + double b = figuresList[i].area(); + if (a < b) { + f = figuresList[i]; + a = b; + } + } + return f; // SLICING CRITERION + } +} + +class Figure{ + int area() {return 5;} +} diff --git a/sdg-core/src/test/res/review-07-2020/P2.java b/sdg-core/src/test/res/review-07-2020/P2.java new file mode 100644 index 0000000..ed08df8 --- /dev/null +++ b/sdg-core/src/test/res/review-07-2020/P2.java @@ -0,0 +1,13 @@ +// Problem: the declaration 'int x;' was not included in the slice +// Solution: declarations were never considered, there is now a naive approach +// which finds the previous declaration for each definition (if there is no declaration in the same node). +// This solution does not take into account the concept of "scopes", for variables declared within a 'for'. +public class Bucles { + public static void main(String[] args){ + int x; + for(int i = 0; i <= 12; i++) + System.out.print("12 * "+ i + " = " + 12 * i + "\n"); + int i = 8; + x = 5+i; + } +} diff --git a/sdg-core/src/test/res/review-07-2020/P3.java b/sdg-core/src/test/res/review-07-2020/P3.java new file mode 100644 index 0000000..1e7cbd6 --- /dev/null +++ b/sdg-core/src/test/res/review-07-2020/P3.java @@ -0,0 +1,47 @@ +// Problem: In the third 'catch' there are no ways to traverse backwards, because the only arc is a CC1. +// The exception sources "should" be connected to the catch so that they can be included. ??? +// Solution: not solved +public class Bucles { + + public static void main(String[] args){ + int x=2; + try { + for(int i = 0; i <= 12; i++) + System.out.print("12 * "+ i + " = " + 12 * i + "\n"); + if (x==0) + throw new ExceptionA(); + if (x==1) + throw new ExceptionB(); + if (x==2) + throw new Exception(); + } + catch (ExceptionB a) + { + System.out.println("Se lanza ExceptionB"); + } + catch (ExceptionA a) + { + System.out.println("Se lanza ExceptionA"); + } + catch (Exception a) + { + System.out.println("Se lanza Exception"); + } + } +} + + class ExceptionA extends Exception{ + + /** + * + */ + private static final long serialVersionUID = 1L; + } + + class ExceptionB extends ExceptionA{ + + /** + * + */ + private static final long serialVersionUID = 1L; + } diff --git a/sdg-core/src/test/res/review-07-2020/P4.java b/sdg-core/src/test/res/review-07-2020/P4.java new file mode 100644 index 0000000..d61478d --- /dev/null +++ b/sdg-core/src/test/res/review-07-2020/P4.java @@ -0,0 +1,63 @@ +// Problem: there is no interprocedural-output edge between normal exit and normal return, exception exit and exception return! +// Modified ESSDG#build to modify MethodCallReplacerVisitor (created a sub-class). +public class Bucles { + + static int x=0; + + public static void main(String[] args){ + + try { + metodoGeneradorExcepciones(); + } + catch (ExceptionB a) + { + System.out.println("Se lanza ExceptionB"); + main(args); + } + catch (ExceptionA a) + { + System.out.println("Se lanza ExceptionA"); + main(args); + } + catch (Exception a) + { + System.out.println("Se lanza Exception"); + main(args); + } + + System.out.println("No se lanza ninguna excepcion"); + + } + + static void metodoGeneradorExcepciones() throws Exception { + if (x==0) { + x=1; + throw new ExceptionA(); + } + if (x==1) { + x=2; + throw new ExceptionB(); + } + if (x==2) { + x=3; + throw new Exception(); + } + } + +} + + class ExceptionA extends Exception{ + + /** + * + */ + private static final long serialVersionUID = 1L; + } + + class ExceptionB extends ExceptionA{ + + /** + * + */ + private static final long serialVersionUID = 1L; + } diff --git a/sdg-core/src/test/res/review-07-2020/P5.java b/sdg-core/src/test/res/review-07-2020/P5.java new file mode 100644 index 0000000..411510d --- /dev/null +++ b/sdg-core/src/test/res/review-07-2020/P5.java @@ -0,0 +1,50 @@ +// Problem: constructors are not traversed && both MethodDeclaration (random) are equal +public class Josep2 { + + public static void main(String[] args) { + int suma =0; + suma += factorial(5); + + Numeros num = new GrandesNumeros(7); + + suma += num.random(); + System.out.print(suma); // SLICING CRITERION + } + + static int factorial(int x) + { + int contador = 1; + int fact = 1; + while (contador Date: Wed, 22 Jul 2020 13:42:34 +0200 Subject: [PATCH 2/9] PHPSlice: More information on errors --- sdg-cli/pom.xml | 2 +- sdg-cli/src/main/java/tfm/cli/PHPSlice.java | 33 ++++++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index fffc0cd..040a0eb 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -6,7 +6,7 @@ tfm sdg-cli - 1.0.0 + 1.0.1 11 diff --git a/sdg-cli/src/main/java/tfm/cli/PHPSlice.java b/sdg-cli/src/main/java/tfm/cli/PHPSlice.java index 6536dda..6390f99 100644 --- a/sdg-cli/src/main/java/tfm/cli/PHPSlice.java +++ b/sdg-cli/src/main/java/tfm/cli/PHPSlice.java @@ -97,20 +97,24 @@ public class PHPSlice { SDG sdg = cliOpts.hasOption("exception-sensitive") ? new ESSDG() : new SDG(); sdg.build(units); - // Slice the SDG - SlicingCriterion sc = new NodeIdSlicingCriterion(scId, ""); - Slice 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); + 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); + } } } @@ -137,6 +141,7 @@ public class PHPSlice { new PHPSlice(args).slice(); } catch (Exception e) { System.err.println("Error!\n" + e.getMessage()); + e.printStackTrace(System.err); } } -- GitLab From eeac3bbc3c15da33f0028882ff9cd6568a6088c4 Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Wed, 22 Jul 2020 14:35:18 +0200 Subject: [PATCH 3/9] Minor fixes * DataDependencyArc: change style * Exit/ReturnNode: removed variable extraction * ESCFG: removed the method call as an exception source. --- sdg-cli/pom.xml | 2 +- sdg-core/pom.xml | 2 +- .../java/tfm/arcs/pdg/DataDependencyArc.java | 3 +- .../tfm/graphs/exceptionsensitive/ESCFG.java | 7 --- .../src/main/java/tfm/nodes/ExitNode.java | 6 ++- .../src/main/java/tfm/nodes/ReturnNode.java | 4 +- sdg-core/src/test/res/review-07-2020/P6.java | 51 +++++++++++++++++++ 7 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 sdg-core/src/test/res/review-07-2020/P6.java diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index 040a0eb..ad293d7 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -27,7 +27,7 @@ tfm sdg-core - 1.0.0 + 1.0.1 compile diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index 71cebbf..acd217b 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -6,7 +6,7 @@ tfm sdg-core - 1.0.0 + 1.0.1 11 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 e133d70..27dc529 100644 --- a/sdg-core/src/main/java/tfm/arcs/pdg/DataDependencyArc.java +++ b/sdg-core/src/main/java/tfm/arcs/pdg/DataDependencyArc.java @@ -57,8 +57,7 @@ public class DataDependencyArc extends Arc { @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/graphs/exceptionsensitive/ESCFG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java index a9d8ed6..268728f 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java @@ -60,10 +60,6 @@ public class ESCFG extends ACFG { exceptions.put(t, true); } - public ExceptionSource(GraphNode source, Collection exceptionTypes) { - this(source, exceptionTypes.toArray(ResolvedType[]::new)); - } - public void deactivateTypes(ResolvedReferenceType type) { exceptions.keySet().stream().filter(type::isAssignableBy).forEach(t -> exceptions.put(t, false)); } @@ -274,9 +270,6 @@ public class ESCFG extends ACFG { clearHanging(); } - // Add an exception source for the call node - populateExceptionSourceMap(new ExceptionSource(stmtNode, resolved.getSpecifiedExceptions())); - // Register set of return nodes pendingNormalReturnNodes.put(normalReturn, returnNodes); diff --git a/sdg-core/src/main/java/tfm/nodes/ExitNode.java b/sdg-core/src/main/java/tfm/nodes/ExitNode.java index 6fbaf15..daeadae 100644 --- a/sdg-core/src/main/java/tfm/nodes/ExitNode.java +++ b/sdg-core/src/main/java/tfm/nodes/ExitNode.java @@ -3,12 +3,14 @@ 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); + super(NodeType.METHOD_EXIT, "Exit", astNode, new LinkedList<>()); } protected ExitNode(NodeType type, String instruction, MethodDeclaration astNode) { - super(type, instruction, astNode); + super(type, instruction, astNode, new LinkedList<>()); } } diff --git a/sdg-core/src/main/java/tfm/nodes/ReturnNode.java b/sdg-core/src/main/java/tfm/nodes/ReturnNode.java index 4d06548..14379df 100644 --- a/sdg-core/src/main/java/tfm/nodes/ReturnNode.java +++ b/sdg-core/src/main/java/tfm/nodes/ReturnNode.java @@ -3,8 +3,10 @@ package tfm.nodes; import com.github.javaparser.ast.expr.MethodCallExpr; import tfm.nodes.type.NodeType; +import java.util.LinkedList; + public abstract class ReturnNode extends SyntheticNode { protected ReturnNode(NodeType type, String instruction, MethodCallExpr astNode) { - super(type, instruction, astNode); + super(type, instruction, astNode, new LinkedList<>()); } } diff --git a/sdg-core/src/test/res/review-07-2020/P6.java b/sdg-core/src/test/res/review-07-2020/P6.java new file mode 100644 index 0000000..6e17e7c --- /dev/null +++ b/sdg-core/src/test/res/review-07-2020/P6.java @@ -0,0 +1,51 @@ + +public class Bucles { + + public static void main(String[] args){ + + int x=2; + try { + + for(int i = 0; i <= 12; i++) + { + System.out.print("12 * "+ i + " = " + 12 * i + "\n"); + metodoGeneradorExcepciones(x); + } + } + catch (ExceptionB a) + { + System.out.println("Se lanza ExceptionB"); + } + catch (ExceptionA a) + { + System.out.println("Se lanza ExceptionB"); + } + catch (Exception a) + { + System.out.println("Se lanza Exception"); + } + } + + static void metodoGeneradorExcepciones(int x) throws Exception { + if (x==0) throw new ExceptionA(); + if (x==1) throw new ExceptionB(); + if (x==2) throw new Exception(); + } + +} + +class ExceptionA extends Exception{ + + /** + * + */ + private static final long serialVersionUID = 1L; +} + +class ExceptionB extends ExceptionA{ + + /** + * + */ + private static final long serialVersionUID = 1L; +} -- GitLab From 9c08cc9bb047080271cd22074daae869978d3b28 Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Thu, 23 Jul 2020 11:04:01 +0200 Subject: [PATCH 4/9] Connect all exception return nodes to some exception exit node Previously, those that did not perfectly match were discarded and left unconnected. --- sdg-cli/pom.xml | 2 +- sdg-core/pom.xml | 2 +- ...ionSensitiveMethodCallReplacerVisitor.java | 87 +++++++++++++++++-- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index ad293d7..09cf8e2 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -27,7 +27,7 @@ tfm sdg-core - 1.0.1 + 1.0.2 compile diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index acd217b..5d04621 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -6,7 +6,7 @@ tfm sdg-core - 1.0.1 + 1.0.2 11 diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java index ea983ea..b0e70d7 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java @@ -2,13 +2,15 @@ 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 tfm.utils.Context; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallReplacerVisitor { @@ -19,11 +21,18 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla @Override public void visit(MethodCallExpr methodCallExpr, Context context) { if (methodCallExpr.resolve().getNumberOfSpecifiedExceptions() > 0) - connectExitToReturn(methodCallExpr); + handleExceptionReturnArcs(methodCallExpr); super.visit(methodCallExpr, context); } - protected void connectExitToReturn(MethodCallExpr call) { + /** 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) @@ -31,6 +40,12 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla 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) @@ -40,21 +55,75 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla .filter(n -> n.getAstNode() == decl) .findAny().orElseThrow(); ((ESSDG) sdg).addReturnArc(normalExit, normalReturn); + } - for (ResolvedType type : resolvedDecl.getSpecifiedExceptions()) { - ReturnNode exceptionReturn = synthNodes.stream() + /** + * 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(); - ExitNode exceptionExit = synthNodes.stream() - .filter(ExceptionExitNode.class::isInstance) - .map(ExceptionExitNode.class::cast) - .filter(n -> n.getAstNode() == decl) + 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 search 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); } } -- GitLab From a1dc71e14cc4fbb4c9e3603377eb7b447e4fa81e Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Thu, 23 Jul 2020 13:12:31 +0200 Subject: [PATCH 5/9] Add SlicerTest (regression tester) --- sdg-cli/pom.xml | 2 +- sdg-core/pom.xml | 2 +- sdg-core/src/test/java/tfm/SlicerTest.java | 94 +++++++++ .../tfm/graphs/pdg/HandCraftedGraphs.java | 99 ---------- .../test/java/tfm/graphs/pdg/PDGTests.java | 187 ------------------ .../src/test/java/tfm/utils/FileFinder.java | 40 ---- 6 files changed, 96 insertions(+), 328 deletions(-) create mode 100644 sdg-core/src/test/java/tfm/SlicerTest.java delete mode 100644 sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java delete mode 100644 sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java delete mode 100644 sdg-core/src/test/java/tfm/utils/FileFinder.java diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index 09cf8e2..1452141 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -27,7 +27,7 @@ tfm sdg-core - 1.0.2 + 1.1.0 compile diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index 5d04621..1c0dda0 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -6,7 +6,7 @@ tfm sdg-core - 1.0.2 + 1.1.0 11 diff --git a/sdg-core/src/test/java/tfm/SlicerTest.java b/sdg-core/src/test/java/tfm/SlicerTest.java new file mode 100644 index 0000000..8904a06 --- /dev/null +++ b/sdg-core/src/test/java/tfm/SlicerTest.java @@ -0,0 +1,94 @@ +package tfm; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.symbolsolver.JavaSymbolSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tfm.graphs.exceptionsensitive.ESSDG; +import tfm.graphs.sdg.SDG; +import tfm.slicing.FileLineSlicingCriterion; +import tfm.slicing.Slice; +import tfm.slicing.SlicingCriterion; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.Scanner; + +public class SlicerTest { + static { + JavaParser.getStaticConfiguration().setAttributeComments(false); + CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver(); + combinedTypeSolver.add(new ReflectionTypeSolver(true)); + JavaParser.getStaticConfiguration().setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver)); + JavaParser.getStaticConfiguration().setAttributeComments(false); + } + + private static final String TEST_FILES = "./sdg-core/src/test/res/dinsa-tests"; + private static final String DOT_JAVA = ".java"; + + public static Collection findFiles(File directory, String suffix) throws FileNotFoundException { + Collection res = new LinkedList<>(); + File[] files = directory.listFiles(); + if (files == null) return Collections.emptyList(); + for (File f : files) { + if (f.getName().endsWith(suffix)) + res.add(Arguments.of(f, getSliceFile(f), getCriterionLine(f))); + if (f.isDirectory()) + res.addAll(findFiles(f, suffix)); + } + return res; + } + + public static Arguments[] findAllFiles() throws FileNotFoundException { + Collection args = findFiles(new File(TEST_FILES), DOT_JAVA); + return args.toArray(Arguments[]::new); + } + + private static File getSliceFile(File file) { + return new File(file.getParent(), file.getName() + ".sdg.sliced"); + } + + private static int getCriterionLine(File file) throws FileNotFoundException { + return new Scanner(new File(file.getParent(), file.getName() + ".sdg.criterion")).nextInt(); + } + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("findAllFiles") + public void sdgCompare(File source, File target, int criterionLine) throws FileNotFoundException { + // Build the SDG + SDG sdg = new ESSDG(); + sdg.build(new NodeList<>(JavaParser.parse(source))); + SlicingCriterion sc = new FileLineSlicingCriterion(source, criterionLine); + Slice slice = sdg.slice(sc); + + // Convert the slice to code and output the result to `outputDir` + NodeList slicedUnits = slice.toAst(); + assert slicedUnits.size() == 1; + if (!target.exists()) { + try (PrintWriter pw = new PrintWriter(target)) { + pw.print(slicedUnits.get(0).toString()); + } + return; + } + String targetSlice; + { + StringBuilder builder = new StringBuilder(); + Scanner in = new Scanner(target); + while (in.hasNextLine()) + builder.append(in.nextLine()).append('\n'); + targetSlice = builder.toString(); + } + String ourSlice = slicedUnits.get(0).toString(); + boolean equal = targetSlice.equals(ourSlice); + assert equal; + } +} diff --git a/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java b/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java deleted file mode 100644 index 7c0d02d..0000000 --- a/sdg-core/src/test/java/tfm/graphs/pdg/HandCraftedGraphs.java +++ /dev/null @@ -1,99 +0,0 @@ -package tfm.graphs.pdg; - -import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.expr.MethodCallExpr; -import com.github.javaparser.ast.stmt.ContinueStmt; -import com.github.javaparser.ast.stmt.EmptyStmt; -import com.github.javaparser.ast.stmt.IfStmt; -import com.github.javaparser.ast.stmt.WhileStmt; -import com.github.javaparser.ast.type.VoidType; -import tfm.graphs.augmented.ACFG; -import tfm.graphs.augmented.APDG; -import tfm.graphs.augmented.PPDG; -import tfm.nodes.GraphNode; -import tfm.nodes.TypeNodeFactory; -import tfm.nodes.type.NodeType; - -public class HandCraftedGraphs { - public static APDG problem1WithGotos() { - // Generate the control flow of a graph - ACFG cfg = new ACFG(); - cfg.buildRootNode("ENTER Problem1", new MethodDeclaration(new NodeList<>(), new VoidType(), "Problem1"), TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - GraphNode wx = cfg.addNode("while (X)", new WhileStmt()); - GraphNode ify = cfg.addNode("L: if (Y)", new IfStmt()); - GraphNode ifz = cfg.addNode("if (Z)", new IfStmt()); - GraphNode a = cfg.addNode("A();", new MethodCallExpr("A")); - GraphNode b = cfg.addNode("B();", new MethodCallExpr("B")); - GraphNode c = cfg.addNode("C();", new MethodCallExpr("C")); - GraphNode d = cfg.addNode("D();", new MethodCallExpr("D")); - GraphNode g1 = cfg.addNode("goto L;", new ContinueStmt("L")); - GraphNode g2 = cfg.addNode("goto L;", new ContinueStmt("L")); - - GraphNode end = cfg.addNode("Exit", new EmptyStmt()); - - cfg.addControlFlowEdge(cfg.getRootNode().get(), wx); - cfg.addControlFlowEdge(wx, ify); - cfg.addControlFlowEdge(wx, d); - cfg.addControlFlowEdge(ify, ifz); - cfg.addControlFlowEdge(ify, c); - cfg.addControlFlowEdge(ifz, a); - cfg.addControlFlowEdge(ifz, b); - cfg.addControlFlowEdge(a, g1); - cfg.addControlFlowEdge(b, g2); - cfg.addControlFlowEdge(c, wx); - cfg.addControlFlowEdge(d, end); - cfg.addNonExecutableControlFlowEdge(g1, b); - cfg.addControlFlowEdge(g1, ify); - cfg.addNonExecutableControlFlowEdge(g2, c); - cfg.addControlFlowEdge(g2, ify); - cfg.addNonExecutableControlFlowEdge(cfg.getRootNode().get(), end); - - PPDG pdg = new PPDG(cfg); - ControlDependencyBuilder gen = new ControlDependencyBuilder(cfg, pdg); - gen.build(); - return pdg; - } - - public static APDG problem1ContinueWithGotos() { - // Generate the control flow of a graph - ACFG cfg = new ACFG(); - cfg.buildRootNode("ENTER Problem1", new MethodDeclaration(new NodeList<>(), new VoidType(), "Problem1"), TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - GraphNode wx = cfg.addNode("while (X)", new WhileStmt()); - GraphNode ify = cfg.addNode("L: if (Y)", new IfStmt()); - GraphNode ifz = cfg.addNode("if (Z)", new IfStmt()); - GraphNode a = cfg.addNode("A();", new MethodCallExpr("A")); - GraphNode b = cfg.addNode("B();", new MethodCallExpr("B")); - GraphNode c = cfg.addNode("C();", new MethodCallExpr("C")); - GraphNode d = cfg.addNode("D();", new MethodCallExpr("D")); - GraphNode g1 = cfg.addNode("goto L1;", new ContinueStmt("L")); - GraphNode g2 = cfg.addNode("goto L2;", new ContinueStmt("L")); - GraphNode g3 = cfg.addNode("goto L3;", new ContinueStmt("L")); - - GraphNode end = cfg.addNode("Exit", new EmptyStmt()); - - cfg.addControlFlowEdge(cfg.getRootNode().get(), wx); - cfg.addControlFlowEdge(wx, ify); - cfg.addControlFlowEdge(wx, d); - cfg.addControlFlowEdge(ify, ifz); - cfg.addControlFlowEdge(ify, c); - cfg.addControlFlowEdge(ifz, a); - cfg.addControlFlowEdge(ifz, b); - cfg.addControlFlowEdge(a, g1); - cfg.addControlFlowEdge(b, g3); - cfg.addControlFlowEdge(c, wx); - cfg.addControlFlowEdge(d, end); - cfg.addNonExecutableControlFlowEdge(g1, b); - cfg.addControlFlowEdge(g1, ify); - cfg.addNonExecutableControlFlowEdge(g2, c); - cfg.addControlFlowEdge(g2, ify); - cfg.addNonExecutableControlFlowEdge(g3, g2); - cfg.addControlFlowEdge(g3, ify); - cfg.addNonExecutableControlFlowEdge(cfg.getRootNode().get(), end); - - PPDG pdg = new PPDG(cfg); - ControlDependencyBuilder gen = new ControlDependencyBuilder(cfg, pdg); - gen.build(); - return pdg; - } -} diff --git a/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java b/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java deleted file mode 100644 index b7ee6c5..0000000 --- a/sdg-core/src/test/java/tfm/graphs/pdg/PDGTests.java +++ /dev/null @@ -1,187 +0,0 @@ -package tfm.graphs.pdg; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ast.Modifier; -import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.stmt.ThrowStmt; -import com.github.javaparser.ast.stmt.TryStmt; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import tfm.graphs.augmented.ACFG; -import tfm.graphs.augmented.APDG; -import tfm.graphs.augmented.PPDG; -import tfm.graphs.cfg.CFG; -import tfm.nodes.GraphNode; -import tfm.nodes.TypeNodeFactory; -import tfm.nodes.type.NodeType; -import tfm.slicing.GraphNodeCriterion; -import tfm.slicing.Slice; -import tfm.slicing.SlicingCriterion; -import tfm.utils.Logger; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; - -public class PDGTests { - static { - JavaParser.getStaticConfiguration().setAttributeComments(false); - } - - private boolean error = false; - - @ParameterizedTest(name = "[{index}] {0} ({1})") - @MethodSource("tfm.utils.FileFinder#findAllMethodDeclarations") - public void ppdgTest(File file, String methodName, MethodDeclaration root) throws IOException { - runPdg(file, methodName, root, new PPDG()); - } - - @ParameterizedTest(name = "[{index}] {0} ({1})") - @MethodSource("tfm.utils.FileFinder#findAllMethodDeclarations") - public void apdgTest(File file, String methodName, MethodDeclaration root) throws IOException { - runPdg(file, methodName, root, new APDG()); - } - - @ParameterizedTest(name = "[{index}] {0} ({1})") - @MethodSource("tfm.utils.FileFinder#findAllMethodDeclarations") - public void pdgTest(File file, String methodName, MethodDeclaration root) throws IOException { - runPdg(file, methodName, root, new PDG()); - } - - private void runPdg(File file, String methodName, MethodDeclaration root, PDG pdg) throws IOException { - pdg.build(root); -// GraphLog graphLog = new PDGLog(pdg); -// graphLog.log(); -// try { -// graphLog.generateImages(file.getPath() + "-" + methodName); -// } catch (Exception e) { -// System.err.println("Could not generate PNG"); -// System.err.println(e.getMessage()); -// } - } - - @ParameterizedTest(name = "[{index}] {0} ({1})") - @MethodSource("tfm.utils.FileFinder#findAllMethodDeclarations") - public void pdgCompare(File file, String methodName, MethodDeclaration root) { - ControlDependencyBuilder ctrlDepBuilder; - - if (containsUnsupportedStatements(root)) { - System.err.println("Contains unsupported instructions"); - } - - // Create PDG - CFG cfg = new CFG(); - cfg.build(root); - PDG pdg = new PDG(cfg); - pdg.buildRootNode("ENTER " + methodName, root, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - ctrlDepBuilder = new ControlDependencyBuilder(cfg, pdg); - ctrlDepBuilder.build(); - - // Create APDG - ACFG acfg = new ACFG(); - acfg.build(root); - APDG apdg = new APDG(acfg); - apdg.buildRootNode("ENTER " + methodName, root, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - ctrlDepBuilder = new ControlDependencyBuilder(acfg, apdg); - ctrlDepBuilder.build(); - - // Create PPDG - PPDG ppdg = new PPDG(acfg); - ppdg.buildRootNode("ENTER " + methodName, root, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); - ctrlDepBuilder = new ControlDependencyBuilder(acfg, ppdg); - ctrlDepBuilder.build(); - - // Print graphs (commented to decrease the test's time) - String filePathNoExt = file.getPath().substring(0, file.getPath().lastIndexOf('.')); -// String name = filePathNoExt + "/" + methodName; -// new PDGLog(pdg).generateImages(name); -// new PDGLog(apdg).generateImages(name); -// new PDGLog(ppdg).generateImages(name); - - // Compare - List slicedMethods = compareGraphs(pdg, apdg, ppdg); - - // Write sliced methods to a java file. - ClassOrInterfaceDeclaration clazz = new ClassOrInterfaceDeclaration(); - slicedMethods.forEach(clazz::addMember); - clazz.setName(methodName); - clazz.setModifier(Modifier.Keyword.PUBLIC, true); - try (PrintWriter pw = new PrintWriter(new File("./out/" + filePathNoExt + "/" + methodName + ".java"))) { - pw.println(clazz); - } catch (Exception e) { - Logger.log("Error! Could not write classes to file"); - } - - assert !error; - } - - public static boolean containsUnsupportedStatements(Node node) { - return node.findFirst(TryStmt.class).isPresent() - || node.findFirst(ThrowStmt.class).isPresent(); - } - - - /** Slices both graphs on every possible node and compares the result */ - public List compareGraphs(PDG... pdgs) { - List slicedMethods = new LinkedList<>(); - assert pdgs.length > 0; - for (GraphNode node : pdgs[0].vertexSet().stream() - .sorted(Comparator.comparingLong(GraphNode::getId)) - .collect(Collectors.toList())) { - // Skip start of graph - if (node.getAstNode() instanceof MethodDeclaration) - continue; - - // Perform slices - SlicingCriterion sc = new GraphNodeCriterion(node, "x"); -// Slice[] slices = Arrays.stream(pdgs).map(p -> p.slice(sc)).toArray(Slice[]::new); - - // Compare slices - boolean ok = true; -// Slice referenceSlice = slices[0]; -// for (Slice slice : slices) { -// ok = referenceSlice.equals(slice); -// error |= !ok; -// if (!ok) break; -// } - - // Display slice - Logger.log("Slicing on " + node.getId()); - if (!ok) - Logger.log("FAILED!"); -// printSlices(pdgs[0], slices); - - // Save slices as MethodDeclaration - int i = 0; -// for (Slice s : slices) { -// i++; -// try { -// MethodDeclaration m = ((MethodDeclaration) s.getAst()); -// m.setName(m.getName() + "_slice" + node.getId() + "_pdg" + i); -// slicedMethods.add(m); -// } catch (RuntimeException e) { -// Logger.log("Error: " + e.getMessage()); -// } -// } - } - return slicedMethods; - } - - public final void printSlices(PDG pdg, Slice... slices) { - pdg.vertexSet().stream() - .sorted(Comparator.comparingLong(GraphNode::getId)) - .forEach(n -> Logger.format("%3d: %s %s", - n.getId(), - Arrays.stream(slices) - .map(s -> s.contains(n) ? "x" : " ") - .reduce((a, b) -> a + " " + b).orElse("--error--"), - n.getInstruction())); - } -} diff --git a/sdg-core/src/test/java/tfm/utils/FileFinder.java b/sdg-core/src/test/java/tfm/utils/FileFinder.java deleted file mode 100644 index b123405..0000000 --- a/sdg-core/src/test/java/tfm/utils/FileFinder.java +++ /dev/null @@ -1,40 +0,0 @@ -package tfm.utils; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ast.body.MethodDeclaration; -import org.junit.jupiter.params.provider.Arguments; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public class FileFinder { - private static final String TEST_FILES = "./src/test/res/"; - private static final String DOT_JAVA = ".java"; - - public static Collection findFiles(File directory, String suffix) throws FileNotFoundException { - Collection res = new ArrayList<>(); - File[] files = directory.listFiles(); - if (files == null) return Collections.emptyList(); - for (File f : files) { - if (f.getName().endsWith(suffix)) - for (MethodDeclaration m : methodsOf(f)) - res.add(Arguments.of(f, m.getNameAsString(), m)); - if (f.isDirectory()) - res.addAll(findFiles(f, suffix)); - } - return res; - } - - public static Arguments[] findAllMethodDeclarations() throws FileNotFoundException { - Collection args = findFiles(new File(TEST_FILES), DOT_JAVA); - return args.toArray(new Arguments[0]); - } - - private static List methodsOf(File file) throws FileNotFoundException { - return JavaParser.parse(file).findAll(MethodDeclaration.class); - } -} -- GitLab From efacbde6b83edd528876ac32b439558598b3cff0 Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Fri, 24 Jul 2020 11:08:04 +0200 Subject: [PATCH 6/9] Fix MethodCallReplacerVisitor: throw statements were not considered --- sdg-cli/pom.xml | 2 +- sdg-core/pom.xml | 2 +- .../src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java | 1 + .../java/tfm/graphs/sdg/MethodCallReplacerVisitor.java | 7 +++++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index 1452141..68ced99 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -27,7 +27,7 @@ tfm sdg-core - 1.1.0 + 1.1.1 compile diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index 1c0dda0..f1d92c6 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -6,7 +6,7 @@ tfm sdg-core - 1.1.0 + 1.1.1 11 diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java index 268728f..f663709 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java @@ -230,6 +230,7 @@ public class ESCFG extends ACFG { 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(); 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 e9e43fa..298f8a6 100644 --- a/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java +++ b/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java @@ -32,6 +32,13 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { this.sdg = sdg; } + @Override + public void visit(ThrowStmt n, Context arg) { + stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); + super.visit(n, arg); + stmtStack.pop(); + } + @Override public void visit(DoStmt n, Context arg) { stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); -- GitLab From 4a9a5b6e9f95351e18cf3a2666d1f510a239c911 Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Fri, 24 Jul 2020 12:54:22 +0200 Subject: [PATCH 7/9] Improved searches within GraphNode with GraphNodeContentVisitor --- sdg-cli/pom.xml | 2 +- sdg-core/pom.xml | 2 +- .../tfm/graphs/GraphNodeContentVisitor.java | 147 +++++++++++++++++ .../tfm/graphs/augmented/ACFGBuilder.java | 3 +- .../main/java/tfm/graphs/cfg/CFGBuilder.java | 2 +- .../tfm/graphs/exceptionsensitive/ESCFG.java | 3 +- .../tfm/graphs/exceptionsensitive/ESSDG.java | 3 +- ...ionSensitiveMethodCallReplacerVisitor.java | 7 +- .../ExceptionSourceSearcher.java | 54 +------ .../graphs/sdg/MethodCallReplacerVisitor.java | 153 ++++-------------- .../src/main/java/tfm/graphs/sdg/SDG.java | 8 +- .../src/main/java/tfm/nodes/GraphNode.java | 6 +- .../main/java/tfm/nodes/VariableVisitor.java | 74 ++------- 13 files changed, 205 insertions(+), 259 deletions(-) create mode 100644 sdg-core/src/main/java/tfm/graphs/GraphNodeContentVisitor.java diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index 68ced99..dd4e679 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -27,7 +27,7 @@ tfm sdg-core - 1.1.1 + 1.1.2 compile diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index f1d92c6..56174fb 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -6,7 +6,7 @@ tfm sdg-core - 1.1.1 + 1.1.2 11 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 0000000..94d2136 --- /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/augmented/ACFGBuilder.java b/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java index 6ed0206..2bf6d32 100644 --- a/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java +++ b/sdg-core/src/main/java/tfm/graphs/augmented/ACFGBuilder.java @@ -255,7 +255,8 @@ 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()) 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 fba7ca9..67a2ef2 100644 --- a/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java +++ b/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java @@ -312,7 +312,7 @@ public class CFGBuilder extends VoidVisitorAdapter { // 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()); diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java index f663709..0ab945e 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESCFG.java @@ -105,7 +105,8 @@ public class ESCFG extends ACFG { if (methodDeclaration.getBody().isEmpty()) throw new IllegalStateException("The method must have a body!"); - buildRootNode("ENTER " + methodDeclaration.getNameAsString(), methodDeclaration, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); + buildRootNode("ENTER " + methodDeclaration.getDeclarationAsString(false, false, false), + methodDeclaration, TypeNodeFactory.fromType(NodeType.METHOD_ENTER)); hangingNodes.add(getRootNode().get()); for (Parameter param : methodDeclaration.getParameters()) diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java index e1be7f9..8a4f5ec 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ESSDG.java @@ -41,7 +41,8 @@ public class ESSDG extends SDG { @Override public void build(NodeList nodeList) { nodeList.accept(createBuilder(), new Context()); - nodeList.accept(new ExceptionSensitiveMethodCallReplacerVisitor(this), new Context()); + Set> vertices = Set.copyOf(vertexSet()); + vertices.forEach(n -> new ExceptionSensitiveMethodCallReplacerVisitor(this).startVisit(n)); new NaiveSummaryArcsBuilder(this).visit(); compilationUnits = nodeList; built = true; diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java index b0e70d7..ff80880 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSensitiveMethodCallReplacerVisitor.java @@ -8,7 +8,6 @@ import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; import tfm.graphs.sdg.MethodCallReplacerVisitor; import tfm.nodes.*; -import tfm.utils.Context; import java.util.*; import java.util.stream.Collectors; @@ -19,10 +18,10 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla } @Override - public void visit(MethodCallExpr methodCallExpr, Context context) { + public void visit(MethodCallExpr methodCallExpr, Void arg) { if (methodCallExpr.resolve().getNumberOfSpecifiedExceptions() > 0) handleExceptionReturnArcs(methodCallExpr); - super.visit(methodCallExpr, context); + super.visit(methodCallExpr, arg); } /** Creates the following connections: @@ -109,7 +108,7 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla // Skip Error, unless Throwable is present as EE node if (type.getQualifiedName().equals("java.lang.Error") && !hasThrowable) continue; - // Object has no ancestors, the search has ended + // Object has no ancestors, the startVisit has ended if (!type.getQualifiedName().equals("java.lang.Object")) newTypeList.addAll(type.asReferenceType().getDirectAncestors()); } diff --git a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java index a577015..a61c877 100644 --- a/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java +++ b/sdg-core/src/main/java/tfm/graphs/exceptionsensitive/ExceptionSourceSearcher.java @@ -2,14 +2,14 @@ package tfm.graphs.exceptionsensitive; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.expr.MethodCallExpr; -import com.github.javaparser.ast.stmt.*; -import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +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 VoidVisitorAdapter { +public class ExceptionSourceSearcher extends GraphNodeContentVisitor { public Collection search(Node node) { try { node.accept(this, null); @@ -31,54 +31,6 @@ public class ExceptionSourceSearcher extends VoidVisitorAdapter { else super.visit(n, arg); } - // The following are completely traversed (they contain no statements): - // AssertStmt, BreakStmt, ContinueStmt, EmptyStmt, ExplicitConstructorInvocationStmt, - // ReturnStmt, ExpressionStmt, LocalClassDeclarationStmt - - // The following are not traversed at all: they only contain statements - @Override - public void visit(BlockStmt n, Void arg) {} - - @Override - public void visit(SwitchEntryStmt n, Void arg) {} - - @Override - public void visit(TryStmt n, Void arg) {} - - @Override - public void visit(LabeledStmt n, Void arg) {} - - // The following are partially traversed (expressions are traversed, statements are not) - @Override - public void visit(DoStmt n, Void arg) { - n.getCondition().accept(this, arg); - } - - @Override - public void visit(ForEachStmt n, Void arg) { - n.getIterable().accept(this, arg); - } - - @Override - public void visit(ForStmt n, Void arg) { - n.getCompare().ifPresent(comp -> comp.accept(this, arg)); - } - - @Override - public void visit(IfStmt n, Void arg) { - n.getCondition().accept(this, arg); - } - - @Override - public void visit(SwitchStmt n, Void arg) { - n.getSelector().accept(this, arg); - } - - @Override - public void visit(WhileStmt n, Void arg) { - n.getCondition().accept(this, arg); - } - static class FoundException extends RuntimeException { protected final Collection types; 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 298f8a6..c59bd0f 100644 --- a/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java +++ b/sdg-core/src/main/java/tfm/graphs/sdg/MethodCallReplacerVisitor.java @@ -1,102 +1,41 @@ package tfm.graphs.sdg; import com.github.javaparser.ast.ArrayCreationLevel; -import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.Parameter; 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.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; -public class MethodCallReplacerVisitor extends VoidVisitorAdapter { - +public class MethodCallReplacerVisitor extends GraphNodeContentVisitor { protected final SDG sdg; - /** The current location, as a stack of statements (e.g. do -> if -> exprStmt). */ - protected Deque> stmtStack = new LinkedList<>(); public MethodCallReplacerVisitor(SDG sdg) { this.sdg = sdg; } @Override - public void visit(ThrowStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(DoStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(ForEachStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(ForStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(IfStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(SwitchStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(WhileStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(ReturnStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); + public void startVisit(GraphNode graphNode) { + if (!(graphNode.getAstNode() instanceof MethodCallExpr) && !(graphNode instanceof SyntheticNode)) + super.startVisit(graphNode); } @Override - public void visit(ExpressionStmt n, Context arg) { - stmtStack.push(sdg.findNodeByASTNode(n).orElseThrow()); - super.visit(n, arg); - stmtStack.pop(); - } - - @Override - public void visit(MethodCallExpr methodCallExpr, Context context) { + public void visit(MethodCallExpr methodCallExpr, Void arg) { GraphNode methodDeclarationNode; try { methodDeclarationNode = methodCallExpr.resolve().toAst() @@ -113,7 +52,7 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { // Create and connect the CALL node CallNode methodCallNode = new CallNode(methodCallExpr); sdg.addNode(methodCallNode); - sdg.addControlDependencyArc(stmtStack.peek(), methodCallNode); + sdg.addControlDependencyArc(graphNode, methodCallNode); sdg.addCallArc(methodCallNode, methodDeclarationNode); @@ -132,8 +71,8 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { i = parameters.size(); } - createActualIn(methodDeclarationNode, methodCallNode, parameter, argument, context); - createActualOut(methodDeclarationNode, methodCallNode, parameter, argument, context); + createActualIn(methodDeclarationNode, methodCallNode, parameter, argument); + createActualOut(methodDeclarationNode, methodCallNode, parameter, argument); } // Add the 'output' node to the call and connect to the METHOD_OUTPUT node (there should be only one -- if any) @@ -143,13 +82,13 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { .forEach(node -> processMethodOutputNode(node, methodCallNode)); } - protected void createActualIn(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument, Context context) { + 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(stmtStack.peek()).stream() + List arcsToRemove = sdg.incomingEdgesOf(graphNode).stream() .filter(Arc::isDataDependencyArc) .map(Arc::asDataDependencyArc) .filter(arc -> arc.getTarget().isContainedIn(argument)) @@ -157,25 +96,20 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { arcsToRemove.forEach(arc -> moveArc(arc, argumentInNode, true)); // Now, find the corresponding method declaration's in node and link argument node with it - Optional optionalParameterInNode = sdg.outgoingEdgesOf(declaration).stream() + Optional optFormalInNode = sdg.outgoingEdgesOf(declaration).stream() .map(sdg::getEdgeTarget) .filter(FormalIONode.class::isInstance) .map(FormalIONode.class::cast) .filter(argumentInNode::matchesFormalIO) .findFirst(); - if (optionalParameterInNode.isPresent()) { - sdg.addParameterInOutArc(argumentInNode, optionalParameterInNode.get()); - } else { - Logger.log(getClass().getSimpleName(), "WARNING: IN declaration node for argument " + argument + " not found."); - Logger.log(getClass().getSimpleName(), String.format("Context: %s, Method: %s, Call: %s", - context.getCurrentMethod().orElseThrow().getNameAsString(), - declaration.getAstNode().getSignature().asString(), - call.getAstNode())); - } + 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."); } - protected void createActualOut(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument, Context context) { + protected void createActualOut(GraphNode declaration, GraphNode call, Parameter parameter, Expression argument) { Set variablesForOutNode = new HashSet<>(); argument.accept(new OutNodeVariableVisitor(), variablesForOutNode); @@ -187,7 +121,7 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { } else if (variablesForOutNode.size() == 1) { String variable = variablesForOutNode.iterator().next(); - List> declarations = sdg.findDeclarationsOfVariable(variable, stmtStack.peek()); + List> declarations = sdg.findDeclarationsOfVariable(variable, graphNode); Logger.log("MethodCallReplacerVisitor", String.format("Declarations of variable: '%s': %s", variable, declarations)); @@ -213,22 +147,17 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { .findFirst(); // Handle data dependency: copy arc from method call node and add it to OUT node - List arcsToRemove = sdg.outgoingEdgesOf(stmtStack.peek()).stream() + 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()) { + if (optionalParameterOutNode.isPresent()) sdg.addParameterInOutArc(optionalParameterOutNode.get(), argumentOutNode); - } else { - Logger.log(getClass().getSimpleName(), "WARNING: OUT declaration node for argument " + argument + " not found."); - Logger.log(getClass().getSimpleName(), String.format("Context: %s, Method: %s, Call: %s", - context.getCurrentMethod().orElseThrow().getNameAsString(), - declaration.getAstNode().getSignature().asString(), - call.getAstNode())); - } + else + Logger.log(getClass().getSimpleName(), "WARNING: FORMAL-OUT node for argument " + argument + " of call " + call + " not found."); } /** @@ -247,37 +176,11 @@ public class MethodCallReplacerVisitor extends VoidVisitorAdapter { protected void processMethodOutputNode(GraphNode methodOutputNode, GraphNode methodCallNode) { GraphNode callReturnNode = sdg.addNode("call output", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_CALL_RETURN)); - VariableAction.Usage usage = stmtStack.peek().addUsedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT)); + VariableAction.Usage usage = graphNode.addUsedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT)); VariableAction.Definition definition = callReturnNode.addDefinedVariable(new NameExpr(CFGBuilder.VARIABLE_NAME_OUTPUT)); sdg.addControlDependencyArc(methodCallNode, callReturnNode); sdg.addDataDependencyArc(definition, usage); sdg.addParameterInOutArc(methodOutputNode, callReturnNode); } - - @Override - public void visit(MethodDeclaration n, Context arg) { - arg.setCurrentMethod(n); - super.visit(n, arg); - arg.setCurrentMethod(null); - } - - @Override - public void visit(ConstructorDeclaration n, Context arg) { -// super.visit(n, arg); SKIPPED BECAUSE IT WAS SKIPPED IN CFG CREATION AND COUNTLESS OTHER VISITORS - } - - @Override - public void visit(ClassOrInterfaceDeclaration n, Context arg) { - arg.setCurrentClass(n); - super.visit(n, arg); - arg.setCurrentClass(null); - } - - @Override - public void visit(CompilationUnit n, Context arg) { - arg.setCurrentCU(n); - super.visit(n, arg); - arg.setCurrentCU(null); - } } 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 cff775b..917a0c7 100644 --- a/sdg-core/src/main/java/tfm/graphs/sdg/SDG.java +++ b/sdg-core/src/main/java/tfm/graphs/sdg/SDG.java @@ -22,10 +22,7 @@ import tfm.slicing.Sliceable; import tfm.slicing.SlicingCriterion; import tfm.utils.Context; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; public class SDG extends Graph implements Sliceable, Buildable> { @@ -49,7 +46,8 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) { nodeList.accept(createBuilder(), new Context()); - nodeList.accept(new MethodCallReplacerVisitor(this), new Context()); + Set> vertices = Set.copyOf(vertexSet()); + vertices.forEach(n -> new MethodCallReplacerVisitor(this).startVisit(n)); new NaiveSummaryArcsBuilder(this).visit(); compilationUnits = nodeList; built = true; diff --git a/sdg-core/src/main/java/tfm/nodes/GraphNode.java b/sdg-core/src/main/java/tfm/nodes/GraphNode.java index 9dd3fdf..f9d07a2 100644 --- a/sdg-core/src/main/java/tfm/nodes/GraphNode.java +++ b/sdg-core/src/main/java/tfm/nodes/GraphNode.java @@ -39,7 +39,7 @@ public class GraphNode implements Comparable> { protected GraphNode(long id, NodeType type, String instruction, @NotNull N astNode) { this(id, type, instruction, astNode, new LinkedList<>()); - extractVariables(astNode); + extractVariables(); } protected GraphNode(NodeType type, String instruction, @NotNull N astNode, List variableActions) { @@ -54,8 +54,8 @@ public class GraphNode implements Comparable> { this.variableActions = variableActions; } - protected void extractVariables(@NotNull Node node) { - new VariableVisitor(this::addUsedVariable, this::addDefinedVariable, this::addDeclaredVariable).search(node); + protected void extractVariables() { + new VariableVisitor().startVisit(this); } public long getId() { diff --git a/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java b/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java index b36963b..2772904 100644 --- a/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java +++ b/sdg-core/src/main/java/tfm/nodes/VariableVisitor.java @@ -1,15 +1,13 @@ package tfm.nodes; -import com.github.javaparser.ast.Node; 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.ForEachStmt; +import tfm.graphs.GraphNodeContentVisitor; import java.util.Set; -import java.util.function.Consumer; -public class VariableVisitor extends VoidVisitorAdapter { +public class VariableVisitor extends GraphNodeContentVisitor { enum Action { DECLARATION, DEFINITION, @@ -29,52 +27,28 @@ public class VariableVisitor extends VoidVisitorAdapter protected static final Set POSTFIX_CHANGE = Set.of( UnaryExpr.Operator.POSTFIX_DECREMENT, UnaryExpr.Operator.POSTFIX_INCREMENT); - protected final Consumer onUse; - protected final Consumer onDef; - protected final Consumer onDecl; - - public VariableVisitor(Consumer onUse, Consumer onDef, Consumer onDecl) { - this.onUse = onUse; - this.onDecl = onDecl; - this.onDef = onDef; - } - - public void search(Node node) { - node.accept(this, Action.USE); + @Override + public void startVisit(GraphNode node) { + startVisit(node, Action.USE); } @Override public void visit(NameExpr n, Action action) { switch (action) { case DECLARATION: - onDecl.accept(n); + graphNode.addDeclaredVariable(n); break; case DEFINITION: - onDef.accept(n); + graphNode.addDefinedVariable(n); break; case USE: - onUse.accept(n); + graphNode.addUsedVariable(n); break; default: throw new UnsupportedOperationException(); } } - // Transparently traversed (they contain no statements): - // AssertStmt, BreakStmt, ContinueStmt, EmptyStmt, ExplicitConstructorInvocationStmt, - // ReturnStmt, ExpressionStmt, LocalClassDeclarationStmt - // Transparently traversed (they contain only USE expressions): - // ArrayAccessExpr, BinaryExpr, ConditionalExpr, EnclosedExpr - - // Not traversed at all (they only contain statements) - @Override - public void visit(BlockStmt n, Action arg) { - } - - @Override - public void visit(TryStmt n, Action arg) { - } - // Partially traversed (only expressions that may contain variables are traversed) @Override public void visit(CastExpr n, Action action) { @@ -86,36 +60,6 @@ public class VariableVisitor extends VoidVisitorAdapter n.getScope().accept(this, action); } - @Override - public void visit(DoStmt n, Action arg) { - n.getCondition().accept(this, Action.USE); - } - - @Override - public void visit(ForStmt n, Action arg) { - n.getCompare().ifPresent(expression -> expression.accept(this, Action.USE)); - } - - @Override - public void visit(WhileStmt n, Action arg) { - n.getCondition().accept(this, Action.USE); - } - - @Override - public void visit(IfStmt n, Action arg) { - n.getCondition().accept(this, Action.USE); - } - - @Override - public void visit(SwitchEntryStmt n, Action arg) { - n.getLabel().ifPresent(expression -> expression.accept(this, Action.USE)); - } - - @Override - public void visit(SwitchStmt n, Action arg) { - n.getSelector().accept(this, Action.USE); - } - // Modified traversal (there may be variable definitions or declarations) @Override public void visit(ForEachStmt n, Action action) { -- GitLab From c53761b3324bb21832bfba993c4e643b2fcf292a Mon Sep 17 00:00:00 2001 From: Javier Costa Date: Sat, 25 Jul 2020 17:24:25 +0200 Subject: [PATCH 8/9] Fix CFGBuilder --- sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java | 9 --------- 1 file changed, 9 deletions(-) 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 9c8cda4..b7113f7 100644 --- a/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java +++ b/sdg-core/src/main/java/tfm/graphs/cfg/CFGBuilder.java @@ -325,16 +325,7 @@ public class CFGBuilder extends VoidVisitorAdapter { returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add); createAndConnectFormalOutNodes(methodDeclaration); - - // Create and connect formal-out nodes sequentially - for (Parameter param : methodDeclaration.getParameters()) { - // Do not generate out for primitives - if (param.getType().isPrimitiveType()) { - continue; - } - connectTo(addFormalOutGraphNode(param)); - } // Create and connect the exit node connectTo(graph.addNode("Exit", new EmptyStmt(), TypeNodeFactory.fromType(NodeType.METHOD_EXIT))); } -- GitLab From feb672c4d0fa4bcfd9942862190c1814d1f1e72a Mon Sep 17 00:00:00 2001 From: Javier Costa Date: Sat, 25 Jul 2020 17:49:09 +0200 Subject: [PATCH 9/9] Updated manifest for sdg-cli --- sdg-cli/META-INF/MANIFEST.MF | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 sdg-cli/META-INF/MANIFEST.MF diff --git a/sdg-cli/META-INF/MANIFEST.MF b/sdg-cli/META-INF/MANIFEST.MF new file mode 100644 index 0000000..653a67f --- /dev/null +++ b/sdg-cli/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: tfm.cli.Slicer + -- GitLab