package org.jf.dexlib.Code.Analysis.ssa.graphs;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
import org.jf.dexlib.Code.Analysis.graphs.GraphWalker;
import org.jf.dexlib.Code.Analysis.ssa.graphs.Edge.Kind;
import org.jgrapht.graph.DirectedPseudograph;
/**
*
* @author Juergen Graf <juergen.graf@gmail.com>
*
*/
public class CFG extends DirectedPseudograph<Node, Edge> {
private static final long serialVersionUID = -8848165249946385503L;
static final CFGEdgeFactory EDGE_FACTORY = new CFGEdgeFactory();
public static CFG build(final List<AnalyzedInstruction> instructions, final String name) {
return build(instructions, name, false);
}
public static CFG build(final List<AnalyzedInstruction> instructions, final String name, final boolean includeExc) {
CFG cfg = new CFG(instructions, name + (includeExc ? " with uncatched exceptions" : ""));
cfg.build(includeExc);
return cfg.stripUnreachableNodes();
}
private final List<AnalyzedInstruction> instructions;
private final EntryNode entry;
private final ExitNode exit;
private final String name;
private CFG(final List<AnalyzedInstruction> instructions, final String name) {
this(instructions, name, new EntryNode(), new ExitNode());
}
private CFG(final List<AnalyzedInstruction> instructions, final String name, EntryNode entry, ExitNode exit) {
super(EDGE_FACTORY);
this.instructions = instructions;
this.name = name;
this.entry = entry;
this.exit = exit;
addVertex(entry);
addVertex(exit);
addEdge(entry, exit);
}
private CFG stripUnreachableNodes() {
final Set<Node> reachable = new HashSet<Node>();
GraphWalker<Node, Edge> walk = new GraphWalker<Node, Edge>(this) {
@Override
public void discover(Node node) {
reachable.add(node);
}
@Override
public void finish(Node node) {
}
};
walk.traverseDFS(entry);
if (reachable.size() == vertexSet().size()) {
// shortcut for cfgs that do not have to be altered.
return this;
}
CFG stripped = new CFG(instructions, name + " - " + (vertexSet().size() - reachable.size())
+ " unreachable nodes", entry, exit);
for (Node node : reachable) {
stripped.addVertex(node);
}
for (Node node : reachable) {
for (Edge edge : outgoingEdgesOf(node)) {
if (reachable.contains(edge.getTarget())) {
stripped.addEdge(node, edge.getTarget(), edge);
}
}
}
return stripped;
}
public String getName() {
return name;
}
public Node getEntry() {
return entry;
}
public Node getExit() {
return exit;
}
public String toExtendedString() {
StringBuilder sb = new StringBuilder("CFG of " + name + "\n");
for (Node n : vertexSet()) {
sb.append(n).append(" -> ");
for (Edge e : outgoingEdgesOf(n)) {
sb.append(e.getTarget()).append(" ");
}
sb.append("\n");
}
return sb.toString();
}
@Override
public String toString() {
return "CFG of " + name + "(" + vertexSet().size() + ", " + edgeSet().size() + ")";
}
public Edge addEdge(Node from, Node to, Kind kind) {
CFGEdge edge = new CFGEdge(from, to, kind);
if (addEdge(from, to, edge)) {
return edge;
} else {
return null;
}
}
private void build(final boolean includeUncatchedExceptions) {
final Map<AnalyzedInstruction, InstructionNode> tmpMap = new HashMap<AnalyzedInstruction, InstructionNode>();
boolean isFirst = true;
for (final AnalyzedInstruction instr : instructions) {
InstructionNode node = new InstructionNode(instr);
addVertex(node);
tmpMap.put(instr, node);
if (isFirst) {
addEdge(entry, node);
isFirst = false;
}
}
for (final AnalyzedInstruction instr : instructions) {
final InstructionNode node = tmpMap.get(instr);
final Iterator<AnalyzedInstruction> successors = instr.getSuccessors().iterator();
if (!successors.hasNext()) {
addEdge(node, exit);
} else {
// successors contains both. exception flow and normal flow
// there may be duplicates, iff the exception flow and the normal
// flow of an instruction result in the same successor
// e.g. empty catch block
final Set<CFGEdge> duplicates = new HashSet<CFGEdge>();
while (successors.hasNext()) {
final AnalyzedInstruction succ = successors.next();
final InstructionNode succNode = tmpMap.get(succ);
if (!containsEdge(node, succNode)) {
addEdge(node, succNode);
} else {
// if an edge is contained a second time in this list, the first
// one is normal and the second one exception flow
duplicates.add(new CFGEdge(node, succNode, Kind.CF_EX));
}
}
for (final AnalyzedInstruction excSucc : instr.getExceptionSuccessors()) {
final InstructionNode succNode = tmpMap.get(excSucc);
final CFGEdge dupEdge = new CFGEdge(node, succNode, Kind.CF_EX);
if (duplicates.contains(dupEdge)) {
// add a new edge if the edge was also duplicated in the successors
addEdge(node, succNode, dupEdge);
} else {
// change the existing edge to an exception edge otherwise
final CFGEdge edge = (CFGEdge) getEdge(node, succNode);
assert edge.kind == Kind.CF;
removeEdge(edge);
addEdge(edge.source, edge.target, Kind.CF_EX);
}
}
}
// we conservatively approximate exception control flow by adding
// control flow to the exit node for each instruction that may throw
// an exception.
if (includeUncatchedExceptions && instr.getOriginalInstruction().opcode.canThrow()) {
addEdge(node, exit, Kind.CF_EX);
}
}
}
}