Documentation and comments
This commit is contained in:
parent
5ca4da62dc
commit
9aad602df3
3 changed files with 195 additions and 44 deletions
|
@ -7,21 +7,58 @@ import com.github.javaparser.ast.stmt.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a control flow graph, offering methods to build it easily with a visitor and an output ready to be
|
||||||
|
* converted to a graph by GraphViz ({@link #toStringList(GraphViz)}).
|
||||||
|
* <br/>
|
||||||
|
* The process should begin with {@link #beginMethod()} and end with {@link #finishMethod()}. Similarly, every opened
|
||||||
|
* {@link Block} and section must be closed. The class offers special support for {@link ContinueStmt} and {@link BreakStmt},
|
||||||
|
* including both the labeled and unlabeled versions.
|
||||||
|
* <br/>
|
||||||
|
* The inner class {@link Block} is used to represent a block statement (not only {@link BlockStmt} but also {@link IfStmt}
|
||||||
|
* and {@link WhileStmt}, for example). {@link Block} should not be used directly, only through the API provided by CFG.
|
||||||
|
*/
|
||||||
public class CFG {
|
public class CFG {
|
||||||
|
/**
|
||||||
|
* List of nodes that exist in this CFG (can either be {@link com.github.javaparser.ast.expr.Expression} or {@link Statement})
|
||||||
|
*/
|
||||||
private final List<Node> nodes = new LinkedList<>();
|
private final List<Node> nodes = new LinkedList<>();
|
||||||
|
/** List of blocks (statements that contain other statements) in this CFG.
|
||||||
|
* Only includes the "surface" blocks, not the ones inside other blocks.
|
||||||
|
* @see Block */
|
||||||
private final LinkedList<Block> blocks = new LinkedList<>();
|
private final LinkedList<Block> blocks = new LinkedList<>();
|
||||||
|
/** Stack of blocks, representing the position in the method. The current position is inside the topmost block,
|
||||||
|
* which is contained inside the previous one. If empty, the position is outside all blocks, in the main level
|
||||||
|
* of the method. */
|
||||||
private final Stack<Block> blockStack = new Stack<>();
|
private final Stack<Block> blockStack = new Stack<>();
|
||||||
|
/** List of {@link Edge}, or connections between nodes. */
|
||||||
private final List<Edge> edges = new LinkedList<>();
|
private final List<Edge> edges = new LinkedList<>();
|
||||||
|
/** First and last node of a method, represented by an {@link EmptyStmt} instance. */
|
||||||
private final Node beginNode = new EmptyStmt(), endNode = new EmptyStmt();
|
private final Node beginNode = new EmptyStmt(), endNode = new EmptyStmt();
|
||||||
|
/** List of nodes that are immediately before the next instruction, and therefore will be linked to it.
|
||||||
|
* May be empty after a break/return/continue, or contain more than one element after a control-flow instruction. */
|
||||||
private final LinkedList<Node> nodeList = new LinkedList<>();
|
private final LinkedList<Node> nodeList = new LinkedList<>();
|
||||||
|
/** Map of nodes to the name that needs to be applied to the edge when that node is connected to the next one.
|
||||||
|
* Used to distinguish the {@code true} and {@code false} branches in conditions. */
|
||||||
private final Map<Node, String> labelMap = new HashMap<>();
|
private final Map<Node, String> labelMap = new HashMap<>();
|
||||||
|
/** Map of loop labels to lists of break statements (all labeled). Each list begins empty and {@link BreakStmt} are
|
||||||
|
* added as found. When the end of the labeled loop is reached, the list is removed and dumped into {@link #nodeList}. */
|
||||||
private final Map<SimpleName, List<BreakStmt>> breakMap = new HashMap<>();
|
private final Map<SimpleName, List<BreakStmt>> breakMap = new HashMap<>();
|
||||||
|
/** Stack of lists of break statements (without label). A new list is added when entering any structure that may be broken out of,
|
||||||
|
* and the list is dumped into the {@link #nodeList} when exiting the loop. */
|
||||||
private final Stack<List<BreakStmt>> breakStack = new Stack<>();
|
private final Stack<List<BreakStmt>> breakStack = new Stack<>();
|
||||||
|
/** Map of loop labels to the node that the loop "continues" to, i.e., the node to which a continue stmt will be
|
||||||
|
* connected. */
|
||||||
private final Map<SimpleName, Node> continueMap = new HashMap<>();
|
private final Map<SimpleName, Node> continueMap = new HashMap<>();
|
||||||
|
/** Stack of the node that the loop "continues" to.
|
||||||
|
* @see #continueMap */
|
||||||
private final Stack<Node> continueStack = new Stack<>();
|
private final Stack<Node> continueStack = new Stack<>();
|
||||||
|
|
||||||
|
/** Last labeled visited, if a {@link LabeledStmt} has been visited, but the corresponding loop hasn't been yet.
|
||||||
|
* Otherwise, {@code null}. */
|
||||||
private SimpleName lastLabel;
|
private SimpleName lastLabel;
|
||||||
|
|
||||||
|
/** Adds a node to the list of nodes known by this tree */
|
||||||
private void registerNode(Node stmt) {
|
private void registerNode(Node stmt) {
|
||||||
nodes.add(stmt);
|
nodes.add(stmt);
|
||||||
if (!blockStack.isEmpty())
|
if (!blockStack.isEmpty())
|
||||||
|
@ -29,19 +66,24 @@ public class CFG {
|
||||||
System.out.println("NODO: " + node2str(stmt));
|
System.out.println("NODO: " + node2str(stmt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Clears the list of nodes and adds the given node. */
|
||||||
public void setNode(Node node) {
|
public void setNode(Node node) {
|
||||||
clearNode();
|
clearNode();
|
||||||
appendNode(node);
|
appendNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Clears the list of nodes. */
|
||||||
public void clearNode() {
|
public void clearNode() {
|
||||||
nodeList.clear();
|
nodeList.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Connect each node in the list of nodes to the current node, then set the node as the only element in the list. */
|
||||||
public void connectTo(Node node) {
|
public void connectTo(Node node) {
|
||||||
connectTo(node, true);
|
connectTo(node, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Connect each node in the list of nodes to the current node, then optionally set the node as the only
|
||||||
|
* element in the list. */
|
||||||
public void connectTo(Node node, boolean overwrite) {
|
public void connectTo(Node node, boolean overwrite) {
|
||||||
if (!nodesContains(node))
|
if (!nodesContains(node))
|
||||||
registerNode(node);
|
registerNode(node);
|
||||||
|
@ -51,6 +93,8 @@ public class CFG {
|
||||||
setNode(node);
|
setNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks if an object is in the node registration list. Implemented to use {@code ==} instead of
|
||||||
|
* {@link List#contains(Object)}, which uses {@link Object#equals(Object)}. */
|
||||||
private boolean nodesContains(Object object) {
|
private boolean nodesContains(Object object) {
|
||||||
for (Node n : nodes)
|
for (Node n : nodes)
|
||||||
if (n == object)
|
if (n == object)
|
||||||
|
@ -58,38 +102,45 @@ public class CFG {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Removes a node from the list of nodes */
|
||||||
public void removeNode(Node node) {
|
public void removeNode(Node node) {
|
||||||
nodeList.remove(node);
|
nodeList.remove(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Adds a node to the list of nodes */
|
||||||
public void appendNode(Node node) {
|
public void appendNode(Node node) {
|
||||||
nodeList.add(node);
|
nodeList.add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void appendNodes(Collection<? extends Node> nodes) {
|
/** Returns an unmodifiable copy of the list of nodes */
|
||||||
nodeList.addAll(nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Node> getNodeList() {
|
public List<Node> getNodeList() {
|
||||||
return Collections.unmodifiableList(nodeList);
|
return Collections.unmodifiableList(nodeList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Replace the list of nodes with this list. The argument may be reused without affecting the list of nodes. */
|
||||||
public void setNodeList(List<Node> nodes) {
|
public void setNodeList(List<Node> nodes) {
|
||||||
nodeList.clear();
|
nodeList.clear();
|
||||||
nodeList.addAll(nodes);
|
nodeList.addAll(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method operations
|
||||||
|
|
||||||
|
/** Begin a method: clear the list of nodes and add the "Start" node */
|
||||||
public void beginMethod() {
|
public void beginMethod() {
|
||||||
nodeList.clear();
|
nodeList.clear();
|
||||||
nodeList.add(beginNode);
|
nodeList.add(beginNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Finish a method: connect each element of the list of nodes to the end (unless they are the end already) */
|
||||||
public void finishMethod() {
|
public void finishMethod() {
|
||||||
for (Node begin : nodeList)
|
for (Node begin : nodeList)
|
||||||
if (begin != endNode)
|
if (begin != endNode)
|
||||||
connect(begin, endNode);
|
connect(begin, endNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block operations
|
||||||
|
|
||||||
|
/** Start a new block, with a node that it represents and its condition. */
|
||||||
public void beginBlock(Node node, Node condition) {
|
public void beginBlock(Node node, Node condition) {
|
||||||
Block b = new Block(node, condition);
|
Block b = new Block(node, condition);
|
||||||
if (condition != null)
|
if (condition != null)
|
||||||
|
@ -101,14 +152,18 @@ public class CFG {
|
||||||
blockStack.push(b);
|
blockStack.push(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** End a block */
|
||||||
public void endBlock() {
|
public void endBlock() {
|
||||||
blockStack.pop();
|
blockStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set the label to be used when the last element of the list of nodes is connected to the next node. Run this
|
||||||
|
* AFTER adding the node with {@link #appendNode(Node)}. */
|
||||||
public void setNextLabel(boolean b) {
|
public void setNextLabel(boolean b) {
|
||||||
labelMap.put(nodeList.getLast(), String.valueOf(b));
|
labelMap.put(nodeList.getLast(), String.valueOf(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Connects one node with another, checking the map of labels and clearing the appropriate entry */
|
||||||
private void connect(Node begin, Node end) {
|
private void connect(Node begin, Node end) {
|
||||||
edges.add(new Edge(begin, end, labelMap.get(begin)));
|
edges.add(new Edge(begin, end, labelMap.get(begin)));
|
||||||
labelMap.remove(begin);
|
labelMap.remove(begin);
|
||||||
|
@ -118,14 +173,20 @@ public class CFG {
|
||||||
return endNode;
|
return endNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Break operations
|
||||||
|
|
||||||
|
/** Begin a new list of break statements associated with the given label */
|
||||||
public void beginBreakSection(SimpleName name) {
|
public void beginBreakSection(SimpleName name) {
|
||||||
breakMap.put(name, new LinkedList<>());
|
breakMap.put(name, new LinkedList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Begin a new list of unlabeled break statements */
|
||||||
public void beginBreakSection() {
|
public void beginBreakSection() {
|
||||||
breakStack.push(new LinkedList<>());
|
breakStack.push(new LinkedList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Connect a break statement to the chain, then add it to the appropriate list, so that when the switch/loop ends
|
||||||
|
* it can be connected to the proper node, which is currently unknown. */
|
||||||
public void connectBreak(BreakStmt stmt) {
|
public void connectBreak(BreakStmt stmt) {
|
||||||
connectTo(stmt);
|
connectTo(stmt);
|
||||||
if (stmt.getLabel().isPresent()) {
|
if (stmt.getLabel().isPresent()) {
|
||||||
|
@ -136,19 +197,29 @@ public class CFG {
|
||||||
clearNode();
|
clearNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ends a list of labeled break statements, adding all the break statements collected to the list of nodes. */
|
||||||
public void endBreakSection(SimpleName name) {
|
public void endBreakSection(SimpleName name) {
|
||||||
nodeList.addAll(breakMap.remove(name));
|
nodeList.addAll(breakMap.remove(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ends a list of unlabeled break statements, adding all the break statements collected to the list of nodes.
|
||||||
|
* Restores the previous list (if it exists), so that nested loops/switch statements work properly */
|
||||||
public void endBreakSection() {
|
public void endBreakSection() {
|
||||||
nodeList.addAll(breakStack.pop());
|
nodeList.addAll(breakStack.pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Continue operations
|
||||||
|
|
||||||
|
/** Begin a labeled continue section (loop). Inside the loop, {@link #beginContinueSection(Node)} must be called for
|
||||||
|
* this method to work properly. */
|
||||||
public void beginContinueSection(SimpleName name) {
|
public void beginContinueSection(SimpleName name) {
|
||||||
assert lastLabel == null;
|
assert lastLabel == null;
|
||||||
lastLabel = name;
|
lastLabel = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Begin a continue section (loop). Will register the given node as the to-go node when a continue statement is
|
||||||
|
* found, and optionally register it with the proper label if {@link #beginContinueSection(SimpleName)} has been
|
||||||
|
* called immediately before. */
|
||||||
public void beginContinueSection(Node node) {
|
public void beginContinueSection(Node node) {
|
||||||
continueStack.push(node);
|
continueStack.push(node);
|
||||||
if (lastLabel != null) {
|
if (lastLabel != null) {
|
||||||
|
@ -158,6 +229,8 @@ public class CFG {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Connect each statement in the list of nodes to the continue statement, then connect the continue to the proper
|
||||||
|
* statement, depending on whether it has a label or not. */
|
||||||
public void connectContinue(ContinueStmt stmt) {
|
public void connectContinue(ContinueStmt stmt) {
|
||||||
connectTo(stmt);
|
connectTo(stmt);
|
||||||
if (stmt.getLabel().isPresent()) {
|
if (stmt.getLabel().isPresent()) {
|
||||||
|
@ -168,14 +241,17 @@ public class CFG {
|
||||||
clearNode();
|
clearNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ends a labeled continue section */
|
||||||
public void endContinueSection(SimpleName name) {
|
public void endContinueSection(SimpleName name) {
|
||||||
continueMap.remove(name);
|
continueMap.remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ends an unlabeled continue section (loop). Works with nested loops */
|
||||||
public void endContinueSection() {
|
public void endContinueSection() {
|
||||||
continueStack.pop();
|
continueStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Outputs the contents of this object as a list of strings, ready to be parsed by dot and turned into a graph. */
|
||||||
public List<String> toStringList(GraphViz gv) {
|
public List<String> toStringList(GraphViz gv) {
|
||||||
List<String> res = new LinkedList<>();
|
List<String> res = new LinkedList<>();
|
||||||
res.add(gv.start_graph());
|
res.add(gv.start_graph());
|
||||||
|
@ -188,12 +264,15 @@ public class CFG {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Converts a node to string, prepending an index and line number, and escaping any quotations. */
|
||||||
private String node2str(Node n) {
|
private String node2str(Node n) {
|
||||||
if (n == beginNode) return "Start";
|
if (n == beginNode) return "Start";
|
||||||
if (n == endNode) return "Stop";
|
if (n == endNode) return "Stop";
|
||||||
return String.format("\"(%d, %d) %s\"", indexOfNode(n) + 1, n.getRange().get().begin.line, escape(n.toString()));
|
return String.format("\"(%d, %d) %s\"", indexOfNode(n) + 1, n.getRange().get().begin.line, escape(n.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Finds the index of a node in the node registration list. Throws an exception if unsuccessful.
|
||||||
|
* This implementation is necessary to use {@code ==} instead of {@link Object#equals(Object)}. */
|
||||||
private int indexOfNode(Node n) {
|
private int indexOfNode(Node n) {
|
||||||
for (int i = 0; i < nodes.size(); i++)
|
for (int i = 0; i < nodes.size(); i++)
|
||||||
if (nodes.get(i) == n)
|
if (nodes.get(i) == n)
|
||||||
|
@ -201,14 +280,25 @@ public class CFG {
|
||||||
throw new RuntimeException("Internal error, can't find node in list");
|
throw new RuntimeException("Internal error, can't find node in list");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Escapes a string. Currently only escapes quotation marks. */
|
||||||
private String escape(String s) {
|
private String escape(String s) {
|
||||||
return s.replace("\"", "\\\"");
|
return s.replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Id for each subgraph/block (incremented with each new block) */
|
||||||
private static int clusterId = 0;
|
private static int clusterId = 0;
|
||||||
|
|
||||||
|
/** Representation of a statement containing {@link Node}, such as an if, loop or switch statement */
|
||||||
class Block extends LinkedList<Node> {
|
class Block extends LinkedList<Node> {
|
||||||
private final Node container, condition;
|
/**
|
||||||
|
* The node that is being represented by this block
|
||||||
|
*/
|
||||||
|
private final Node container;
|
||||||
|
/**
|
||||||
|
* The condition to enter the current block, or the condition of the if
|
||||||
|
*/
|
||||||
|
private final Node condition;
|
||||||
|
/** The sub-blocks present in this Block */
|
||||||
private final List<Block> inners = new LinkedList<>();
|
private final List<Block> inners = new LinkedList<>();
|
||||||
|
|
||||||
Block(Node container, Node condition) {
|
Block(Node container, Node condition) {
|
||||||
|
@ -216,29 +306,36 @@ public class CFG {
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Add a sub-block to this block */
|
||||||
void addSubBlock(Block block) {
|
void addSubBlock(Block block) {
|
||||||
inners.add(block);
|
inners.add(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get a representation ready to be used in dot (of the subgraph) */
|
||||||
List<String> toStringList(GraphViz gv) {
|
List<String> toStringList(GraphViz gv) {
|
||||||
List<String> res = new LinkedList<>();
|
List<String> res = new LinkedList<>();
|
||||||
res.add(gv.start_subgraph(clusterId++));
|
res.add(gv.start_subgraph(clusterId++)); // start
|
||||||
res.add("label = \"" + getTitle() + "\"");
|
res.add("label = \"" + getTitle() + "\""); // title of the sub-graph
|
||||||
|
// Add the condition node with a special shape, then reset the shape back to the default
|
||||||
if (condition != null) {
|
if (condition != null) {
|
||||||
res.add("node [ shape = rectangle ]; " + node2str(condition));
|
res.add("node [ shape = rectangle ]; " + node2str(condition));
|
||||||
res.add("node [ shape = ellipse ]");
|
res.add("node [ shape = ellipse ]");
|
||||||
}
|
}
|
||||||
|
// Add all inner blocks
|
||||||
for (Block b : inners) {
|
for (Block b : inners) {
|
||||||
res.addAll(b.toStringList(gv));
|
res.addAll(b.toStringList(gv));
|
||||||
}
|
}
|
||||||
|
// Add all nodes as part of the sub-graph
|
||||||
for (Node n : this) {
|
for (Node n : this) {
|
||||||
res.add(node2str(n));
|
res.add(node2str(n));
|
||||||
}
|
}
|
||||||
res.add(gv.end_subgraph());
|
res.add(gv.end_subgraph()); // end
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generate a title for the sub-graph based on the block instruction it represents. */
|
||||||
String getTitle() {
|
String getTitle() {
|
||||||
|
// Variables to be filled for String#format(String, Object[])
|
||||||
String template;
|
String template;
|
||||||
Object[] filler;
|
Object[] filler;
|
||||||
if (container instanceof IfStmt) {
|
if (container instanceof IfStmt) {
|
||||||
|
@ -269,22 +366,23 @@ public class CFG {
|
||||||
filler = new Object[]{((SwitchStmt) container).getSelector()};
|
filler = new Object[]{((SwitchStmt) container).getSelector()};
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unimplemented!!!");
|
throw new RuntimeException("Unimplemented!!!");
|
||||||
// TODO: remove
|
|
||||||
}
|
}
|
||||||
return String.format(template, filler);
|
return String.format(template, filler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Edge {
|
/** Representation of a connection between two nodes, with an optional label */
|
||||||
|
private class Edge {
|
||||||
private final Node origin, destination;
|
private final Node origin, destination;
|
||||||
private final String label;
|
private final String label;
|
||||||
|
|
||||||
Edge(Node origin, Node destination, String label) {
|
private Edge(Node origin, Node destination, String label) {
|
||||||
this.origin = origin;
|
this.origin = origin;
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String s = node2str(origin) + " -> " + node2str(destination);
|
String s = node2str(origin) + " -> " + node2str(destination);
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
|
|
|
@ -19,6 +19,9 @@ public class Transformador {
|
||||||
analyzeFile(file);
|
analyzeFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs {@link #analyzeFile(File)} for each Java file found in this directory, recursively.
|
||||||
|
*/
|
||||||
public static void analyzeDir(File dir) throws FileNotFoundException {
|
public static void analyzeDir(File dir) throws FileNotFoundException {
|
||||||
for (File f : dir.listFiles()) {
|
for (File f : dir.listFiles()) {
|
||||||
if (f.isDirectory())
|
if (f.isDirectory())
|
||||||
|
@ -28,6 +31,7 @@ public class Transformador {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Analyzes and generates a CFG in a PDF from a Java file. */
|
||||||
public static void analyzeFile(File file) throws FileNotFoundException {
|
public static void analyzeFile(File file) throws FileNotFoundException {
|
||||||
System.out.println("BEGIN FILE " + file.getPath());
|
System.out.println("BEGIN FILE " + file.getPath());
|
||||||
// Ruta del fichero con el programa que vamos a transformar
|
// Ruta del fichero con el programa que vamos a transformar
|
||||||
|
|
|
@ -11,6 +11,10 @@ import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a compilation unit and extracts the control flow graph, registering all the data in the argument to all
|
||||||
|
* functions: {@link CFG}. This class contains no information, all data is saved in the control flow graph.
|
||||||
|
*/
|
||||||
public class Visitador extends VoidVisitorAdapter<CFG> {
|
public class Visitador extends VoidVisitorAdapter<CFG> {
|
||||||
@Override
|
@Override
|
||||||
public void visit(MethodDeclaration methodDeclaration, CFG graph) {
|
public void visit(MethodDeclaration methodDeclaration, CFG graph) {
|
||||||
|
@ -19,6 +23,10 @@ public class Visitador extends VoidVisitorAdapter<CFG> {
|
||||||
graph.finishMethod();
|
graph.finishMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return statements are connected to the previous statement and to the end node of the graph.
|
||||||
|
* Schema: {@code -> return -> Stop}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void visit(ReturnStmt n, CFG arg) {
|
public void visit(ReturnStmt n, CFG arg) {
|
||||||
arg.connectTo(n);
|
arg.connectTo(n);
|
||||||
|
@ -26,24 +34,29 @@ public class Visitador extends VoidVisitorAdapter<CFG> {
|
||||||
arg.clearNode();
|
arg.clearNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** If statements are set up as a block, with a condition and supports any configuration, including missing else,
|
||||||
|
* empty then/else blocks. */
|
||||||
@Override
|
@Override
|
||||||
public void visit(IfStmt n, CFG graph) {
|
public void visit(IfStmt n, CFG graph) {
|
||||||
// Connect to the condition
|
// prev --> cond
|
||||||
Node ifStart = n.getCondition();
|
Node ifStart = n.getCondition();
|
||||||
graph.beginBlock(n, ifStart);
|
graph.beginBlock(n, ifStart);
|
||||||
graph.connectTo(ifStart);
|
graph.connectTo(ifStart);
|
||||||
// Connect condition to then block
|
// cond --> then (if true)
|
||||||
graph.setNextLabel(true);
|
graph.setNextLabel(true);
|
||||||
n.getThenStmt().accept(this, graph);
|
n.getThenStmt().accept(this, graph);
|
||||||
// Save end of then block
|
// then --> next (saved for later)
|
||||||
List<Node> newPrev = new LinkedList<>(graph.getNodeList());
|
List<Node> newPrev = new LinkedList<>(graph.getNodeList());
|
||||||
// Connect condition to else block
|
// Else processing (either connect cond --> else --> next or cond --> next)
|
||||||
graph.setNode(ifStart);
|
graph.setNode(ifStart);
|
||||||
graph.setNextLabel(false);
|
graph.setNextLabel(false);
|
||||||
if (n.getElseStmt().isPresent()) {
|
if (n.getElseStmt().isPresent()) {
|
||||||
|
// cond --> else (if false)
|
||||||
n.getElseStmt().get().accept(this, graph);
|
n.getElseStmt().get().accept(this, graph);
|
||||||
|
// else --> next
|
||||||
newPrev.addAll(graph.getNodeList());
|
newPrev.addAll(graph.getNodeList());
|
||||||
} else {
|
} else {
|
||||||
|
// cond --> next (if false)
|
||||||
newPrev.add(ifStart);
|
newPrev.add(ifStart);
|
||||||
}
|
}
|
||||||
// Set ends of then/else as the new list
|
// Set ends of then/else as the new list
|
||||||
|
@ -51,6 +64,7 @@ public class Visitador extends VoidVisitorAdapter<CFG> {
|
||||||
graph.endBlock();
|
graph.endBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Registers the label used for the loop statement in the break and continue sections of the CFG */
|
||||||
@Override
|
@Override
|
||||||
public void visit(LabeledStmt n, CFG graph) {
|
public void visit(LabeledStmt n, CFG graph) {
|
||||||
graph.beginBreakSection(n.getLabel());
|
graph.beginBreakSection(n.getLabel());
|
||||||
|
@ -60,130 +74,165 @@ public class Visitador extends VoidVisitorAdapter<CFG> {
|
||||||
graph.endBreakSection(n.getLabel());
|
graph.endBreakSection(n.getLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** While statements are set up as a block. They also generate an unlabeled break and continue section.
|
||||||
|
* The schema followed is {@code -> cond -> body -> cond ->} */
|
||||||
@Override
|
@Override
|
||||||
public void visit(WhileStmt n, CFG graph) {
|
public void visit(WhileStmt n, CFG graph) {
|
||||||
Node whileStart = n.getCondition();
|
Node whileStart = n.getCondition();
|
||||||
|
// Begin block/sections
|
||||||
graph.beginBlock(n, whileStart);
|
graph.beginBlock(n, whileStart);
|
||||||
graph.beginBreakSection();
|
graph.beginBreakSection();
|
||||||
graph.beginContinueSection(whileStart);
|
graph.beginContinueSection(whileStart);
|
||||||
|
// prev --> cond
|
||||||
graph.connectTo(whileStart);
|
graph.connectTo(whileStart);
|
||||||
|
// cond --> body (if true)
|
||||||
graph.setNextLabel(true);
|
graph.setNextLabel(true);
|
||||||
n.getBody().accept(this, graph);
|
n.getBody().accept(this, graph);
|
||||||
|
// body --> cond
|
||||||
graph.connectTo(whileStart);
|
graph.connectTo(whileStart);
|
||||||
|
// cond --> next (if false)
|
||||||
graph.setNextLabel(false);
|
graph.setNextLabel(false);
|
||||||
|
// End block/sections
|
||||||
graph.endContinueSection();
|
graph.endContinueSection();
|
||||||
graph.endBreakSection();
|
graph.endBreakSection();
|
||||||
graph.endBlock();
|
graph.endBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Do while statement, set up as block. Generates break and continue section. Schema:
|
||||||
|
* {@code -> body -> cond -> body -> cond ->} */
|
||||||
@Override
|
@Override
|
||||||
public void visit(DoStmt n, CFG graph) {
|
public void visit(DoStmt n, CFG graph) {
|
||||||
Node condition = n.getCondition();
|
Node condition = n.getCondition();
|
||||||
|
// Begin block/section
|
||||||
graph.beginBlock(n, condition);
|
graph.beginBlock(n, condition);
|
||||||
graph.beginBreakSection();
|
graph.beginBreakSection();
|
||||||
graph.beginContinueSection(condition);
|
graph.beginContinueSection(condition);
|
||||||
|
// prev --> body
|
||||||
|
// cond --> body (if true)
|
||||||
graph.appendNode(condition);
|
graph.appendNode(condition);
|
||||||
n.getBody().accept(this, graph);
|
n.getBody().accept(this, graph);
|
||||||
|
// body --> cond
|
||||||
graph.connectTo(condition);
|
graph.connectTo(condition);
|
||||||
|
// cond --> next (if false)
|
||||||
graph.setNextLabel(false);
|
graph.setNextLabel(false);
|
||||||
|
// End block/section
|
||||||
graph.endContinueSection();
|
graph.endContinueSection();
|
||||||
graph.endBreakSection();
|
graph.endBreakSection();
|
||||||
graph.endBlock();
|
graph.endBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** For statement, set up as a block. Generates break and continue section. If the condition is empty, it will be
|
||||||
|
* set as a {@code true} literal expression. Schema: {@code -> initializer(s) -> cond -> body -> update(s) -> cond -> } */
|
||||||
@Override
|
@Override
|
||||||
public void visit(ForStmt n, CFG graph) {
|
public void visit(ForStmt n, CFG graph) {
|
||||||
|
// Begin block and break sections (continue section needs the comparison, see below)
|
||||||
graph.beginBlock(n, n.getCompare().orElse(null));
|
graph.beginBlock(n, n.getCompare().orElse(null));
|
||||||
graph.beginBreakSection();
|
graph.beginBreakSection();
|
||||||
// Initialization expressions
|
// prev --> initializer(s), chained in serial
|
||||||
if (n.getInitialization() != null) {
|
for (Expression e : n.getInitialization())
|
||||||
for (Expression e : n.getInitialization()) {
|
|
||||||
graph.connectTo(e);
|
graph.connectTo(e);
|
||||||
}
|
// Set the comparison expression to "true" if the expression was empty, as a safeguard and to ease the treatment
|
||||||
}
|
// of cases where the loop is of the form "for (;;) ;", which would require special processing.
|
||||||
// Comparison expression
|
|
||||||
// TODO: shortcut conditions (||, &&)
|
|
||||||
assert n.getTokenRange().isPresent();
|
assert n.getTokenRange().isPresent();
|
||||||
if (!n.getCompare().isPresent())
|
if (!n.getCompare().isPresent())
|
||||||
n.setCompare(new BooleanLiteralExpr(n.getTokenRange().get(), true));
|
n.setCompare(new BooleanLiteralExpr(n.getTokenRange().get(), true));
|
||||||
|
// Begin continue section (with the comparison on hand)
|
||||||
graph.beginContinueSection(n.getCompare().get());
|
graph.beginContinueSection(n.getCompare().get());
|
||||||
|
// initializer(s) --> cond
|
||||||
graph.connectTo(n.getCompare().get());
|
graph.connectTo(n.getCompare().get());
|
||||||
|
// cond --> body (if true)
|
||||||
graph.setNextLabel(true);
|
graph.setNextLabel(true);
|
||||||
// Loop body
|
|
||||||
n.getBody().accept(this, graph);
|
n.getBody().accept(this, graph);
|
||||||
// Update expressions
|
// body --> update(s), chained in serial
|
||||||
if (n.getUpdate() != null)
|
|
||||||
for (Expression e : n.getUpdate())
|
for (Expression e : n.getUpdate())
|
||||||
graph.connectTo(e);
|
graph.connectTo(e);
|
||||||
// Connect to comparison expression
|
// update(s) --> comp
|
||||||
// Set comparison as last possible statement
|
|
||||||
graph.connectTo(n.getCompare().get());
|
graph.connectTo(n.getCompare().get());
|
||||||
|
// comp --> next (if false)
|
||||||
graph.setNextLabel(false);
|
graph.setNextLabel(false);
|
||||||
|
// End block/section (all of them)
|
||||||
graph.endContinueSection();
|
graph.endContinueSection();
|
||||||
graph.endBreakSection();
|
graph.endBreakSection();
|
||||||
graph.endBlock();
|
graph.endBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Foreach statement, set up as a block. Generates break and continue sections. Schema: {@code -> cond -> body -> cond ->} */
|
||||||
@Override
|
@Override
|
||||||
public void visit(ForeachStmt n, CFG graph) {
|
public void visit(ForeachStmt n, CFG graph) {
|
||||||
|
// Create a copy of the foreach statement to use as the condition node. The alternative would be to "emulate" the
|
||||||
|
// foreach statement with a for statement.
|
||||||
ForeachStmt copy = new ForeachStmt(n.getTokenRange().orElse(null), n.getVariable(), n.getIterable(), new EmptyStmt());
|
ForeachStmt copy = new ForeachStmt(n.getTokenRange().orElse(null), n.getVariable(), n.getIterable(), new EmptyStmt());
|
||||||
|
// Begin block/section
|
||||||
graph.beginBlock(n, copy);
|
graph.beginBlock(n, copy);
|
||||||
graph.beginBreakSection();
|
graph.beginBreakSection();
|
||||||
graph.beginContinueSection(copy);
|
graph.beginContinueSection(copy);
|
||||||
|
// prev --> cond
|
||||||
graph.connectTo(copy);
|
graph.connectTo(copy);
|
||||||
|
// cond --> body (if true)
|
||||||
graph.setNextLabel(true);
|
graph.setNextLabel(true);
|
||||||
n.getBody().accept(this, graph);
|
n.getBody().accept(this, graph);
|
||||||
|
// body --> cond
|
||||||
graph.connectTo(copy);
|
graph.connectTo(copy);
|
||||||
|
// cond --> next (if false)
|
||||||
graph.setNextLabel(false);
|
graph.setNextLabel(false);
|
||||||
graph.endContinueSection();
|
graph.endContinueSection();
|
||||||
graph.endBreakSection();
|
graph.endBreakSection();
|
||||||
graph.endBlock();
|
graph.endBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Switch entry, considered part of the condition of the switch. */
|
||||||
@Override
|
@Override
|
||||||
public void visit(SwitchEntryStmt n, CFG graph) {
|
public void visit(SwitchEntryStmt n, CFG graph) {
|
||||||
// Case header
|
// Case header (prev -> case EXPR)
|
||||||
Node caseNode = new SwitchEntryStmt(n.getTokenRange().orElse(null), n.getLabel().orElse(null), new NodeList<>());
|
Node caseNode = new SwitchEntryStmt(n.getTokenRange().orElse(null), n.getLabel().orElse(null), new NodeList<>());
|
||||||
graph.connectTo(caseNode);
|
graph.connectTo(caseNode);
|
||||||
// Case body
|
// Case body (case EXPR --> body)
|
||||||
n.getStatements().accept(this, graph);
|
n.getStatements().accept(this, graph);
|
||||||
|
// body --> next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Switch statement, set up as a block. Generates an unlabeled break section. The statement begins with an expression,
|
||||||
|
* which is compared to each of the cases, entering through the first one that matches. Each case may end in a break
|
||||||
|
* or return statement, or not. When it doesn't the control falls through the next cases. If the switch doesn't have
|
||||||
|
* a default entry, the */
|
||||||
@Override
|
@Override
|
||||||
public void visit(SwitchStmt n, CFG graph) {
|
public void visit(SwitchStmt n, CFG graph) {
|
||||||
// Link previous statement to the switch's selector
|
// Link previous statement to the switch's selector
|
||||||
Node selectorNode = n.getSelector();
|
Node selectorNode = n.getSelector();
|
||||||
|
// Begin block and break section
|
||||||
graph.beginBlock(n, selectorNode);
|
graph.beginBlock(n, selectorNode);
|
||||||
graph.beginBreakSection();
|
graph.beginBreakSection();
|
||||||
|
// prev --> expr
|
||||||
graph.connectTo(selectorNode);
|
graph.connectTo(selectorNode);
|
||||||
// Analyze switch's cases
|
// expr --> each case (fallthrough by default, so case --> case too)
|
||||||
for (SwitchEntryStmt entry : n.getEntries()) {
|
for (SwitchEntryStmt entry : n.getEntries()) {
|
||||||
entry.accept(this, graph);
|
entry.accept(this, graph); // expr && prev case --> case --> next case
|
||||||
graph.appendNode(selectorNode);
|
graph.appendNode(selectorNode); // expr --> next case
|
||||||
}
|
}
|
||||||
// The next statement will be linked to:
|
// The next statement will be linked to:
|
||||||
// 1. All break statements that broke from the switch
|
// 1. All break statements that broke from the switch (done with break section)
|
||||||
// 2. If the switch doesn't have a default statement, the switch's selector
|
// 2. If the switch doesn't have a default statement, the switch's selector (already present)
|
||||||
// 3. If the last entry doesn't break, to the last statement
|
// 3. If the last entry doesn't break, to the last statement (present already)
|
||||||
|
// If the last case is a default case, remove the selector node from the list of nodes (see 2)
|
||||||
|
if (!n.getEntries().get(n.getEntries().size() - 1).getLabel().isPresent())
|
||||||
graph.removeNode(selectorNode);
|
graph.removeNode(selectorNode);
|
||||||
if (n.getEntries().get(n.getEntries().size() - 1).getLabel().isPresent())
|
// End block and break section
|
||||||
graph.appendNode(selectorNode);
|
|
||||||
graph.endBreakSection();
|
graph.endBreakSection();
|
||||||
graph.endBlock();
|
graph.endBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(BreakStmt n, CFG graph) {
|
public void visit(BreakStmt n, CFG graph) {
|
||||||
graph.connectBreak(n);
|
graph.connectBreak(n); // prev --> break --> end of loop
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(ContinueStmt n, CFG graph) {
|
public void visit(ContinueStmt n, CFG graph) {
|
||||||
graph.connectContinue(n);
|
graph.connectContinue(n); // prev --> continue --> loop condition
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(ExpressionStmt es, CFG graph) {
|
public void visit(ExpressionStmt es, CFG graph) {
|
||||||
graph.connectTo(es);
|
graph.connectTo(es); // prev --> es --> next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue