package org.jf.dexlib.Code.Analysis.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.Opcode;
import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
import org.jf.dexlib.Code.Analysis.graphs.CFG.Edge.Kind;
import org.jgrapht.EdgeFactory;
import org.jgrapht.graph.DirectedPseudograph;
/**
*
* @author Juergen Graf <juergen.graf@gmail.com>
*
*/
public class CFG extends DirectedPseudograph<CFG.Node, CFG.Edge> {
private static final long serialVersionUID = -8848165249946385503L;
public 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() {
StringBuffer sb = new StringBuffer("CFG of " + name + "\n");
for (Node n : vertexSet()) {
sb.append(n + " -> ");
for (Edge e : outgoingEdgesOf(n)) {
sb.append(e.getTarget() + " ");
}
sb.append("\n");
}
return sb.toString();
}
public String toString() {
return "CFG of " + name + "(" + vertexSet().size() + ", " + edgeSet().size() + ")" ;
}
public interface Edge {
public enum Kind {
/* control flow */ CF,
/* exception control flow */ CF_EX,
/* control dependence */ CD }
public Node getSource();
public Node getTarget();
public Kind getKind();
}
public interface Node {
public boolean isInstruction();
public boolean isEntry();
public boolean isExit();
public boolean isPHI();
public AnalyzedInstruction getInstruction();
public PHI getPHI();
}
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);
}
}
}
private static class CFGEdge implements Edge {
private final Node source;
private final Node target;
private final Kind kind;
private CFGEdge(final Node source, final Node target) {
this(source, target, Kind.CF);
}
private CFGEdge(final Node source, final Node target, final Kind kind) {
this.source = source;
this.target = target;
this.kind = kind;
}
public Node getSource() {
return source;
}
public Node getTarget() {
return target;
}
public Kind getKind() {
return kind;
}
public int hashCode() {
return source.hashCode() * (target.hashCode() >> 6);
}
public boolean equals(Object o) {
if (o instanceof CFGEdge) {
CFGEdge other = (CFGEdge) o;
return kind == other.kind && source.equals(other.source) && target.equals(other.target);
}
return false;
}
public String toString() {
return kind.name();
}
}
public static class PHI {
//TODO
}
private static abstract class AbstractNode implements Node {
public boolean isInstruction() {
return false;
}
public boolean isEntry() {
return false;
}
public boolean isExit() {
return false;
}
public boolean isPHI() {
return false;
}
public AnalyzedInstruction getInstruction() {
throw new IllegalStateException("Not an instruction node. Use isInstruction()");
}
public PHI getPHI() {
throw new IllegalStateException("Not a phi node. Use isPHI()");
}
public abstract String toString();
}
private static class EntryNode extends AbstractNode {
public boolean isEntry() {
return true;
}
@Override
public String toString() {
return "ENTRY";
}
}
private static class ExitNode extends AbstractNode {
public boolean isExit() {
return true;
}
@Override
public String toString() {
return "EXIT";
}
}
private static class InstructionNode extends AbstractNode {
private final AnalyzedInstruction instr;
public InstructionNode(final AnalyzedInstruction instr) {
this.instr = instr;
}
public AnalyzedInstruction getInstruction() {
return instr;
}
public boolean isInstruction() {
return true;
}
public int hashCode() {
return instr.hashCode() * 23;
}
public boolean equals(Object o) {
if (o instanceof InstructionNode) {
InstructionNode other = (InstructionNode) o;
return instr.equals(other.instr);
}
return false;
}
@Override
public String toString() {
final int index = instr.getInstructionIndex();
final Opcode op = instr.getInstruction().opcode;
return "[" + index + "] " + (op.setsRegister() ? "v" + instr.getDestinationRegister() + " = " : "")
+ op.name;
}
}
private static class PHINode extends AbstractNode {
private final PHI phi;
public PHINode(final PHI phi) {
this.phi = phi;
}
public boolean isPHI() {
return true;
}
public PHI getPHI() {
return phi;
}
public int hashCode() {
return phi.hashCode() * 7;
}
public boolean equals(Object o) {
if (o instanceof PHINode) {
PHINode other = (PHINode) o;
return phi.equals(other.phi);
}
return false;
}
@Override
public String toString() {
return phi.toString();
}
}
public static final class CFGEdgeFactory implements EdgeFactory<Node, Edge> {
private CFGEdgeFactory() {}
public CFGEdge createEdge(final Node from, final Node to) {
return new CFGEdge(from, to);
}
public CFGEdge createEdge(final Node from, final Node to, final Kind kind) {
return new CFGEdge(from, to, kind);
}
}
}