
org.checkerframework.dataflow.cfg.ControlFlowGraph Maven / Gradle / Ivy
package org.checkerframework.dataflow.cfg;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.Block.BlockType;
import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock;
import org.checkerframework.dataflow.cfg.block.SpecialBlock;
import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
/**
* A control flow graph (CFG for short) of a single method.
*
* @author Stefan Heule
*/
public class ControlFlowGraph {
/** The entry block of the control flow graph. */
protected final SpecialBlock entryBlock;
/** The regular exit block of the control flow graph. */
protected final SpecialBlock regularExitBlock;
/** The exceptional exit block of the control flow graph. */
protected final SpecialBlock exceptionalExitBlock;
/** The AST this CFG corresponds to. */
protected UnderlyingAST underlyingAST;
/**
* Maps from AST {@link Tree}s to {@link Node}s. Every Tree that produces a value will have at
* least one corresponding Node. Trees that undergo conversions, such as boxing or unboxing, can
* map to two distinct Nodes. The Node for the pre-conversion value is stored in treeLookup,
* while the Node for the post-conversion value is stored in convertedTreeLookup.
*/
protected IdentityHashMap treeLookup;
/** Map from AST {@link Tree}s to post-conversion {@link Node}s. */
protected IdentityHashMap convertedTreeLookup;
/** Map from AST {@link UnaryTree}s to corresponding {@link AssignmentNode}s. */
protected IdentityHashMap unaryAssignNodeLookup;
/**
* All return nodes (if any) encountered. Only includes return statements that actually return
* something
*/
protected final List returnNodes;
/** Map from AST {@link Tree}s to generated {@link Tree}s. */
protected final IdentityHashMap> generatedTreesLookupMap;
public ControlFlowGraph(
SpecialBlock entryBlock,
SpecialBlockImpl regularExitBlock,
SpecialBlockImpl exceptionalExitBlock,
UnderlyingAST underlyingAST,
IdentityHashMap treeLookup,
IdentityHashMap convertedTreeLookup,
IdentityHashMap unaryAssignNodeLookup,
List returnNodes,
IdentityHashMap> generatedTreesLookupMap) {
super();
this.entryBlock = entryBlock;
this.underlyingAST = underlyingAST;
this.treeLookup = treeLookup;
this.unaryAssignNodeLookup = unaryAssignNodeLookup;
this.convertedTreeLookup = convertedTreeLookup;
this.regularExitBlock = regularExitBlock;
this.exceptionalExitBlock = exceptionalExitBlock;
this.returnNodes = returnNodes;
this.generatedTreesLookupMap = generatedTreesLookupMap;
}
/** @return the {@link Node} to which the {@link Tree} {@code t} corresponds. */
public Node getNodeCorrespondingToTree(Tree t) {
if (convertedTreeLookup.containsKey(t)) {
return convertedTreeLookup.get(t);
} else {
return treeLookup.get(t);
}
}
/** @return the entry block of the control flow graph. */
public SpecialBlock getEntryBlock() {
return entryBlock;
}
public List getReturnNodes() {
return returnNodes;
}
public SpecialBlock getRegularExitBlock() {
return regularExitBlock;
}
public SpecialBlock getExceptionalExitBlock() {
return exceptionalExitBlock;
}
/** @return the AST this CFG corresponds to. */
public UnderlyingAST getUnderlyingAST() {
return underlyingAST;
}
/** @return the set of all basic block in this control flow graph */
public Set getAllBlocks() {
Set visited = new HashSet<>();
Queue worklist = new ArrayDeque<>();
Block cur = entryBlock;
visited.add(entryBlock);
// traverse the whole control flow graph
while (true) {
if (cur == null) {
break;
}
Queue succs = new ArrayDeque<>();
if (cur.getType() == BlockType.CONDITIONAL_BLOCK) {
ConditionalBlock ccur = ((ConditionalBlock) cur);
succs.add(ccur.getThenSuccessor());
succs.add(ccur.getElseSuccessor());
} else {
assert cur instanceof SingleSuccessorBlock;
Block b = ((SingleSuccessorBlock) cur).getSuccessor();
if (b != null) {
succs.add(b);
}
}
if (cur.getType() == BlockType.EXCEPTION_BLOCK) {
ExceptionBlock ecur = (ExceptionBlock) cur;
for (Set exceptionSuccSet : ecur.getExceptionalSuccessors().values()) {
succs.addAll(exceptionSuccSet);
}
}
for (Block b : succs) {
if (!visited.contains(b)) {
visited.add(b);
worklist.add(b);
}
}
cur = worklist.poll();
}
return visited;
}
/**
* @return the list of all basic block in this control flow graph in reversed depth-first
* postorder sequence.
* Blocks may appear more than once in the sequence.
*/
public List getDepthFirstOrderedBlocks() {
List dfsOrderResult = new ArrayList<>();
Set visited = new HashSet<>();
Deque worklist = new ArrayDeque<>();
worklist.add(entryBlock);
while (!worklist.isEmpty()) {
Block cur = worklist.getLast();
if (visited.contains(cur)) {
dfsOrderResult.add(cur);
worklist.removeLast();
} else {
visited.add(cur);
Deque successors = getSuccessors(cur);
successors.removeAll(visited);
worklist.addAll(successors);
}
}
Collections.reverse(dfsOrderResult);
return dfsOrderResult;
}
/**
* Get a list of all successor Blocks for cur
*
* @return a Deque of successor Blocks
*/
private Deque getSuccessors(Block cur) {
Deque succs = new ArrayDeque<>();
if (cur.getType() == BlockType.CONDITIONAL_BLOCK) {
ConditionalBlock ccur = ((ConditionalBlock) cur);
succs.add(ccur.getThenSuccessor());
succs.add(ccur.getElseSuccessor());
} else {
assert cur instanceof SingleSuccessorBlock;
Block b = ((SingleSuccessorBlock) cur).getSuccessor();
if (b != null) {
succs.add(b);
}
}
if (cur.getType() == BlockType.EXCEPTION_BLOCK) {
ExceptionBlock ecur = (ExceptionBlock) cur;
for (Set exceptionSuccSet : ecur.getExceptionalSuccessors().values()) {
succs.addAll(exceptionSuccSet);
}
}
return succs;
}
/** @return the copied tree-lookup map */
public IdentityHashMap getTreeLookup() {
return new IdentityHashMap<>(treeLookup);
}
/** @return the copied lookup-map of the assign node for unary operation */
public IdentityHashMap getUnaryAssignNodeLookup() {
return new IdentityHashMap<>(unaryAssignNodeLookup);
}
/** @return the copied map to lookup generated {@link Tree}s from {@link Tree} */
public IdentityHashMap> getGeneratedTreesLookup() {
return new IdentityHashMap<>(generatedTreesLookupMap);
}
/**
* Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in
* the CFG or null otherwise.
*/
public /*@Nullable*/ MethodTree getContainingMethod(Tree t) {
if (treeLookup.containsKey(t)) {
if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) {
UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST;
return cfgMethod.getMethod();
}
}
return null;
}
/**
* Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in
* the CFG or null otherwise.
*/
public /*@Nullable*/ ClassTree getContainingClass(Tree t) {
if (treeLookup.containsKey(t)) {
if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) {
UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST;
return cfgMethod.getClassTree();
}
}
return null;
}
}