diff --git a/README.md b/README.md index 96e58dc99d7c120ca42377a6175088d801adb366..08caa2898106b75ba24a9ff773b2bbba79c5ef9a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A fat jar containing all the project's dependencies can be then located at `./sd ### Slice a Java program -The slicing criterion can be specified with the flag `-c {file}#{line}:{var}[!{occurrence}`, where the file, line and variable can be specified. If the variable appears multiple times in the given line, an occurrence can be set (append `:2` to select the second occurrence). +The slicing criterion can be specified with the flag `-c {file}#{line}:{var}`, where the file, line and variable can be specified. If the variable appears multiple times in the given line, all of them will be selected. If we wish to slice following program with respect to variable `sum` in line 11, diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/PHPSlice.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/PHPSlice.java index 6a4b7f4cc16f43abd28d09da019462f32c4bf82b..4b7182549a3c3bab0b7874a4350f8605956eceb3 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/PHPSlice.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/PHPSlice.java @@ -10,7 +10,6 @@ import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.slicing.FileLineSlicingCriterion; -import es.upv.mist.slicing.slicing.NodeIdSlicingCriterion; import es.upv.mist.slicing.slicing.Slice; import es.upv.mist.slicing.slicing.SlicingCriterion; import es.upv.mist.slicing.utils.StaticTypeSolver; @@ -20,6 +19,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; +import java.util.Set; public class PHPSlice { protected static final Options OPTIONS = new Options(); @@ -108,7 +108,7 @@ public class PHPSlice { } sdg.build(units); - SlicingCriterion sc = new NodeIdSlicingCriterion(0, ""); + SlicingCriterion sc = graph -> Set.of(graph.findNodeBy(n -> n.getId() == (long) 0).orElseThrow()); Slice slice = new Slice(); if (scId != 0) { // Slice the SDG diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/SlicedSDGLog.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/SlicedSDGLog.java index b838d8b480806d92070ab201692878825ca2b8f4..09f5562de4a9bb298ae96ef8f92ad45299053fc6 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/SlicedSDGLog.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/SlicedSDGLog.java @@ -11,11 +11,13 @@ import org.jgrapht.nio.dot.DOTExporter; import java.util.HashMap; import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; /** 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; + protected final Set> sc; public SlicedSDGLog(SDG graph, Slice slice) { this(graph, slice, null); @@ -24,7 +26,11 @@ public class SlicedSDGLog extends SDGLog { public SlicedSDGLog(SDG graph, Slice slice, SlicingCriterion sc) { super(graph); this.slice = slice; - this.sc = sc == null ? null : sc.findNode(graph).orElse(null); + Set> set = null; + try { + set = sc.findNode(graph); + } catch (NullPointerException | NoSuchElementException ignored) {} + this.sc = set; } @Override @@ -38,12 +44,16 @@ public class SlicedSDGLog extends SDGLog { protected Map vertexAttributes(GraphNode node) { Map map = new HashMap<>(); - if (slice.contains(node) && node.equals(sc)) + if (slice.contains(node) && sc.contains(node)) map.put("style", DefaultAttribute.createAttribute("filled,bold")); + else if (slice.contains(node) && node.isImplicitInstruction()) + map.put("style", DefaultAttribute.createAttribute("filled,dashed")); else if (slice.contains(node)) map.put("style", DefaultAttribute.createAttribute("filled")); - else if (node.equals(sc)) + else if (sc.contains(node)) map.put("style", DefaultAttribute.createAttribute("bold")); + else if (node.isImplicitInstruction()) + map.put("style", DefaultAttribute.createAttribute("dashed")); map.put("label", DefaultAttribute.createAttribute(node.getLongLabel())); return map; } diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java index 6035f2dae86bd0ca1f3d76e42d83857fdcf3c905..984e0b9b9b2aafc2a9a3ed5c1e9451a9bd5e67cf 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java @@ -7,9 +7,9 @@ import com.github.javaparser.ast.comments.BlockComment; import com.github.javaparser.ast.nodeTypes.NodeWithName; import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; import es.upv.mist.slicing.graphs.augmented.ASDG; -import es.upv.mist.slicing.graphs.jsysdg.JSysDG; import es.upv.mist.slicing.graphs.augmented.PSDG; import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.slicing.FileLineSlicingCriterion; import es.upv.mist.slicing.slicing.Slice; @@ -21,7 +21,9 @@ import org.apache.commons.cli.*; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -37,9 +39,8 @@ public class Slicer { static { String fileRe = "(?[^#]+\\.java)"; String lineRe = "(?[1-9]\\d*)"; - String varsRe = "(?[a-zA-Z_]\\w*(?:,[a-zA-Z_]\\w*)*)"; - String numsRe = "(?[1-9]\\d*(?:,[1-9]\\d*)*)"; - SC_PATTERN = Pattern.compile(fileRe + "#" + lineRe + "(?::" + varsRe + "(?:!" + numsRe + ")?)?"); + String varsRe = "(?[a-zA-Z_]\\w*(?:,[a-zA-Z_]\\w*)*)"; + SC_PATTERN = Pattern.compile(fileRe + "#" + lineRe + "(?::" + varsRe + ")?"); } static { @@ -50,30 +51,21 @@ public class Slicer { .build()); OPTIONS.addOption(Option .builder("l").longOpt("line") - .hasArg().argName("lineNumber").type(Number.class) + .hasArg().argName("line-number").type(Number.class) .desc("The line that contains the statement of the slicing criterion.") .build()); OPTIONS.addOption(Option .builder("v").longOpt("var") - .hasArgs().argName("variableName").valueSeparator(',') + .hasArgs().argName("variable-name") .desc("The name of the variable of the slicing criterion. Not setting this option is" + - " equivalent to selecting an empty set; setting multiple variables is allowed," + - " separated by commas") - .build()); - OPTIONS.addOption(Option - .builder("n").longOpt("number") - .hasArgs().argName("occurrenceNumber").valueSeparator(',') - .desc("The occurrence number of the variable(s) selected. If this argument is not set, it will" + - " default to the first appearance of each variable. If the occurrence number must be set" + - " for every variable in the same order.") + " equivalent to selecting all the code in the given line number.") .build()); OPTIONS.addOption(Option .builder("c").longOpt("criterion") - .hasArg().argName("file#line[:var[!occurrence]]") - .desc("The slicing criterion, in the format \"file#line:var\". Optionally, the occurrence can be" + - " appended as \"!occurrence\". This option may be replaced by \"-f\", \"-l\", \"-v\" and \"-n\"." + - " If both are specified, the others are discarded. It functions in a similar way: the variable" + - " and occurrence may be skipped or declared multiple times.") + .hasArg().argName("file#line[:var]") + .desc("The slicing criterion, in the format \"file#line:var\". The variable is optional."+ + " This option may be replaced by \"-f\", \"-l\" and \"-v\"." + + " If this argument is set, it will override the individual ones.") .build()); OPTIONS.addOption(Option .builder("i").longOpt("include") @@ -84,13 +76,13 @@ public class Slicer { .build()); OPTIONS.addOption(Option .builder("o").longOpt("output") - .hasArg().argName("outputDir") + .hasArg().argName("output-dir") .desc("The directory where the sliced source code should be placed. By default, it is placed at " + DEFAULT_OUTPUT_DIR) .build()); OPTIONS.addOption(Option .builder("t").longOpt("type") - .hasArg().argName("graph_type") + .hasArg().argName("graph-type") .desc("The type of graph to be built. Available options are SDG, ASDG, PSDG, ESSDG.") .build()); OPTIONS.addOption(Option @@ -103,8 +95,7 @@ public class Slicer { private File outputDir = DEFAULT_OUTPUT_DIR; private File scFile; private int scLine; - private final List scVars = new ArrayList<>(); - private final List scVarOccurrences = new ArrayList<>(); + private String scVar; private final CommandLine cliOpts; public Slicer(String... cliArgs) throws ParseException { @@ -117,23 +108,14 @@ public class Slicer { throw new ParseException("Invalid format for slicing criterion, see --help for more details"); setScFile(matcher.group("file")); setScLine(Integer.parseInt(matcher.group("line"))); - String vars = matcher.group("vars"); - String nums = matcher.group("nums"); - if (vars != null) { - if (nums != null) - setScVars(vars.split(","), nums.split(",")); - else - setScVars(vars.split(",")); - } + String var = matcher.group("var"); + if (var != null) + setScVar(var); } else if (cliOpts.hasOption('f') && cliOpts.hasOption('l')) { setScFile(cliOpts.getOptionValue('f')); setScLine(((Number) cliOpts.getParsedOptionValue("l")).intValue()); - if (cliOpts.hasOption('v')) { - if (cliOpts.hasOption('n')) - setScVars(cliOpts.getOptionValues('v'), cliOpts.getOptionValues('n')); - else - setScVars(cliOpts.getOptionValues('v')); - } + if (cliOpts.hasOption('v')) + setScVar(cliOpts.getOptionValue('v')); } else { throw new ParseException("Slicing criterion not specified: either use \"-c\" or \"-f\" and \"-l\"."); } @@ -164,26 +146,8 @@ public class Slicer { scLine = line; } - private void setScVars(String[] scVars) throws ParseException { - String[] array = new String[scVars.length]; - Arrays.fill(array, "1"); - setScVars(scVars, array); - } - - private void setScVars(String[] scVars, String[] scOccurrences) throws ParseException { - if (scVars.length != scOccurrences.length) - throw new ParseException("If the number of occurrence is specified, it must be specified once per variable."); - try { - for (int i = 0; i < scVars.length; i++) { - this.scVars.add(scVars[i]); - int n = Integer.parseUnsignedInt(scOccurrences[i]); - if (n <= 0) - throw new ParseException("The number of occurrence must be larger than 0."); - this.scVarOccurrences.add(n); - } - } catch (NumberFormatException e) { - throw new ParseException(e.getMessage()); - } + private void setScVar(String scVar) { + this.scVar = scVar; } public Set getDirIncludeSet() { @@ -202,12 +166,8 @@ public class Slicer { return scLine; } - public List getScVars() { - return Collections.unmodifiableList(scVars); - } - - public List getScVarOccurrences() { - return Collections.unmodifiableList(scVarOccurrences); + public String getScVar() { + return scVar; } public void slice() throws ParseException { @@ -245,7 +205,7 @@ public class Slicer { sdg.build(new NodeList<>(units)); // Slice the SDG - SlicingCriterion sc = new FileLineSlicingCriterion(scFile, scLine); + SlicingCriterion sc = new FileLineSlicingCriterion(scFile, scLine, scVar); Slice slice = sdg.slice(sc); // Convert the slice to code and output the result to `outputDir` @@ -285,8 +245,8 @@ public class Slicer { protected String getDisclaimer(CompilationUnit.Storage s) { return String.format("\n\tThis file was automatically generated as part of a slice with criterion" + - "\n\tfile: %s, line: %d, variable(s): %s\n\tOriginal file: %s\n", - scFile, scLine, String.join(", ", scVars), s.getPath()); + "\n\tfile: %s, line: %d, variable: %s\n\tOriginal file: %s\n", + scFile, scLine, scVar, s.getPath()); } protected void printHelp() { diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index efc9e873e851d0c153a1a25679784ad452954ff5..e027b85b2fb0114824b9befd98b9cffdf13ef787 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -34,12 +34,12 @@ com.github.javaparser javaparser-core - 3.17.0 + 3.19.0 com.github.javaparser javaparser-symbol-solver-core - 3.17.0 + 3.19.0 org.jgrapht diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/Arc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/Arc.java index 1a46b08bc0fa6c736e80a587685cd6bddac4ba39..12f7cc83f71aba92c63b9b6ae7c3e666c6ed6a2e 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/Arc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/Arc.java @@ -89,6 +89,10 @@ public abstract class Arc extends DefaultEdge { throw new UnsupportedOperationException("Not a DataDependencyArc"); } + public boolean isObjectFlow() { + return false; + } + // =========================== SDG =========================== /** Whether or not this is an interprocedural arc that connects a call site to its declaration. */ diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/DataDependencyArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/DataDependencyArc.java index acc2ae2f19b9b8b33d5e22ab52fbb1a7ac90ff26..ed8650c4a364fd83406656c3f2d90e0830d50a44 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/DataDependencyArc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/DataDependencyArc.java @@ -31,7 +31,7 @@ public class DataDependencyArc extends Arc { protected final VariableAction targetVar; public DataDependencyArc(VariableAction sourceVar, VariableAction targetVar) { - super(sourceVar.getVariable()); + super(sourceVar.getName()); if (VALID_VA_COMBOS.stream().noneMatch(p -> p.test(sourceVar, targetVar))) throw new IllegalArgumentException("Illegal combination of actions: " + sourceVar + ", " + targetVar); this.sourceVar = sourceVar; diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java new file mode 100644 index 0000000000000000000000000000000000000000..27a11746bed51c4bd8069930a6396cf02b742c66 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java @@ -0,0 +1,25 @@ +package es.upv.mist.slicing.arcs.pdg; + +import es.upv.mist.slicing.arcs.Arc; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.DefaultAttribute; + +import java.util.Map; + +/** Represents a data dependency in an object-oriented SDG or PDG. */ +public class FlowDependencyArc extends Arc { + public FlowDependencyArc() { + super(); + } + + public FlowDependencyArc(String variable) { + super(variable); + } + + @Override + public Map getDotAttributes() { + Map map = super.getDotAttributes(); + map.put("color", DefaultAttribute.createAttribute("red")); + return map; + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/ObjectFlowDependencyArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/ObjectFlowDependencyArc.java new file mode 100644 index 0000000000000000000000000000000000000000..b2dd036ffb4ba0fb0d45b5333d1544e45ff0f8d7 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/ObjectFlowDependencyArc.java @@ -0,0 +1,28 @@ +package es.upv.mist.slicing.arcs.pdg; + +import es.upv.mist.slicing.arcs.Arc; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.DefaultAttribute; + +import java.util.Map; + +/** Represents a data dependency between objects or between a field + * and its parent in an object oriented SDG or PDG. */ +public class ObjectFlowDependencyArc extends Arc { + public ObjectFlowDependencyArc() { + super(); + } + + @Override + public boolean isObjectFlow() { + return true; + } + + @Override + public Map getDotAttributes() { + Map map = super.getDotAttributes(); + map.put("color", DefaultAttribute.createAttribute("red")); + map.put("style", DefaultAttribute.createAttribute("dashed")); + return map; + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/StructuralArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/StructuralArc.java new file mode 100644 index 0000000000000000000000000000000000000000..904ef1a2b7272b7f20a8c8d232529f456dc2ac6d --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/StructuralArc.java @@ -0,0 +1,19 @@ +package es.upv.mist.slicing.arcs.pdg; + +import es.upv.mist.slicing.arcs.Arc; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.DefaultAttribute; + +import java.util.Map; + +/** Replaces control dependencies that were used to generate a tree-like + * structure in the PDG and SDG. Some examples include the connection + * of actual/formal-in/out and object trees. They should always be traversed. */ +public class StructuralArc extends Arc { + @Override + public Map getDotAttributes() { + Map map = super.getDotAttributes(); + map.put("style", DefaultAttribute.createAttribute("dashed")); + return map; + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/TotalDefinitionDependenceArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/TotalDefinitionDependenceArc.java new file mode 100644 index 0000000000000000000000000000000000000000..4d19a6d55259732e29fff6cfc49ae86aaf96da6c --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/TotalDefinitionDependenceArc.java @@ -0,0 +1,19 @@ +package es.upv.mist.slicing.arcs.pdg; + +import es.upv.mist.slicing.arcs.Arc; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.DefaultAttribute; + +import java.util.Map; + +/** Represents a dependence where the source completely defines + * the target. This is used when the type of an object or just + * its existence is required, but not any specific field. */ +public class TotalDefinitionDependenceArc extends Arc { + @Override + public Map getDotAttributes() { + Map map = super.getDotAttributes(); + map.put("color", DefaultAttribute.createAttribute("pink")); + return map; + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/CallArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/CallArc.java index 9f96b9791d20378592ac4ab30ca3da54f63c26f7..4a43afea0e42fa3775853adddbf8bd2e42f6ad15 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/CallArc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/CallArc.java @@ -1,22 +1,10 @@ package es.upv.mist.slicing.arcs.sdg; -import org.jgrapht.nio.Attribute; -import org.jgrapht.nio.DefaultAttribute; - -import java.util.Map; - /** * An interprocedural arc that connects a call site with its * corresponding declaration. It is considered an interprocedural input. */ public class CallArc extends InterproceduralArc { - @Override - public Map getDotAttributes() { - Map map = super.getDotAttributes(); - map.put("style", DefaultAttribute.createAttribute("dashed")); - return map; - } - @Override public boolean isInterproceduralInputArc() { return true; diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArc.java index 549b97ecef322baebf35da5c61012d17e2a2ac32..58efcb7d452285424165fb0274a17637134b8aa8 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArc.java @@ -17,12 +17,11 @@ public abstract class InterproceduralArc extends Arc { public Map getDotAttributes() { var map = super.getDotAttributes(); map.put("penwidth", DefaultAttribute.createAttribute("3")); + map.put("style", DefaultAttribute.createAttribute("dashed")); if (isInterproceduralInputArc()) - map.put("color", DefaultAttribute.createAttribute("orange")); + map.put("color", DefaultAttribute.createAttribute("darkgreen")); else if (isInterproceduralOutputArc()) map.put("color", DefaultAttribute.createAttribute("blue")); - else - map.put("color", DefaultAttribute.createAttribute("green")); return map; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/ParameterInOutArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/ParameterInOutArc.java index ccaba8b0b9bd38d50cfe186c70de5c12d54543dd..23f80016ba1e551cd20d0454e7c7ee352c1044a0 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/ParameterInOutArc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/ParameterInOutArc.java @@ -5,6 +5,7 @@ import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.nodes.io.FormalIONode; import es.upv.mist.slicing.nodes.io.OutputNode; +import es.upv.mist.slicing.nodes.oo.MemberNode; import org.jgrapht.nio.Attribute; import org.jgrapht.nio.DefaultAttribute; @@ -13,27 +14,44 @@ import java.util.Map; /** An interprocedural arc connecting {@link ActualIONode actual} and {@link FormalIONode formal} * nodes. The source and target must match: both must either be inputs or outputs. This arc may be an input or output. */ public class ParameterInOutArc extends InterproceduralArc { - @Override - public Map getDotAttributes() { - Map map = super.getDotAttributes(); - map.put("style", DefaultAttribute.createAttribute("dashed")); - return map; - } - @Override public boolean isInterproceduralInputArc() { - GraphNode source = (GraphNode) getSource(); - GraphNode target = (GraphNode) getTarget(); + GraphNode source = getNodeCommon(getSource()); + GraphNode target = getNodeCommon(getTarget()); return source instanceof ActualIONode && ((ActualIONode) source).isInput() && target instanceof FormalIONode && ((FormalIONode) target).isInput(); } @Override public boolean isInterproceduralOutputArc() { - GraphNode source = (GraphNode) getSource(); - GraphNode target = (GraphNode) getTarget(); + GraphNode source = getNodeCommon(getSource()); + GraphNode target = getNodeCommon(getTarget()); return (source instanceof FormalIONode && ((FormalIONode) source).isOutput() && target instanceof ActualIONode && ((ActualIONode) target).isOutput()) || (source instanceof OutputNode && target instanceof CallNode.Return); } + + protected GraphNode getNodeCommon(Object node) { + GraphNode graphNode = (GraphNode) node; + while (graphNode instanceof MemberNode) + graphNode = ((MemberNode) graphNode).getParent(); + return graphNode; + } + + /** Represents an input/output arc with an additional traversal restriction. + * It merges the functions of an interprocedural input/output arc and an + * object-flow arc. */ + public static class ObjectFlow extends ParameterInOutArc { + @Override + public boolean isObjectFlow() { + return true; + } + + @Override + public Map getDotAttributes() { + Map map = super.getDotAttributes(); + map.put("style", DefaultAttribute.createAttribute("dotted")); + return map; + } + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/SummaryArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/SummaryArc.java index a423c25a52c89b65a146bcff1e328ef5037dd9c6..e4fa633a5cee56bb6bbad7bea79b455155588808 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/SummaryArc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/sdg/SummaryArc.java @@ -14,6 +14,7 @@ public class SummaryArc extends Arc { public Map getDotAttributes() { Map map = super.getDotAttributes(); map.put("style", DefaultAttribute.createAttribute("bold")); + map.put("color", DefaultAttribute.createAttribute("#a01210")); // rojo oscuro return map; } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java index 3d1a880f6ce70fb837c46a9885890c7a7bf9e547..d01113b19f8e2dbad683bb0b34769497c7b46cc9 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java @@ -53,7 +53,7 @@ public class CallGraph extends DirectedPseudograph> getCallTargets(Resolvable call) { return edgeSet().stream() - .filter(e -> e.getCall() == call) + .filter(e -> ASTUtils.equalsInDeclaration((Node) e.getCall(), (Node) call)) .map(this::getEdgeTarget) .map(Vertex::getDeclaration) .map(decl -> (CallableDeclaration) decl); @@ -167,23 +167,20 @@ public class CallGraph extends DirectedPseudograph createPolyEdges(decl, n)); - if (ASTUtils.shouldVisitArgumentsForMethodCalls(n)) - super.visit(n, arg); + n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n)); + super.visit(n, arg); } @Override public void visit(ObjectCreationExpr n, Void arg) { n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n)); - if (ASTUtils.shouldVisitArgumentsForMethodCalls(n)) - super.visit(n, arg); + super.visit(n, arg); } @Override public void visit(ExplicitConstructorInvocationStmt n, Void arg) { n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n)); - if (ASTUtils.shouldVisitArgumentsForMethodCalls(n)) - super.visit(n, arg); + super.visit(n, arg); } protected void createPolyEdges(MethodDeclaration decl, MethodCallExpr call) { @@ -241,9 +238,9 @@ public class CallGraph extends DirectedPseudograph, Edge> getDOTExporter() { - DOTExporter, Edge> dot = new DOTExporter<>(); - dot.setVertexAttributeProvider(decl -> Utils.dotLabel(decl.getDeclarationAsString(false, false, false))); + public DOTExporter> getDOTExporter() { + DOTExporter> dot = new DOTExporter<>(); + dot.setVertexAttributeProvider(vertex -> Utils.dotLabel(vertex.declaration.getDeclarationAsString(false, false, false))); dot.setEdgeAttributeProvider(edge -> Utils.dotLabel(edge.getCall().toString())); return dot; } @@ -275,7 +272,7 @@ public class CallGraph extends DirectedPseudograph implements Buildable> { + private static ClassGraph instance = null; + + /** Generates and returns a new class graph. This destroys the reference to the previous instance. */ + public static ClassGraph getNewInstance() { + instance = null; + return getInstance(); + } + + public static ClassGraph getInstance() { + if (instance == null) + instance = new ClassGraph(); + return instance; + } /** The key of the vertex map needs to be a String because extendedTypes represent extended classes * as ClassOrInterfaceType objects while class declarations define classes as ClassOrInterfaceDeclaration @@ -25,7 +41,7 @@ public class ClassGraph extends DirectedPseudograph generateObjectTreeForReturnOf(CallableDeclaration callableDeclaration) { + if (callableDeclaration.isMethodDeclaration()) { + MethodDeclaration method = callableDeclaration.asMethodDeclaration(); + if (method.getType().isClassOrInterfaceType()) + try { + return Optional.of(generateObjectTreeFor(method.getType().asClassOrInterfaceType().resolve())); + } catch (UnsolvedSymbolException e) { + return Optional.empty(); + } + else + return Optional.empty(); + } else if (callableDeclaration.isConstructorDeclaration()) { + return Optional.of(generateObjectTreeFor(ASTUtils.getClassNode(callableDeclaration))); + } else { + throw new IllegalArgumentException("Invalid callable declaration type"); + } + } + + public Optional generateObjectTreeForType(ResolvedType type) { + if (type.isReferenceType()) { + Vertex v = vertexDeclarationMap.get(mapKey(type.asReferenceType())); + if (v != null) + return Optional.of(generateObjectTreeFor(v)); + } + return Optional.empty(); + } + + public ObjectTree generateObjectTreeFor(ClassOrInterfaceDeclaration declaration) { + return generateObjectTreeFor(vertexDeclarationMap.get(mapKey(declaration))); + } + + public ObjectTree generateObjectTreeFor(ResolvedReferenceType type) { + return generateObjectTreeFor(vertexDeclarationMap.get(mapKey(type))); + } + + protected ObjectTree generateObjectTreeFor(Vertex classVertex) { + if (classVertex == null) + return new ObjectTree(); + return generateObjectTreeFor(classVertex, new ObjectTree(), ObjectTree.ROOT_NAME); + } + + protected ObjectTree generateObjectTreeFor(Vertex classVertex, ObjectTree tree, String level) { + Map classFields = findAllFieldsOf(classVertex); + for (Map.Entry entry : classFields.entrySet()) { + tree.addField(level + '.' + entry.getKey()); + if (entry.getValue() != null) + generateObjectTreeFor(entry.getValue(), tree, level + '.' + entry.getKey()); + } + return tree; + } + + protected Map findAllFieldsOf(Vertex classVertex) { + assert classVertex.declaration instanceof ClassOrInterfaceDeclaration; + assert !classVertex.declaration.asClassOrInterfaceDeclaration().isInterface(); + ClassOrInterfaceDeclaration clazz = classVertex.getDeclaration().asClassOrInterfaceDeclaration(); + Map fieldMap = new HashMap<>(); + while (clazz != null) { + for (FieldDeclaration field : clazz.getFields()) { + for (VariableDeclarator var : field.getVariables()) { + if (fieldMap.containsKey(var.getNameAsString())) + continue; + Vertex v = null; + if (var.getType().isClassOrInterfaceType()) { + try { + v = vertexDeclarationMap.get(mapKey(var.getType().asClassOrInterfaceType().resolve())); + } catch (UnsolvedSymbolException ignored) { + } + } + fieldMap.put(var.getNameAsString(), v); + } + } + Optional parent = parentOf(clazz); + if (parent.isEmpty()) + break; + clazz = parent.get(); + } + return fieldMap; + } + @Override public void build(NodeList arg) { if (isBuilt()) diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..2d4fb0fe9ba0bcfc9bda0767ffeb4816365a8b10 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java @@ -0,0 +1,261 @@ +package es.upv.mist.slicing.graphs; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.github.javaparser.resolution.Resolvable; +import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.utils.Pair; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.ObjectTree; +import es.upv.mist.slicing.nodes.VariableAction; +import es.upv.mist.slicing.utils.ASTUtils; + +import java.util.LinkedList; +import java.util.List; + +import static es.upv.mist.slicing.graphs.cfg.CFGBuilder.VARIABLE_NAME_OUTPUT; + +/** + * A helper class that locates and connects all object trees that can be the output of an expression. + *
+ * Examples: + *
    + *
  • {@code a} results on that variable being linked.
  • + *
  • {@code a ? b : c} links both {@code b} and {@code c}.
  • + *
  • {@code a.b} links the subtree of {@code b}.
  • + *
  • {@code f()} links the output of the call.
  • + *
+ * These patterns can be mixed in any order. + *
+ *

Practical usage

+ * This class can be used to link assignments, declarations and other statements, + * to link, for example, the expression in {@code return a ? b : c} to the -output- + * variable action. This linkage must be performed in the same GraphNode, so the previous + * return would have a DEF(-output-), and that action is the target of the links setup + * by this class. + *

Assignments, declarations

+ * Generate a new object and use the method {@link #handleAssignExpr(AssignExpr, VariableAction, String) handleAssignExpr()}, + * {@link #handleVariableDeclarator(VariableDeclarator, String) handleVariableDeclarator()} or {@link #handleArrayAssignExpr(AssignExpr) handleArrayAssignExpr()}. + *

Other statements

+ * Generate a new object and use either {@link #locateAndMarkTransferenceToRoot(Expression, VariableAction)}, to link to + * the given variable action or {@link #locateAndMarkTransferenceToRoot(Expression, int)} to extract the variable action + * from the graph node used in the constructor. This is used for return and throw statements, and actual-in/out nodes. + */ +public class ExpressionObjectTreeFinder { + + /** The node that contains both the expression to be scanned, the source variable action and + * the desired variable action target. */ + protected final GraphNode graphNode; + + /** Creates a new ExpressionObjectTreeFinder for the given GraphNode. */ + public ExpressionObjectTreeFinder(GraphNode graphNode) { + this.graphNode = graphNode; + } + + /** Prepares the connection between the right-hand side of a variable declarator and the variable. + * The variable declarator must have an initializer, and the realName indicates the absolute (fields + * prefixed by 'this.') name of the variable being declared. */ + public void handleVariableDeclarator(VariableDeclarator variableDeclarator, String realName) { + if (variableDeclarator.getInitializer().isEmpty()) + throw new IllegalArgumentException("The variableDeclarator must have an initializer!"); + VariableAction targetAction = locateVAVariableDeclarator(realName); + ClassGraph.getInstance().generateObjectTreeForType(variableDeclarator.getType().resolve()) + .ifPresent(objectTree -> targetAction.getObjectTree().addAll(objectTree)); + locateExpressionResultTrees(variableDeclarator.getInitializer().get()) + .forEach(pair -> markTransference(pair, targetAction, "")); + } + + /** Finds the variable action that corresponds to the definition of a variable + * in a VariableDeclarator with initializer. */ + protected VariableAction locateVAVariableDeclarator(String realName) { + String root = realName.contains(".") ? ObjectTree.removeFields(realName) : realName; + boolean foundDecl = false; + VariableAction lastDef = null; + for (VariableAction a : graphNode.getVariableActions()) { + if (a.isDeclaration()) { + if (a.getName().equals(realName)) + foundDecl = true; + else if (foundDecl) + return lastDef; + } else if (a.isDefinition() && a.getName().equals(root)) { + if (root.equals(realName) || a.hasTreeMember(realName)) + lastDef = a; + } + } + if (lastDef == null) + throw new IllegalStateException("Could not find DEF for variable declaration of " + realName); + return lastDef; + } + + /** Prepares the connection between the right-hand side of an assignment to the left-hand side. + * The caller must provide the variable action that represents the definition of the variable action. + * If the LHS of this assignment is an array access expression, the method + * {@link #handleArrayAssignExpr(AssignExpr)} should be used. */ + public void handleAssignExpr(AssignExpr assignExpr, VariableAction assignTarget, String targetMember) { + ClassGraph.getInstance().generateObjectTreeForType(assignExpr.getTarget().calculateResolvedType()) + .ifPresent(fields -> assignTarget.getObjectTree().addAll(fields)); + locateExpressionResultTrees(assignExpr.getValue()) + .forEach(pair -> markTransference(pair, assignTarget, targetMember)); + } + + /** Prepares the connection between the right-hand side of the assignment and the GraphNode + * that represents the assignment, as currently array access expressions are treated as primitives. */ + public void handleArrayAssignExpr(AssignExpr assignExpr) { + locateExpressionResultTrees(assignExpr.getValue()) + .forEach(pair -> pair.a.setPDGValueConnection(pair.b)); + } + + /** + * Finds the variable action corresponding to the given index in the GraphNode used in the constructor + * and then connects each valid tree found in the given expression to the former variable action. + * Negative indices may be used, and will access variable actions from the end of the GraphNode's list, + * starting at -1 for the tail of the list. + */ + public void locateAndMarkTransferenceToRoot(Expression expr, int index) { + List list = graphNode.getVariableActions(); + locateAndMarkTransferenceToRoot(expr, list.get(index < 0 ? list.size() + index : index)); + } + + /** Connects each valid tree found in the given expression to the given variable action. */ + public void locateAndMarkTransferenceToRoot(Expression expression, VariableAction targetAction) { + locateExpressionResultTrees(expression) + .forEach(pair -> markTransference(pair, targetAction, "")); + } + + /** + * Finds object trees that correspond to the output of the given expression. + * @param expression An expression that outputs an object. + * @return A list of pairs, containing the variable actions and the member of such + * actions where the link must be placed. + */ + protected List> locateExpressionResultTrees(Expression expression) { + List> list = new LinkedList<>(); + expression.accept(new VoidVisitorAdapter() { + @Override + public void visit(ArrayAccessExpr n, String arg) { + n.getName().accept(this, arg); + } + + @Override + public void visit(AssignExpr n, String arg) { + n.getValue().accept(this, arg); + } + + @Override + public void visit(ConditionalExpr n, String arg) { + n.getThenExpr().accept(this, arg); + n.getElseExpr().accept(this, arg); + } + + @Override + public void visit(NameExpr n, String arg) { + ResolvedValueDeclaration resolved = n.resolve(); + if (resolved.isType()) + return; + if (resolved.isField() && !resolved.asField().isStatic()) { + new FieldAccessExpr(new ThisExpr(), n.getNameAsString()).accept(this, arg); + return; + } + for (VariableAction action : graphNode.getVariableActions()) { + if (action.isUsage() && action.getName().equals(n.getNameAsString())) { + list.add(new Pair<>(action, arg)); + return; + } + } + throw new IllegalStateException("Cannot find USE action for var " + n); + } + + @Override + public void visit(ThisExpr n, String arg) { + for (VariableAction action : graphNode.getVariableActions()) { + if (action.isUsage() && action.getName().matches("^.*this$")) { + list.add(new Pair<>(action, arg)); + return; + } + } + throw new IllegalStateException("Could not find USE(this)"); + } + + @Override + public void visit(FieldAccessExpr n, String arg) { + if (!arg.isEmpty()) + arg = "." + arg; + n.getScope().accept(this, n.getNameAsString() + arg); + } + + @Override + public void visit(ObjectCreationExpr n, String arg) { + visitCall(n, arg); + } + + @Override + public void visit(MethodCallExpr n, String arg) { + visitCall(n, arg); + } + + protected void visitCall(Expression call, String arg) { + if (ASTUtils.shouldVisitArgumentsForMethodCalls((Resolvable) call)) + return; + VariableAction lastUseOut = null; + for (VariableAction variableAction : graphNode.getVariableActions()) { + if (variableAction instanceof VariableAction.CallMarker) { + VariableAction.CallMarker marker = (VariableAction.CallMarker) variableAction; + if (ASTUtils.equalsWithRange((Node) marker.getCall(), call) && !marker.isEnter()) { + assert lastUseOut != null; + list.add(new Pair<>(lastUseOut, arg)); + return; + } + } + if (variableAction.isUsage() && variableAction.getName().equals(VARIABLE_NAME_OUTPUT)) { + lastUseOut = variableAction; + } + } + throw new IllegalStateException("Could not find USE(-output-) corresponding to call " + call); + } + + @Override + public void visit(ArrayCreationExpr n, String arg) {} + + @Override + public void visit(ArrayInitializerExpr n, String arg) {} + + @Override + public void visit(BinaryExpr n, String arg) {} + + @Override + public void visit(ClassExpr n, String arg) {} + + @Override + public void visit(InstanceOfExpr n, String arg) {} + + @Override + public void visit(UnaryExpr n, String arg) {} + + @Override + public void visit(LambdaExpr n, String arg) {} + + @Override + public void visit(MethodReferenceExpr n, String arg) {} + + @Override + public void visit(PatternExpr n, String arg) {} + }, ""); + return list; + } + + /** Prepares a tree connection, to be applied when the PDG is built. This class is used in the construction + * of the CFG, and the object trees are not yet placed, so the arcs cannot be generated yet. */ + protected void markTransference(Pair sourcePair, VariableAction targetAction, String targetMember) { + VariableAction sourceAction = sourcePair.a; + String sourceMember = sourcePair.b; + if (targetAction.hasObjectTree()) { + ObjectTree.copyTargetTreeToSource(sourceAction.getObjectTree(), targetAction.getObjectTree(), sourceMember, targetMember); + sourceAction.setPDGTreeConnectionTo(targetAction, sourceMember, targetMember); + } else { + sourceAction.setPDGValueConnection(sourceMember); + } + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/Graph.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/Graph.java index 0498f174081c93441c2c7cfe706186eba3d6ba69..f83402553316ce17224143cffc16e06417685e9a 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/Graph.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/Graph.java @@ -46,11 +46,6 @@ public abstract class Graph extends DirectedPseudograph, Arc> { throw new IllegalStateException("There may only be one real node representing each AST node in the graph!"); } - /** Search for a node in the graph given its id. */ - public Optional> findNodeById(long id) { - return findNodeBy(n -> n.getId() == id); - } - /** Search for a node in the graph given a predicate it must pass. * If multiple nodes match the predicate, the first one found is returned. */ public Optional> findNodeBy(Predicate> p) { diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFG.java index 82144f46272bbce60c8b63ad73e2cbb1cd4a9478..8ebbb6efc085c96c57fd276ce92753375e1d0879 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFG.java @@ -34,4 +34,11 @@ public class ACFG extends CFG { public boolean isPseudoPredicate(GraphNode node) { return outgoingEdgesOf(node).stream().filter(Arc::isNonExecutableControlFlowArc).count() == 1; } + + /** Whether the given node is a predicate or not. A node is a predicate if + * it has more than one outgoing edge and it is not a pseudo-predicate. */ + @Override + public boolean isPredicate(GraphNode graphNode) { + return super.isPredicate(graphNode) && !isPseudoPredicate(graphNode); + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFGBuilder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFGBuilder.java index d0aa7cf350c8750280dbf67df67659072ab88b28..bd819ead5a552587d340f0c2c8e72da8dd4063f8 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFGBuilder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/ACFGBuilder.java @@ -4,7 +4,6 @@ import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.stmt.*; import es.upv.mist.slicing.graphs.cfg.CFGBuilder; import es.upv.mist.slicing.nodes.GraphNode; -import es.upv.mist.slicing.nodes.io.MethodExitNode; import es.upv.mist.slicing.utils.ASTUtils; import java.util.Deque; @@ -131,10 +130,6 @@ public class ACFGBuilder extends CFGBuilder { @Override public void visit(ReturnStmt returnStmt, Void arg) { GraphNode node = connectTo(returnStmt); - returnStmt.getExpression().ifPresent(n -> { - n.accept(this, arg); - node.addDefinedVariable(null, VARIABLE_NAME_OUTPUT, n); - }); returnList.add(node); clearHanging(); nonExecHangingNodes.add(node); // NEW vs CFG @@ -145,17 +140,8 @@ public class ACFGBuilder extends CFGBuilder { // ====================================================================== @Override - protected void visitCallableDeclaration(CallableDeclaration callableDeclaration, Void arg) { - graph.buildRootNode(callableDeclaration); - hangingNodes.add(graph.getRootNode()); - - ASTUtils.getCallableBody(callableDeclaration).accept(this, arg); - returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add); - nonExecHangingNodes.add(graph.getRootNode()); // NEW vs CFG - - MethodExitNode exit = new MethodExitNode(callableDeclaration); - graph.addVertex(exit); - addMethodOutput(callableDeclaration, exit); - connectTo(exit); + protected void buildExit(CallableDeclaration callableDeclaration) { + nonExecHangingNodes.add(graph.getRootNode()); + super.buildExit(callableDeclaration); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/PPDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/PPDG.java index 853d85d3c4a68417b75ae9500e0e51bd61625c14..07d3a494a0b1b85882230e0810f9a470b8a28dbb 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/PPDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/augmented/PPDG.java @@ -46,7 +46,7 @@ public class PPDG extends APDG { /** Finds the CD arcs that are only present in the PPDG and marks them as such. */ protected void markPPDGExclusiveEdges(CallableDeclaration declaration) { - APDG apdg = new APDG((ACFG) cfg); + APDG apdg = new APDG(); apdg.build(declaration); Set apdgArcs = apdg.edgeSet().stream() .filter(Arc::isUnconditionalControlDependencyArc) diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java index 32187e110c485e155d8d923f1abce56845fb1d68..347fbeab25c10151359ed5da9b3fb9e5c2759317 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java @@ -54,6 +54,11 @@ public class CFG extends GraphWithRootNode> { addEdge(from, to, arc); } + /** Whether the given node is a predicate or not. A node is a predicate if it has more than one outgoing edge. */ + public boolean isPredicate(GraphNode graphNode) { + return outgoingEdgesOf(graphNode).size() > 1; + } + /** Obtain the definitions that may have reached the given variable action. */ public List findLastDefinitionsFrom(VariableAction variable) { return findLastVarActionsFrom(variable, VariableAction::isDefinition); @@ -93,12 +98,13 @@ public class CFG extends GraphWithRootNode> { stream = stream.takeWhile(va -> va != var); List list = stream.filter(var::matches).filter(filter).collect(Collectors.toList()); if (!list.isEmpty()) { - for (int i = list.size() - 1; i >= 0; i--) { + boolean found = false; + for (int i = list.size() - 1; i >= 0 && !found; i--) { result.add(list.get(i)); if (!list.get(i).isOptional()) - break; + found = true; } - if (!list.get(0).isOptional()) + if (found) return true; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java index 0f8278eea8e7942ebdcd3db337357836475e9ab8..f34565ae4e7ddbc189e74c60ebe7cac7021fd302 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java @@ -328,10 +328,6 @@ 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(null, VARIABLE_NAME_OUTPUT, n); - }); returnList.add(node); clearHanging(); } @@ -362,12 +358,25 @@ public class CFGBuilder extends VoidVisitorAdapter { * to the exit node. */ protected void visitCallableDeclaration(CallableDeclaration callableDeclaration, Void arg) { + buildEnter(callableDeclaration); + visitCallableDeclarationBody(callableDeclaration, arg); + buildExit(callableDeclaration); + } + + /** Generate the ENTER node and add it to the list of hanging nodes. */ + protected void buildEnter(CallableDeclaration callableDeclaration) { graph.buildRootNode(callableDeclaration); hangingNodes.add(graph.getRootNode()); + } + /** Visit the body and add the return nodes to the hanging nodes list. */ + protected void visitCallableDeclarationBody(CallableDeclaration callableDeclaration, Void arg) { ASTUtils.getCallableBody(callableDeclaration).accept(this, arg); returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add); + } + /** Generate the method EXIT node. */ + protected void buildExit(CallableDeclaration callableDeclaration) { MethodExitNode exit = new MethodExitNode(callableDeclaration); graph.addVertex(exit); addMethodOutput(callableDeclaration, exit); @@ -378,8 +387,8 @@ public class CFGBuilder extends VoidVisitorAdapter { * @see #VARIABLE_NAME_OUTPUT */ protected void addMethodOutput(CallableDeclaration callableDeclaration, GraphNode exit) { if (!(callableDeclaration instanceof MethodDeclaration) || !((MethodDeclaration) callableDeclaration).getType().isVoidType()) { - VariableAction usage = new VariableAction.Usage(null, VARIABLE_NAME_OUTPUT, exit); - exit.addMovableVariable(new VariableAction.Movable(usage, OutputNode.create(callableDeclaration))); + VariableAction usage = new VariableAction.Usage(VariableAction.DeclarationType.SYNTHETIC, VARIABLE_NAME_OUTPUT, exit); + exit.addVariableAction(new VariableAction.Movable(usage, OutputNode.create(callableDeclaration))); } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java index cbde73c183e1cc47c6631f3382b01fd32907bdfd..c9e038a20b300f03485c9833b0786c21369ea6d7 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java @@ -17,7 +17,6 @@ import es.upv.mist.slicing.graphs.cfg.CFGBuilder; import es.upv.mist.slicing.nodes.GraphNode; import es.upv.mist.slicing.nodes.exceptionsensitive.*; import es.upv.mist.slicing.nodes.io.MethodExitNode; -import es.upv.mist.slicing.utils.ASTUtils; import es.upv.mist.slicing.utils.Logger; import java.util.*; @@ -30,7 +29,8 @@ import java.util.*; * and multiple calls with exceptions per CFG node are not considered. */ public class ESCFG extends ACFG { - protected static final String ACTIVE_EXCEPTION_VARIABLE = "-activeException-"; + /** The name for the currently active exception variable. */ + public static final String ACTIVE_EXCEPTION_VARIABLE = "-activeException-"; @Override protected CFGBuilder newCFGBuilder() { @@ -109,13 +109,7 @@ public class ESCFG extends ACFG { } @Override - protected void visitCallableDeclaration(CallableDeclaration callableDeclaration, Void arg) { - buildRootNode(callableDeclaration); - hangingNodes.add(getRootNode()); - - ASTUtils.getCallableBody(callableDeclaration).accept(this, arg); - returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add); - // NEW vs ACFG + protected void buildExit(CallableDeclaration callableDeclaration) { if (!exceptionSourceMap.isEmpty()) { // Normal exit NormalExitNode normalExit = new NormalExitNode(callableDeclaration); @@ -127,7 +121,7 @@ public class ESCFG extends ACFG { // Exception exit Collection exceptionExits = processExceptionSources(callableDeclaration); for (ExceptionExitNode node : exceptionExits) { - node.addUsedVariable(null, ACTIVE_EXCEPTION_VARIABLE); + node.addVAUseActiveException(); hangingNodes.add(node); lastNodes.addAll(hangingNodes); clearHanging(); @@ -138,12 +132,12 @@ public class ESCFG extends ACFG { MethodExitNode exit = new MethodExitNode(callableDeclaration); addVertex(exit); - if (exceptionSourceMap.isEmpty()) // NEW vs ACFG + if (exceptionSourceMap.isEmpty()) addMethodOutput(callableDeclaration, exit); - nonExecHangingNodes.addAll(exitNonExecHangingNodes); // NEW vs ACFG + nonExecHangingNodes.addAll(exitNonExecHangingNodes); connectTo(exit); - processPendingNormalResultNodes(); // NEW vs ACFG + processPendingNormalResultNodes(); } /** Converts the remaining exception sources into a collection of exception exit nodes. @@ -240,7 +234,6 @@ public class ESCFG extends ACFG { stmtStack.push(n); GraphNode stmt = connectTo(n); n.getExpression().accept(this, arg); - stmt.addDefinedVariable(null, ACTIVE_EXCEPTION_VARIABLE, n.getExpression()); populateExceptionSourceMap(new ExceptionSource(stmt, n.getExpression().calculateResolvedType())); clearHanging(); nonExecHangingNodes.add(stmt); @@ -286,7 +279,7 @@ public class ESCFG extends ACFG { for (ResolvedType type : resolved.getSpecifiedExceptions()) { hangingNodes.add(stmtNode); ExceptionReturnNode exceptionReturn = addExceptionReturnNode(call, type); - exceptionReturn.addDefinedVariable(null, ACTIVE_EXCEPTION_VARIABLE, null); // TODO: improve initializer + exceptionReturn.addVADefineActiveException(null); populateExceptionSourceMap(new ExceptionSource(exceptionReturn, type)); returnNodes.add(exceptionReturn); connectTo(exceptionReturn); @@ -313,7 +306,7 @@ public class ESCFG extends ACFG { for (ExceptionSource src : sources) (src.isActive() ? hangingNodes : nonExecHangingNodes).add(src.source); GraphNode node = connectTo(n, "catch (" + n.getParameter().toString() + ")"); - node.addUsedVariable(null, ACTIVE_EXCEPTION_VARIABLE); + node.addVAUseActiveException(); exceptionSourceMap.clear(); // 2. Set up as exception source ExceptionSource catchES = ExceptionSource.merge(node, sources); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java index b184a5f7aec197487e95117c83174ca573f50a3d..c7f4a3695db29a3819217681be99ea2a6e13f2a9 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java @@ -5,18 +5,32 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.ThisExpr; import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import com.github.javaparser.ast.visitor.Visitable; +import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.graphs.ClassGraph; +import es.upv.mist.slicing.graphs.ExpressionObjectTreeFinder; import es.upv.mist.slicing.graphs.cfg.CFGBuilder; import es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG; import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.VariableAction; import es.upv.mist.slicing.nodes.io.MethodExitNode; import es.upv.mist.slicing.utils.ASTUtils; +import es.upv.mist.slicing.utils.NodeHashSet; +import es.upv.mist.slicing.utils.NodeNotFoundException; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * An SDG that is tailored for Java, including a class graph, inheritance, @@ -28,9 +42,10 @@ public class JSysCFG extends ESCFG { throw new UnsupportedOperationException("Use build(CallableDeclaration, ClassGraph, Set)"); } - public void build(CallableDeclaration declaration, Set implicitConstructors) { + public void build(CallableDeclaration declaration, Set implicitConstructors, ClassGraph classGraph) { Builder builder = (Builder) newCFGBuilder(); builder.implicitDeclaration = implicitConstructors.contains(declaration); + builder.classGraph = classGraph; declaration.accept(builder, null); // Verify that it has been built exitNode = vertexSet().stream().filter(MethodExitNode.class::isInstance).findFirst() @@ -43,10 +58,114 @@ public class JSysCFG extends ESCFG { return new Builder(this); } + /** Given a usage of an object member, find the last definitions of that member. + * This method returns a list of variable actions, where the caller can find the member. */ + public List findLastDefinitionOfObjectMember(VariableAction usage, String member) { + return findLastVarActionsFrom(usage, def -> def.isDefinition() && def.hasTreeMember(member)); + } + + /** Given a usage of a primitive variable, find the last def actions that affect it. */ + public List findLastDefinitionOfPrimitive(VariableAction usage) { + return findLastVarActionsFrom(usage, VariableAction::isDefinition); + } + + /** Given the usage of a root object variable, find the last root definitions that affect it. */ + public List findLastDefinitionOfObjectRoot(VariableAction usage) { + return findLastVarActionsFrom(usage, VariableAction::isDefinition); + } + + /** Given a field declaration, locate all definitions that affect the given member. */ + public List findAllFutureObjectDefinitionsFor(VariableAction action) { + List list = new LinkedList<>(); + Predicate filter = a -> a.isDefinition() && a.getName().equals("this") && a.hasTreeMember(action.getName()); + findAllFutureVarActionsFor(new HashSet<>(), list, action.getGraphNode(), action, filter); + return list; + } + + /** Locate variable actions that match the given filter, starting in {@code currentNode}, at variable action + * {@code var} and searching forwards through the control-flow graph. The resulting variable actions are + * placed in the given argument. This search does not stop when an action in that control-flow branch is found. */ + protected void findAllFutureVarActionsFor(Set> visited, List result, + GraphNode currentNode, VariableAction var, + Predicate filter) { + // Base case + if (visited.contains(currentNode)) + return; + visited.add(currentNode); + + Stream stream = currentNode.getVariableActions().stream(); + if (var.getGraphNode().equals(currentNode)) + stream = stream.dropWhile(va -> va != var); + stream.filter(filter).forEach(result::add); + + // always traverse forwards! + for (Arc arc : outgoingEdgesOf(currentNode)) + if (arc.isExecutableControlFlowArc()) + findAllFutureVarActionsFor(visited, result, getEdgeTarget(arc), var, filter); + } + + /** Given an action that defines a member, locates the previous total definition that gave + * it value. */ + public List findLastTotalDefinitionOf(VariableAction action, String member) { + return findLastVarActionsFrom(action, def -> + (def.isDeclaration() && def.hasTreeMember(member)) + || (def.isDefinition() && def.asDefinition().isTotallyDefinedMember(member))); + } + + /** Given a definition of a given member, locate all definitions of the same object until a definition + * containing the given member is found (not including that last one). If the member is found in the + * given definition, it will return a list with only the given definition. */ + public List findNextObjectDefinitionsFor(VariableAction definition, String member) { + if (!this.containsVertex(definition.getGraphNode())) + throw new NodeNotFoundException(definition.getGraphNode(), this); + if (definition.hasTreeMember(member)) + return List.of(definition); + List list = new LinkedList<>(); + findNextVarActionsFor(new HashSet<>(), list, definition.getGraphNode(), definition, VariableAction::isDefinition, member); + return list; + } + + /** Locate variable actions that match the given filter and variable name, starting in {@code currentNode}, + * at variable action {@code var} and searching backwards. The resulting variable actions are placed in + * the given argument. This search stops after finding a matching action in each branch. */ + protected boolean findNextVarActionsFor(Set> visited, List result, + GraphNode currentNode, VariableAction var, + Predicate filter, String memberName) { + // Base case + if (visited.contains(currentNode)) + return true; + visited.add(currentNode); + + Stream stream = currentNode.getVariableActions().stream(); + if (var.getGraphNode().equals(currentNode)) + stream = stream.dropWhile(va -> va != var); + List list = stream.filter(var::matches).filter(filter).collect(Collectors.toList()); + if (!list.isEmpty()) { + boolean found = false; + for (VariableAction variableAction : list) { + if (!variableAction.isOptional() && variableAction.hasTreeMember(memberName)) { + found = true; + break; + } + result.add(variableAction); + } + if (found) + return true; + } + + // Not found: traverse forwards! + boolean allBranches = !outgoingEdgesOf(currentNode).isEmpty(); + for (Arc arc : outgoingEdgesOf(currentNode)) + if (arc.isExecutableControlFlowArc()) + allBranches &= findNextVarActionsFor(visited, result, getEdgeTarget(arc), var, filter, memberName); + return allBranches; + } + public class Builder extends ESCFG.Builder { + protected ClassGraph classGraph; /** List of implicit instructions inserted explicitly in this CFG. * They should be included in the graph as ImplicitNodes. */ - protected List methodInsertedInstructions = new LinkedList<>(); + protected NodeHashSet methodInsertedInstructions = new NodeHashSet<>(); /** Whether we are building a CFG for an implicit method or not. */ protected boolean implicitDeclaration = false; @@ -90,14 +209,19 @@ public class JSysCFG extends ESCFG { // Insert call to super() if it is implicit. if (!ASTUtils.constructorHasExplicitConstructorInvocation(n)){ var superCall = new ExplicitConstructorInvocationStmt(null, null, false, null, new NodeList<>()); - var returnThis = new ReturnStmt(new ThisExpr()); methodInsertedInstructions.add(superCall); - methodInsertedInstructions.add(returnThis); n.getBody().addStatement(0, superCall); - n.getBody().addStatement(returnThis); } + // insert return this; at the end of the constructor + var returnThis = new ReturnStmt(); + methodInsertedInstructions.add(returnThis); + n.getBody().addStatement(returnThis); + // modify every return statement so that it returns 'this' + modifyAllReturnExpr(n, ThisExpr::new); // Perform the same task as previous graphs. super.visit(n, arg); + // restore return statements + modifyAllReturnExpr(n, () -> null); // Convert enter/exit nodes to implicit if appropriate if (implicitDeclaration) { getRootNode().markAsImplicit(); @@ -106,5 +230,60 @@ public class JSysCFG extends ESCFG { .forEach(GraphNode::markAsImplicit); } } + + /** + * Sets the expression for all return statements contained in its argument. + * @param node The AST to search for return statements. + * @param expressionSupplier The expression to be set. + */ + protected void modifyAllReturnExpr(Node node, Supplier expressionSupplier) { + node.accept(new ModifierVisitor() { + @Override + public Visitable visit(ReturnStmt n, Void arg) { + n.setExpression(expressionSupplier.get()); + return n; + } + }, null); + } + + @Override + protected void addMethodOutput(CallableDeclaration callableDeclaration, GraphNode exit) { + super.addMethodOutput(callableDeclaration, exit); + for (VariableAction action : exit.getVariableActions()) { + if (action.getName().equals(VARIABLE_NAME_OUTPUT)) { + expandOutputVariable(callableDeclaration, action); + break; + } + } + } + + /** + * Generates the object tree for the output of a declaration, and copies that same tree + * to each of its return statements. It also sets the connection between them, to be applied + * later. + * @param callableDeclaration The root of the declarations' AST. + * @param useOutput The variable at the method's exit that uses -output-. + */ + protected void expandOutputVariable(CallableDeclaration callableDeclaration, VariableAction useOutput) { + // Generate the full tree for the method's returned type (static) + var fields = classGraph.generateObjectTreeForReturnOf(callableDeclaration); + if (fields.isPresent()) { + // Insert tree into the OutputNode + useOutput.getObjectTree().addAll(fields.get()); + // Insert tree into GraphNode nodes, the last action is always DEF(-output-) + vertexSet().stream() + .filter(gn -> gn.getAstNode() instanceof ReturnStmt) + .map(GraphNode::getLastVariableAction) + .map(VariableAction::getObjectTree) + .forEach(tree -> tree.addAll(fields.get())); + } + // Generate the assignment trees and prepare for linking + vertexSet().stream() + .filter(gn -> gn.getAstNode() instanceof ReturnStmt) + .forEach(gn -> { + Expression expr = ((ReturnStmt) gn.getAstNode()).getExpression().orElseThrow(); + new ExpressionObjectTreeFinder(gn).locateAndMarkTransferenceToRoot(expr, -1); + }); + } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java new file mode 100644 index 0000000000000000000000000000000000000000..05b5f15f3abf51db1c886d5e90dceff5c4759b6e --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java @@ -0,0 +1,88 @@ +package es.upv.mist.slicing.graphs.jsysdg; + +import es.upv.mist.slicing.graphs.CallGraph; +import es.upv.mist.slicing.graphs.cfg.CFGBuilder; +import es.upv.mist.slicing.graphs.exceptionsensitive.ExceptionSensitiveCallConnector; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.VariableAction; +import es.upv.mist.slicing.nodes.io.ActualIONode; +import es.upv.mist.slicing.nodes.io.CallNode; +import es.upv.mist.slicing.nodes.io.FormalIONode; +import es.upv.mist.slicing.nodes.io.OutputNode; +import es.upv.mist.slicing.utils.ASTUtils; + +import java.util.List; + +/** + * Generates interprocedural arcs between call sites and declarations. + * In the JSysDG, these include: + *
    + *
  • Actual-in to formal-in.
  • + *
  • Formal-out to actual-out.
  • + *
  • Output to call return (equivalent to the previous element but for the method's output).
  • + *
  • Exception exit to exception return.
  • + *
  • Normal exit to normal return.
  • + *
+ * For each node that features an object tree, that tree is connected in the same manner. + */ +public class JSysCallConnector extends ExceptionSensitiveCallConnector { + public JSysCallConnector(JSysDG sdg) { + super(sdg); + } + + @Override + public void connectAllCalls(CallGraph callGraph) { + super.connectAllCalls(callGraph); + } + + @Override + protected void createActualInConnection(ActualIONode actualIn, FormalIONode formalIn) { + super.createActualInConnection(actualIn, formalIn); + if (formalIsObject(formalIn)) + connectObjectInterprocedurally(actualIn, formalIn); + } + + @Override + protected void createActualOutConnection(FormalIONode formalOut, ActualIONode actualOut) { + super.createActualOutConnection(formalOut, actualOut); + if (formalIsObject(formalOut)) + connectObjectInterprocedurally(formalOut, actualOut); + } + + /** Whether the given formal node represents an object with an object tree. */ + protected boolean formalIsObject(FormalIONode formalNode) { + return formalNode.getVariableName().equals("this") + || !formalNode.getAstNode().getParameterByName(formalNode.getVariableName()) + .orElseThrow().getType().isPrimitiveType(); + } + + /** Connects the object tree from the last variable action in the source node to + * each object tree in the target node. */ + protected void connectObjectInterprocedurally(GraphNode source, GraphNode target) { + assert !target.getVariableActions().isEmpty(); + assert !source.getVariableActions().isEmpty(); + for (VariableAction targetVar : target.getVariableActions()) + source.getLastVariableAction().applySDGTreeConnection((JSysDG) sdg, targetVar); + } + + @Override + protected void createOutputReturnConnection(OutputNode outputNode, CallNode.Return callReturnNode) { + if (ASTUtils.declarationReturnIsObject(outputNode.getAstNode())) + connectObjectOutput(outputNode, callReturnNode); + else + super.createOutputReturnConnection(outputNode, callReturnNode); + } + + /** Generates the tree connection between the output and return nodes (definition to call). */ + protected void connectObjectOutput(GraphNode methodOutputNode, GraphNode callReturnNode) { + List outputList = methodOutputNode.getVariableActions(); + assert outputList.size() == 1; + assert outputList.get(0).isUsage() && outputList.get(0).getName().equals(CFGBuilder.VARIABLE_NAME_OUTPUT); + List returnList = callReturnNode.getVariableActions(); + assert returnList.size() == 1; + assert returnList.get(0).isDefinition() && returnList.get(0).getName().equals(CFGBuilder.VARIABLE_NAME_OUTPUT); + VariableAction source = outputList.get(0); + VariableAction target = returnList.get(0); + source.applySDGTreeConnection((JSysDG) sdg, target); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java index 809c0d663c2cfc8bc2e42470d868543a8ae3be7e..6956213f4728797d9eaa816d96cda7646b759f83 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java @@ -8,14 +8,21 @@ import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import es.upv.mist.slicing.graphs.ClassGraph; import es.upv.mist.slicing.graphs.augmented.PSDG; import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; import es.upv.mist.slicing.graphs.exceptionsensitive.ExceptionSensitiveCallConnector; import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.slicing.JSysDGSlicingAlgorithm; +import es.upv.mist.slicing.slicing.SlicingAlgorithm; import es.upv.mist.slicing.utils.NodeHashSet; public class JSysDG extends ESSDG { + @Override + protected SlicingAlgorithm createSlicingAlgorithm() { + return new JSysDGSlicingAlgorithm(this); + } @Override protected JSysDG.Builder createBuilder() { @@ -48,7 +55,7 @@ public class JSysDG extends ESSDG { @Override protected void buildCFG(CallableDeclaration declaration, CFG cfg) { - ((JSysCFG) cfg).build(declaration, newlyInsertedConstructors); + ((JSysCFG) cfg).build(declaration, newlyInsertedConstructors, ClassGraph.getInstance()); } @Override @@ -61,5 +68,15 @@ public class JSysDG extends ESSDG { assert cfg instanceof JSysCFG; return new JSysPDG((JSysCFG) cfg); } + + @Override + protected void connectCalls() { + new JSysCallConnector(JSysDG.this).connectAllCalls(callGraph); + } + + @Override + protected void createSummaryArcs() { + new SummaryArcAnalyzer(JSysDG.this, callGraph).analyze(); + } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java index 487ed1d06cb7b59170c518f6b5ce51621e62ffa5..51a5f1d316e914132860f07a9d3c0dadccfc4da3 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java @@ -1,6 +1,23 @@ package es.upv.mist.slicing.graphs.jsysdg; +import es.upv.mist.slicing.arcs.pdg.FlowDependencyArc; +import es.upv.mist.slicing.arcs.pdg.ObjectFlowDependencyArc; +import es.upv.mist.slicing.arcs.pdg.StructuralArc; +import es.upv.mist.slicing.arcs.pdg.TotalDefinitionDependenceArc; +import es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG; import es.upv.mist.slicing.graphs.exceptionsensitive.ESPDG; +import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.VariableAction; +import es.upv.mist.slicing.nodes.io.ActualIONode; +import es.upv.mist.slicing.nodes.io.CallNode; +import es.upv.mist.slicing.nodes.oo.MemberNode; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NAME; public class JSysPDG extends ESPDG { public JSysPDG() { @@ -10,4 +27,228 @@ public class JSysPDG extends ESPDG { public JSysPDG(JSysCFG cfg) { super(cfg); } + + @Override + protected PDG.Builder createBuilder() { + return new Builder(); + } + + protected void addStructuralArc(GraphNode source, GraphNode target) { + addEdge(source, target, new StructuralArc()); + } + + // definicion de raiz --object-flow--> uso de raiz + protected void addObjectFlowDependencyArc(VariableAction definition, VariableAction usage) { + addEdge(graphNodeOf(definition), graphNodeOf(usage), new ObjectFlowDependencyArc()); + } + + // definicion de miembro --object-flow--> definicion de raiz + protected void addObjectFlowDependencyArc(VariableAction nextDefinitionRoot, String memberDefined, VariableAction definition) { + MemberNode defMember = definition.getObjectTree().getNodeFor(memberDefined); + addEdge(defMember, graphNodeOf(nextDefinitionRoot), new ObjectFlowDependencyArc()); + } + + // def --flow--> use || dec --flow--> def + protected void addFlowDependencyArc(VariableAction source, VariableAction target) { + addEdge(graphNodeOf(source), graphNodeOf(target), new FlowDependencyArc(source.getName())); + } + + protected void addDeclarationFlowDependencyArc(VariableAction declaration, VariableAction definition) { + MemberNode defMember = definition.getObjectTree().getNodeFor(declaration.getName()); + addEdge(graphNodeOf(declaration), defMember, new FlowDependencyArc()); + } + + // definicion de miembro --flow--> uso de miembro + protected void addFlowDependencyArc(VariableAction definition, VariableAction usage, String objMember) { + GraphNode defMember = definition.getObjectTree().getNodeFor(objMember); + GraphNode useMember = usage.getObjectTree().getNodeFor(objMember); + addEdge(defMember, useMember, new FlowDependencyArc(objMember)); + } + + protected void addValueDependencyArc(VariableAction usage, String member, GraphNode statement) { + addEdge(usage.getObjectTree().getNodeFor(member), statement, new FlowDependencyArc(member)); + } + + protected void addTotalDefinitionDependencyArc(VariableAction totalDefinition, VariableAction target, String member) { + if (member.equals(ROOT_NAME)) + addEdge(graphNodeOf(totalDefinition), graphNodeOf(target), new TotalDefinitionDependenceArc()); + else + addEdge(totalDefinition.getObjectTree().getNodeFor(member), + target.getObjectTree().getNodeFor(member), + new TotalDefinitionDependenceArc()); + } + + protected GraphNode graphNodeOf(VariableAction action) { + if (action.hasObjectTree()) + return action.getObjectTree().getMemberNode(); + if (action instanceof VariableAction.Movable) + return ((VariableAction.Movable) action).getRealNode(); + return action.getGraphNode(); + } + + @Override + public void addDataDependencyArc(VariableAction src, VariableAction tgt) { + throw new UnsupportedOperationException("Use flow or object-flow dependency"); + } + + protected class Builder extends ESPDG.Builder { + + @Override + protected void buildDataDependency() { + addSyntheticNodesToPDG(); + applyTreeConnections(); + buildJSysDataDependency(); + valueDependencyForThrowStatements(); + } + + /** Compute flow, object flow and total definition dependence. */ + protected void buildJSysDataDependency() { + JSysCFG jSysCFG = (JSysCFG) cfg; + for (GraphNode node : vertexSet()) { + for (VariableAction varAct : node.getVariableActions()) { + // Total definition dependence + buildTotalDefinitionDependence(jSysCFG, varAct); + if (varAct.isUsage()) + buildUsageDependencies(jSysCFG, varAct); + else if (varAct.isDefinition()) + buildDefinitionDependencies(jSysCFG, varAct); + else if (varAct.isDeclaration()) + buildDeclarationDependencies(jSysCFG, varAct); + } + } + } + + /** Generate total definition dependence. Only generated for non-primitive usages and non-primitive, + * non-synthetic definitions. Connects each member to its previous total definition. */ + private void buildTotalDefinitionDependence(JSysCFG jSysCFG, VariableAction varAct) { + if (!varAct.isPrimitive() && (varAct.isUsage() || (varAct.isDefinition() && !varAct.isSynthetic()))) { + jSysCFG.findLastTotalDefinitionOf(varAct, ROOT_NAME).forEach(totalDef -> addTotalDefinitionDependencyArc(totalDef, varAct, ROOT_NAME)); + if (!varAct.hasObjectTree()) + return; + for (String member : varAct.getObjectTree().nameIterable()) + jSysCFG.findLastTotalDefinitionOf(varAct, member).forEach(totalDef -> addTotalDefinitionDependencyArc(totalDef, varAct, member)); + } + } + + /** Generate dependencies to usages, including flow dependency for primitives, + * object flow for object roots and flow for object members. */ + private void buildUsageDependencies(JSysCFG jSysCFG, VariableAction varAct) { + if (varAct.isPrimitive()) { + jSysCFG.findLastDefinitionOfPrimitive(varAct).forEach(def -> addFlowDependencyArc(def, varAct)); + } else { + jSysCFG.findLastDefinitionOfObjectRoot(varAct).forEach(def -> addObjectFlowDependencyArc(def, varAct)); + if (!varAct.hasObjectTree()) + return; + for (String member : varAct.getObjectTree().nameIterable()) + jSysCFG.findLastDefinitionOfObjectMember(varAct, member).forEach(def -> addFlowDependencyArc(def, varAct, member)); + } + } + + /** Generates dec --> def flow and def --> def object flow dependencies. */ + private void buildDefinitionDependencies(JSysCFG jSysCFG, VariableAction varAct) { + // Flow declaration --> definition + if (!varAct.isSynthetic()) + jSysCFG.findDeclarationFor(varAct).ifPresent(dec -> addFlowDependencyArc(dec, varAct)); + // Object flow definition --> definition + if (varAct.isPrimitive() || !varAct.hasObjectTree()) + return; + for (String member : varAct.getObjectTree().nameIterable()) + jSysCFG.findNextObjectDefinitionsFor(varAct, member).forEach(def -> addObjectFlowDependencyArc(varAct, member, def)); + } + + /** Generates dec --> def declaration dependencies for objects (constructors only). */ + private void buildDeclarationDependencies(JSysCFG jSysCFG, VariableAction varAct) { + if (!varAct.getName().startsWith("this.")) + return; + jSysCFG.findAllFutureObjectDefinitionsFor(varAct).forEach(def -> addDeclarationFlowDependencyArc(varAct, def)); + } + + @Override + protected void expandCalls() { + for (GraphNode graphNode : vertexSet()) { + for (VariableAction action : List.copyOf(graphNode.getVariableActions())) { + if (action instanceof VariableAction.Movable) { + ((VariableAction.Movable) action).moveOnly(); + } + } + } + } + + /** Add movables to the PDG, and all MemberNodes contained in object trees. */ + protected void addSyntheticNodesToPDG() { + for (GraphNode node : cfg.vertexSet()) { + Deque callNodeStack = new LinkedList<>(); + for (VariableAction va : node.getVariableActions()) { + if (va instanceof VariableAction.CallMarker) { + // Compute the call node, if entering the marker. Additionally, it places the node + // in the graph and makes it control-dependent on its container. + if (!((VariableAction.CallMarker) va).isEnter()) { + callNodeStack.pop(); + } else { + CallNode callNode = CallNode.create(((VariableAction.CallMarker) va).getCall()); + if (node.isImplicitInstruction()) + callNode.markAsImplicit(); + addVertex(callNode); + addStructuralArc(node, callNode); + callNodeStack.push(callNode); + } + continue; + } + GraphNode parentNode; // node that represents the root of the object tree + if (va instanceof VariableAction.Movable) { + VariableAction.Movable movable = (VariableAction.Movable) va; + addVertex(movable.getRealNode()); + connectRealNode(node, callNodeStack.peek(), movable.getRealNode()); + parentNode = movable.getRealNode(); + } else { + parentNode = node; + } + if (!va.hasObjectTree()) + continue; + // Extract the member nodes contained within the object tree + insertMemberNode(va.getObjectTree().getMemberNode(), parentNode); + for (MemberNode memberNode : va.getObjectTree().nodeIterable()) { + insertMemberNode(memberNode, parentNode); + } + } + assert callNodeStack.isEmpty(); + } + } + + @Override + protected void connectRealNode(GraphNode graphNode, CallNode callNode, GraphNode realNode) { + if (realNode instanceof ActualIONode || realNode instanceof CallNode.Return) { + assert callNode != null; + addStructuralArc(callNode, realNode); + } else { + addStructuralArc(graphNode == cfg.getExitNode() ? rootNode : graphNode, realNode); + } + } + + /** Apply the pre-assigned connections between object trees. */ + protected void applyTreeConnections() { + cfg.vertexSet().stream() + .flatMap(node -> node.getVariableActions().stream()) + .forEach(va -> va.applyPDGTreeConnections(JSysPDG.this)); + } + + /** Inserts a member node from an object tree onto the PDG. */ + protected void insertMemberNode(MemberNode memberNode, GraphNode parentNode) { + if (memberNode.getParent() == null) + memberNode.setParent(parentNode); + assert containsVertex(memberNode.getParent()); + addVertex(memberNode); + addStructuralArc(memberNode.getParent(), memberNode); + } + + /** Connects the tree that represents the active exception to its parent graph node. */ + protected void valueDependencyForThrowStatements() { + for (GraphNode node : vertexSet()) + for (VariableAction action : node.getVariableActions()) + if (action.isDefinition() + && action.hasObjectTree() + && action.getName().equals(ESCFG.ACTIVE_EXCEPTION_VARIABLE)) + addValueDependencyArc(action, ROOT_NAME, node); + } + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java new file mode 100644 index 0000000000000000000000000000000000000000..faa86740b7b527efc4df40df170507805b28a691 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java @@ -0,0 +1,82 @@ +package es.upv.mist.slicing.graphs.jsysdg; + +import com.github.javaparser.ast.body.CallableDeclaration; +import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.graphs.CallGraph; +import es.upv.mist.slicing.graphs.sdg.AbstractSummaryArcAnalyzer; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.SyntheticNode; +import es.upv.mist.slicing.nodes.VariableAction; +import es.upv.mist.slicing.nodes.io.FormalIONode; +import es.upv.mist.slicing.nodes.oo.MemberNode; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** + * Generates and places on the graph all summary arcs between actual-in and actual-out, return and exception/normal + * return nodes. Additionally, it generates them between the object trees of each of the aforementioned nodes. + */ +public class SummaryArcAnalyzer extends AbstractSummaryArcAnalyzer, SyntheticNode, SyntheticNode> { + public SummaryArcAnalyzer(JSysDG sdg, CallGraph graph) { + super(sdg, graph); + } + + @Override + protected Set> getFormalOutNodes(CallableDeclaration declaration) { + Set> set = super.getFormalOutNodes(declaration); + for (var node : Set.copyOf(set)) { + if (node.getVariableActions().isEmpty()) + continue; + assert node.getVariableActions().size() == 1; + VariableAction action = node.getVariableActions().get(0); + if (action.hasObjectTree()) { + set.add(action.getObjectTree().getMemberNode()); + for (MemberNode memberNode : action.getObjectTree().nodeIterable()) + set.add(memberNode); + } + } + return set; + } + + @Override + protected Set> computeFormalIn(SyntheticNode formalOut) { + Set> result = new HashSet<>(); + for (GraphNode graphNode : ((JSysDG) sdg).createSlicingAlgorithm().traverseProcedure(formalOut).getGraphNodes()) + if (isFormalIn(graphNode) && graphNode instanceof SyntheticNode) + result.add((SyntheticNode) graphNode); + return result; + } + + @Override + protected Optional> findActualIn(CallGraph.Edge edge, SyntheticNode formalIn) { + return sdg.incomingEdgesOf(formalIn).stream() + .filter(Arc::isInterproceduralInputArc) + .map(sdg::getEdgeSource) + .filter(actualIn -> goToParent(actualIn).getAstNode() == edge.getCall()) + .map(node -> (SyntheticNode) node) + .findAny(); + } + + @Override + protected Optional> findOutputNode(CallGraph.Edge edge, SyntheticNode formalOut) { + return sdg.outgoingEdgesOf(formalOut).stream() + .filter(Arc::isInterproceduralOutputArc) + .map(sdg::getEdgeTarget) + .filter(actualOut -> goToParent(actualOut).getAstNode() == edge.getCall()) + .map(node -> (SyntheticNode) node) + .findAny(); + } + + private boolean isFormalIn(GraphNode graphNode) { + GraphNode parent = goToParent(graphNode); + return parent instanceof FormalIONode && ((FormalIONode) parent).isInput(); + } + + private GraphNode goToParent(GraphNode memberNode) { + if (memberNode instanceof MemberNode) + return goToParent(((MemberNode) memberNode).getParent()); + return memberNode; + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java index f6d561282b31b701250365a03759655ed26173c6..779606894f598d0e32c65148ff461f5c0d52a917 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java @@ -75,12 +75,8 @@ public class DynamicTypeResolver { /** Searches for the corresponding VariableAction object, then calls {@link #resolveVariableAction(VariableAction)}. */ protected Stream resolveVariable(Expression expression, GraphNode graphNode) { - Optional va = graphNode.getVariableActions().stream() - .filter(action -> action.hasVariableExpression() && ASTUtils.equalsWithRange(action.getVariableExpression(), expression)) - .findFirst(); - if (va.isEmpty()) - return anyTypeOf(expression); - return resolveVariableAction(va.get()); + // TODO: implement a search like ExpressionObjectTreeFinder. + return anyTypeOf(expression); } /** diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/AbstractSummaryArcAnalyzer.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/AbstractSummaryArcAnalyzer.java new file mode 100644 index 0000000000000000000000000000000000000000..ca5a407a391ba6eac0e05bced5328ddef4597f96 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/AbstractSummaryArcAnalyzer.java @@ -0,0 +1,105 @@ +package es.upv.mist.slicing.graphs.sdg; + +import com.github.javaparser.ast.body.CallableDeclaration; +import es.upv.mist.slicing.graphs.BackwardDataFlowAnalysis; +import es.upv.mist.slicing.graphs.CallGraph; +import es.upv.mist.slicing.nodes.SyntheticNode; +import es.upv.mist.slicing.nodes.exceptionsensitive.ExitNode; +import es.upv.mist.slicing.nodes.io.FormalIONode; +import es.upv.mist.slicing.nodes.io.OutputNode; + +import java.util.*; +import java.util.stream.Stream; + +/** + * Base class for generating and placing in an SDG the summary arcs. + * @param The type of node for actual-in nodes. + * @param The type of node for formal-out nodes. + * @param The type of node for formal-in nodes. + */ +public abstract class AbstractSummaryArcAnalyzer, FormalOut extends SyntheticNode, FormalIn extends SyntheticNode> + extends BackwardDataFlowAnalysis, Map>> { + protected final SDG sdg; + + protected AbstractSummaryArcAnalyzer(SDG sdg, CallGraph graph) { + super(graph); + this.sdg = sdg; + } + + @Override + protected Map> compute(CallGraph.Vertex vertex, Set predecessors) { + saveDeclaration(vertex); + return initialValue(vertex); + } + + @Override + protected Map> initialValue(CallGraph.Vertex vertex) { + Map> value; + if (vertexDataMap.containsKey(vertex)) { + value = vertexDataMap.get(vertex); + } else { + value = new HashMap<>(); + for (var formalOut : getFormalOutNodes(vertex.getDeclaration())) + value.put(formalOut, new HashSet<>()); + } + value.replaceAll((key, oldValue) -> computeFormalIn(key)); + return value; + } + + /** Obtain all nodes that represent the output of a method declaration. These include formal-out, + * return nodes and normal/exception exit nodes (for exception handling). */ + protected Set getFormalOutNodes(CallableDeclaration declaration) { + Set set = new HashSet<>(); + Stream.concat( + Stream.concat( + sdg.vertexSet().stream() // formal-out nodes + .filter(FormalIONode.class::isInstance) + .map(FormalIONode.class::cast) + .filter(FormalIONode::isOutput), + sdg.vertexSet().stream() // output nodes (the value returned) + .filter(OutputNode.class::isInstance) + .map(OutputNode.class::cast)), + sdg.vertexSet().stream() // normal/exception exit nodes (for exception handling) + .filter(ExitNode.class::isInstance) + .map(ExitNode.class::cast)) + // Only nodes that match the current declaration + .filter(node -> node.getAstNode() == declaration) + .forEach(e -> set.add((FormalOut) e)); + return set; + } + + /** Given an output or formal-out node, locate the formal-in nodes it depends on. + * This search should be performed intra-procedurally, the parent class will take + * care of the rest of cases by adding summary arcs computed for other declarations. */ + protected abstract Set computeFormalIn(FormalOut formalOut); + + /** Generate all summary arcs for a given call. Arc generation should be idempotent: + * if this method is called repeatedly it should not create duplicate summary arcs. */ + protected void saveDeclaration(CallGraph.Vertex vertex) { + var result = vertexDataMap.get(vertex); + for (CallGraph.Edge edge : graph.incomingEdgesOf(vertex)) { + for (var entry : result.entrySet()) { + var actualOutOpt = findOutputNode(edge, entry.getKey()); + if (actualOutOpt.isEmpty()) + continue; + for (var formalIn : entry.getValue()) { + var actualInOpt = findActualIn(edge, formalIn); + if (actualInOpt.isEmpty()) + continue; + if (!sdg.containsEdge(actualInOpt.get(), actualOutOpt.get())) + sdg.addSummaryArc(actualInOpt.get(), actualOutOpt.get()); + } + } + } + } + + /** Find the actual-in that represents the given formal-in in the given call. + * There may not be one. In that case, the dependency between formal-in/out should + * not result in a summary arc. */ + protected abstract Optional findActualIn(CallGraph.Edge edge, FormalIn formalIn); + + /** Find the actual-out, return or exception/normal return node that represents the given + * formal-out, output or exception/normal exit node in the given call. There may not be one. + * In that case, the dependency between formal-in/out should not result in a summary arc. */ + protected abstract Optional> findOutputNode(CallGraph.Edge edge, FormalOut formalOut); +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java index 971f3736c49beb1aecdd2dc6702fca40c7293fc7..26d3c86fe0e6f17f6fec6dea3cedb7f68628c351 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java @@ -3,7 +3,6 @@ package es.upv.mist.slicing.graphs.sdg; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.resolution.Resolvable; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; -import es.upv.mist.slicing.arcs.Arc; import es.upv.mist.slicing.arcs.sdg.CallArc; import es.upv.mist.slicing.arcs.sdg.ParameterInOutArc; import es.upv.mist.slicing.graphs.CallGraph; @@ -12,7 +11,6 @@ import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.nodes.io.FormalIONode; import es.upv.mist.slicing.nodes.io.OutputNode; -import es.upv.mist.slicing.utils.Logger; /** Adds interprocedural arcs between the 'PDG components' of an SDG. * Arcs generated include {@link ParameterInOutArc parameter input/output} and @@ -38,11 +36,6 @@ public class CallConnector { var callExpr = (Resolvable) callNode.getAstNode(); callGraph.getCallTargets(callExpr) .map(sdg::findNodeByASTNode) - .filter(opt -> { - if (opt.isEmpty()) - Logger.format("Method declaration not found: '%s'. Discarding", callExpr); - return opt.isPresent(); - }) .map(opt -> opt.orElseThrow(IllegalArgumentException::new)) .forEach(node -> connectCall(callNode, node)); } @@ -54,7 +47,6 @@ public class CallConnector { // Locate and connect all ACTUAL nodes sdg.outgoingEdgesOf(callNode).stream() - .filter(Arc::isControlDependencyArc) .map(sdg::getEdgeTarget) .filter(ActualIONode.class::isInstance) .map(ActualIONode.class::cast) @@ -67,9 +59,9 @@ public class CallConnector { // Locate and connect the -output- node sdg.outgoingEdgesOf(callNode).stream() - .filter(Arc::isControlDependencyArc) .map(sdg::getEdgeTarget) .filter(CallNode.Return.class::isInstance) + .map(CallNode.Return.class::cast) .forEach(n -> connectOutput(declarationNode, n)); } @@ -80,7 +72,11 @@ public class CallConnector { .filter(FormalIONode.class::isInstance) .map(FormalIONode.class::cast) .filter(actualIn::matchesFormalIO) - .forEach(formalIn -> sdg.addParameterInOutArc(actualIn, formalIn)); + .forEach(formalIn -> createActualInConnection(actualIn, formalIn)); + } + + protected void createActualInConnection(ActualIONode actualIn, FormalIONode formalIn) { + sdg.addParameterInOutArc(actualIn, formalIn); } /** Connects an actual-out node to its formal-out counterpart. Arc in reverse direction. */ @@ -90,14 +86,23 @@ public class CallConnector { .filter(FormalIONode.class::isInstance) .map(FormalIONode.class::cast) .filter(actualOut::matchesFormalIO) - .forEach(formalOut -> sdg.addParameterInOutArc(formalOut, actualOut)); + .forEach(formalOut -> createActualOutConnection(formalOut, actualOut)); + } + + protected void createActualOutConnection(FormalIONode formalOut, ActualIONode actualOut) { + sdg.addParameterInOutArc(formalOut, actualOut); } /** Connects a method call return node to its method output counterpart. Arc in reverse direction. */ - protected void connectOutput(GraphNode> methodDeclaration, GraphNode methodOutputNode) { + protected void connectOutput(GraphNode> methodDeclaration, CallNode.Return callReturnNode) { sdg.outgoingEdgesOf(methodDeclaration).stream() .map(sdg::getEdgeTarget) .filter(OutputNode.class::isInstance) - .forEach(n -> sdg.addParameterInOutArc(n, methodOutputNode)); + .map(OutputNode.class::cast) + .forEach(n -> createOutputReturnConnection(n, callReturnNode)); + } + + protected void createOutputReturnConnection(OutputNode outputNode, CallNode.Return callReturnNode) { + sdg.addParameterInOutArc(outputNode, callReturnNode); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java index 92e8289eeb3fcecc168496465ca175e49acc53a4..f6b0482df67940e41d9600b2bc4b9fa96f257510 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java @@ -2,39 +2,32 @@ package es.upv.mist.slicing.graphs.sdg; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.MethodCallExpr; -import com.github.javaparser.ast.expr.ThisExpr; -import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; -import com.github.javaparser.resolution.Resolvable; import com.github.javaparser.resolution.UnsolvedSymbolException; -import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import es.upv.mist.slicing.graphs.BackwardDataFlowAnalysis; import es.upv.mist.slicing.graphs.CallGraph; import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.nodes.VariableAction; import es.upv.mist.slicing.utils.ASTUtils; import es.upv.mist.slicing.utils.Logger; +import es.upv.mist.slicing.utils.Utils; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.stream.Collectors; import java.util.stream.Stream; -// TODO: this approach of generating actual nodes may skip an argument; this is only a problem if there is a definition -// TODO: update placement of actual and formal outputs for ESSDG (see if the definition/usage reaches all/any exits). /** * A backward data flow analysis on the call graph and a map of CFGs, to find which callable * declarations define, use or declare which variables, interprocedurally. * @param The action to be searched for */ -public abstract class InterproceduralActionFinder extends BackwardDataFlowAnalysis, Set>> { +public abstract class InterproceduralActionFinder extends BackwardDataFlowAnalysis, Set> { protected final Map, CFG> cfgMap; + /** A map from vertex and action to its corresponding stored action, to avoid generating duplicate nodes. */ + protected final Map> actionStoredMap = new HashMap<>(); - public InterproceduralActionFinder(CallGraph callGraph, Map, CFG> cfgMap) { + protected InterproceduralActionFinder(CallGraph callGraph, Map, CFG> cfgMap) { super(callGraph); this.cfgMap = cfgMap; } @@ -49,18 +42,26 @@ public abstract class InterproceduralActionFinder exte graph.vertexSet().forEach(this::saveDeclaration); } + /** Obtains the StoredAction object with information on which actions have been stored. */ + protected StoredAction getStored(CallGraph.Vertex vertex, A action) { + return actionStoredMap.get(vertex).get(action); + } + /** Save the current set of actions associated to the given declaration. It will avoid saving * duplicates by default, so this method may be called multiple times safely. */ protected void saveDeclaration(CallGraph.Vertex vertex) { - Set> storedActions = vertexDataMap.get(vertex); - + var actions = vertexDataMap.get(vertex); + // Update stored action map + actionStoredMap.computeIfAbsent(vertex, v -> new HashMap<>()); + for (A a : actions) + actionStoredMap.get(vertex).computeIfAbsent(a, __ -> new StoredAction()); // FORMAL: per declaration (1) - for (StoredAction sa : storedActions) - sa.storeFormal(a -> sandBoxedHandler(vertex.getDeclaration(), a, this::handleFormalAction)); + for (A a : actions) + getStored(vertex, a).storeFormal(() -> sandBoxedHandler(vertex, a, this::handleFormalAction)); // ACTUAL: per call (n) for (CallGraph.Edge edge : graph.incomingEdgesOf(vertex)) - storedActions.stream().sorted(new ParameterFieldSorter(edge)) - .forEach(sa -> sa.storeActual(edge, (e, a) -> sandBoxedHandler(e, a, this::handleActualAction))); + actions.stream().sorted(new ParameterFieldSorter(edge)).forEach(a -> + getStored(vertex, a).storeActual(edge, e -> sandBoxedHandler(e, a, this::handleActualAction))); } /** A sandbox to avoid resolution errors when a variable is included that is a class name @@ -69,12 +70,12 @@ public abstract class InterproceduralActionFinder exte try { handler.accept(location, action); } catch (UnsolvedSymbolException e) { - Logger.log("Skipping a symbol, cannot be resolved: " + action.getVariable()); + Logger.log("Skipping a symbol, cannot be resolved: " + action.getName()); } } /** Generate the formal node(s) related to this action and declaration. */ - protected abstract void handleFormalAction(CallableDeclaration declaration, A action); + protected abstract void handleFormalAction(CallGraph.Vertex vertex, A action); /** Generate the actual node(s) related to this action and call. */ protected abstract void handleActualAction(CallGraph.Edge edge, A action); @@ -83,63 +84,14 @@ public abstract class InterproceduralActionFinder exte // ============== AUXILIARY METHODS FOR CHILDREN ============= // =========================================================== - /** Given a call, obtains the scope. If none is present it may return null. - * ExpressionConstructorInvocations result in a this expression, as they - * may be seen as dynamic method calls that can modify 'this'. */ - protected static Expression obtainScope(Resolvable call) { - if (call instanceof MethodCallExpr) { - var methodCall = (MethodCallExpr) call; - return methodCall.getScope().orElse(null); - } else if (call instanceof ExplicitConstructorInvocationStmt) { - return new ThisExpr(); - } else { - throw new IllegalArgumentException("The given call is not of a valid type"); - } - } - /** Obtains the expression passed as argument for the given action at the given call. If {@code input} * is false, primitive parameters will be skipped, as their value cannot be redefined.*/ - protected Expression extractArgument(ResolvedParameterDeclaration p, CallGraph.Edge edge, boolean input) { + protected Optional extractArgument(VariableAction action, CallGraph.Edge edge, boolean input) { CallableDeclaration callTarget = graph.getEdgeTarget(edge).getDeclaration(); - if (!input && p.getType().isPrimitive()) - return null; // primitives do not have actual-out! - int paramIndex = ASTUtils.getMatchingParameterIndex(callTarget, p); - return ASTUtils.getResolvableArgs(edge.getCall()).get(paramIndex); - } - - /** Generate the name that should be given to an object in a caller method, given an action - * in the callee method. This is used to transform a reference to 'this' into the scope - * of a method. */ - protected static String obtainAliasedFieldName(VariableAction action, CallGraph.Edge edge) { - if (edge.getCall() instanceof MethodCallExpr) { - Optional optScope = ((MethodCallExpr) edge.getCall()).getScope(); - return obtainAliasedFieldName(action, edge, optScope.isPresent() ? optScope.get().toString() : ""); - } else if (edge.getCall() instanceof ExplicitConstructorInvocationStmt) { - // The only possibility is 'this' or its fields, so we return empty scope and 'type.this.' is generated - return obtainAliasedFieldName(action, edge, ""); - } else { - throw new IllegalArgumentException("The given call is not of a valid type"); - } - } - - /** To be used by {@link #obtainAliasedFieldName(VariableAction, CallGraph.Edge)} exclusively.
- * Given a scope, name inside a method and call, translates the name of a variable, such that 'this' becomes - * the scope of the method. */ - protected static String obtainAliasedFieldName(VariableAction action, CallGraph.Edge edge, String scope) { - if (scope.isEmpty()) { - return action.getVariable(); - } else { - String newPrefix = scope; - newPrefix = newPrefix.replaceAll("((\\.)super|^super)(\\.)?", "$2this$3"); - if (newPrefix.equals("this")) { - String fqName = ASTUtils.getClassNode(edge.getGraphNode().getAstNode()).getFullyQualifiedName().orElseThrow(); - newPrefix = fqName + ".this"; - } - String withPrefix = action.getVariable(); - String withoutPrefix = withPrefix.replaceFirst("^((.*\\.)?this\\.?)", ""); - String result = newPrefix + withoutPrefix; - return result.replaceFirst("this(\\.this)+", "this"); - } + if (!input && action.isPrimitive()) + return Optional.empty(); // primitives do not have actual-out! + int paramIndex = ASTUtils.getMatchingParameterIndex(callTarget, action.getName()); + return Optional.of(ASTUtils.getResolvableArgs(edge.getCall()).get(paramIndex)); } // =========================================================== @@ -147,19 +99,18 @@ public abstract class InterproceduralActionFinder
exte // =========================================================== @Override - protected Set> compute(CallGraph.Vertex vertex, Set predecessors) { + protected Set compute(CallGraph.Vertex vertex, Set predecessors) { saveDeclaration(vertex); - Set> newValue = new HashSet<>(vertexDataMap.get(vertex)); + Set newValue = new HashSet<>(vertexDataMap.get(vertex)); newValue.addAll(initialValue(vertex)); return newValue; } @Override - protected Set> initialValue(CallGraph.Vertex vertex) { + protected Set initialValue(CallGraph.Vertex vertex) { CFG cfg = cfgMap.get(vertex.getDeclaration()); - if (cfg == null) - return Collections.emptySet(); - Stream stream = cfg.vertexSet().stream() + assert cfg != null; + Stream actionStream = cfg.vertexSet().stream() // Ignore root node, it is literally the entrypoint for interprocedural actions. .filter(n -> n != cfg.getRootNode()) .flatMap(n -> n.getVariableActions().stream()) @@ -167,9 +118,18 @@ public abstract class InterproceduralActionFinder exte .filter(Predicate.not(VariableAction::isSynthetic)) // We skip over non-root variables (for each 'x.a' action we'll find 'x' later) .filter(VariableAction::isRootAction); - return mapAndFilterActionStream(stream, cfg) - .map(StoredAction::new) - .collect(Collectors.toSet()); + Stream filteredStream = mapAndFilterActionStream(actionStream, cfg); + Set set = new HashSet<>(); + for (Iterator it = filteredStream.iterator(); it.hasNext(); ) { + A a = it.next(); + if (set.contains(a)) { + if (a.hasObjectTree()) + Utils.setGet(set, a).getObjectTree().addAll(a.getObjectTree()); + } else { + set.add(a.createCopy()); + } + } + return set; } /** Given a stream of VariableAction objects, map it to the finders' type and @@ -183,83 +143,53 @@ public abstract class InterproceduralActionFinder exte /** A comparator to sort parameters and fields in the generation of actual nodes. It will sort * {@link StoredAction}s in the following order: fields, then parameters by descending index number. * The actual nodes will be generated in that order and inserted in reverse order in the graph node. */ - private class ParameterFieldSorter implements Comparator> { + private class ParameterFieldSorter implements Comparator { protected final CallGraph.Edge edge; public ParameterFieldSorter(CallGraph.Edge edge) { this.edge = edge; } @Override - public int compare(StoredAction o1, StoredAction o2) { - ResolvedValueDeclaration r1 = null; - ResolvedValueDeclaration r2 = null; - try { - r1 = o1.getAction().getResolvedValueDeclaration(); - r2 = o2.getAction().getResolvedValueDeclaration(); - if (r1.isParameter() && r2.isParameter()) - return -Integer.compare(ASTUtils.getMatchingParameterIndex(graph.getEdgeTarget(edge).getDeclaration(), r1.asParameter()), - ASTUtils.getMatchingParameterIndex(graph.getEdgeTarget(edge).getDeclaration(), r2.asParameter())); - else if (r1.isField() && r2.isField()) - return 0; - else if (r1.isParameter() && r2.isField()) - return -1; - else if (r1.isField() && r2.isParameter()) - return 1; - } catch (UnsolvedSymbolException e) { - if (r1 == null) - return 1; - else if (r2 == null) - return -1; - else - return 0; - } + public int compare(A o1, A o2) { + if (o1.isParameter() && o2.isParameter()) + return -Integer.compare(ASTUtils.getMatchingParameterIndex(graph.getEdgeTarget(edge).getDeclaration(), o1.getName()), + ASTUtils.getMatchingParameterIndex(graph.getEdgeTarget(edge).getDeclaration(), o2.getName())); + else if (o1.isField() && o2.isField()) + return 0; + else if (o1.isParameter() && o2.isField()) + return -1; + else if (o1.isField() && o2.isParameter()) + return 1; throw new IllegalArgumentException("One or more arguments is not a field or parameter"); } } /** A wrapper around a variable action, which keeps track of whether formal and actual nodes * have been saved to the graph or not. */ - protected static class StoredAction { - protected final A action; + protected static class StoredAction { /** Whether the action has been saved as actual node for each call. */ private final Map, Boolean> actualStoredMap = new HashMap<>(); /** Whether the action has been saved as formal node. */ protected boolean formalStored = false; - private StoredAction(A action) { - this.action = action; - } - - public A getAction() { - return action; - } + private StoredAction() {} /** If this action has not yet been saved as formal node, use the argument to do so, then mark it as stored. */ - private void storeFormal(Consumer save) { + private void storeFormal(Runnable save) { if (!formalStored) { - save.accept(action); + save.run(); formalStored = true; } } /** If this action has not yet been saved as actual node for the given edge, * use the consumer to do so, then mark it as stored. */ - private void storeActual(CallGraph.Edge edge, BiConsumer, A> save) { + private void storeActual(CallGraph.Edge edge, Consumer> save) { if (!actualStoredMap.getOrDefault(edge, false)) { - save.accept(edge, action); + save.accept(edge); actualStoredMap.put(edge, true); } } - - @Override - public int hashCode() { - return Objects.hash(action); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof StoredAction && Objects.equals(action, ((StoredAction) obj).action); - } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralDefinitionFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralDefinitionFinder.java index b9ca7713f272b299bbadd6225da8d00c4de79c6c..bbfd848e3f0b69f4c17b69ad1e0569c3dc8a92cf 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralDefinitionFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralDefinitionFinder.java @@ -2,73 +2,89 @@ package es.upv.mist.slicing.graphs.sdg; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; -import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import es.upv.mist.slicing.graphs.CallGraph; import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.ObjectTree; import es.upv.mist.slicing.nodes.VariableAction; +import es.upv.mist.slicing.nodes.VariableAction.DeclarationType; +import es.upv.mist.slicing.nodes.VariableAction.Definition; +import es.upv.mist.slicing.nodes.VariableAction.Movable; import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.FormalIONode; +import es.upv.mist.slicing.utils.ASTUtils; import java.util.*; import java.util.stream.Stream; /** An interprocedural definition finder, which adds the associated actions to formal and actual nodes in the CFGs. */ -public class InterproceduralDefinitionFinder extends InterproceduralActionFinder { +public class InterproceduralDefinitionFinder extends InterproceduralActionFinder { public InterproceduralDefinitionFinder(CallGraph callGraph, Map, CFG> cfgMap) { super(callGraph, cfgMap); } @Override - protected void handleFormalAction(CallableDeclaration declaration, VariableAction.Definition def) { - CFG cfg = cfgMap.get(declaration); - ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration(); - if (!resolved.isParameter() || !resolved.getType().isPrimitive()) { - FormalIONode formalOut = FormalIONode.createFormalOut(declaration, resolved); - cfg.getExitNode().addMovableVariable(new VariableAction.Movable(def.toUsage(cfg.getExitNode()), formalOut)); + protected void handleFormalAction(CallGraph.Vertex vertex, Definition def) { + CFG cfg = cfgMap.get(vertex.getDeclaration()); + if (!def.isParameter() || !def.isPrimitive()) { + FormalIONode formalOut = FormalIONode.createFormalOut(vertex.getDeclaration(), def.getName()); + Movable movable = new Movable(def.toUsage(cfg.getExitNode()), formalOut); + cfg.getExitNode().addVariableAction(movable); } - FormalIONode formalIn = FormalIONode.createFormalInDecl(declaration, resolved); - cfg.getRootNode().addMovableVariable(new VariableAction.Movable(def.toDeclaration(cfg.getRootNode()), formalIn)); + FormalIONode formalIn = FormalIONode.createFormalInDecl(vertex.getDeclaration(), def.getName()); + cfg.getRootNode().addVariableAction(new Movable(def.toDeclaration(cfg.getRootNode()), formalIn)); } @Override - protected void handleActualAction(CallGraph.Edge edge, VariableAction.Definition def) { - List movables = new LinkedList<>(); + protected void handleActualAction(CallGraph.Edge edge, Definition def) { + List movables = new LinkedList<>(); GraphNode graphNode = edge.getGraphNode(); - ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration(); - if (resolved.isParameter()) { - Expression arg = extractArgument(resolved.asParameter(), edge, false); - if (arg == null) + if (def.isParameter()) { + Optional arg = extractArgument(def, edge, false); + if (arg.isEmpty()) return; - ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), resolved, arg); - if (resolved.isParameter()) { - Set exprSet = new HashSet<>(); - arg.accept(new OutNodeVariableVisitor(), exprSet); - for (NameExpr nameExpr : exprSet) - movables.add(new VariableAction.Movable(new VariableAction.Definition(nameExpr, nameExpr.toString(), graphNode), actualOut)); + ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), def.getName(), arg.get()); + extractOutputVariablesAsMovables(arg.get(), movables, graphNode, actualOut, def); + } else if (def.isField()) { + if (def.isStatic()) { + // Known limitation: static fields } else { - movables.add(new VariableAction.Movable(def.toDefinition(graphNode), actualOut)); + assert !(edge.getCall() instanceof ObjectCreationExpr); + ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), def.getName(), null); + Optional scope = ASTUtils.getResolvableScope(edge.getCall()); + if (scope.isPresent()) { + extractOutputVariablesAsMovables(scope.get(), movables, graphNode, actualOut, def); + } else { + assert def.hasObjectTree(); + var movableDef = new Definition(DeclarationType.FIELD, "this", graphNode, (ObjectTree) def.getObjectTree().clone()); + movables.add(new Movable(movableDef, actualOut)); + } } - } else if (resolved.isField()) { - // Known limitation: static fields - // An object creation expression doesn't alter an existing object via actual-out - // it is returned and assigned via -output-. - if (edge.getCall() instanceof ObjectCreationExpr) - return; - String aliasedName = obtainAliasedFieldName(def, edge); - ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), resolved, null); - var movableDef = new VariableAction.Definition(obtainScope(edge.getCall()), aliasedName, graphNode, null); - movables.add(new VariableAction.Movable(movableDef, actualOut)); } else { throw new IllegalStateException("Definition must be either from a parameter or a field!"); } graphNode.addActionsForCall(movables, edge.getCall(), false); } + /** For each variable of an expression that may be passed through it (i.e., that if passed as argument of a function, could + * a modification to that reference affect any part of the variables passed as input?). It then generates the necessary + * definitions and links between trees in order to define each of them as a function of the given actual out. */ + protected void extractOutputVariablesAsMovables(Expression e, List movables, GraphNode graphNode, ActualIONode actualOut, VariableAction def) { + Set defExpressions = new HashSet<>(); + e.accept(new OutNodeVariableVisitor(), defExpressions); + for (Expression expression : defExpressions) { + assert def.hasObjectTree(); + DeclarationType type = DeclarationType.valueOf(expression); + Definition inner = new Definition(type, expression.toString(), graphNode, (ObjectTree) def.getObjectTree().clone()); + if (defExpressions.size() > 1) + inner.setOptional(true); + movables.add(new Movable(inner, actualOut)); + } + } + @Override - protected Stream mapAndFilterActionStream(Stream stream, CFG cfg) { + protected Stream mapAndFilterActionStream(Stream stream, CFG cfg) { return stream.filter(VariableAction::isDefinition) .map(VariableAction::asDefinition) .filter(def -> cfg.findDeclarationFor(def).isEmpty()); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java index 979200fa12e0d87c6099d6849493687921a9e295..e429cc02ef73529cd608b3acc0cdd19d4c097ca7 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java @@ -1,67 +1,88 @@ package es.upv.mist.slicing.graphs.sdg; +import com.github.javaparser.ast.Node; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.ObjectCreationExpr; -import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.ast.expr.ThisExpr; import es.upv.mist.slicing.graphs.CallGraph; +import es.upv.mist.slicing.graphs.ExpressionObjectTreeFinder; import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.ObjectTree; import es.upv.mist.slicing.nodes.VariableAction; -import es.upv.mist.slicing.nodes.VariableVisitor; +import es.upv.mist.slicing.nodes.VariableAction.Definition; +import es.upv.mist.slicing.nodes.VariableAction.Movable; +import es.upv.mist.slicing.nodes.VariableAction.Usage; import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.FormalIONode; +import es.upv.mist.slicing.utils.ASTUtils; -import java.util.LinkedList; -import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; /** An interprocedural usage finder, which adds the associated actions to formal and actual nodes in the CFGs. */ -public class InterproceduralUsageFinder extends InterproceduralActionFinder { +public class InterproceduralUsageFinder extends InterproceduralActionFinder { public InterproceduralUsageFinder(CallGraph callGraph, Map, CFG> cfgMap) { super(callGraph, cfgMap); } @Override - protected void handleFormalAction(CallableDeclaration declaration, VariableAction.Usage use) { - CFG cfg = cfgMap.get(declaration); - ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration(); - FormalIONode formalIn = FormalIONode.createFormalIn(declaration, resolved); - cfg.getRootNode().addMovableVariable(new VariableAction.Movable(use.toDefinition(cfg.getRootNode()), formalIn)); + protected void handleFormalAction(CallGraph.Vertex vertex, Usage use) { + CFG cfg = cfgMap.get(vertex.getDeclaration()); + FormalIONode formalIn = FormalIONode.createFormalIn(vertex.getDeclaration(), use.getName()); + Movable movable = new Movable(use.toDefinition(cfg.getRootNode()), formalIn); + cfg.getRootNode().addVariableAction(movable); } @Override - protected void handleActualAction(CallGraph.Edge edge, VariableAction.Usage use) { - List movables = new LinkedList<>(); + protected void handleActualAction(CallGraph.Edge edge, Usage use) { GraphNode graphNode = edge.getGraphNode(); - ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration(); - if (resolved.isParameter()) { - Expression argument = extractArgument(resolved.asParameter(), edge, true); - ActualIONode actualIn = ActualIONode.createActualIn(edge.getCall(), resolved, argument); - argument.accept(new VariableVisitor( - (n, exp, name) -> movables.add(new VariableAction.Movable(new VariableAction.Declaration(exp, name, graphNode), actualIn)), - (n, exp, name, expression) -> movables.add(new VariableAction.Movable(new VariableAction.Definition(exp, name, graphNode, expression), actualIn)), - (n, exp, name) -> movables.add(new VariableAction.Movable(new VariableAction.Usage(exp, name, graphNode), actualIn)) - ), VariableVisitor.Action.USE); - } else if (resolved.isField()) { - // Known limitation: static fields - // An object creation expression input an existing object via actual-in because it creates it. - if (edge.getCall() instanceof ObjectCreationExpr) - return; - String aliasedName = obtainAliasedFieldName(use, edge); - ActualIONode actualIn = ActualIONode.createActualIn(edge.getCall(), resolved, null); - var movableUse = new VariableAction.Usage(obtainScope(edge.getCall()), aliasedName, graphNode); - movables.add(new VariableAction.Movable(movableUse, actualIn)); + if (use.isParameter()) { + if (!use.isPrimitive()) { + assert use.hasObjectTree(); + ActualIONode actualIn = locateActualInNode(edge, use.getName()); + Definition def = new Definition(VariableAction.DeclarationType.SYNTHETIC, "-arg-in-", graphNode, (ObjectTree) use.getObjectTree().clone()); + Movable movDef = new Movable(def, actualIn); + graphNode.addVariableActionAfterLastMatchingRealNode(movDef, actualIn); + ExpressionObjectTreeFinder finder = new ExpressionObjectTreeFinder(graphNode); + finder.locateAndMarkTransferenceToRoot(actualIn.getArgument(), def); + } + } else if (use.isField()) { + if (use.isStatic()) { + // Known limitation: static fields + } else { + // An object creation expression input an existing object via actual-in because it creates it. + assert !(edge.getCall() instanceof ObjectCreationExpr); + ActualIONode actualIn = locateActualInNode(edge, use.getName()); + Definition def = new Definition(VariableAction.DeclarationType.SYNTHETIC, "-scope-in-", graphNode, (ObjectTree) use.getObjectTree().clone()); + Movable movDef = new Movable(def, actualIn); + Expression scope = Objects.requireNonNullElseGet(actualIn.getArgument(), ThisExpr::new); + graphNode.addVariableActionAfterLastMatchingRealNode(movDef, actualIn); + ExpressionObjectTreeFinder finder = new ExpressionObjectTreeFinder(graphNode); + finder.locateAndMarkTransferenceToRoot(scope, def); + } } else { throw new IllegalStateException("Definition must be either from a parameter or a field!"); } - graphNode.addActionsForCall(movables, edge.getCall(), true); + } + + /** Locates the actual-in node associated with the given variable name and call edge. */ + protected ActualIONode locateActualInNode(CallGraph.Edge edge, String name) { + return edge.getGraphNode().getSyntheticNodesInMovables().stream() + .filter(ActualIONode.class::isInstance) + .map(ActualIONode.class::cast) + .filter(ActualIONode::isInput) + .filter(actual -> actual.getVariableName().equals(name)) + .filter(actual -> ASTUtils.equalsWithRange(actual.getAstNode(), (Node) edge.getCall())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("can't locate actual-in node")); } @Override - protected Stream mapAndFilterActionStream(Stream stream, CFG cfg) { + protected Stream mapAndFilterActionStream(Stream stream, CFG cfg) { return stream.filter(VariableAction::isUsage) .map(VariableAction::asUsage) .filter(Predicate.not(cfg::isCompletelyDefined)); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/OutNodeVariableVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/OutNodeVariableVisitor.java index aaabe19cb8494f06d08bacafd27c58d26451e435..611c1355597547ec05134f5eeb5da2d1c43807a3 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/OutNodeVariableVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/OutNodeVariableVisitor.java @@ -1,59 +1,80 @@ package es.upv.mist.slicing.graphs.sdg; import com.github.javaparser.ast.expr.*; -import com.github.javaparser.ast.stmt.ExpressionStmt; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import java.util.Set; /** Visitor that obtains a set of variables that may have been redefined in * an expression passed as parameter to a call. */ -public class OutNodeVariableVisitor extends VoidVisitorAdapter> { +public class OutNodeVariableVisitor extends VoidVisitorAdapter> { @Override - public void visit(ArrayAccessExpr n, Set variables) { + public void visit(ArrayAccessExpr n, Set variables) { n.getName().accept(this, variables); } @Override - public void visit(CastExpr n, Set variables) { + public void visit(CastExpr n, Set variables) { n.getExpression().accept(this, variables); } @Override - public void visit(ConditionalExpr n, Set variables) { + public void visit(ConditionalExpr n, Set variables) { n.getThenExpr().accept(this, variables); n.getElseExpr().accept(this, variables); } @Override - public void visit(EnclosedExpr n, Set variables) { + public void visit(EnclosedExpr n, Set variables) { n.getInner().accept(this, variables); } @Override - public void visit(ExpressionStmt n, Set variables) { - n.getExpression().accept(this, variables); + public void visit(FieldAccessExpr n, Set variables) { + n.getScope().accept(this, variables); } @Override - public void visit(FieldAccessExpr n, Set variables) { - n.getScope().accept(this, variables); + public void visit(ThisExpr n, Set variables) { + variables.add(n); } @Override - public void visit(NameExpr n, Set variables) { + public void visit(NameExpr n, Set variables) { variables.add(n); } + // Expressions that stop the visit: no object can be outputted, modified inside the call and returned so that + // we may access its value. + @Override - public void visit(UnaryExpr n, Set variables) { - switch (n.getOperator()) { - case POSTFIX_DECREMENT: - case POSTFIX_INCREMENT: - case PREFIX_DECREMENT: - case PREFIX_INCREMENT: - n.getExpression().accept(this, variables); - } - } + public void visit(UnaryExpr n, Set variables) {} + + @Override + public void visit(ArrayCreationExpr n, Set arg) {} + + @Override + public void visit(ArrayInitializerExpr n, Set arg) {} + + @Override + public void visit(BinaryExpr n, Set arg) {} + + @Override + public void visit(ClassExpr n, Set arg) {} + + @Override + public void visit(InstanceOfExpr n, Set arg) {} + + @Override + public void visit(MethodCallExpr n, Set arg) {} + + @Override + public void visit(ObjectCreationExpr n, Set arg) {} + + @Override + public void visit(LambdaExpr n, Set arg) {} + + @Override + public void visit(MethodReferenceExpr n, Set arg) {} } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java index a5767b2bbc3f9734a209ec4fb310f978c650fea0..02236c2aaf2f54f38dabb71d7333997f32ce8c04 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java @@ -5,7 +5,6 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import es.upv.mist.slicing.arcs.pdg.ControlDependencyArc; import es.upv.mist.slicing.arcs.pdg.DataDependencyArc; @@ -21,18 +20,13 @@ import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.graphs.pdg.PDG; import es.upv.mist.slicing.nodes.GraphNode; import es.upv.mist.slicing.nodes.SyntheticNode; -import es.upv.mist.slicing.nodes.VariableAction; -import es.upv.mist.slicing.nodes.io.ActualIONode; -import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.slicing.*; import es.upv.mist.slicing.utils.ASTUtils; import java.util.Collection; -import java.util.List; import java.util.Map; -import java.util.Optional; - -import static es.upv.mist.slicing.graphs.cfg.CFGBuilder.VARIABLE_NAME_OUTPUT; +import java.util.NoSuchElementException; +import java.util.Set; /** * The System Dependence Graph represents the statements of a program in @@ -59,10 +53,13 @@ public class SDG extends Graph implements Sliceable, Buildable> optSlicingNode = slicingCriterion.findNode(this); - if (optSlicingNode.isEmpty()) - throw new IllegalArgumentException("Could not locate the slicing criterion in the SDG"); - return createSlicingAlgorithm().traverse(optSlicingNode.get()); + Set> slicingCriterionNodes; + try { + slicingCriterionNodes = slicingCriterion.findNode(this); + } catch (NoSuchElementException e) { + throw new IllegalArgumentException("Could not locate the slicing criterion " + slicingCriterion); + } + return createSlicingAlgorithm().traverse(slicingCriterionNodes); } protected SlicingAlgorithm createSlicingAlgorithm() { @@ -92,6 +89,16 @@ public class SDG extends Graph implements Sliceable, Buildable node) { + if (node instanceof SyntheticNode) + return false; + for (CFG cfg : cfgMap.values()) + if (cfg.containsVertex(node)) + return cfg.isPredicate(node); + throw new IllegalArgumentException("Node " + node.getId() + "'s associated CFG cannot be found!"); + } + public void addCallArc(GraphNode from, GraphNode> to) { this.addEdge(from, to, new CallArc()); } @@ -100,7 +107,7 @@ public class SDG extends Graph implements Sliceable, Buildable to) { + public void addSummaryArc(SyntheticNode from, SyntheticNode to) { this.addEdge(from, to, new SummaryArc()); } @@ -108,19 +115,18 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) { // See creation strategy at http://kaz2.dsic.upv.es:3000/Fzg46cQvT1GzHQG9hFnP1g#Using-data-flow-in-the-SDG // This ordering cannot be altered, as each step requires elements from the previous one. - classGraph = createClassGraph(nodeList); // 0 - buildCFGs(nodeList); // 1 - callGraph = createCallGraph(nodeList); // 2 - dataFlowAnalysis(); // 3 - buildAndCopyPDGs(); // 4 - connectCalls(); // 5 - createSummaryArcs(); // 6 + createClassGraph(nodeList); // 0 + buildCFGs(nodeList); // 1 + createCallGraph(nodeList); // 2 + dataFlowAnalysis(); // 3 + buildAndCopyPDGs(); // 4 + connectCalls(); // 5 + createSummaryArcs(); // 6 } /** Build a CFG per declaration found in the list of compilation units. */ @@ -148,44 +154,21 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) { - CallGraph callGraph = new CallGraph(cfgMap, classGraph); + protected void createCallGraph(NodeList nodeList) { + callGraph = new CallGraph(cfgMap, ClassGraph.getInstance()); callGraph.build(nodeList); - return callGraph; } /** Create class graph from the list of compilation units. */ - protected ClassGraph createClassGraph(NodeList nodeList){ - ClassGraph classGraph = new ClassGraph(); - classGraph.build(nodeList); - return classGraph; + protected void createClassGraph(NodeList nodeList){ + ClassGraph.getNewInstance().build(nodeList); } - /** Perform interprocedural analyses to determine the actual, formal and call return nodes. */ + /** Perform interprocedural analyses to determine the actual and formal nodes. */ protected void dataFlowAnalysis() { new InterproceduralDefinitionFinder(callGraph, cfgMap).save(); // 3.1 new InterproceduralUsageFinder(callGraph, cfgMap).save(); // 3.2 - insertCallOutput(); // 3.3 - } - - /** Insert {@link CallNode.Return call return} nodes onto all appropriate calls. */ - protected void insertCallOutput() { - for (CallGraph.Edge edge : callGraph.edgeSet()) { - if (ASTUtils.resolvableIsVoid(edge.getCall())) - continue; - // We handle super()/this() in VariableVisitor - if (edge.getCall() instanceof ExplicitConstructorInvocationStmt) - continue; - GraphNode graphNode = edge.getGraphNode(); - // A node defines -output- - var def = new VariableAction.Definition(null, VARIABLE_NAME_OUTPUT, graphNode); - var defMov = new VariableAction.Movable(def, CallNode.Return.create(edge.getCall())); - graphNode.addActionsForCall(List.of(defMov), edge.getCall(), false); - // The container of the call uses -output- - var use = new VariableAction.Usage(null, VARIABLE_NAME_OUTPUT, graphNode); - graphNode.addActionsAfterCall(edge.getCall(), use); - } } /** Build a PDG per declaration, based on the CFGs built previously and enhanced by data analyses. */ diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SummaryArcAnalyzer.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SummaryArcAnalyzer.java index c1ab83bd2dc58e9145b2cc2ea3ba329054bc484a..e380702e0a4e7469865e08d814916687025e2921 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SummaryArcAnalyzer.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SummaryArcAnalyzer.java @@ -1,7 +1,6 @@ package es.upv.mist.slicing.graphs.sdg; import com.github.javaparser.ast.body.CallableDeclaration; -import es.upv.mist.slicing.graphs.BackwardDataFlowAnalysis; import es.upv.mist.slicing.graphs.CallGraph; import es.upv.mist.slicing.nodes.SyntheticNode; import es.upv.mist.slicing.nodes.exceptionsensitive.ExitNode; @@ -11,63 +10,19 @@ import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.nodes.io.FormalIONode; import es.upv.mist.slicing.nodes.io.OutputNode; -import java.util.*; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class SummaryArcAnalyzer extends BackwardDataFlowAnalysis, Map>, Set>> { - protected final SDG sdg; +/** + * Generates the summary arcs between actual-in and actual-out, return and exception/exit return nodes. + */ +public class SummaryArcAnalyzer extends AbstractSummaryArcAnalyzer>, FormalIONode> { public SummaryArcAnalyzer(SDG sdg, CallGraph graph) { - super(graph); - this.sdg = sdg; - } - - @Override - protected Map>, Set> compute(CallGraph.Vertex vertex, Set predecessors) { - saveDeclaration(vertex); - return initialValue(vertex); + super(sdg, graph); } @Override - protected Map>, Set> initialValue(CallGraph.Vertex vertex) { - Map>, Set> value; - if (vertexDataMap.containsKey(vertex)) { - value = vertexDataMap.get(vertex); - } else { - value = new HashMap<>(); - for (var formalOut : getFormalOutNodes(vertex.getDeclaration())) - value.put(formalOut, new HashSet<>()); - } - value.replaceAll((key, oldValue) -> computeFormalIn(key)); - return value; - } - - /** Obtain all nodes that represent the output of a method declaration. These include formal-out, - * return nodes and normal/exception exit nodes (for exception handling). */ - protected Set>> getFormalOutNodes(CallableDeclaration declaration) { - Set>> set = new HashSet<>(); - Stream.concat( - Stream.concat( - sdg.vertexSet().stream() // formal-out nodes - .filter(FormalIONode.class::isInstance) - .map(FormalIONode.class::cast) - .filter(FormalIONode::isOutput), - sdg.vertexSet().stream() // output nodes (the value returned) - .filter(OutputNode.class::isInstance) - .map(OutputNode.class::cast)), - sdg.vertexSet().stream() // normal/exception exit nodes (for exception handling) - .filter(ExitNode.class::isInstance) - .map(ExitNode.class::cast)) - // Only nodes that match the current declaration - .filter(node -> node.getAstNode() == declaration) - .forEach(set::add); - return set; - } - - /** Given an output or formal-out node, locate the formal-in nodes it depends on. - * This search should be performed intra-procedurally, the parent class will take - * care of the rest of cases by adding summary arcs computed for other declarations. */ protected Set computeFormalIn(SyntheticNode> formalOut) { return sdg.createSlicingAlgorithm().traverseProcedure(formalOut).getGraphNodes().stream() .filter(FormalIONode.class::isInstance) @@ -76,29 +31,7 @@ public class SummaryArcAnalyzer extends BackwardDataFlowAnalysis edge : graph.incomingEdgesOf(vertex)) { - for (var entry : result.entrySet()) { - var actualOutOpt = findOutputNode(edge, entry.getKey()); - if (actualOutOpt.isEmpty()) - continue; - for (var formalIn : entry.getValue()) { - var actualInOpt = findActualIn(edge, formalIn); - if (actualInOpt.isEmpty()) - continue; - if (!sdg.containsEdge(actualInOpt.get(), actualOutOpt.get())) - sdg.addSummaryArc(actualInOpt.get(), actualOutOpt.get()); - } - } - } - } - - /** Find the actual-in that represents the given formal-in in the given call. - * There may not be one. In that case, the dependency between formal-in/out should - * not result in a summary arc. */ + @Override protected Optional findActualIn(CallGraph.Edge edge, FormalIONode formalIn) { return sdg.vertexSet().stream() .filter(ActualIONode.class::isInstance) @@ -108,9 +41,7 @@ public class SummaryArcAnalyzer extends BackwardDataFlowAnalysis> findOutputNode(CallGraph.Edge edge, SyntheticNode> formalOut) { if (formalOut instanceof FormalIONode) return findActualOut(edge, (FormalIONode) formalOut); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java index 58c936b469fd070394be887141d3bfda56b79b7d..19f5438a820b0bac3b906ef19c023a2699e75bbd 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java @@ -9,10 +9,9 @@ import es.upv.mist.slicing.graphs.pdg.PDG; import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.utils.ASTUtils; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; +import java.util.*; + +import static es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG.ACTIVE_EXCEPTION_VARIABLE; /** * Represents a node in the various graphs ({@link CFG CFG}, {@link PDG PDG} and {@link SDG SDG}), @@ -32,6 +31,9 @@ public class GraphNode implements Comparable> { protected final List variableActions; /** The method calls contained */ protected final List> methodCalls = new LinkedList<>(); + /** Nodes that are generated as a result of the instruction represented by this GraphNode and that may + * be included in Movable actions. */ + protected final Set> syntheticNodesInMovables = new HashSet<>(); /** @see #isImplicitInstruction() */ protected boolean isImplicit = false; @@ -79,6 +81,14 @@ public class GraphNode implements Comparable> { return Collections.unmodifiableList(variableActions); } + /** Returns the last variable action in this node. + * @throws IllegalStateException If there are no variable actions. */ + public VariableAction getLastVariableAction() { + if (variableActions.isEmpty()) + throw new IllegalStateException("There are no variable actions in this node"); + return variableActions.get(variableActions.size() - 1); + } + /** The node's label. It represents the portion of the node that * is covered by this node, in the case of block statements. */ public String getLabel() { @@ -87,9 +97,9 @@ public class GraphNode implements Comparable> { /** The node's long-form label, including its id and information on variables. */ public String getLongLabel() { - String label = getId() + ": " + getLabel(); + String label = getId() + ": " + getLabel().replace("\\", "\\\\"); if (!getVariableActions().isEmpty()) - label += "\n" + getVariableActions().stream().map(Object::toString).reduce((a, b) -> a + "," + b).orElse("--"); + label += "\\n" + getVariableActions().stream().map(Object::toString).reduce((a, b) -> a + "," + b).orElse("--"); return label; } @@ -134,36 +144,60 @@ public class GraphNode implements Comparable> { throw new IllegalArgumentException("Could not find markers for " + call.resolve().getSignature() + " in " + this); } - /** Append the given actions to after the actions of the given call. */ - public void addActionsAfterCall(Resolvable call, VariableAction... actions) { + /** Register a node that is contained in this node until the CFG + * is converted into the PDG. */ + public void addSyntheticNode(SyntheticNode node) { + syntheticNodesInMovables.add(node); + } + + /** @see #syntheticNodesInMovables */ + public Collection> getSyntheticNodesInMovables() { + return Collections.unmodifiableSet(syntheticNodesInMovables); + } + + /** Append a variable action to the list of variable actions. When the action + * is movable, its real node is registered in {@link #syntheticNodesInMovables}. */ + public void addVariableAction(VariableAction action) { + if (action instanceof VariableAction.Movable) + syntheticNodesInMovables.add(((VariableAction.Movable) action).getRealNode()); + variableActions.add(action); + } + + /** + * Searches for the last variable action that matches the given real node, and appends + * the given action immediately after. + */ + @SuppressWarnings("unchecked") + public void addVariableActionAfterLastMatchingRealNode(VariableAction.Movable action, SyntheticNode realNode) { + boolean found = false; for (int i = 0; i < variableActions.size(); i++) { - VariableAction var = variableActions.get(i); - if (var instanceof VariableAction.CallMarker) { - VariableAction.CallMarker marker = (VariableAction.CallMarker) var; - if (marker.getCall().equals(call) && !marker.isEnter()) { - variableActions.addAll(i + 1, List.of(actions)); - return; - } + VariableAction a = variableActions.get(i); + if (a instanceof VariableAction.Movable + && ((VariableAction.Movable) a).getRealNode() == realNode) { + found = true; + } else if (found) { + // The previous one matched, this one does not. Add before this one. + variableActions.add(i, action); + return; } } - throw new IllegalArgumentException("Could not find markers for " + call.resolve().getSignature() + " in " + this); - } - - /** Create and append a declaration of a variable to the list of actions of this node. */ - public void addDeclaredVariable(Expression variable, String realName) { - variableActions.add(new VariableAction.Declaration(variable, realName, this)); + // If the last one matched, add to the end + if (found) + variableActions.add(action); + else { + assert syntheticNodesInMovables.contains(realNode); + addActionsForCall(List.of(action), (Resolvable) realNode.getAstNode(), true); + } } - /** Create and append a definition of a variable to the list of actions of this node. */ - public void addDefinedVariable(Expression variable, String realName, Expression expression) { - VariableAction.Definition def = new VariableAction.Definition(variable, realName, this, expression); - variableActions.add(def); + /** Adds the variable action DEF(-active-exception-) to the end of this method. */ + public void addVADefineActiveException(Expression expression) { + variableActions.add(new VariableAction.Definition(VariableAction.DeclarationType.SYNTHETIC, ACTIVE_EXCEPTION_VARIABLE, this, expression)); } - /** Create and append a usage of a variable to the list of actions of this node. */ - public void addUsedVariable(Expression variable, String realName) { - VariableAction.Usage use = new VariableAction.Usage(variable, realName, this); - variableActions.add(use); + /** Adds the variable action USE(-active-exception-) to the end of this method. */ + public void addVAUseActiveException() { + variableActions.add(new VariableAction.Usage(VariableAction.DeclarationType.SYNTHETIC, ACTIVE_EXCEPTION_VARIABLE, this)); } /** Create and append a call marker to the list of actions of this node. */ @@ -172,11 +206,6 @@ public class GraphNode implements Comparable> { variableActions.add(new VariableAction.CallMarker(call, this, enter)); } - /** Create and append a movable variable action to the list of actions of this node. */ - public void addMovableVariable(VariableAction.Movable movable) { - variableActions.add(movable); - } - // ============================================================ // ======================= Overridden ======================= // ============================================================ diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java new file mode 100644 index 0000000000000000000000000000000000000000..b3ca2a7bf3b1e103350da5ad6efc4a88e9dd05ea --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java @@ -0,0 +1,311 @@ +package es.upv.mist.slicing.nodes; + +import es.upv.mist.slicing.nodes.oo.MemberNode; +import es.upv.mist.slicing.utils.Utils; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A tree data structure that mimics the tree found in an object's fields. + * Each tree contains a MemberNode that represents its, including a name. + * If the variable is undefined when the tree is created, the root of this + * tree will be named "-root-". The real name of the root can be found in + * its associated VariableAction.
+ * + * Object trees may not be reused, and must be cloned via {@link #clone()}. + * Otherwise, the MemberNodes representing the tree will be the same in the graph. + */ +public class ObjectTree implements Cloneable { + /** The default name of a tree's root. */ + public static final String ROOT_NAME = "-root-"; + + /** Regex pattern to split the root from the fields of a field access expression. */ + private static final Pattern FIELD_SPLIT = Pattern.compile("^(?(([_0-9A-Za-z]+\\.)*this)|([_0-9A-Za-z]+)|(-root-))(\\.(?.+))?$"); + + /** Direct children of this tree node, mapped by field name. */ + private final Map childrenMap = new HashMap<>(); + /** The MemberNode that represents this tree node in the PDG and SDG. */ + private final MemberNode memberNode; + + /** Create a root of a new object tree with the default name. */ + public ObjectTree() { + this(ROOT_NAME); + } + + /** Create a root of a new object tree with the given name. */ + public ObjectTree(String memberName) { + memberNode = new MemberNode(memberName, null); + } + + /** Create a child tree node for the given field, whose node is linked to the given parent. */ + private ObjectTree(String memberName, ObjectTree parent) { + this.memberNode = new MemberNode(memberName, parent.memberNode); + } + + /** The name of the variable or field represented by this tree. It doesn't include ancestors. */ + protected String getMemberName() { + return memberNode == null ? ROOT_NAME : memberNode.getLabel(); + } + + public MemberNode getMemberNode() { + return memberNode; + } + + /** Whether this object tree has fields. */ + public boolean hasChildren() { + return !childrenMap.isEmpty(); + } + + /** + * Insert a field with the given name. This method should only be called on a root object tree. + * This method may be used to add multiple levels simultaneously, calling this method with + * the argument {@code "a.b.c"} on a new root tree, it will create the tree "b" inside the root + * and "c" inside "b". + * @param fieldName The field to be added, should include the root variable name. For example, + * to add the field "x" to a variable "a", this argument should be "a.x". + */ + public void addField(String fieldName) { + String members = removeRoot(fieldName); + addNonRootField(members); + } + + /** Similar to {@link #addField(String)}, but may be called at any level + * and the argument must not contain the root variable. */ + private void addNonRootField(String members) { + if (members.contains(".")) { + int firstDot = members.indexOf('.'); + String first = members.substring(0, firstDot); + String rest = members.substring(firstDot + 1); + childrenMap.computeIfAbsent(first, f -> new ObjectTree(f, this)); + childrenMap.get(first).addNonRootField(rest); + } else { + childrenMap.computeIfAbsent(members, f -> new ObjectTree(f, this)); + } + } + + /** Copies the structure of another object tree into this object tree. + * All elements inserted in the current tree are a copy of the argument's children and members. */ + public void addAll(ObjectTree tree) { + for (Map.Entry entry : tree.childrenMap.entrySet()) + if (childrenMap.containsKey(entry.getKey())) + childrenMap.get(entry.getKey()).addAll(entry.getValue()); + else + childrenMap.put(entry.getKey(), entry.getValue().clone(this)); + } + + /** + * Copies a subtree from source into another subtree in target. + * + * @param source The source of the nodes. + * @param target The tree where nodes will be added + * @param sourcePrefix The prefix to be consumed before copying nodes. Without root. + * @param targetPrefix The prefix to be consumed before copying nodes. Without root. + */ + public static void copyTargetTreeToSource(ObjectTree source, ObjectTree target, String sourcePrefix, String targetPrefix) { + ObjectTree a = source.findObjectTreeOfMember(sourcePrefix); + ObjectTree b = target.findObjectTreeOfMember(targetPrefix); + a.addAll(b); + } + + /** + * Locate an object tree that represents a field of this object. + * @param member The field, without a root prefix. + */ + ObjectTree findObjectTreeOfMember(String member) { + ObjectTree result = this; + while (!member.isEmpty()) { + int firstDot = member.indexOf('.'); + String first, rest; + if (firstDot != -1) { + first = member.substring(0, firstDot); + rest = member.substring(firstDot + 1); + } else { + first = member; + rest = ""; + } + result = result.childrenMap.get(first); + member = rest; + } + return result; + } + + /** Whether this object tree contains the given member. The argument should contain the root variable name. */ + public boolean hasMember(String member) { + String field = removeRoot(member); + return hasNonRootMember(field); + } + + /** Similar to hasMember, but valid at any level of the tree and the argument should not contain + * the root variable's name. + * @see #hasMember(String) */ + private boolean hasNonRootMember(String members) { + if (members.contains(".")) { + int firstDot = members.indexOf('.'); + String first = members.substring(0, firstDot); + String rest = members.substring(firstDot + 1); + return childrenMap.containsKey(first) && childrenMap.get(first).hasNonRootMember(rest); + } else { + return childrenMap.containsKey(members); + } + } + + /** Obtain the member node that corresponds to the given field name (with root). */ + public MemberNode getNodeFor(String member) { + String field = removeRoot(member); + return getNodeForNonRoot(field); + } + + /** Similar to getNodeFor, but valid at any level of the tree, and the argument must be the field only. + * @see #getNodeFor(String) */ + MemberNode getNodeForNonRoot(String members) { + if (members.isEmpty()) { + return memberNode; + } else if (members.contains(".")) { + int firstDot = members.indexOf('.'); + String first = members.substring(0, firstDot); + String rest = members.substring(firstDot + 1); + assert childrenMap.containsKey(first); + return childrenMap.get(first).getNodeForNonRoot(rest); + } else { + assert childrenMap.containsKey(members); + return childrenMap.get(members).memberNode; + } + } + + /** @return An iterable through the names (with full prefixes) of all members of this tree, + * excluding the root. */ + public Iterable nameIterable() { + return () -> new Iterator<>() { + final Iterator it = treeIterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public String next() { + ObjectTree element = it.next(); + StringBuilder builder = new StringBuilder(); + MemberNode node = element.memberNode; + if (node == null) + return ROOT_NAME; + else + builder.append(node.getLabel()); + while (node.getParent() instanceof MemberNode && node.getParent().getLabel().matches("^(USE|DEF|DEC)\\{")) { + node = (MemberNode) node.getParent(); + builder.insert(0, '.'); + builder.insert(0, node.getLabel()); + } + return builder.insert(0, ROOT_NAME + ".").toString(); + } + }; + } + + /** @return An iterable through the nodes of all members of this tree, excluding the root. */ + public Iterable nodeIterable() { + return () -> new Iterator<>() { + final Iterator it = treeIterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public MemberNode next() { + return it.next().memberNode; + } + }; + } + + /** @return An iterator through all the trees of this structure, excluding the root. */ + private Iterator treeIterator() { + return new Iterator<>() { + final Set remaining = new HashSet<>(childrenMap.values()); + Iterator childIterator = null; + + @Override + public boolean hasNext() { + if (childIterator == null || !childIterator.hasNext()) + return !remaining.isEmpty(); + else + return true; + } + + @Override + public ObjectTree next() { + if (childIterator == null || !childIterator.hasNext()) { + ObjectTree tree = Utils.setPop(remaining); + childIterator = tree.treeIterator(); + return tree; + } else { + return childIterator.next(); + } + } + }; + } + + /** @see #treeIterator() */ + Iterable treeIterable() { + return this::treeIterator; + } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public Object clone() { + ObjectTree clone = new ObjectTree(memberNode.getLabel()); + for (Map.Entry entry : childrenMap.entrySet()) + clone.childrenMap.put(entry.getKey(), entry.getValue().clone(clone)); + return clone; + } + + private ObjectTree clone(ObjectTree parent) { + ObjectTree clone = new ObjectTree(getMemberName(), parent); + for (Map.Entry entry : childrenMap.entrySet()) + clone.childrenMap.put(entry.getKey(), entry.getValue().clone(clone)); + return clone; + } + + /** + * Utility method to remove the root variable from a string. The root element or root of + * the object tree should be either "-root-", a valid variable name or an optionally type-prefixed + * this (A.this, package.A.this or this). + * @throws IllegalArgumentException When there is no root to remove. + */ + public static String removeRoot(String fieldWithRoot) { + Matcher matcher = FIELD_SPLIT.matcher(fieldWithRoot); + if (matcher.matches()) + return matcher.group("fields") != null ? matcher.group("fields") : ""; + throw new IllegalArgumentException("Field should be of the form ., .this., where may not contain dots."); + } + + /** + * Utility method to remove the fields a string, retaining just the root. The root element or root of + * the object tree should be either "-root-", a valid variable name or an optionally type-prefixed + * this (A.this, package.A.this or this). + * @throws IllegalArgumentException When there are no fields to remove. + */ + public static String removeFields(String fieldWithRoot) { + Matcher matcher = FIELD_SPLIT.matcher(fieldWithRoot); + if (matcher.matches() && matcher.group("root") != null) + return matcher.group("root"); + throw new IllegalArgumentException("Field should be of the form ., .this., where may not contain dots."); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ObjectTree tree = (ObjectTree) o; + return Objects.equals(getMemberName(), tree.getMemberName()) && + childrenMap.values().equals(tree.childrenMap.values()); + } + + @Override + public int hashCode() { + return Objects.hash(getMemberName(), childrenMap); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..035c1a9a64d82d8cef8543340d1d2c435069293f --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java @@ -0,0 +1,83 @@ +package es.upv.mist.slicing.nodes; + +import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.arcs.pdg.FlowDependencyArc; +import es.upv.mist.slicing.arcs.pdg.ObjectFlowDependencyArc; +import es.upv.mist.slicing.arcs.sdg.ParameterInOutArc; +import es.upv.mist.slicing.graphs.Graph; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; +import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; +import es.upv.mist.slicing.nodes.oo.MemberNode; + +import java.util.function.Supplier; + +/** A connection between two object trees. This object can specify the connection between two different + * levels of object trees, for example to represent the assignment {@code a.b = c.d}. */ +class ObjectTreeConnection implements VariableAction.PDGConnection { + + protected final VariableAction sourceAction; + protected final VariableAction targetAction; + protected final String sourceMember; + protected final String targetMember; + + protected boolean applied = false; + + public ObjectTreeConnection(VariableAction sourceAction, VariableAction targetAction, String sourceMember, String targetMember) { + this.sourceAction = sourceAction; + this.targetAction = targetAction; + this.sourceMember = sourceMember; + this.targetMember = targetMember; + } + + /** Apply the connection represented by this object on an SDG. This means that all arcs will be interprocedural. */ + public void applySDG(JSysDG graph) { + if (!applied) { + connectTrees(graph, ParameterInOutArc::new, ParameterInOutArc.ObjectFlow::new); + applied = true; + } + } + + @Override + public void apply(JSysPDG graph) { + if (!applied) { + connectTrees(graph, FlowDependencyArc::new, ObjectFlowDependencyArc::new); + applied = true; + } + } + + protected void connectTrees(Graph graph, Supplier flowSupplier, Supplier objFlowSupplier) { + Supplier valueSupplier = flowSupplier; + ObjectTree source = null, target = null; + GraphNode rootSrc, rootTgt; + assert sourceMember.isEmpty() || sourceAction.hasObjectTree(); + assert targetMember.isEmpty() || targetAction.hasObjectTree(); + if (sourceAction.hasObjectTree()) { + source = sourceAction.getObjectTree().findObjectTreeOfMember(sourceMember); + rootSrc = source.getMemberNode(); + } else { + rootSrc = sourceAction.getGraphNode(); + } + if (targetAction.hasObjectTree()) { + target = targetAction.getObjectTree().findObjectTreeOfMember(targetMember); + rootTgt = target.getMemberNode(); + } else { + rootTgt = targetAction.getGraphNode(); + } + if (source == null || target == null) { + if (!rootSrc.equals(rootTgt)) + graph.addEdge(rootSrc, rootTgt, valueSupplier.get()); + } else { + graph.addEdge(rootSrc, rootTgt, objFlowSupplier.get()); + graph.addEdge(rootSrc, rootTgt, valueSupplier.get()); + for (ObjectTree tree : target.treeIterable()) { + MemberNode src = source.getNodeForNonRoot(tree.getMemberName()); + MemberNode tgt = tree.getMemberNode(); + if (tree.hasChildren()) { + graph.addEdge(src, tgt, objFlowSupplier.get()); + graph.addEdge(src, tgt, valueSupplier.get()); + } else + graph.addEdge(src, tgt, flowSupplier.get()); + } + } + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..f3648fd7592b2ffc8c18cde3f6fb8931b85467e8 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java @@ -0,0 +1,29 @@ +package es.upv.mist.slicing.nodes; + +import es.upv.mist.slicing.arcs.pdg.FlowDependencyArc; +import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; + +import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NAME; + +/** A connection that represents a value dependence, from one element of an object tree to the + * main GraphNode that represents the instruction. */ +public class ValueConnection implements VariableAction.PDGConnection { + protected final VariableAction action; + protected final String member; + + public ValueConnection(VariableAction action, String member) { + this.action = action; + this.member = member.isEmpty() ? ROOT_NAME : ROOT_NAME + "." + member; + } + + @Override + public void apply(JSysPDG graph) { + GraphNode statementNode; + if (action instanceof VariableAction.Movable) + statementNode = ((VariableAction.Movable) action).getRealNode(); + else + statementNode = action.getGraphNode(); + if (action.hasTreeMember(member)) + graph.addEdge(action.getObjectTree().getNodeFor(member), statementNode, new FlowDependencyArc()); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java index 556b12f6d74e455b6985fa89e7e9c69435e67508..9a0fabbfde8cd9163bce15ba25861a7e8245f736 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java @@ -1,185 +1,225 @@ package es.upv.mist.slicing.nodes; -import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.FieldAccessExpr; +import com.github.javaparser.ast.expr.*; import com.github.javaparser.resolution.Resolvable; +import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; -import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; import es.upv.mist.slicing.arcs.Arc; import es.upv.mist.slicing.arcs.pdg.DataDependencyArc; import es.upv.mist.slicing.graphs.Graph; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; +import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; import es.upv.mist.slicing.graphs.pdg.PDG; -import org.jgrapht.graph.DefaultEdge; -import org.jgrapht.graph.SimpleDirectedGraph; -import java.lang.reflect.InvocationTargetException; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; +import static es.upv.mist.slicing.nodes.VariableAction.DeclarationType.*; + /** An action upon a variable (e.g. usage, definition, declaration) */ public abstract class VariableAction { - protected static final String VARIABLE_PATTERN = "([a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)"; - protected static final String FIELD_PATTERN = "^" + VARIABLE_PATTERN + "(\\." + VARIABLE_PATTERN + ")*" + "$"; + /** The kinds of declaration that an action can act upon. */ + public enum DeclarationType { + FIELD, + STATIC_FIELD, + PARAMETER, + LOCAL_VARIABLE, + SYNTHETIC; + + public static DeclarationType valueOf(ResolvedValueDeclaration resolved) { + if (resolved.isType()) + return STATIC_FIELD; + if (resolved.isField() && resolved.asField().isStatic()) + return STATIC_FIELD; + if (resolved.isField()) + return FIELD; + if (resolved.isParameter()) + return PARAMETER; + if (resolved.isVariable()) + return LOCAL_VARIABLE; + if (resolved instanceof JavaParserSymbolDeclaration) + return LOCAL_VARIABLE; + throw new IllegalArgumentException("Invalid resolved value declaration"); + } + + public static DeclarationType valueOf(Expression expression) { + if (expression instanceof ThisExpr || expression instanceof SuperExpr) + return FIELD; + else if (expression instanceof NameExpr) + try { + return valueOf(expression.asNameExpr().resolve()); + } catch (UnsolvedSymbolException e) { + return STATIC_FIELD; + } + else if (expression instanceof FieldAccessExpr) + return valueOf(expression.asFieldAccessExpr().getScope()); + else + throw new IllegalStateException("Invalid expression type"); + } + } - protected final Expression variable; - protected final String realName; - protected final GraphNode graphNode; - protected final SimpleDirectedGraph objectTree = new SimpleDirectedGraph<>(null, DefaultEdge::new, false); + protected final String name; + protected final DeclarationType declarationType; + protected GraphNode graphNode; + protected ObjectTree objectTree; protected boolean optional = false; - protected ResolvedValueDeclaration resolvedVariableCache; - public VariableAction(Expression variable, String realName, GraphNode graphNode) { - assert realName != null && !realName.isEmpty(); - this.variable = variable; - this.realName = realName; + /** A list of pairs representing connections to be made between trees in the PDG. + * The variable action that contains the tree we must connect to in the PDG. + * The string, or member where the tree connection must start (in PDG). E.g.: our tree is "a.b.c" and this variable is "a", + * the members "a.b" and "a.b.c" will be connected to "b" and "b.c" in treeConnectionTarget's tree.. */ + protected final List pdgTreeConnections = new LinkedList<>(); + + private VariableAction(DeclarationType declarationType, String name, GraphNode graphNode) { + this(declarationType, name, graphNode, null); + } + + private VariableAction(DeclarationType declarationType, String name, GraphNode graphNode, ObjectTree objectTree) { + assert name != null && !name.isEmpty(); + this.declarationType = declarationType; + this.name = name; this.graphNode = graphNode; - this.objectTree.addVertex(realName); + this.objectTree = objectTree; } - /** Add a field of this object, such that the same action performed on the object - * is applied to this field too. Fields of fields may be specified separated by dots. */ - public void addObjectField(String fieldName) { - String parent = null; - for (String element : fieldName.split("\\.")) { - objectTree.addVertex(element); - if (parent != null) - objectTree.addEdge(parent, element); - parent = element; - } + // ====================================================== + // ================= BASIC GETTERS/SETTERS ============== + // ====================================================== + + public boolean isParameter() { + return PARAMETER == declarationType; } - public VariableAction getRootAction() { - assert !isRootAction(); - assert variable == null || variable.isNameExpr() || variable.isFieldAccessExpr() || variable.isThisExpr(); - if (this instanceof Movable) { - Movable movable = (Movable) this; - return new Movable(movable.inner.getRootAction(), (SyntheticNode) graphNode); - } - Expression nVar; - String nRealName = getRootVariable(); - GraphNode nNode = graphNode; - Expression nExpr = isDefinition() ? asDefinition().expression : null; - if (variable == null || !(variable instanceof FieldAccessExpr)) { - // This appears only when generated from a field: just set the variable to null - assert realName.contains(".this."); - nVar = null; - } else { // We are in a FieldAccessExpr - nVar = variable; - while (nVar.isFieldAccessExpr()) - nVar = variable.asFieldAccessExpr().getScope(); - } - if (this instanceof Usage) - return new Usage(nVar, nRealName, nNode); - if (this instanceof Definition) - return new Definition(nVar, nRealName, nNode, nExpr); - if (this instanceof Declaration) - throw new UnsupportedOperationException("Can't create a root node for a declaration!"); - throw new IllegalStateException("Invalid action type"); + public boolean isField() { + return declarationType == FIELD || declarationType == STATIC_FIELD; } - public String getRootVariable() { - Pattern rootVariable = Pattern.compile("^(?(([_0-9A-Za-z]+\\.)*this)|([_0-9A-Za-z]+)).*$"); - Matcher matcher = rootVariable.matcher(realName); - if (matcher.matches()) { - if (matcher.group("root") != null) - return matcher.group("root"); // [type.this] or [this] - else - throw new IllegalStateException("Invalid real name: " + realName); - } else { - return null; - } + public boolean isStatic() { + return declarationType == STATIC_FIELD; } - public boolean isRootAction() { - return isSynthetic() || Objects.equals(getRootVariable(), realName); + public boolean isLocalVariable() { + return declarationType == LOCAL_VARIABLE; + } + + /** + * Warning! This method implicitly creates an object tree if there is none. + * To avoid modifying the variable action, check with {@link #hasObjectTree()} + * before calling this method. + */ + public ObjectTree getObjectTree() { + if (!hasObjectTree()) + setObjectTree(new ObjectTree(getName())); + return objectTree; + } + + protected void setObjectTree(ObjectTree objectTree) { + this.objectTree = objectTree; } - public static boolean typeMatches(VariableAction a, VariableAction b) { - return (a.isDeclaration() && b.isDeclaration()) || - (a.isDefinition() && b.isDefinition()) || - (a.isUsage() && b.isUsage()); + public String getName() { + return name; } - public static boolean rootMatches(VariableAction a, VariableAction b) { - return a.getRootVariable().equals(b.getRootVariable()); + /** Whether this action is always performed when its parent node is executed or not. */ + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + /** The node that performs this action, in which this object is contained. */ + public GraphNode getGraphNode() { + return graphNode; } /** Whether this action is performed upon an invented variable, * introduced by this library (e.g. the active exception or the returned value). */ public boolean isSynthetic() { - return !getVariable().matches(FIELD_PATTERN); + return declarationType == SYNTHETIC; } - public String getVariable() { - return realName; + /** Whether the argument is performed upon the same variable as this action. */ + public boolean matches(VariableAction action) { + return name.equals(action.name); } - public boolean hasVariableExpression() { - return variable != null; + public boolean isPrimitive() { + return isRootAction() && !hasObjectTree(); } - public Expression getVariableExpression() { - return variable; + // ====================================================== + // =================== OBJECT TREE ====================== + // ====================================================== + + public boolean hasTreeMember(String member) { + if (member.isEmpty()) + return hasObjectTree(); + if (!hasObjectTree()) + return false; + return getObjectTree().hasMember(member); } - /** - * Returns the resolved value declaration. When the action being performed - * is done so on a ThisExpr, the resulting declaration has the following properties: - *
    - *
  • Can return type and name
  • - *
  • Is not a parameter, it's a field.
  • - *
  • All other methods are left to their default implementations.
  • - *
- */ - public ResolvedValueDeclaration getResolvedValueDeclaration() { - if (resolvedVariableCache == null) { - if (variable instanceof Resolvable) { - var resolved = ((Resolvable) variable).resolve(); - if (resolved instanceof ResolvedValueDeclaration) - resolvedVariableCache = (ResolvedValueDeclaration) resolved; - } - if (resolvedVariableCache == null) - resolvedVariableCache = new ResolvedValueDeclaration() { - @Override - public ResolvedType getType() { - return null; - } + public boolean hasObjectTree() { + return objectTree != null; + } - @Override - public String getName() { - return realName; - } + public void setPDGTreeConnectionTo(VariableAction targetAction, String sourcePrefixWithoutRoot, String targetPrefixWithoutRoot) { + pdgTreeConnections.add(new ObjectTreeConnection(this, targetAction, sourcePrefixWithoutRoot, targetPrefixWithoutRoot)); + } - @Override - public boolean isField() { - return true; - } - }; - } - return resolvedVariableCache; + public void setPDGValueConnection(String member) { + pdgTreeConnections.add(new ValueConnection(this, member)); } - // TODO: detected optional actions - /** Whether this action is always performed when its parent node is executed or not. */ - public boolean isOptional() { - return optional; + public void applyPDGTreeConnections(JSysPDG pdg) { + pdgTreeConnections.forEach(c -> c.apply(pdg)); } - /** The node that performs this action, in which this object is contained. */ - public GraphNode getGraphNode() { - return graphNode; + public void applySDGTreeConnection(JSysDG sdg, VariableAction targetAction) { + ObjectTreeConnection connection = new ObjectTreeConnection(this, targetAction, "", ""); + connection.applySDG(sdg); } - /** Whether the argument is performed upon the same variable as this action. */ - public boolean matches(VariableAction action) { - return Objects.equals(action.realName, realName); + // ====================================================== + // =================== ROOT ACTIONS ===================== + // ====================================================== + + public VariableAction getRootAction() { + assert !isRootAction(); + if (this instanceof Movable) { + Movable movable = (Movable) this; + return new Movable(movable.inner.getRootAction(), movable.getRealNode()); + } + if (this instanceof Usage) + return new Usage(declarationType, ObjectTree.removeFields(name), graphNode); + if (this instanceof Definition) + return new Definition(declarationType, ObjectTree.removeFields(name), graphNode, asDefinition().expression); + if (this instanceof Declaration) + throw new UnsupportedOperationException("Can't create a root node for a declaration!"); + throw new IllegalStateException("Invalid action type"); } + public boolean isRootAction() { + return isSynthetic() || Objects.equals(ObjectTree.removeFields(name), name); + } + + public boolean rootMatches(VariableAction b) { + return ObjectTree.removeFields(name).equals(ObjectTree.removeFields(b.name)); + } + + // ====================================================== + // ============== SUBTYPES AND CLONING ================== + // ====================================================== + public boolean isUsage() { return this instanceof Usage; } @@ -206,37 +246,67 @@ public abstract class VariableAction { /** Creates a new usage action with the same variable and the given node. */ public final Usage toUsage(GraphNode graphNode) { - return new Usage(variable, realName, graphNode); + ObjectTree tree = hasObjectTree() ? (ObjectTree) getObjectTree().clone() : null; + return new Usage(declarationType, name, graphNode, tree); } /** Creates a new definition action with the same variable and the given node. */ public final Definition toDefinition(GraphNode graphNode) { - return new Definition(variable, realName, graphNode); + ObjectTree tree = hasObjectTree() ? (ObjectTree) getObjectTree().clone() : null; + return new Definition(declarationType, name, graphNode, tree); } /** Creates a new declaration action with the same variable and the given node. */ public final Declaration toDeclaration(GraphNode graphNode) { - return new Declaration(variable, realName, graphNode); + ObjectTree tree = hasObjectTree() ? (ObjectTree) getObjectTree().clone() : null; + return new Declaration(declarationType, name, graphNode, tree); + } + + public final
A createCopy() { + return createCopy(null); + } + + @SuppressWarnings("unchecked") + public final A createCopy(GraphNode graphNode) { + if (this instanceof Usage) + return (A) toUsage(graphNode); + if (this instanceof Definition) + return (A) toDefinition(graphNode); + if (this instanceof Declaration) + return (A) toDeclaration(graphNode); + if (this instanceof Movable) { + assert graphNode == null || graphNode instanceof SyntheticNode; + Movable m = (Movable) this; + return (A) new Movable(m.inner.createCopy(), (SyntheticNode) graphNode); + } + throw new IllegalStateException("This kind of variable action can't be copied"); } + // ====================================================== + // =============== OVERRIDDEN METHODS =================== + // ====================================================== + @Override public boolean equals(Object obj) { return obj instanceof VariableAction && obj.getClass().equals(getClass()) && - Objects.equals(variable, ((VariableAction) obj).variable) && - realName.equals(((VariableAction) obj).realName); + name.equals(((VariableAction) obj).name); } @Override public int hashCode() { - return Objects.hash(getClass(), variable, realName); + return Objects.hash(getClass(), name); } @Override public String toString() { - return "{" + realName + "}"; + return "{" + name + "}"; } + // ====================================================== + // ==================== SUBCLASSES ====================== + // ====================================================== + /** An invented action used to locate the relative position of the start and end of a call inside a list of actions. */ public static class CallMarker extends VariableAction { protected final Resolvable call; @@ -248,6 +318,11 @@ public abstract class VariableAction { this.enter = enter; } + @Override + public boolean isRootAction() { + return true; + } + /** The call this marker represents. */ public Resolvable getCall() { return call; @@ -272,8 +347,12 @@ public abstract class VariableAction { /** A usage of a variable. */ public static class Usage extends VariableAction { - public Usage(Expression variable, String realName, GraphNode graphNode) { - super(variable, realName, graphNode); + public Usage(DeclarationType declarationType, String name, GraphNode graphNode) { + super(Objects.requireNonNull(declarationType), name, graphNode); + } + + public Usage(DeclarationType declarationType, String name, GraphNode graphNode, ObjectTree objectTree) { + super(Objects.requireNonNull(declarationType), name, graphNode, objectTree); } @Override @@ -286,16 +365,42 @@ public abstract class VariableAction { public static class Definition extends VariableAction { /** The value to which the variable has been defined. */ protected final Expression expression; + /** The members of the object tree that are total definitions. */ + protected String totallyDefinedMember; - public Definition(Expression variable, String realName, GraphNode graphNode) { - this(variable, realName, graphNode, null); + public Definition(DeclarationType declarationType, String name, GraphNode graphNode) { + this(declarationType, name, graphNode, (Expression) null); } - public Definition(Expression variable, String realName, GraphNode graphNode, Expression expression) { - super(variable, realName, graphNode); + public Definition(DeclarationType declarationType, String name, GraphNode graphNode, Expression expression) { + super(Objects.requireNonNull(declarationType), name, graphNode); this.expression = expression; } + public Definition(DeclarationType declarationType, String name, GraphNode graphNode, ObjectTree objectTree) { + this(declarationType, name, graphNode, null, objectTree); + } + + public Definition(DeclarationType declarationType, String name, GraphNode graphNode, Expression expression, ObjectTree objectTree) { + super(Objects.requireNonNull(declarationType), name, graphNode, objectTree); + this.expression = expression; + } + + public void setTotallyDefinedMember(String totallyDefinedMember) { + this.totallyDefinedMember = Objects.requireNonNull(totallyDefinedMember); + } + + public boolean isTotallyDefinedMember(String member) { + if (totallyDefinedMember == null) + return false; + if (totallyDefinedMember.equals(member)) + return true; + if (member.startsWith(totallyDefinedMember) + || ObjectTree.removeRoot(member).startsWith(ObjectTree.removeRoot(totallyDefinedMember))) + return ObjectTree.removeRoot(member).isEmpty() || hasTreeMember(member); + return false; + } + /** @see #expression */ public Expression getExpression() { return expression; @@ -309,8 +414,12 @@ public abstract class VariableAction { /** A declaration of a variable. */ public static class Declaration extends VariableAction { - public Declaration(Expression variable, String realName, GraphNode graphNode) { - super(variable, realName, graphNode); + public Declaration(DeclarationType declarationType, String name, GraphNode graphNode) { + super(Objects.requireNonNull(declarationType), name, graphNode); + } + + public Declaration(DeclarationType declarationType, String name, GraphNode graphNode, ObjectTree objectTree) { + super(Objects.requireNonNull(declarationType), name, graphNode, objectTree); } @Override @@ -332,13 +441,30 @@ public abstract class VariableAction { * to generate dependencies and a {@link PDG PDG} node that * is the final location of this action. */ public Movable(VariableAction inner, SyntheticNode pdgNode) { - super(inner.variable, inner.realName, inner.graphNode); + super(inner.declarationType, inner.name, inner.graphNode); if (inner instanceof Movable) throw new IllegalArgumentException("'inner' must be an unmovable action"); this.realNode = pdgNode; this.inner = inner; } + @Override + public ObjectTree getObjectTree() { + if (!inner.hasObjectTree()) + inner.setObjectTree(new ObjectTree(getName())); + return inner.getObjectTree(); + } + + @Override + protected void setObjectTree(ObjectTree objectTree) { + inner.objectTree = objectTree; + } + + @Override + public boolean hasObjectTree() { + return inner.objectTree != null; + } + /** The final location of this action. This node may not yet be present * in the graph, if {@link #move(Graph)} has not been invoked. */ public SyntheticNode getRealNode() { @@ -349,31 +475,25 @@ public abstract class VariableAction { * the action is deleted from its original node's list, a copy is created with the real * target and any {@link DataDependencyArc} is relocated to match this change. */ public VariableAction move(Graph graph) { - // Create unwrapped action (the graphNode field must be changed). - VariableAction newAction; - try { - if (inner instanceof Definition && inner.asDefinition().getExpression() != null) - newAction = inner.getClass().getConstructor(Expression.class, String.class, GraphNode.class, Expression.class) - .newInstance(variable, realName, realNode, inner.asDefinition().expression); - else - newAction = inner.getClass().getConstructor(Expression.class, String.class, GraphNode.class) - .newInstance(variable, realName, realNode); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new UnsupportedOperationException("The VariableAction constructor has changed!", e); - } // Add node graph.addVertex(realNode); // Move to node - graphNode.variableActions.remove(this); - realNode.variableActions.add(newAction); + moveOnly(); // Move data dependencies Set.copyOf(graph.edgesOf(graphNode).stream() .filter(Arc::isDataDependencyArc) .map(Arc::asDataDependencyArc) .filter(arc -> arc.getSourceVar() == this || arc.getTargetVar() == this) .collect(Collectors.toSet())) // copying to avoid modifying while iterating - .forEach(arc -> moveDataDependencyArc(arc, graph, newAction)); - return newAction; + .forEach(arc -> moveDataDependencyArc(arc, graph, inner)); + return inner; + } + + /** Relocate the inner VA from its current node to its real node. */ + public void moveOnly() { + graphNode.variableActions.remove(this); + realNode.variableActions.add(inner); + inner.graphNode = realNode; } /** Relocates a data dependency arc, by creating a new one with matching information and deleting the old one. */ @@ -432,4 +552,14 @@ public abstract class VariableAction { return Objects.hash(super.hashCode(), realNode, inner); } } + + /** + * A connection that is setup in the CFG creation stage, but + * cannot be applied until the PDG creation stage. + */ + public interface PDGConnection { + /** Apply the connection in the given PDG. + * This action can be performed multiple times, but the connection will only be made once. */ + void apply(JSysPDG graph); + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java index 276207cc015fd64f7807040a465de87311e5deb3..8afd7e5f4d65d137e9f053bebb6444a210539486 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java @@ -1,19 +1,25 @@ package es.upv.mist.slicing.nodes; import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; 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.CatchClause; -import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; -import com.github.javaparser.ast.stmt.ForEachStmt; +import com.github.javaparser.ast.nodeTypes.NodeWithArguments; +import com.github.javaparser.ast.stmt.*; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.resolution.Resolvable; import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.AssociableToAST; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserMethodDeclaration; +import es.upv.mist.slicing.graphs.ClassGraph; +import es.upv.mist.slicing.graphs.ExpressionObjectTreeFinder; import es.upv.mist.slicing.graphs.GraphNodeContentVisitor; +import es.upv.mist.slicing.nodes.VariableAction.DeclarationType; +import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.utils.ASTUtils; import es.upv.mist.slicing.utils.Logger; @@ -21,9 +27,13 @@ import es.upv.mist.slicing.utils.Logger; import java.util.Deque; import java.util.LinkedList; import java.util.List; -import java.util.Objects; +import java.util.Optional; import static es.upv.mist.slicing.graphs.cfg.CFGBuilder.VARIABLE_NAME_OUTPUT; +import static es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG.ACTIVE_EXCEPTION_VARIABLE; +import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NAME; +import static es.upv.mist.slicing.nodes.VariableAction.DeclarationType.*; +import static es.upv.mist.slicing.nodes.VariableVisitor.Action.*; /** A graph node visitor that extracts the actions performed in a given GraphNode. An initial action mode can * be set, to consider variables found a declaration, definition or usage (default). @@ -50,39 +60,21 @@ public class VariableVisitor extends GraphNodeContentVisitor definitionStack = new LinkedList<>(); - - /** A variable visitor that will add each action to the list of actions of the graph node. - * The entry-point for this graph MUST be {@link #startVisit(GraphNode)} or {@link #startVisit(GraphNode, Action)} */ - public VariableVisitor() { - this(GraphNode::addDeclaredVariable, GraphNode::addDefinedVariable, GraphNode::addUsedVariable); - } - - /** A variable visitor that will perform the given actions when a variable is found. A node can accept this visitor, - * but calls will be ignored if the entry-point is not {@link #startVisit(GraphNode)} or {@link #startVisit(GraphNode, Action)}. - * The arguments are the actions to be performed when an action is found in the corresponding node. */ - public VariableVisitor(DeclarationConsumer declConsumer, DefinitionConsumer defConsumer, UsageConsumer useConsumer) { - this.declConsumer = Objects.requireNonNullElse(declConsumer, DeclarationConsumer.defaultConsumer()); - this.defConsumer = Objects.requireNonNullElse(defConsumer, DefinitionConsumer.defaultConsumer()); - this.useConsumer = Objects.requireNonNullElse(useConsumer, UsageConsumer.defaultConsumer()); - } + /** If this stack is non-empty, every action created must be of type Movable, and its real node must be + * the top of this stack. Used for actual-in nodes. */ + protected final Deque> realNodeStack = new LinkedList<>(); public void visitAsDefinition(Node node, Expression value, Action action) { definitionStack.push(value); - node.accept(this, action.or(Action.DEFINITION)); + node.accept(this, action.or(DEFINITION)); definitionStack.pop(); } @Override public void startVisit(GraphNode node) { - startVisit(node, Action.USE); + startVisit(node, USE); groupActionsByRoot(node); } @@ -96,6 +88,11 @@ public class VariableVisitor extends GraphNodeContentVisitor l.accept(this, arg)); } @@ -186,7 +241,72 @@ public class VariableVisitor extends GraphNodeContentVisitor vaList = graphNode.getVariableActions(); + if (vaList.size() >= 5) { // call-super, DEC(this), USE(-output-), ret-super, DEF(this) + VariableAction useOutput = vaList.get(vaList.size() - 3); + VariableAction defThis = graphNode.getLastVariableAction(); + assert useOutput.isUsage() && useOutput.getName().equals(VARIABLE_NAME_OUTPUT); + assert defThis.isDefinition() && defThis.getName().equals("this"); + defThis.asDefinition().setTotallyDefinedMember("this"); + ObjectTree.copyTargetTreeToSource(defThis.getObjectTree(), useOutput.getObjectTree(), "", ""); + useOutput.setPDGTreeConnectionTo(defThis, "", ""); } } @@ -287,33 +425,72 @@ public class VariableVisitor extends GraphNodeContentVisitor call, Action arg) { + protected boolean visitCall(Resolvable call, Action action) { + // If we don't have the AST for the call, we should visit the rest of the call. if (ASTUtils.shouldVisitArgumentsForMethodCalls(call, graphNode)) return true; + CallableDeclaration decl = ASTUtils.getResolvedAST(call.resolve()).orElseThrow(); + // Start graphNode.addCallMarker(call, true); - if (call instanceof ExplicitConstructorInvocationStmt) { - declareThis((ExplicitConstructorInvocationStmt) call); + // Scope + if (call instanceof ExplicitConstructorInvocationStmt) + acceptAction(FIELD, "this", DECLARATION); + if (call instanceof MethodCallExpr && !((JavaParserMethodDeclaration) call.resolve()).isStatic()) { + ActualIONode scopeIn = ActualIONode.createActualIn(call, "this", ((MethodCallExpr) call).getScope().orElse(null)); + graphNode.addSyntheticNode(scopeIn); + realNodeStack.push(scopeIn); + ASTUtils.getResolvableScope(call).ifPresentOrElse( + scope -> scope.accept(this, action), + () -> acceptAction(FIELD, "this", USE)); + realNodeStack.pop(); + } + // Args + NodeWithArguments callWithArgs = (NodeWithArguments) call; + for (int i = 0; i < callWithArgs.getArguments().size(); i++) { + Expression argument = callWithArgs.getArguments().get(i); + ActualIONode actualIn = ActualIONode.createActualIn(call, decl.getParameter(i).getNameAsString(), argument); + graphNode.addSyntheticNode(actualIn); + realNodeStack.push(actualIn); + argument.accept(this, action); + realNodeStack.pop(); } - ASTUtils.getResolvableScope(call).ifPresent(s -> s.accept(this, arg)); + // Return + insertOutputDefAndUse(call); + // End graphNode.addCallMarker(call, false); - return false; - } - - /** Adds a declaration for the variable 'this'. */ - protected void declareThis(ExplicitConstructorInvocationStmt call) { - String variableName = getFQClassName(call) + ".this"; - declConsumer.acceptDeclaration(graphNode, null, variableName); - } - - /** Obtains the fully qualified class name of the class that contains an AST node. */ - protected String getFQClassName(Node node) { - // Known limitation: anonymous classes do not have a FQ class name. - return ASTUtils.getClassNode(node).getFullyQualifiedName().orElseThrow(); + return false; // We have manually visited each element, don't call super.visit(...) + } + + protected void insertOutputDefAndUse(Resolvable call) { + if (ASTUtils.resolvableIsVoid(call)) + return; + // A node defines -output- + var fields = getFieldsForReturn(call); + var def = new VariableAction.Definition(SYNTHETIC, VARIABLE_NAME_OUTPUT, graphNode, + fields.map(tree -> (ObjectTree) tree.clone()).orElse(null)); + def.setTotallyDefinedMember(ROOT_NAME); + var defMov = new VariableAction.Movable(def, CallNode.Return.create(call)); + graphNode.addVariableAction(defMov); + // The container of the call uses -output-, unless the call is wrapped in an ExpressionStmt + Optional parentNode = ((Node) call).getParentNode(); + if (parentNode.isEmpty() || !(parentNode.get() instanceof ExpressionStmt)) + graphNode.addVariableAction(new VariableAction.Usage(SYNTHETIC, VARIABLE_NAME_OUTPUT, graphNode, + fields.map(tree -> (ObjectTree) tree.clone()).orElse(null))); + } + + protected Optional getFieldsForReturn(Resolvable call) { + ResolvedMethodLikeDeclaration resolved = call.resolve(); + if (resolved instanceof AssociableToAST) { + Optional n = ((AssociableToAST) resolved).toAst(); + if (n.isPresent() && n.get() instanceof CallableDeclaration) + return ClassGraph.getInstance().generateObjectTreeForReturnOf((CallableDeclaration) n.get()); + } + return Optional.empty(); } /** Prepends [declaring class name].this. to the name of the given variable declarator. */ protected String getRealNameForFieldDeclaration(VariableDeclarator decl) { - return ASTUtils.getClassNode(decl).getFullyQualifiedName().orElseThrow() + ".this." + decl.getNameAsString(); + return "this." + decl.getNameAsString(); } /** Obtain the prefixed name of a variable, to improve matching of variables @@ -325,9 +502,19 @@ public class VariableVisitor extends GraphNodeContentVisitor graphNode, Expression variable, String realName); - - static DeclarationConsumer defaultConsumer() { - return (a, b, c) -> {}; - } - } - - @FunctionalInterface - public interface DefinitionConsumer { - void acceptDefinition(GraphNode graphNode, Expression variable, String realName, Expression valueAssigned); - - static DefinitionConsumer defaultConsumer() { - return (a, b, c, d) -> {}; - } + private static boolean typeMatches(VariableAction a, VariableAction b) { + return (a.isDeclaration() && b.isDeclaration()) || + (a.isDefinition() && b.isDefinition()) || + (a.isUsage() && b.isUsage()); } - @FunctionalInterface - public interface UsageConsumer { - void acceptUsage(GraphNode graphNode, Expression variable, String realName); - - static UsageConsumer defaultConsumer() { - return (a, b, c) -> {}; - } + protected VariableAction.Definition getLastDefinition() { + return graphNode.getLastVariableAction().asDefinition(); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/ActualIONode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/ActualIONode.java index e0745563653e14fd120539fbbf3ec8367656fbf2..fc30e15883ee6ae60a354a80ed7a390efff599ab 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/ActualIONode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/ActualIONode.java @@ -4,10 +4,8 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.body.BodyDeclaration; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.resolution.Resolvable; -import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import es.upv.mist.slicing.utils.ASTUtils; import java.util.Objects; @@ -15,8 +13,8 @@ import java.util.Objects; public class ActualIONode extends IONode { protected final Expression argument; - protected ActualIONode(Resolvable astNode, ResolvedValueDeclaration variable, Expression argument, boolean isInput) { - super(createLabel(isInput, variable.getName(), argument), (Node) astNode, variable.getName(), isInput); + protected ActualIONode(Resolvable astNode, String variable, Expression argument, boolean isInput) { + super(createLabel(isInput, variable, argument), (Node) astNode, variable, isInput); this.argument = argument; } @@ -35,14 +33,10 @@ public class ActualIONode extends IONode { && Objects.equals(o.getAstNode(), resolvedASTNode()); } + @SuppressWarnings("unchecked") protected BodyDeclaration resolvedASTNode() { - @SuppressWarnings("unchecked") - ResolvedMethodLikeDeclaration declaration = ((Resolvable) astNode).resolve(); - if (declaration instanceof ResolvedConstructorDeclaration) - return ((ResolvedConstructorDeclaration) declaration).toAst().orElse(null); - else if (declaration instanceof ResolvedMethodDeclaration) - return ((ResolvedMethodDeclaration) declaration).toAst().orElse(null); - throw new IllegalStateException("AST node of invalid type"); + return ASTUtils.getResolvedAST(((Resolvable) astNode).resolve()) + .orElse(null); } @Override @@ -63,11 +57,11 @@ public class ActualIONode extends IONode { return String.format("%s = %s_out", arg, paramName); } - public static ActualIONode createActualIn(Resolvable methodCallExpr, ResolvedValueDeclaration resolvedDeclaration, Expression argument) { - return new ActualIONode(methodCallExpr, resolvedDeclaration, argument, true); + public static ActualIONode createActualIn(Resolvable methodCallExpr, String name, Expression argument) { + return new ActualIONode(methodCallExpr, name, argument, true); } - public static ActualIONode createActualOut(Resolvable methodCallExpr, ResolvedValueDeclaration resolvedDeclaration, Expression argument) { - return new ActualIONode(methodCallExpr, resolvedDeclaration, argument, false); + public static ActualIONode createActualOut(Resolvable methodCallExpr, String name, Expression argument) { + return new ActualIONode(methodCallExpr, name, argument, false); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/FormalIONode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/FormalIONode.java index 4b3864ea9a180ed78efc73d31b84024e6b4acdfd..a639d15ccc189fa08d033edea958abbac69b4829 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/FormalIONode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/FormalIONode.java @@ -1,7 +1,6 @@ package es.upv.mist.slicing.nodes.io; import com.github.javaparser.ast.body.CallableDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; /** A formal-in or formal-out node, displaying interprocedural data dependencies. */ public class FormalIONode extends IONode> { @@ -20,16 +19,15 @@ public class FormalIONode extends IONode> { return String.format("%s_out = %1$s", varName); } - public static FormalIONode createFormalIn(CallableDeclaration declaration, ResolvedValueDeclaration resolvedValue) { - return new FormalIONode(declaration, resolvedValue.getName(), true); + public static FormalIONode createFormalIn(CallableDeclaration declaration, String name) { + return new FormalIONode(declaration, name, true); } - public static FormalIONode createFormalOut(CallableDeclaration declaration, ResolvedValueDeclaration resolvedValue) { - return new FormalIONode(declaration, resolvedValue.getName(), false); + public static FormalIONode createFormalOut(CallableDeclaration declaration, String name) { + return new FormalIONode(declaration, name, false); } - public static FormalIONode createFormalInDecl(CallableDeclaration declaration, ResolvedValueDeclaration resolvedValue) { - String name = resolvedValue.getName(); + public static FormalIONode createFormalInDecl(CallableDeclaration declaration, String name) { return new FormalIONode(name, declaration, name, true); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/IONode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/IONode.java index d6ee3847ff4f99ca956bce231e5f0f92d0395905..f4f1a8578223342f50b143b7972d4b1fb3bdac8b 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/IONode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/io/IONode.java @@ -17,6 +17,10 @@ public abstract class IONode extends SyntheticNode { this.isInput = isInput; } + public String getVariableName() { + return variableName; + } + public boolean isInput() { return isInput; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java new file mode 100644 index 0000000000000000000000000000000000000000..dc2cd0bd384e0e58ff4dbbe20174aab384ab8143 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java @@ -0,0 +1,36 @@ +package es.upv.mist.slicing.nodes.oo; + +import com.github.javaparser.ast.Node; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.SyntheticNode; + +import java.util.LinkedList; + +/** A synthetic node that represents an object or field that is within a + * VariableAction. They are placed in the graph when the PDG is built, + * and allow for a more granular representation and slicing of objects. */ +public class MemberNode extends SyntheticNode { + protected GraphNode parent; + + public MemberNode(String instruction, GraphNode parent) { + super(instruction, null, new LinkedList<>()); + this.parent = parent; + } + + public GraphNode getParent() { + return parent; + } + + public void setParent(GraphNode parent) { + this.parent = parent; + } + + @Override + public String toString() { + return String.format("%s{id: %s, label: '%s'}", + getClass().getSimpleName(), + getId(), + getLabel() + ); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/type/NodeType.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/type/NodeType.java deleted file mode 100644 index a3931779374e9e2d1d81457c75a603d3b775e57a..0000000000000000000000000000000000000000 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/type/NodeType.java +++ /dev/null @@ -1,53 +0,0 @@ -package es.upv.mist.slicing.nodes.type; - -public enum NodeType { - /** An instruction in the program. Statements, predicates and - * pseudo-predicates are included in this {@link NodeType}. */ - STATEMENT, - /** The Enter node of a method. */ - 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 accessible variable that - * has been used in a method call. */ - ACTUAL_IN, - /** An argument or globally accessible variable that - * has been modified in a method call. */ - ACTUAL_OUT, - /** An argument or globally accessible variable that - * has been used in a method declaration. */ - FORMAL_IN, - /** 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. */ - METHOD_OUTPUT; - - public boolean is(NodeType type) { - if (this == type) { - return true; - } - - if (this == METHOD_CALL_RETURN) { - return type == ACTUAL_OUT; - } - - if (this == METHOD_OUTPUT) { - return type == FORMAL_OUT; - } - - return false; - } -} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ClassicSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ClassicSlicingAlgorithm.java index 7d4ed1514a92bd39f1a5ea0304885d7219942b6a..a8af80a7b9982992e8e631df429956b342436bc5 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ClassicSlicingAlgorithm.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ClassicSlicingAlgorithm.java @@ -29,9 +29,9 @@ public class ClassicSlicingAlgorithm implements SlicingAlgorithm { } @Override - public Slice traverse(GraphNode slicingCriterion) { + public Slice traverse(Set> slicingCriterion) { Slice slice = new Slice(); - slice.add(slicingCriterion); + slice.addAll(slicingCriterion); pass(slice, this::ignorePass1); pass(slice, this::ignorePass2); return slice; diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java index 0aa3a3e4726c83399c53752f1a7c8142d315a9a4..c8d382e623b4ca3d99a51f7d8a1b3818a62f120f 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java @@ -35,7 +35,7 @@ public class ExceptionSensitiveSlicingAlgorithm implements SlicingAlgorithm { protected static final Predicate SDG_PASS_2 = Arc::isInterproceduralInputArc; protected final ESSDG graph; - protected GraphNode slicingCriterion; + protected Set> slicingCriterion; /** Set of the arcs that have been traversed in the slicing process. */ protected final Set traversedArcSet = new HashSet<>(); @@ -47,24 +47,28 @@ public class ExceptionSensitiveSlicingAlgorithm implements SlicingAlgorithm { } @Override - public Slice traverse(GraphNode slicingCriterion) { + public Slice traverse(Set> slicingCriterion) { this.slicingCriterion = slicingCriterion; Slice slice = new Slice(); - slice.add(slicingCriterion); - pass(slice, SDG_PASS_1.or(this::ppdgIgnore).or(this::essdgIgnore)); - pass(slice, SDG_PASS_2.or(this::ppdgIgnore).or(this::essdgIgnore)); + slice.addAll(slicingCriterion); + pass(slice, SDG_PASS_1.or(this::commonIgnoreConditions)); + pass(slice, SDG_PASS_2.or(this::commonIgnoreConditions)); return slice; } @Override public Slice traverseProcedure(GraphNode slicingCriterion) { - this.slicingCriterion = slicingCriterion; + this.slicingCriterion = Set.of(slicingCriterion); Slice slice = new Slice(); slice.add(slicingCriterion); pass(slice, INTRAPROCEDURAL); return slice; } + protected boolean commonIgnoreConditions(Arc arc) { + return ppdgIgnore(arc) || essdgIgnore(arc); + } + /** * Perform a round of traversal, until no new nodes can be added to the slice. Then, apply rule 5. * @param slice A slice object that will serve as initial work-list and where nodes will be added. @@ -117,7 +121,7 @@ public class ExceptionSensitiveSlicingAlgorithm implements SlicingAlgorithm { return arc.isUnconditionalControlDependencyArc() && graph.isPseudoPredicate(target) && reachedStream(target).allMatch(Arc::isUnconditionalControlDependencyArc) && - !target.equals(slicingCriterion); + !slicingCriterion.contains(target); } /** Applies rule 4 of the algorithm. */ diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/FileLineSlicingCriterion.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/FileLineSlicingCriterion.java index 8a1afdbe4adcd0d34b07d30578efae257b20688d..dd7f3987a240d92ec49c5c28a665ef65faf41043 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/FileLineSlicingCriterion.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/FileLineSlicingCriterion.java @@ -11,7 +11,11 @@ public class FileLineSlicingCriterion extends LineNumberCriterion { protected final File file; public FileLineSlicingCriterion(File file, int lineNumber) { - super(lineNumber, null); + this(file, lineNumber, null); + } + + public FileLineSlicingCriterion(File file, int lineNumber, String variable) { + super(lineNumber, variable); this.file = file; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/GraphNodeCriterion.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/GraphNodeCriterion.java deleted file mode 100644 index 1d9c8e25f1e9f431bd0c9ca3dec3cfe5699ffb67..0000000000000000000000000000000000000000 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/GraphNodeCriterion.java +++ /dev/null @@ -1,33 +0,0 @@ -package es.upv.mist.slicing.slicing; - -import es.upv.mist.slicing.graphs.cfg.CFG; -import es.upv.mist.slicing.graphs.pdg.PDG; -import es.upv.mist.slicing.graphs.sdg.SDG; -import es.upv.mist.slicing.nodes.GraphNode; - -import java.util.Optional; - -/** A criterion that locates nodes by {@link GraphNode}. */ -public class GraphNodeCriterion extends SlicingCriterion { - private final GraphNode node; - - public GraphNodeCriterion(GraphNode node, String variable) { - super(variable); - this.node = node; - } - - @Override - public Optional> findNode(CFG graph) { - return graph.findNodeById(node.getId()); - } - - @Override - public Optional> findNode(PDG graph) { - return graph.findNodeById(node.getId()); - } - - @Override - public Optional> findNode(SDG graph) { - return graph.findNodeById(node.getId()); - } -} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/JSysDGSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/JSysDGSlicingAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..8bc4ef3fdb2c5617550675830db0e3b82db61481 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/JSysDGSlicingAlgorithm.java @@ -0,0 +1,27 @@ +package es.upv.mist.slicing.slicing; + +import com.github.javaparser.ast.stmt.CatchClause; +import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.exceptionsensitive.ExceptionExitNode; + +public class JSysDGSlicingAlgorithm extends ExceptionSensitiveSlicingAlgorithm { + public JSysDGSlicingAlgorithm(JSysDG graph) { + super(graph); + } + + @Override + protected boolean commonIgnoreConditions(Arc arc) { + return objectFlowIgnore(arc) || super.commonIgnoreConditions(arc); + } + + protected boolean objectFlowIgnore(Arc arc) { + GraphNode target = graph.getEdgeTarget(arc); + return arc.isObjectFlow() && // 1. The arc is object flow + !slicingCriterion.contains(target) && // 2. The target is not the slicing criterion + reachedStream(target).noneMatch(Arc::isObjectFlow) && // 3. The target hasn't been reached by object flow arcs + !graph.isPredicate(target) && // 4. The target is not a predicate + !(target.getAstNode() instanceof CatchClause) && !(target instanceof ExceptionExitNode); // Some extra conditions for exceptions + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java index 84fd9481a945de44fae5f47c621d868408f39ebd..de7bd6f2cf5380d13c0fac3f1359ae9425b8b183 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java @@ -4,55 +4,45 @@ import com.github.javaparser.Position; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.stmt.Statement; -import es.upv.mist.slicing.graphs.cfg.CFG; -import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.arcs.pdg.StructuralArc; import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.nodes.GraphNode; -import es.upv.mist.slicing.utils.Logger; +import es.upv.mist.slicing.nodes.ObjectTree; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** A criterion that locates nodes by line. It may only be used in single-declaration graphs. */ -public class LineNumberCriterion extends SlicingCriterion { +public class LineNumberCriterion implements SlicingCriterion { protected static final Position DEFAULT_POSITION = new Position(0, 0); protected final int lineNumber; + protected String variable; public LineNumberCriterion(int lineNumber, String variable) { - super(variable); + this.variable = variable; this.lineNumber = lineNumber; } @Override - public Optional> findNode(CFG graph) { - return Optional.empty(); - } - - @Override - public Optional> findNode(PDG graph) { - // find node by line number - return graph.vertexSet().stream().filter(node -> { - Node astNode = node.getAstNode(); - - if (astNode.getBegin().isEmpty() || astNode.getEnd().isEmpty()) - return false; - - int begin = astNode.getBegin().get().line; - int end = astNode.getEnd().get().line; - - Logger.format("begin %s end %s", begin, end); - - return lineNumber == begin || lineNumber == end; - }).findFirst(); - } - - @Override - public Optional> findNode(SDG graph) { + public Set> findNode(SDG graph) { Optional optCu = findCompilationUnit(graph.getCompilationUnits()); if (optCu.isEmpty()) - return Optional.empty(); - return optCu.get().findFirst(Statement.class, this::matchesLine).flatMap(graph::findNodeByASTNode); + throw new NoSuchElementException(); + + Set> set = optCu.get().findAll(Node.class, this::matchesLine).stream() + .map(graph::findNodeByASTNode) + .filter(Optional::isPresent) + .map(Optional::get) + .flatMap(node -> locateVariableNodes(node, graph)) + .collect(Collectors.toSet()); + if (set.isEmpty() && !variable.startsWith("this")) { + variable = "this." + variable; + return findNode(graph); + } else { + return set; + } } /** Locates the compilation unit that corresponds to this criterion's file. */ @@ -65,6 +55,37 @@ public class LineNumberCriterion extends SlicingCriterion { return node.getBegin().orElse(DEFAULT_POSITION).line == lineNumber; } + protected Stream> locateVariableNodes(GraphNode graphNode, SDG graph) { + if (variable == null) + return locateAllNodes(graphNode, graph); + return locateAllNodes(graphNode, graph) + .map(GraphNode::getVariableActions).flatMap(List::stream) + .map(variableAction -> { + if (variableAction.getName().equals(variable)) { + if (variableAction.hasObjectTree()) + return variableAction.getObjectTree().getMemberNode(); + else + return variableAction.getGraphNode(); + } else if (variable.contains(".") && variableAction.getName().equals(ObjectTree.removeFields(variable))) { + if (variableAction.hasTreeMember(variable)) + return variableAction.getObjectTree().getNodeFor(variable); + else + return null; + } else { + return null; + } + }) + .filter(Objects::nonNull); + } + + protected Stream> locateAllNodes(GraphNode graphNode, SDG graph) { + Stream> result = graph.outgoingEdgesOf(graphNode).stream() + .filter(StructuralArc.class::isInstance) + .map(graph::getEdgeTarget) + .flatMap(node -> locateAllNodes(node, graph)); + return Stream.concat(Stream.of(graphNode), result); + } + @Override public String toString() { return String.format("(%s, %s)", lineNumber, variable); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/NodeIdSlicingCriterion.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/NodeIdSlicingCriterion.java deleted file mode 100644 index 58c6fdcd724d3e8895fd25bf05fa3029e9eb963d..0000000000000000000000000000000000000000 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/NodeIdSlicingCriterion.java +++ /dev/null @@ -1,33 +0,0 @@ -package es.upv.mist.slicing.slicing; - -import es.upv.mist.slicing.graphs.cfg.CFG; -import es.upv.mist.slicing.graphs.pdg.PDG; -import es.upv.mist.slicing.graphs.sdg.SDG; -import es.upv.mist.slicing.nodes.GraphNode; - -import java.util.Optional; - -/** A criterion that locates nodes by numerical id. */ -public class NodeIdSlicingCriterion extends SlicingCriterion { - protected final long id; - - public NodeIdSlicingCriterion(long id, String variable) { - super(variable); - this.id = 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/es/upv/mist/slicing/slicing/PseudoPredicateSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/PseudoPredicateSlicingAlgorithm.java index eb7292bae412d08d25c84a0a4c5ecc3d42d8e21e..20467863ab3467c8cfc1ca9b5d6c9931107b0ebf 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/PseudoPredicateSlicingAlgorithm.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/PseudoPredicateSlicingAlgorithm.java @@ -4,8 +4,10 @@ import es.upv.mist.slicing.arcs.Arc; import es.upv.mist.slicing.graphs.augmented.PSDG; import es.upv.mist.slicing.nodes.GraphNode; +import java.util.Set; + public class PseudoPredicateSlicingAlgorithm extends ClassicSlicingAlgorithm { - protected GraphNode slicingCriterion; + protected Set> slicingCriterion; public PseudoPredicateSlicingAlgorithm(PSDG graph) { super(graph); @@ -13,12 +15,12 @@ public class PseudoPredicateSlicingAlgorithm extends ClassicSlicingAlgorithm { @Override public Slice traverseProcedure(GraphNode slicingCriterion) { - this.slicingCriterion = slicingCriterion; + this.slicingCriterion = Set.of(slicingCriterion); return super.traverseProcedure(slicingCriterion); } @Override - public Slice traverse(GraphNode slicingCriterion) { + public Slice traverse(Set> slicingCriterion) { this.slicingCriterion = slicingCriterion; return super.traverse(slicingCriterion); } @@ -42,6 +44,6 @@ public class PseudoPredicateSlicingAlgorithm extends ClassicSlicingAlgorithm { GraphNode target = graph.getEdgeTarget(arc); return ((PSDG) graph).isPseudoPredicate(target) && arc.isControlDependencyArc() - && target != slicingCriterion; + && !slicingCriterion.contains(target); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/Slice.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/Slice.java index 22ef03f2918daff1029e319f0a9cab3b5194e8a5..1f271daef8835aec982203d3ecca2fb212d63a91 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/Slice.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/Slice.java @@ -33,11 +33,6 @@ public class Slice { return map.containsKey(node.getId()); } - /** Whether the slice contains the given AST node. */ - public boolean contains(Node node) { - return map.values().stream().anyMatch(gn -> gn.getAstNode() == node); - } - @Override public int hashCode() { return map.hashCode(); @@ -60,7 +55,7 @@ public class Slice { // Add each node to the corresponding bucket of the map // Nodes may not belong to a compilation unit (fictional nodes), and they are skipped for the slice. for (GraphNode graphNode : map.values()) { - if (graphNode.isImplicitInstruction()) + if (graphNode.isImplicitInstruction() || graphNode.getAstNode() == null) continue; Optional cu = graphNode.getAstNode().findCompilationUnit(); if (cu.isEmpty()) continue; diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java index f5569b099709900bfc2a5290131b5fb307bc5b33..2d96a3484f4f41cf0077026ddb244a5dd85e2481 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java @@ -19,13 +19,10 @@ import java.util.stream.Collectors; public class SlicePruneVisitor extends ModifierVisitor> { // ========== Utility methods ========== - /** Place a valid placeholder in this node's body, if any. */ - protected void fillBody(Node n) { - if (!(n instanceof NodeWithBody)) - return; - NodeWithBody nb = ((NodeWithBody) n); - if (nb.getBody() == null) - nb.setBody(new EmptyStmt()); + /** Place a valid placeholder in this node's body, if there is none. */ + protected void fillBody(NodeWithBody n) { + if (n.getBody() == null) + n.setBody(new EmptyStmt()); } // ========== File visitors ========== diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingAlgorithm.java index 4e271c2d8324537203fed667a0ff2f6d2a390fac..f52a3e6f109e7ec97b0d6208fdffc6efbe055a0d 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingAlgorithm.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingAlgorithm.java @@ -2,9 +2,11 @@ package es.upv.mist.slicing.slicing; import es.upv.mist.slicing.nodes.GraphNode; +import java.util.Set; + public interface SlicingAlgorithm { /** Obtain the nodes reached by this algorithm in a classic 2-pass interprocedural slice. */ - Slice traverse(GraphNode slicingCriterion); + Slice traverse(Set> slicingCriterion); /** Obtain the nodes reached by this algorithm intraprocedurally (i.e. without traversing interprocedural arcs. */ Slice traverseProcedure(GraphNode slicingCriterion); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingCriterion.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingCriterion.java index c1e2bfc1b2a54563ca89239e836f3e04af4edbbc..b17c5b3425f1772268f3213de951d6ff115f3649 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingCriterion.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicingCriterion.java @@ -1,34 +1,19 @@ package es.upv.mist.slicing.slicing; -import es.upv.mist.slicing.graphs.cfg.CFG; -import es.upv.mist.slicing.graphs.pdg.PDG; import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.nodes.GraphNode; -import java.util.Optional; +import java.util.NoSuchElementException; +import java.util.Set; /** A slicing criterion, or the point of interest in slicing. The selected variable(s) * (if any) must produce the same sequence of values in the original program as in the sliced one. */ -public abstract class SlicingCriterion { - protected final String variable; - - public SlicingCriterion(String variable) { - this.variable = variable; - } - - public String getVariable() { - return variable; - } - - /** Locate the slicing criterion in a control flow graph. */ - public abstract Optional> findNode(CFG graph); - /** Locate the slicing criterion in a program dependence graph. */ - public abstract Optional> findNode(PDG graph); - /** Locate the slicing criterion in a system dependence graph. */ - public abstract Optional> findNode(SDG graph); - - @Override - public String toString() { - return "(" + variable + ")"; - } +public interface SlicingCriterion { + /** + * Locates the nodes that represent the slicing criterion in the SDG. + * @return A set with at least one element, all of which directly represent + * the selected criterion in the SDG. + * @throws NoSuchElementException When the slicing criterion cannot be located. + */ + Set> findNode(SDG sdg); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java b/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java index 88b171751ed88d597518ee796548dcb9611f811c..3ab147b15e1e0676e227b877e67ee9b035c6a823 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java @@ -1,16 +1,17 @@ package es.upv.mist.slicing.utils; import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.expr.*; -import com.github.javaparser.ast.stmt.BlockStmt; -import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; -import com.github.javaparser.ast.stmt.SwitchEntry; -import com.github.javaparser.ast.stmt.SwitchStmt; +import com.github.javaparser.ast.stmt.*; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.resolution.Resolvable; -import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; import es.upv.mist.slicing.nodes.GraphNode; @@ -23,13 +24,6 @@ public class ASTUtils { throw new UnsupportedOperationException("This is a static, utility class"); } - public static boolean isContained(Node upper, Node contained) { - Optional parent = contained.getParentNode(); - if (parent.isEmpty()) - return false; - return equalsWithRangeInCU(upper, parent.get()) || isContained(upper, parent.get()); - } - public static boolean switchHasDefaultCase(SwitchStmt stmt) { return switchGetDefaultCase(stmt) != null; } @@ -41,11 +35,23 @@ public class ASTUtils { return null; } + public static boolean equalsInDeclaration(Node n1, Node n2) { + if (n1 == n2) + return true; + CallableDeclaration d1 = getDeclarationNode(n1); + CallableDeclaration d2 = getDeclarationNode(n2); + return n1.equals(n2) && equalsWithRange(d1, d2); + } + public static boolean equalsWithRange(Node n1, Node n2) { + if (n1 == null || n2 == null) + return n1 == n2; return Objects.equals(n1.getRange(), n2.getRange()) && Objects.equals(n1, n2); } public static boolean equalsWithRangeInCU(Node n1, Node n2) { + if (n1 == null || n2 == null) + return n1 == n2; return n1.findCompilationUnit().equals(n2.findCompilationUnit()) && equalsWithRange(n1, n2); } @@ -59,25 +65,22 @@ public class ASTUtils { throw new IllegalArgumentException("Call didn't resolve to either method or constructor!"); } - public static int getMatchingParameterIndex(CallableDeclaration declaration, ResolvedParameterDeclaration param) { - var parameters = declaration.getParameters(); - for (int i = 0; i < parameters.size(); i++) - if (resolvedParameterEquals(param, parameters.get(i).resolve())) - return i; - throw new IllegalArgumentException("Expression resolved to a parameter, but could not be found!"); + public static boolean declarationReturnIsObject(CallableDeclaration declaration) { + if (declaration.isMethodDeclaration()) + return declaration.asMethodDeclaration().getType().isClassOrInterfaceType(); + if (declaration.isConstructorDeclaration()) + return true; + throw new IllegalArgumentException("Declaration wasn't method or constructor"); } - public static int getMatchingParameterIndex(ResolvedMethodLikeDeclaration declaration, ResolvedParameterDeclaration param) { - for (int i = 0; i < declaration.getNumberOfParams(); i++) - if (resolvedParameterEquals(declaration.getParam(i), param)) + public static int getMatchingParameterIndex(CallableDeclaration declaration, String paramName) { + var parameters = declaration.getParameters(); + for (int i = 0; i < parameters.size(); i++) + if (parameters.get(i).getNameAsString().equals(paramName)) return i; throw new IllegalArgumentException("Expression resolved to a parameter, but could not be found!"); } - protected static boolean resolvedParameterEquals(ResolvedParameterDeclaration p1, ResolvedParameterDeclaration p2) { - return p2.getType().equals(p1.getType()) && p2.getName().equals(p1.getName()); - } - public static List getResolvableArgs(Resolvable call) { if (call instanceof MethodCallExpr) return ((MethodCallExpr) call).getArguments(); @@ -123,8 +126,8 @@ public class ASTUtils { } public static boolean constructorHasExplicitConstructorInvocation(ConstructorDeclaration declaration) { - return !getCallableBody(declaration).getStatements().isEmpty() && - getCallableBody(declaration).getStatements().getFirst().get() instanceof ExplicitConstructorInvocationStmt; + final NodeList statements = declaration.getBody().getStatements(); + return !statements.isEmpty() && statements.get(0) instanceof ExplicitConstructorInvocationStmt; } /** @@ -185,6 +188,14 @@ public class ASTUtils { return (ClassOrInterfaceDeclaration) upperNode; } + public static CallableDeclaration getDeclarationNode(Node n) { + assert n instanceof Statement || n instanceof Expression || n instanceof CallableDeclaration; + Node upperNode = n; + while (!(upperNode instanceof CallableDeclaration)) + upperNode = upperNode.getParentNode().orElseThrow(); + return (CallableDeclaration) upperNode; + } + /** Generates the default initializer, given a field. In Java, reference types * default to null, booleans to false and all other primitives to 0. */ public static Expression initializerForField(FieldDeclaration field) { diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/utils/Logger.java b/sdg-core/src/main/java/es/upv/mist/slicing/utils/Logger.java index 20dd0303f6a3b9ba818e9ffd9ab9d1fd039d1b96..6c60d5dc47c935ffdc1a9faa1dbb218938743157 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/utils/Logger.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/utils/Logger.java @@ -38,12 +38,7 @@ public class Logger { } public static void log(String context, String message) { - printStreams.forEach(out -> out.println( - String.format("%s%s", - context.isEmpty() ? "" : String.format("[%s]: ", context), - message - ) - )); + printStreams.forEach(out -> out.println((context.isEmpty() ? "" : "[" + context + "]: ") + message)); } public static void format(String message, Object... args) { diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java b/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java index d5f2dc8899bfc2884994f0ff62d4fc9100c20336..7b3b337b89859f666fabd5abdcdfa1af4ab8df6f 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java @@ -3,10 +3,7 @@ package es.upv.mist.slicing.utils; import org.jgrapht.nio.Attribute; import org.jgrapht.nio.DefaultAttribute; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; +import java.util.*; /** General utilities. */ public class Utils { @@ -29,4 +26,12 @@ public class Utils { map.put("label", DefaultAttribute.createAttribute(label)); return map; } + + /** Find the matching element in the set and return it. */ + public static E setGet(Set set, E object) { + for (E element : set) + if (element.hashCode() == object.hashCode() && Objects.equals(element, object)) + return element; + throw new NoSuchElementException("Could not locate " + object + " in set."); + } } diff --git a/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java b/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java index f111440cb79d4e69a680e573fa80a4a2ccbb84c4..83dadb2a2323166aa3b1b2b4157d7876cc573a5e 100644 --- a/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java +++ b/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java @@ -59,7 +59,7 @@ public class SlicerTest { return Optional.of(Arguments.of(javaFile, slice, criterion.get())); } - @ParameterizedTest(name = "[{index}] {0}") + @ParameterizedTest(name = "{0}") @MethodSource("findAllFiles") public void slicerRegressionTest(File source, File target, SlicingCriterion sc) throws FileNotFoundException { if (!target.exists()) diff --git a/sdg-core/src/test/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArcTest.java b/sdg-core/src/test/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArcTest.java new file mode 100644 index 0000000000000000000000000000000000000000..24842d8c74088f74cf76aa2a8cf7e6a35fe162d1 --- /dev/null +++ b/sdg-core/src/test/java/es/upv/mist/slicing/arcs/sdg/InterproceduralArcTest.java @@ -0,0 +1,31 @@ +package es.upv.mist.slicing.arcs.sdg; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.NodeList; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; +import es.upv.mist.slicing.graphs.sdg.SDG; +import es.upv.mist.slicing.slicing.SlicingCriterion; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.File; +import java.io.FileNotFoundException; + +public class InterproceduralArcTest { + @ParameterizedTest(name = "{0}") + @MethodSource("es.upv.mist.slicing.SlicerTest#findAllFiles") + public void interproceduralArcsMustBeInputXorOutputTest(File source, File target, SlicingCriterion sc) throws FileNotFoundException { + if (!target.exists()) + return; + SDG sdg; + sdg = new JSysDG(); + sdg.build(new NodeList<>(StaticJavaParser.parse(source))); + sdg.edgeSet().stream() + .filter(InterproceduralArc.class::isInstance) + .map(InterproceduralArc.class::cast) + .forEach(arc -> { + assert arc.isInterproceduralInputArc() || arc.isInterproceduralOutputArc(); + assert !arc.isInterproceduralInputArc() || !arc.isInterproceduralOutputArc(); + }); + } +} diff --git a/sdg-core/src/test/res/regression/carlos/Problem1.java b/sdg-core/src/test/res/regression/carlos/Problem1.java index 925a4e82ac490e6cadbe52a61efbc73387133f37..045b39a93a125019fefe25b885c05a8ef96ea340 100644 --- a/sdg-core/src/test/res/regression/carlos/Problem1.java +++ b/sdg-core/src/test/res/regression/carlos/Problem1.java @@ -1,4 +1,6 @@ public class Problem1 { + static boolean X = true, Y = true, Z = true; + public static void main(String[] args) { while (X) { if (Y) { diff --git a/sdg-core/src/test/res/regression/carlos/Problem1.java.sdg.sliced b/sdg-core/src/test/res/regression/carlos/Problem1.java.sdg.sliced index 624bb93025b503564fefca4f658488224f7c7e7d..24dd516bdd86899be99b066351858ac98900e845 100644 --- a/sdg-core/src/test/res/regression/carlos/Problem1.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/carlos/Problem1.java.sdg.sliced @@ -1,6 +1,11 @@ public class Problem1 { public static void main(String[] args) { - System.out.println("D"); + while (X) { + if (Y) { + break; + } + System.out.println("C"); + } } } diff --git a/sdg-core/src/test/res/regression/carlos/Problem3.java.sdg.sliced b/sdg-core/src/test/res/regression/carlos/Problem3.java.sdg.sliced index f181758b8ee19c4a838550ceff8028bcfb8cc363..4e3ade0f0a4c24e8ef3f80827d6982cb7335811e 100644 --- a/sdg-core/src/test/res/regression/carlos/Problem3.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/carlos/Problem3.java.sdg.sliced @@ -1,12 +1,10 @@ public class Problem3 { public static void main() throws Exception { - x = 0; try { f(); } catch (Exception e) { } - x = 1; f(); } diff --git a/sdg-core/src/test/res/regression/carlos/Problem3nonStatic.java b/sdg-core/src/test/res/regression/carlos/Problem3nonStatic.java new file mode 100644 index 0000000000000000000000000000000000000000..38c35ee5718fdffe3b4799a408e827b339c2a8be --- /dev/null +++ b/sdg-core/src/test/res/regression/carlos/Problem3nonStatic.java @@ -0,0 +1,20 @@ +class Problem3 { + int x; + + public void main() throws Exception { + x = 0; + try { + f(); + } catch (Exception e) { + System.out.println("error"); + } + x = 1; + f(); + } + + public void f() throws Exception { + if (x % 2 == 0) + throw new Exception("error!"); + System.out.println("x = " + x); + } +} diff --git a/sdg-core/src/test/res/regression/carlos/TestJosep.java b/sdg-core/src/test/res/regression/carlos/TestJosep.java index 518eba5edb59eb5f9af5e67adbe33bb3bb445621..1f661e1cc1a8e266e989ef4a3bbb53d967435aa0 100644 --- a/sdg-core/src/test/res/regression/carlos/TestJosep.java +++ b/sdg-core/src/test/res/regression/carlos/TestJosep.java @@ -1,5 +1,5 @@ public class TestJosep { - int x = 0; + static int x = 0; public static void main(int y) { int z = 0; while (y > z) { @@ -29,15 +29,15 @@ public class TestJosep { log(z); } - public void objetos() { - Object o = new Object(); - o.x = 10; - Object y = new Object(); - y.z = 210; - o = y; - log(o); - log(y); - } +// public void objetos() { +// Object o = new Object(); +// o.x = 10; +// Object y = new Object(); +// y.z = 210; +// o = y; +// log(o); +// log(y); +// } - public void log(Object o) {} + public static void log(Object o) {} } diff --git a/sdg-core/src/test/res/regression/carlos/TestJosep.java.sdg.sliced b/sdg-core/src/test/res/regression/carlos/TestJosep.java.sdg.sliced index da134e6834fce5a16b7c1e7fc0e530188cb1b7a0..2393031530cbebacce9fd36164843b3c33530aa6 100644 --- a/sdg-core/src/test/res/regression/carlos/TestJosep.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/carlos/TestJosep.java.sdg.sliced @@ -1,6 +1,7 @@ public class TestJosep { public static void main(int y) { + int z = 0; log(z); } } diff --git a/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced b/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced index 074020810eecf70c9e2f14fb9ad8f93aaee625a4..859cc9db04c5b21518c8f7e5f87d94e79a66e4c7 100644 --- a/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced @@ -21,7 +21,7 @@ public class Josep2 { class Numeros { - int noHaceFalta = 1; + int haceFalta = 1; int random() { return haceFalta; @@ -31,9 +31,6 @@ class Numeros { class GrandesNumeros extends Numeros { GrandesNumeros(double x) { - } - - int random() { - return haceFalta; + haceFalta = 0; } } diff --git a/sdg-core/src/test/res/regression/dinsa-tests/Josep3.java.sdg.sliced b/sdg-core/src/test/res/regression/dinsa-tests/Josep3.java.sdg.sliced index 739b3a49a11a428c16e42f83cf547b9aad0da2cf..d1643c890046a0607eb275e14e97c61ab8496e86 100644 --- a/sdg-core/src/test/res/regression/dinsa-tests/Josep3.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/dinsa-tests/Josep3.java.sdg.sliced @@ -3,6 +3,7 @@ class A { int x = 0; A(int x) { + this.x = x; } public static void main(int[] args) { diff --git a/sdg-core/src/test/res/regression/dinsa-tests/Josep4.java.sdg.sliced b/sdg-core/src/test/res/regression/dinsa-tests/Josep4.java.sdg.sliced index 6551a9e6dd436831a68a0deedfbe7402716b04f1..071079bf5deed268344014514fc698e784266d5b 100644 --- a/sdg-core/src/test/res/regression/dinsa-tests/Josep4.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/dinsa-tests/Josep4.java.sdg.sliced @@ -3,6 +3,7 @@ class A { int x = 0; A(int x) { + this.x = x; } int getx() { diff --git a/sdg-core/src/test/res/regression/dinsa-tests/Josep6.java.sdg.sliced b/sdg-core/src/test/res/regression/dinsa-tests/Josep6.java.sdg.sliced index 90e514e8ac03b35ecec7e5f370d32438a357538a..3440484086842f1caa7ed904b3ca5256ff102378 100644 --- a/sdg-core/src/test/res/regression/dinsa-tests/Josep6.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/dinsa-tests/Josep6.java.sdg.sliced @@ -1,9 +1,6 @@ class A { - int xA = 0; - A(int newx) { - xA = newx; } public static void main(String[] args) { @@ -16,6 +13,8 @@ class A { class B extends A { + int xB = 5; + B(double valor) { super((int) valor); } diff --git a/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java new file mode 100644 index 0000000000000000000000000000000000000000..47ac26241a7ce506e7a74d480c12860020c7b454 --- /dev/null +++ b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java @@ -0,0 +1,20 @@ +class Test { + + int a; + + Test(int a, boolean b, int c) { + if (b) { + this.a = a * c / (a + c); + return; + } else { + this.a = 10; + return; + } + } + + public static void main(String[] args) { + Test t = new Test(15, true, 10); + int a = t.a; + System.out.println(a); + } +} \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java.sdg.criterion b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java.sdg.criterion new file mode 100644 index 0000000000000000000000000000000000000000..25bf17fc5aaabd17402e77a2b16f95fbea7310d2 --- /dev/null +++ b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java.sdg.criterion @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java.sdg.sliced b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java.sdg.sliced new file mode 100644 index 0000000000000000000000000000000000000000..47ac26241a7ce506e7a74d480c12860020c7b454 --- /dev/null +++ b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithAllReturns.java.sdg.sliced @@ -0,0 +1,20 @@ +class Test { + + int a; + + Test(int a, boolean b, int c) { + if (b) { + this.a = a * c / (a + c); + return; + } else { + this.a = 10; + return; + } + } + + public static void main(String[] args) { + Test t = new Test(15, true, 10); + int a = t.a; + System.out.println(a); + } +} \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java new file mode 100644 index 0000000000000000000000000000000000000000..e5a72ed96b1036694a6df204a66d7969f5d17357 --- /dev/null +++ b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java @@ -0,0 +1,17 @@ +class Test { + int a; + + Test(int a, boolean b, int c) { + if (b) { + this.a = a * c / (a + c); + return; + } + this.a = 10; + } + + public static void main(String[] args) { + Test t = new Test(15, true, 10); + int a = t.a; + System.out.println(a); + } +} \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java.sdg.criterion b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java.sdg.criterion new file mode 100644 index 0000000000000000000000000000000000000000..3f10ffe7a4c473619c926cfb1e8d95e726e5a0ec --- /dev/null +++ b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java.sdg.criterion @@ -0,0 +1 @@ +15 \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java.sdg.sliced b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java.sdg.sliced new file mode 100644 index 0000000000000000000000000000000000000000..19fa5112d5f456862372f1330a0490df5f64829c --- /dev/null +++ b/sdg-core/src/test/res/regression/programs/sdg/ConstructorWithReturn.java.sdg.sliced @@ -0,0 +1,18 @@ +class Test { + + int a; + + Test(int a, boolean b, int c) { + if (b) { + this.a = a * c / (a + c); + return; + } + this.a = 10; + } + + public static void main(String[] args) { + Test t = new Test(15, true, 10); + int a = t.a; + System.out.println(a); + } +} diff --git a/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced b/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced index 074020810eecf70c9e2f14fb9ad8f93aaee625a4..859cc9db04c5b21518c8f7e5f87d94e79a66e4c7 100644 --- a/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced @@ -21,7 +21,7 @@ public class Josep2 { class Numeros { - int noHaceFalta = 1; + int haceFalta = 1; int random() { return haceFalta; @@ -31,9 +31,6 @@ class Numeros { class GrandesNumeros extends Numeros { GrandesNumeros(double x) { - } - - int random() { - return haceFalta; + haceFalta = 0; } }