/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com) * Copyright (C) 2010, Stefan Hepp (stefan@stefant.org) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common.code; import com.jopdesign.common.AppEventHandler; import com.jopdesign.common.AppInfo; import com.jopdesign.common.ImplementationFinder; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.graphutils.AdvancedDOTExporter; import com.jopdesign.common.graphutils.AdvancedDOTExporter.DOTLabeller; import com.jopdesign.common.graphutils.AdvancedDOTExporter.DOTNodeLabeller; import com.jopdesign.common.graphutils.DefaultFlowGraph; import com.jopdesign.common.graphutils.FlowGraph; import com.jopdesign.common.graphutils.LoopColoring; import com.jopdesign.common.graphutils.TopOrder; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.BadGraphException; import com.jopdesign.common.misc.HashedString; import com.jopdesign.common.misc.Iterators; import com.jopdesign.common.misc.MiscUtils; import com.jopdesign.common.misc.MiscUtils.F1; import com.jopdesign.common.type.MethodRef; import org.apache.bcel.Constants; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InvokeInstruction; import org.apache.log4j.Logger; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.SimpleDirectedGraph; import org.jgrapht.traverse.TopologicalOrderIterator; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import static com.jopdesign.common.code.BasicBlock.FlowInfo; import static com.jopdesign.common.code.BasicBlock.FlowTarget; /** * General purpose control flow graph, for use in WCET analysis. * <p/> * <p> * A flow graph is a directed graph with a dedicated entry and exit node. * Nodes include dedicated nodes (like entry, exit, split, join), basic block nodes * and invoke nodes. Edges carry information about the associated (branch) instruction. * The basic blocks associated with the CFG are stored seperately are referenced from * basic block nodes. * </p> * <p/> * <p> * This class supports * <ul> * <li/> loop detection * <li/> extracting annotations from the source code * <li/> resolving virtual invokations (possible, as all methods are known at compile time) * <li/> inserting split nodes for nodes with more than one successor * </ul></p> * * @author Benedikt Huber (benedikt.huber@gmail.com) * @author Stefan Hepp (stefan@stefant.org) */ public class ControlFlowGraph { private static final Logger logger = Logger.getLogger(LogConfig.LOG_CFG + ".ControlFlowGraph"); @SuppressWarnings({"UncheckedExceptionClass"}) public static class ControlFlowError extends Error { private static final long serialVersionUID = 1L; private ControlFlowGraph cfg; public ControlFlowError(String msg) { super("Error in Control Flow Graph: " + msg); } public ControlFlowError(String msg, ControlFlowGraph cfg) { this(msg); this.cfg = cfg; } public ControlFlowError(String message, Throwable cause) { super("Error in Control Flow Graph: "+message, cause); } public ControlFlowGraph getAffectedCFG() { return cfg; } } /*---------------------------------------------------------------------------* * CFG Node classes *---------------------------------------------------------------------------*/ /** * Visitor for flow graph nodes */ public interface CfgVisitor { /** * visit a special kind of node without basic blocks */ void visitVirtualNode(VirtualNode n); /** * visit a basic block node */ void visitBasicBlockNode(BasicBlockNode n); /** * visit an invoke node. InvokeNode's won't call visitBasicBlockNode. * @param n the visited node */ void visitInvokeNode(InvokeNode n); /** * visit a return node. Return ndoes won't call visitSpecialNode. * @param n the visited node */ void visitReturnNode(ReturnNode n); /** * visit a summary node * @param n the visited node */ void visitSummaryNode(SummaryNode n); } /** * Abstract base class for flow graph nodes */ public abstract class CFGNode implements Comparable<CFGNode> { private int id; protected String name; protected CFGNode(int id, String name) { this.id = id; this.name = name; } public int compareTo(CFGNode o) { return new Integer(this.hashCode()).compareTo(o.hashCode()); } public String toString() { return "#" + id + " " + name; } public String getName() { return name; } public abstract BasicBlock getBasicBlock(); /** * This is a helper function to access {@link BasicBlock#getLoopBound()}. * <p> * LoopBounds can only be attached to BasicBlocks, since they are stored with InstructionHandles. * </p> * @return the LoopBound of the basic block, or null if not set. */ public LoopBound getLoopBound() { BasicBlock bb = getBasicBlock(); if (bb != null) { return bb.getLoopBound(); } return null; } public int getId() { return id; } void setId(int newId) { this.id = newId; } public abstract void accept(CfgVisitor v); public ControlFlowGraph getControlFlowGraph() { return ControlFlowGraph.this; } protected void register() { } protected boolean isRegistered() { return false; } protected void dispose() { } } /** * Names for special purpose nodes (entry node, exit node, split and join nodes, return nodes) */ public enum VirtualNodeKind { ENTRY, EXIT, SPLIT, JOIN, RETURN } /** * Special purpose virtual nodes (without basic blocks attached) */ public class VirtualNode extends CFGNode { private VirtualNodeKind kind; public VirtualNodeKind getKind() { return kind; } private VirtualNode(VirtualNodeKind kind) { super(idGen++, kind.toString()); this.kind = kind; } @Override public BasicBlock getBasicBlock() { return null; } @Override public void accept(CfgVisitor v) { v.visitVirtualNode(this); } } /** * Dedicated flow graph nodes */ public class ReturnNode extends VirtualNode { private InvokeNode invokeNode; private ReturnNode(InvokeNode invNode) { super(VirtualNodeKind.RETURN); this.invokeNode = invNode; } @Override public void accept(CfgVisitor v) { v.visitReturnNode(this); } } /*---------------------------------------------------------------------------* * CFG BasicBlock node classes *---------------------------------------------------------------------------*/ /** * Flow graph nodes representing basic blocks */ public class BasicBlockNode extends CFGNode { protected final BasicBlock block; public BasicBlockNode(BasicBlock block) { super(idGen++, "basic(" + blocks.indexOf(block) + ")"); this.block = block; } @Override protected void register() { for (InstructionHandle ih : block.getInstructions()) { // TODO to support multiple registered CFGs per method, add a Map<ControlFlowGraph,BasicBlockNode> instead ih.addAttribute(KEY_CFGNODE, this); } } @Override protected boolean isRegistered() { if (block.getInstructions().isEmpty()) return false; return this.equals(getHandleNode(block.getFirstInstruction(),true)); } @Override protected void dispose() { for (InstructionHandle ih : block.getInstructions()) { ih.removeAttribute(KEY_CFGNODE); } } public BasicBlock getBasicBlock() { return block; } @Override public void accept(CfgVisitor v) { v.visitBasicBlockNode(this); } } /** Invoke nodes (Basic block with exactly one instruction) * transfer control to a different method. */ public class InvokeNode extends BasicBlockNode { private InvokeInstruction instr; private MethodRef referenced; private MethodInfo receiverImpl; private InvokeNode instantiatedFrom; private InvokeNode(BasicBlock block) { super(block); } public InvokeNode(BasicBlock block, InstructionHandle instr) { super(block); this.instr = (InvokeInstruction) instr.getInstruction(); // TODO keep the InvokeSite instead of instr+referenced .. InvokeSite invokeSite = methodInfo.getCode().getInvokeSite(instr); this.referenced = invokeSite.getInvokeeRef(); /* if virtual / interface, this method has to be resolved first */ if (invokeSite.isVirtual()) { receiverImpl = null; } else { receiverImpl = referenced.getMethodInfo(); } this.name = "invoke(" + this.referenced + ")"; } @Override public void accept(CfgVisitor v) { v.visitInvokeNode(this); } public InstructionHandle getInstructionHandle() { return block.getLastInstruction(); } /** * @return For non-virtual methods, get the implementation of the method */ public MethodInfo getImplementingMethod() { return receiverImpl; } /** * @return all possible implementations of the invoked method, using the context and the implementation finder * of the CFG. */ public Set<MethodInfo> getImplementingMethods() { return getImplementingMethods(context, implementationFinder); } public InvokeSite getInvokeSite() { return getMethodInfo().getCode().getInvokeSite(getInstructionHandle()); } /** * @param ctx the callstring of the invocation * @param finder the finder to use for finding implementations of this invokesite. * @return all possible implementations of the invoked method in * the given context */ public Set<MethodInfo> getImplementingMethods(CallString ctx, ImplementationFinder finder) { if (!isVirtual()) { return Collections.singleton(getImplementingMethod()); } else { return finder.findImplementations(ctx.push(getInvokeSite())); } } /** * For non-virtual methods, get the implementation of the method * @return the CFG of the implementing receiver or null if unknown. */ public ControlFlowGraph receiverFlowGraph() { if (isVirtual()) return null; // TODO if context and implementationFinder is set, use them instead return getImplementingMethod().getCode().getControlFlowGraph(false); } public ControlFlowGraph invokerFlowGraph() { return ControlFlowGraph.this; } public MethodRef getReferenced() { return referenced; } /** * @return true if the invocation denotes an interface, not an implementation */ public boolean isVirtual() { return receiverImpl == null; } /** * @return true if this node is a nonvirtual instance of a virtual node, false if this node is either virtual or statically resolved. */ public boolean isVirtualInstance() { return instantiatedFrom != null; } /** * If this is the implementation of a virtual/interface invoke instruction, * return the InvokeNode for the virtual invoke instruction. * TODO: This can be removed, if we ever remove {@link ControlFlowGraph#resolveVirtualInvokes()} * @return the node representing the virtual invoke or null if not instantiated from a virtual invoke. */ public InvokeNode getVirtualNode() { if (this.instantiatedFrom != null) return this.instantiatedFrom; else return this; } /** * Create an implementation node from this node * * @param impl the implementing method * @param virtual invoke node for the virtual method * @return a new nonvirtual invoke node */ @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) public InvokeNode createImplNode(MethodInfo impl, InvokeNode virtual) { InvokeNode n = new InvokeNode(block); n.name = "invoke(" + impl.getFQMethodName() + ")"; n.instr = this.instr; n.referenced = this.referenced; n.receiverImpl = impl; n.instantiatedFrom = virtual; return n; } @Override protected void register() { if (instantiatedFrom == null) { // only register the 'virtual' node, since we do not register multiple nodes per handle super.register(); } else { instantiatedFrom.register(); } } @Override protected boolean isRegistered() { if (instantiatedFrom == null) { return super.isRegistered(); } else { return instantiatedFrom.isRegistered(); } } } /** * SpecialInvokeNode represents target-specific invokes, e.g. bytecodes * implemented in Java */ public class SpecialInvokeNode extends InvokeNode { private InstructionHandle instr; private MethodInfo receiverImpl; public SpecialInvokeNode(BasicBlock block, MethodInfo javaImpl) { super(block); this.instr = block.getLastInstruction(); this.name = "jimplBC(" + javaImpl + ")"; this.receiverImpl = javaImpl; } @Override public void accept(CfgVisitor v) { v.visitInvokeNode(this); } @Override public InstructionHandle getInstructionHandle() { return instr; } @Override public MethodInfo getImplementingMethod() { return this.receiverImpl; } @Override public ControlFlowGraph receiverFlowGraph() { // TODO if context and implementationFinder is set, use them instead return receiverImpl.getCode().getControlFlowGraph(false); } /** * @return true if the invokation denotes an interface, not an implementation */ @Override public boolean isVirtual() { return receiverImpl == null; } @Override public InvokeNode createImplNode(MethodInfo impl, InvokeNode _) { return this; /* no dynamic dispatch */ } } public class SummaryNode extends CFGNode { private ControlFlowGraph subGraph; public SummaryNode(String name, ControlFlowGraph subGraph) { super(idGen++, name); this.subGraph = subGraph; } public ControlFlowGraph getSubGraph() { return subGraph; } @Override public void accept(CfgVisitor v) { v.visitSummaryNode(this); } @Override public BasicBlock getBasicBlock() { throw new UnsupportedOperationException("getBasicBlock() is meaningless for SummaryNode"); } } /*---------------------------------------------------------------------------* * CFG edge classes *---------------------------------------------------------------------------*/ /** * Type of flow graph edges */ public enum EdgeKind { ENTRY_EDGE, EXIT_EDGE, NEXT_EDGE, GOTO_EDGE, SELECT_EDGE, BRANCH_EDGE, JSR_EDGE, DISPATCH_EDGE, RETURN_EDGE, FLOW_EDGE, LOW_LEVEL_EDGE } /** * Edges of the flow graph */ public static class CFGEdge extends DefaultEdge { private static final long serialVersionUID = 1L; private EdgeKind kind; public CFGEdge(EdgeKind kind) { this.kind = kind; } public EdgeKind getKind() { return kind; } @SuppressWarnings({"CloneDoesntCallSuperClone"}) public CFGEdge clone() { return new CFGEdge(kind); } } /*---------------------------------------------------------------------------* * Fields *---------------------------------------------------------------------------*/ // FIXME: [wcet-frontend] Remove the ugly ih.getAttribute() hack for CFG Nodes private static final Object KEY_CFGNODE = new HashedString("ControlFlowGraph.CFGNode"); private int idGen = 0; /* linking to java */ private AppInfo appInfo; private MethodInfo methodInfo; /* basic blocks associated with the CFG */ private List<BasicBlock> blocks; /* graph */ private FlowGraph<CFGNode, CFGEdge> graph; private Set<CFGNode> deadNodes; private boolean hasThrowEdges; /* this we need for resolveVirtualInvokes() */ private CallString context; private ImplementationFinder implementationFinder; /* analysis stuff, needs to be reevaluated when graph changes */ private TopOrder<CFGNode, CFGEdge> topOrder = null; private LoopColoring<CFGNode, CFGEdge> loopColoring = null; private Boolean isLeafMethod = null; private boolean clean = false; private boolean virtualInvokesResolved = false; private boolean hasReturnNodes = false; private boolean hasContinueLoopNodes = false; private boolean hasSplitNodes = false; private boolean hasSummaryNodes = false; private static boolean ignoreATHROW = false; /*---------------------------------------------------------------------------* * CFG Creation *---------------------------------------------------------------------------*/ /** * Build a new flow graph for the given method * * @param method needs attached code (<code>method.getCode() != null</code>) * @throws BadGraphException if the bytecode results in an invalid flow graph */ public ControlFlowGraph(MethodInfo method) throws BadGraphException { this(method, CallString.EMPTY, method.getAppInfo()); } /** * Build a new flow graph for the given method * * @param method needs attached code (<code>method.getCode() != null</code>) * @param context the callstring leading to this method, used to resolve virtual invokes. Can be empty. * @param finder the implementation finder to find implementations of an invokesite (who would have guessed? :) ) * @throws BadGraphException if the bytecode results in an invalid flow graph */ public ControlFlowGraph(MethodInfo method, CallString context, ImplementationFinder finder) throws BadGraphException { this.methodInfo = method; // we set this in the constructor instead of passing it to resolveVirtualInvokes, so that it is harder to // use this incorrectly (ie. attaching a callgraph for a context to the context-less MethodCode) this.context = context; this.implementationFinder = finder; this.appInfo = method.getAppInfo(); createFlowGraph(method); check(); sendCreateGraphEvent(); } /** * @return true if there virtual dispatch nodes have been removed */ public boolean areVirtualInvokesResolved() { return virtualInvokesResolved; } /** * @param ignore true to disable ATHROW warnings during callgraph creation. */ public static void setIgnoreATHROW(boolean ignore) { ignoreATHROW = ignore; } private ControlFlowGraph(AppInfo appInfo) { this.appInfo = appInfo; CFGNode subEntry = new VirtualNode(VirtualNodeKind.ENTRY); CFGNode subExit = new VirtualNode(VirtualNodeKind.EXIT); this.graph = new DefaultFlowGraph<CFGNode, CFGEdge>(CFGEdge.class, subEntry, subExit); this.deadNodes = new LinkedHashSet<CFGNode>(); // TODO should we do this? currently only used to create an internal subgraph //sendCreateGraphEvent(); } /* worker: create the flow graph */ private void createFlowGraph(MethodInfo method) { logger.debug("creating flow graph for: " + method); Map<Integer, BasicBlockNode> nodeTable = new LinkedHashMap<Integer, BasicBlockNode>(); graph = new DefaultFlowGraph<CFGNode, CFGEdge>( CFGEdge.class, new VirtualNode(VirtualNodeKind.ENTRY), new VirtualNode(VirtualNodeKind.EXIT)); blocks = new ArrayList<BasicBlock>(); /* Create basic block vertices */ int i = 0; for (BasicBlock bb : BasicBlock.buildBasicBlocks(method.getCode())) { BasicBlockNode n = addBasicBlock(i++, bb); nodeTable.put(bb.getFirstInstruction().getPosition(), n); } /* entry edge */ graph.addEdge(graph.getEntry(), nodeTable.get(blocks.get(0).getFirstInstruction().getPosition()), createEntryEdge()); /* flow edges */ hasThrowEdges = false; for (BasicBlockNode bbNode : nodeTable.values()) { BasicBlock bb = bbNode.getBasicBlock(); FlowInfo bbf = bb.getExitFlowInfo(); if (bbf.isExit()) { // exit edge // do not connect exception edges if (bbNode.getBasicBlock().getLastInstruction().getInstruction().getOpcode() == Constants.ATHROW) { if (ignoreATHROW) { // Yep, you are looking at a hack.. We know that ATHROW is bad, no need to spam the log output. if (logger.isTraceEnabled()) { logger.trace("Found ATHROW edge in " + this.toString() + " - ignoring (results in dead and stuck code)"); } } else { logger.warn("Found ATHROW edge in " + this.toString() + " - ignoring (results in dead and stuck code)"); } hasThrowEdges = true; } else { graph.addEdge(bbNode, graph.getExit(), createExitEdge()); } } else if (!bbf.isAlwaysTaken()) { // next block edge BasicBlockNode bbSucc = nodeTable.get(bbNode.getBasicBlock().getLastInstruction().getNext().getPosition()); if (bbSucc == null) { internalError("Next Edge to non-existing next block from " + bbNode.getBasicBlock().getLastInstruction()); } graph.addEdge(bbNode, bbSucc, new CFGEdge(EdgeKind.NEXT_EDGE)); } for (FlowTarget target : bbf.getTargets()) { // jmps BasicBlockNode targetNode = nodeTable.get(target.getTarget().getPosition()); if (targetNode == null) internalError("No node for flow target: " + bbNode + " -> " + target); graph.addEdge(bbNode, targetNode, new CFGEdge(target.getEdgeKind())); } } // FIXME: Is this really sensible? I cannot remember we I added this edge // this.graph.addEdge(graph.getEntry(), graph.getExit(), createExitEdge()); } private void sendCreateGraphEvent() { for (AppEventHandler e : appInfo.getEventHandlers()) { e.onCreateControlFlowGraph(this); } } /*---------------------------------------------------------------------------* * CFG modify, compile, dispose *---------------------------------------------------------------------------*/ /** * Add a basic block to this graph. The instruction list of the block must not be empty. * * @param insertBefore insert the block at this position in the block list. * @param bb block to add * @return the new block node, either an InvokeNode, SpecialInvokeNode or BasicBlockNode, depending on the * contained instructions. */ public BasicBlockNode addBasicBlock(int insertBefore, BasicBlock bb) { BasicBlockNode n; Instruction lastInstr = bb.getLastInstruction().getInstruction(); InstructionHandle theInvoke = bb.getTheInvokeInstruction(); // This needs to be done before creating the Node, else blocks.indexOf returns -1 blocks.add(insertBefore, bb); if (theInvoke != null) { n = new InvokeNode(bb, theInvoke); } else if (appInfo.getProcessorModel().isImplementedInJava(methodInfo, lastInstr)) { MethodInfo javaImpl = appInfo.getProcessorModel().getJavaImplementation(appInfo, bb.getMethodInfo(),lastInstr); n = new SpecialInvokeNode(bb, javaImpl); } else { n = new BasicBlockNode(bb); } graph.addVertex(n); return n; } /** * Create a new basic block and add it to the graph as a node. * @param insertBefore the position to add the block to in the block list. * @return a new BasicBlockNode with an empty basic block. */ public BasicBlockNode createBasicBlock(int insertBefore) { BasicBlock bb = new BasicBlock(methodInfo.getCode()); blocks.add(insertBefore, bb); BasicBlockNode bbn = new BasicBlockNode(bb); graph.addVertex(bbn); return bbn; } /** * Compile the CFG back into an instruction list and store it in the associated * MethodCode. * <p> * We do not order the blocks here, this is a separate optimization * Also, we do not insert jump instructions if the fallthrough edge of a block does not * link to the next block in the block list. Instead an error is raised. * </p> * TODO create method to insert jump blocks where necessary */ public void compile() { InstructionList il = new InstructionList(); Object[] attributes = {KEY_CFGNODE}; Map<BasicBlock, BasicBlockNode> blockMap = buildBlockNodeMap(); for (int i = 0; i < blocks.size(); i++) { BasicBlock bb = blocks.get(i); BasicBlockNode bbn = blockMap.get(bb); if (bbn == null) { // Basic block without a node in the graph? Dead code due to removal of exception-errors or // infeasible edges.. continue; } // bb.appendTo(il, attributes); for (CFGEdge e : graph.outgoingEdgesOf(bbn)) { if (e.getKind() != EdgeKind.NEXT_EDGE) continue; BasicBlock target = graph.getEdgeTarget(e).getBasicBlock(); // TODO edge targets a SPLIT node, handle correctly .. if(target == null) { throw new ControlFlowError("Block "+i+" does fallthrough to a virtual node of type " + ((VirtualNode) graph.getEdgeTarget(e)).getKind() + " in " + methodInfo); } if (blocks.get(i+1) != target) { throw new ControlFlowError("Block "+i+" does not fall through to the next block in "+methodInfo); } } } // TODO compile is not yet fully implemented (retarget, exception-handlers, ..) // methodInfo.getCode().setInstructionList(il); } /** * Clean up all known references to the objects of this graph (i.e. InstructionHandle attributes,..) */ public void dispose() { for (CFGNode node : graph.vertexSet()) { node.dispose(); } } /*---------------------------------------------------------------------------* * Standard getter *---------------------------------------------------------------------------*/ public AppInfo getAppInfo() { return this.appInfo; } /** * @return the context for which this callgraph is valid. */ public CallString getContext() { return context; } public ImplementationFinder getImplementationFinder() { return implementationFinder; } /** * get the method this flow graph models * * @return the MethodInfo the flow graph was build from */ public MethodInfo getMethodInfo() { return this.methodInfo; } /** * @return the (dedicated) entry node of the flow graph */ public CFGNode getEntry() { return graph.getEntry(); } /** * @return the (dedicated) exit node of the flow graph */ public CFGNode getExit() { return graph.getExit(); } /** * @return Get the actual flow graph */ public FlowGraph<CFGNode, CFGEdge> getGraph() { return graph; } public Set<CFGNode> vertexSet() { return graph.vertexSet(); } public Set<CFGEdge> edgeSet() { return graph.edgeSet(); } public CFGEdge getEdge(BasicBlockNode source, BasicBlockNode target) { return graph.getEdge(source, target); } public CFGNode getEdgeSource(CFGEdge edge) { return graph.getEdgeSource(edge); } public CFGNode getEdgeTarget(CFGEdge edge) { return graph.getEdgeTarget(edge); } public Set<CFGEdge> outgoingEdgesOf(CFGNode node) { return graph.outgoingEdgesOf(node); } public Set<CFGEdge> incomingEdgesOf(CFGNode node) { return graph.incomingEdgesOf(node); } public List<BasicBlock> getBlocks() { return blocks; } public Iterable<CFGNode> getSuccessors(CFGNode currentBlock) { return Iterators.mapEntries(graph.outgoingEdgesOf(currentBlock), new F1<CFGEdge, CFGNode>() { @Override public CFGNode apply(CFGEdge e) { return graph.getEdgeTarget(e); } }); } public Map<BasicBlock, BasicBlockNode> buildBlockNodeMap() { Map<BasicBlock, BasicBlockNode> map = new LinkedHashMap<BasicBlock, BasicBlockNode>(blocks.size()); for (CFGNode node : graph.vertexSet()) { if (node instanceof BasicBlockNode) { BasicBlockNode bbn = (BasicBlockNode) node; map.put(bbn.getBasicBlock(), bbn); } } return map; } /** * Set the basic block nodes to the instruction handles of the method code. * <p> * You should make sure that {@link #dispose()} is called if this is used when this graph is not used anymore, * else the blocks and hence the whole graph will stay attached to the instructions. * </p> * For now only one CFG per method can be registered. */ public void registerHandleNodes() { for (CFGNode node : graph.vertexSet()) { node.register(); } } /** * Get the node for an instruction handle. This only works if {@link #registerHandleNodes()} has been called first. * * @param ih The instruction handle of a method which has this CFG associated with it * @return The basic block node associated with an instruction handle */ public BasicBlockNode getHandleNode(InstructionHandle ih) { return getHandleNode(ih, false); } public BasicBlockNode getHandleNode(InstructionHandle ih, boolean ignoreMissingNodes) { BasicBlockNode blockNode = (BasicBlockNode) ih.getAttribute(KEY_CFGNODE); if (blockNode == null && !ignoreMissingNodes) { String errMsg = "No basic block recorded for instruction " + ih.toString(true); logger.error(errMsg); return null; } return blockNode; } public boolean isLeafMethod() { return isLeafMethod; } /*---------------------------------------------------------------------------* * Resolve invokes, insert analysis nodes, makes the graph "dirty" *---------------------------------------------------------------------------*/ /** * @return returns false after additional nodes for analyses have been inserted using any of the * insert* methods or after resolving the invoke nodes. */ public boolean isClean() { return clean; } /** * resolve all virtual invoke nodes, and replace them by actual implementations. * <p> * This uses the context and the implementation finder passed to the constructor of this graph. * </p> * * @throws BadGraphException If the flow graph analysis (post replacement) fails */ @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) public void resolveVirtualInvokes() throws BadGraphException { // Hack to make this optional if (virtualInvokesResolved) return; virtualInvokesResolved = true; clean = false; if(hasReturnNodes) { throw new AssertionError("Virtuals need to be resolved before inserting return nodes (file a bug)"); } List<InvokeNode> virtualInvokes = new ArrayList<InvokeNode>(); /* find virtual invokes */ for (CFGNode n : this.graph.vertexSet()) { if (n instanceof InvokeNode) { InvokeNode in = (InvokeNode) n; if (in.isVirtual()) { virtualInvokes.add(in); } } } /* replace them */ for (InvokeNode inv : virtualInvokes) { // Magic: this uses the context and the implementation finder passed to the constructor of this graph. Set<MethodInfo> impls = inv.getImplementingMethods(); if (impls.size() == 0) internalError("No implementations for " + inv.referenced + " possibly invoked from " + inv.getBasicBlock().getMethodInfo()); if (impls.size() == 1) { InvokeNode implNode = inv.createImplNode(impls.iterator().next(), inv); graph.addVertex(implNode); for (CFGEdge inEdge : graph.incomingEdgesOf(inv)) { graph.addEdge(graph.getEdgeSource(inEdge), implNode, new CFGEdge(inEdge.kind)); } for (CFGEdge outEdge : graph.outgoingEdgesOf(inv)) { graph.addEdge(implNode, graph.getEdgeTarget(outEdge), new CFGEdge(outEdge.kind)); } } else { /* more than one impl, create split/join nodes */ CFGNode split = createSplitNode(); graph.addVertex(split); for (CFGEdge inEdge : graph.incomingEdgesOf(inv)) { graph.addEdge(graph.getEdgeSource(inEdge), split, new CFGEdge(inEdge.kind)); } CFGNode join = createJoinNode(); graph.addVertex(join); for (CFGEdge outEdge : graph.outgoingEdgesOf(inv)) { graph.addEdge(join, graph.getEdgeTarget(outEdge), new CFGEdge(outEdge.kind)); } for (MethodInfo impl : impls) { InvokeNode implNode = inv.createImplNode(impl, inv); graph.addVertex(implNode); graph.addEdge(split, implNode, new CFGEdge(EdgeKind.DISPATCH_EDGE)); graph.addEdge(implNode, join, new CFGEdge(EdgeKind.RETURN_EDGE)); } } graph.removeVertex(inv); } this.invalidate(); this.check(); this.analyseFlowGraph(); } /** * For all BasicBlock nodes with more than one outgoing edge, * add a split node, s.t. after this transformation all basic block nodes * have a single outgoing edge. * * @throws BadGraphException if the graph check after the transformation fails */ public void insertSplitNodes() throws BadGraphException { if (hasSplitNodes) return; hasSplitNodes = true; clean = false; List<CFGNode> trav = this.getTopOrder().getTopologicalTraversal(); for (CFGNode n : trav) { if (n instanceof BasicBlockNode && graph.outDegreeOf(n) > 1) { VirtualNode splitNode = this.createSplitNode(); graph.addVertex(splitNode); /* copy, as the iterators don't work when removing elements while iterating */ List<CFGEdge> outEdges = new ArrayList<CFGEdge>(graph.outgoingEdgesOf(n)); /* move edges */ for (CFGEdge e : outEdges) { graph.addEdge(splitNode, graph.getEdgeTarget(e), e.clone()); graph.removeEdge(e); } graph.addEdge(n, splitNode, new CFGEdge(EdgeKind.FLOW_EDGE)); } } this.invalidate(); this.check(); this.analyseFlowGraph(); } /** * Insert dedicates return nodes after invoke * * @throws BadGraphException if the graph check after the transformation fails */ public void insertReturnNodes() throws BadGraphException { if (hasReturnNodes) return; hasReturnNodes = true; clean = false; List<CFGNode> trav = this.getTopOrder().getTopologicalTraversal(); for (CFGNode n : trav) { if (n instanceof InvokeNode) { ReturnNode returnNode = new ReturnNode((InvokeNode) n); graph.addVertex(returnNode); /* copy, as the iterators don't work when removing elements while iterating */ List<CFGEdge> outEdges = new ArrayList<CFGEdge>(graph.outgoingEdgesOf(n)); /* move edges */ for (CFGEdge e : outEdges) { graph.addEdge(returnNode, graph.getEdgeTarget(e), e.clone()); graph.removeEdge(e); } graph.addEdge(n, returnNode, new CFGEdge(EdgeKind.RETURN_EDGE)); } } this.invalidate(); this.check(); this.analyseFlowGraph(); } /** * Insert continue-loop nodes, to simplify order for model checker. * If the head of loop has more than one incoming 'continue' edge, * an redirect the continue edges. * * @throws BadGraphException if the graph check after the transformation fails */ public void insertContinueLoopNodes() throws BadGraphException { if (hasContinueLoopNodes) return; hasContinueLoopNodes = true; clean = false; List<CFGNode> trav = this.getTopOrder().getTopologicalTraversal(); for (CFGNode n : trav) { if (getLoopColoring().getHeadOfLoops().contains(n)) { List<CFGEdge> backEdges = getLoopColoring().getBackEdgesTo(n); if (backEdges.size() > 1) { VirtualNode splitNode = this.createSplitNode(); graph.addVertex(splitNode); /* move edges */ for (CFGEdge e : backEdges) { CFGNode src = graph.getEdgeSource(e); graph.addEdge(src, splitNode, e.clone()); graph.removeEdge(e); } graph.addEdge(splitNode, n, new CFGEdge(EdgeKind.FLOW_EDGE)); } } } this.invalidate(); this.check(); this.analyseFlowGraph(); } /** * Prototype: Insert summary nodes to speed up UPPAAL search * Currently only for loops which do not contain invoke() and have a single exit * * @throws BadGraphException if the graph check after the transformation fails */ public void insertSummaryNodes() throws BadGraphException { if (hasSummaryNodes) return; hasSummaryNodes = true; clean = false; SimpleDirectedGraph<CFGNode, DefaultEdge> loopNestForest = this.getLoopColoring().getLoopNestDAG(); TopologicalOrderIterator<CFGNode, DefaultEdge> lnfIter = new TopologicalOrderIterator<CFGNode, DefaultEdge>(loopNestForest); List<CFGNode> summaryLoops = new ArrayList<CFGNode>(); Set<CFGNode> marked = new LinkedHashSet<CFGNode>(); while (lnfIter.hasNext()) { CFGNode hol = lnfIter.next(); if (marked.contains(hol)) continue; Collection<CFGEdge> exitEdges = getLoopColoring().getExitEdgesOf(hol); CFGNode theTarget = null; boolean failed = false; for (CFGEdge e : exitEdges) { CFGNode target = graph.getEdgeTarget(e); if (theTarget == null) theTarget = target; else if (theTarget != target) { failed = true; break; } } if (failed) continue; Set<CFGNode> loopNodes = getLoopColoring().getNodesOfLoop(hol); for (CFGNode n : loopNodes) { if (n instanceof InvokeNode) { failed = true; break; } } if (failed) continue; summaryLoops.add(hol); for (CFGNode n : loopNodes) { marked.add(n); } } for (CFGNode hol : summaryLoops) { insertSummaryNode(hol, getLoopColoring().getExitEdgesOf(hol), getLoopColoring().getNodesOfLoop(hol)); } this.invalidate(); this.check(); this.analyseFlowGraph(); } @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) private void insertSummaryNode(CFGNode hol, Collection<CFGEdge> exitEdges, Set<CFGNode> loopNodes) { /* summary subgraph */ /* create a new flow graph */ ControlFlowGraph subCFG = new ControlFlowGraph(appInfo); subCFG.methodInfo = methodInfo; subCFG.blocks = blocks; FlowGraph<CFGNode, CFGEdge> subGraph = subCFG.graph; for (CFGNode n : loopNodes) { subGraph.addVertex(n); } for (CFGNode n : loopNodes) { if (n == hol) { subGraph.addEdge(subGraph.getEntry(), n, subCFG.createEntryEdge()); for (CFGEdge e : getLoopColoring().getBackEdgesByHOL().get(hol)) { subGraph.addEdge(graph.getEdgeSource(e), hol, e.clone()); } } else { for (CFGEdge e : graph.incomingEdgesOf(n)) { subGraph.addEdge(graph.getEdgeSource(e), n, e.clone()); } } } for (CFGEdge e : exitEdges) { subGraph.addEdge(graph.getEdgeSource(e), subGraph.getExit(), e.clone()); } try { // TODO hmm, maybe make dump optional :) FileWriter writer; writer = new FileWriter(File.createTempFile("subcfg", ".dot")); new CFGExport(subCFG).exportDOT(writer, subGraph); writer.close(); } catch (IOException e1) { e1.printStackTrace(); } /* summary node */ SummaryNode summary = new SummaryNode("SUMMARY_" + hol.id, subCFG); Set<CFGEdge> inEdges = graph.incomingEdgesOf(hol); this.graph.addVertex(summary); for (CFGEdge e : inEdges) { CFGNode src = graph.getEdgeSource(e); graph.addEdge(src, summary, e.clone()); } for (CFGEdge e : exitEdges) { CFGNode target = graph.getEdgeTarget(e); graph.addEdge(summary, target, e.clone()); } this.graph.removeAllVertices(loopNodes); } /*---------------------------------------------------------------------------* * Various CFG analyses *---------------------------------------------------------------------------*/ /** * Create a new map of loopbounds for CFG nodes. The map is not cached, try to use * {@link BasicBlock#getLoopBound()} or {@link CFGNode#getLoopBound()} instead. * * @see CFGNode#getLoopBound() * @see BasicBlock#getLoopBound() * @return a new mapping of loopbounds to cfg nodes. Contains only nodes which have basic * blocks with loopbounds attached. */ public Map<CFGNode, LoopBound> buildLoopBoundMap() { Map<CFGNode, LoopBound> map = new LinkedHashMap<CFGNode, LoopBound>(); for (CFGNode node : graph.vertexSet()) { LoopBound lb = node.getLoopBound(); if (lb != null) { map.put(node,lb); } } return map; } /* Check that the graph is connectet, with entry and exit dominating resp. postdominating all nodes */ /** * Calculate (cached) the "loop coloring" of the flow graph. * * @return a loop coloring assigning each flowgraph node the set of loops it * participates in */ public LoopColoring<CFGNode, CFGEdge> getLoopColoring() { if (loopColoring == null) analyseFlowGraph(); return loopColoring; } public TopOrder<CFGNode, CFGEdge> getTopOrder() { if (topOrder == null) analyseFlowGraph(); return topOrder; } /** * Get the length of the implementation * * @return the length in bytes */ public int getNumberOfBytes() { int sum = 0; for (BasicBlock bb : this.blocks) { sum += bb.getNumberOfBytes(); } return sum; } public int getNumberOfWords() { return MiscUtils.bytesToWords(getNumberOfBytes()); } // /** // * get single entry single exit sets // * @return // */ // public Collection<Set<CFGNode>> getSESESets() { // DominanceFrontiers<CFGNode, CFGEdge> df = // new DominanceFrontiers<CFGNode, CFGEdge>(this.graph,graph.getEntry(),graph.getExit()); // return df.getSingleEntrySingleExitSets(); // } // // public Map<CFGNode, Set<CFGEdge>> getControlDependencies() { // DominanceFrontiers<CFGNode, CFGEdge> df = // new DominanceFrontiers<CFGNode, CFGEdge>(this.graph,graph.getEntry(),graph.getExit()); // return df.getControlDependencies(); // } /*---------------------------------------------------------------------------* * Export graph *---------------------------------------------------------------------------*/ public void exportDOT(File file) throws IOException { //noinspection unchecked exportDOT(file, (Map) null, null); } public void exportDOT(File file, DOTNodeLabeller<CFGNode> nl, DOTLabeller<CFGEdge> el) throws IOException { CFGExport export = new CFGExport(this, nl, el); FileWriter w = new FileWriter(file); export.exportDOT(w, graph); w.close(); } public void exportDOT(File file, Map<CFGNode, ?> nodeAnnotations, Map<CFGEdge, ?> edgeAnnotations) throws IOException { CFGExport export = new CFGExport(this, nodeAnnotations, edgeAnnotations); FileWriter w = new FileWriter(file); export.exportDOT(w, graph); w.close(); } @Override public String toString() { return super.toString() + this.methodInfo.getFQMethodName(); } /*---------------------------------------------------------------------------* * Private methods *---------------------------------------------------------------------------*/ private void check() throws BadGraphException { /* Remove unreachable and stuck code */ deadNodes = TopOrder.findDeadNodes(graph, getEntry()); Set<CFGNode> stuckNodes = TopOrder.findStuckNodes(graph, getExit()); if (( !deadNodes.isEmpty() || !stuckNodes.isEmpty()) && !hasThrowEdges) { if(!deadNodes.isEmpty()) { logger.error("Unexpected dead nodes in " +this.toString()+ " (no ATHROW edges): "+deadNodes); } if(!stuckNodes.isEmpty()) { logger.error("Unexpected stuck nodes in " +this.toString()+ " (no ATHROW edges): "+stuckNodes); } } Set<CFGNode> unreachableNodes = stuckNodes; unreachableNodes.addAll(deadNodes); if (!unreachableNodes.isEmpty()) { graph.removeAllVertices(unreachableNodes); this.invalidate(); } /* now checks should succeed */ try { TopOrder.checkIsFlowGraph(graph, getEntry(), getExit()); checkInvokeNodes(); } catch (BadGraphException ex) { debugDumpGraph(); throw ex; } } /* check invoke and return nodes */ private void checkInvokeNodes() throws BadGraphException { for(CFGNode n :this.getGraph().vertexSet()) { if(n instanceof InvokeNode) { if(hasReturnNodes) { if(this.getGraph().outDegreeOf(n) != 1) { throw new BadGraphException("CFG with return nodes but outdegree of invoke node != 1"); } CFGEdge re = getGraph().outgoingEdgesOf(n).iterator().next(); if(re.getKind() != EdgeKind.RETURN_EDGE) { throw new BadGraphException("CFG with return nodes but return edge has wrong kind"); } CFGNode r = getGraph().getEdgeTarget(re); if(getGraph().inDegreeOf(r) != 1) { throw new BadGraphException("CFG with return nodes but indegree of return node != 1"); } } } } } private void invalidate() { this.topOrder = null; this.loopColoring = null; this.isLeafMethod = null; } /* flow graph should have been checked before analyseFlowGraph is called */ @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) private void analyseFlowGraph() { try { topOrder = new TopOrder<CFGNode, CFGEdge>(this.graph, this.graph.getEntry()); idGen = 0; this.isLeafMethod = true; for (CFGNode vertex : topOrder.getTopologicalTraversal()) { if (vertex instanceof InvokeNode) this.isLeafMethod = false; vertex.id = idGen++; } for (CFGNode vertex : TopOrder.findDeadNodes(graph, this.graph.getEntry())) vertex.id = idGen++; loopColoring = new LoopColoring<CFGNode, CFGEdge>(this.graph, topOrder, graph.getExit()); } catch (BadGraphException e) { logger.error("Bad flow graph: " + getGraph().toString()); throw new ControlFlowError("[FATAL] Analyse flow graph failed ", e); } } private void internalError(String reason) { logger.error("[INTERNAL ERROR] " + reason); logger.error("CFG of " + this.getMethodInfo().getFQMethodName() + "\n"); logger.error(this.getMethodInfo().getMethod(false).getCode().toString(true)); throw new AssertionError(reason); } private void debugDumpGraph() { try { File tmpFile = File.createTempFile("cfg-dump", ".dot"); FileWriter fw = new FileWriter(tmpFile); new AdvancedDOTExporter<CFGNode, CFGEdge>(new AdvancedDOTExporter.DefaultNodeLabeller<CFGNode>() { @Override public String getLabel(CFGNode node) { String s = node.toString(); if (node.getBasicBlock() != null) s += "\n" + node.getBasicBlock().dump(); return s; } }, null).exportDOT(fw, graph); fw.close(); logger.error("[CFG DUMP] Dumped graph to '" + tmpFile + "'"); } catch (IOException e) { logger.error("[CFG DUMP] Dumping graph failed: " + e); } } private VirtualNode createSplitNode() { return new VirtualNode(VirtualNodeKind.SPLIT); } private VirtualNode createJoinNode() { return new VirtualNode(VirtualNodeKind.JOIN); } private CFGEdge createEntryEdge() { return new CFGEdge(EdgeKind.ENTRY_EDGE); } private CFGEdge createExitEdge() { return new CFGEdge(EdgeKind.EXIT_EDGE); } private CFGEdge createNextEdge() { return new CFGEdge(EdgeKind.NEXT_EDGE); } }