/* * 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.AppInfo; import com.jopdesign.common.ClassInfo; import com.jopdesign.common.ImplementationFinder; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.config.Config; import com.jopdesign.common.config.Config.BadConfigurationError; import com.jopdesign.common.config.Config.BadConfigurationException; import com.jopdesign.common.config.Option; import com.jopdesign.common.config.StringOption; import com.jopdesign.common.graphutils.AdvancedDOTExporter; import com.jopdesign.common.graphutils.BackEdgeFinder; import com.jopdesign.common.graphutils.DirectedCycleDetector; import com.jopdesign.common.graphutils.GraphUtils; import com.jopdesign.common.graphutils.InvokeDot; import com.jopdesign.common.graphutils.Pair; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.AppInfoException; import com.jopdesign.common.misc.MethodNotFoundException; import com.jopdesign.common.misc.MiscUtils; import com.jopdesign.common.misc.Ternary; import com.jopdesign.common.type.MethodRef; import org.apache.log4j.Logger; import org.jgrapht.DirectedGraph; import org.jgrapht.EdgeFactory; import org.jgrapht.event.GraphEdgeChangeEvent; import org.jgrapht.event.GraphListener; import org.jgrapht.event.GraphVertexChangeEvent; import org.jgrapht.event.TraversalListenerAdapter; import org.jgrapht.event.VertexTraversalEvent; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DirectedMaskSubgraph; import org.jgrapht.graph.EdgeReversedGraph; import org.jgrapht.graph.ListenableDirectedGraph; import org.jgrapht.graph.MaskFunctor; import org.jgrapht.graph.SimpleDirectedGraph; import org.jgrapht.graph.UnmodifiableDirectedGraph; import org.jgrapht.traverse.DepthFirstIterator; import org.jgrapht.traverse.TopologicalOrderIterator; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; /** * <p>Java call graph, whose nodes represent control flow graphs and * dynamic dispatches.</p> * <p>If some instruction in the flow graph represented by {@code MethodImplNode m1} * possibly invokes a {@code MethodImplNode m2},there is an edge from {@code m1} * to {@code m2}.</p> * * <p>Note that this callgraph only contains MethodInfos, not MethodRefs, so invocations of unknown methods * are not represented in this graph. </p> * * <p> * This callgraph implementation does not require all nodes to have the same callstring length. * However, to simplify some algorithms some methods which modify the graph may assume that * the callstring length never decreases along any path in the callgraph.. for now. * We may later remove this restriction to compress the graph, by merging nodes with different contexts into * a single node when the invoked methods are identical for each invokesite. If you want to find all references * of an invokesite in the callstrings of the nodes of the graph, you need to go down from the invoker as deep as * the maximum callstring length used to create the graph. * </p> * <p> * In any case we require that the set of children for every node does not contain matching execution contexts. * Two execution contexts match if they refer to the same method and one of their callstrings is a suffix of the other * (note that we do not need to handle the case if the callstrings are equal, since in this case the contexts are equal * and the callgraph does not contain duplicate contexts). This implies that if a node has a context with an empty * callstring as child it cannot have another context with a nonempty callstring for the same method as child. * </p> * * @see CallgraphBuilder#getInvokedMethods(ExecutionContext) * * @author Benedikt Huber (benedikt.huber@gmail.com) * @author Stefan Hepp (stefan@stefant.org) */ public class CallGraph implements ImplementationFinder { public static final Logger logger = Logger.getLogger(LogConfig.LOG_CODE + ".CallGraph"); public enum DUMPTYPE { off, full, merged, both } // This option should always be added to Config.getDebugGroup() private static final StringOption CALLGRAPH_DIR = new StringOption("cgdir", "Directory to put the callgraph files into", "${outdir}/callgraph"); /** * Needs to be added to the debug optiongroup if the dumpCallgraph method is used. */ private static final Option[] dumpOptions = { CALLGRAPH_DIR }; public static void registerOptions(Config config) { config.getDebugGroup().addOptions(dumpOptions); InvokeDot.registerOptions(config); } /** * Interface for a callgraph construction. */ public interface CallgraphBuilder { /** * Get a set of all execution contexts possibly invoked by a given context. * To be consistent with {@link AppInfo#findImplementations(InvokeSite)} and to detect * incomplete classes, this must return an empty set if only some of the possible implementations * are found (e.g. superclasses are missing). * <p> * To simplify some implementations, the callstrings returned by this method should never be * shorter than the callstring of the argument. * </p> * * @param context the calling context * @return a set of all possible invoked implementations or an empty set if unknown */ Set<ExecutionContext> getInvokedMethods(ExecutionContext context); } /*---------------------------------------------------------------------------* * Graph node and edge classes *---------------------------------------------------------------------------*/ /** * A node representing a methodInfo, and stores references to all * execution contexts of this method in the callgraph. */ public static class MethodNode implements MethodContainer { private final MethodInfo methodInfo; private final Set<ExecutionContext> instances; public MethodNode(MethodInfo methodInfo) { this.methodInfo = methodInfo; instances = new LinkedHashSet<ExecutionContext>(); } public MethodInfo getMethodInfo() { return methodInfo; } public Set<ExecutionContext> getInstances() { return Collections.unmodifiableSet(instances); } @Override public String toString() { String txt = (instances.size() == 1 ? "1 instance" : instances.size() + " instances"); return methodInfo.toString() + " (" + txt + ")"; } protected void addInstance(ExecutionContext context) { instances.add(context); } protected void removeInstance(ExecutionContext context) { instances.remove(context); } } /** * An edge representing a possible invokation of a method B by a method A, and * stores all invoke sites of A which might call B. */ public static class InvokeEdge { private final Set<InvokeSite> invokeSites; public InvokeEdge() { invokeSites = new LinkedHashSet<InvokeSite>(); } public Set<InvokeSite> getInvokeSites() { return invokeSites; } @Override public String toString() { return (invokeSites.size() == 1) ? "1 invokesite" : invokeSites.size() + " invokesites"; } protected void addInvokeSite(InvokeSite site) { invokeSites.add(site); } protected void removeInvokeSite(InvokeSite invokeSite) { invokeSites.remove(invokeSite); } } public static class ContextEdge { private final ExecutionContext source; private final ExecutionContext target; private final int hash; private ContextEdge(ExecutionContext source, ExecutionContext target) { this.source = source; this.target = target; // this result is used *very* often .. this.hash = source.hashCode() * 31 + target.hashCode(); } public ExecutionContext getSource() { return source; } public ExecutionContext getTarget() { return target; } @Override public int hashCode() { return hash; } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null) return false; if (!(obj instanceof ContextEdge)) return false; ContextEdge other = (ContextEdge) obj; return source.equals(other.getSource()) && target.equals(other.getTarget()); } } /*---------------------------------------------------------------------------* * Internal classes *---------------------------------------------------------------------------*/ /** * This listener is attached to the (sub-)callgraph, and updates the methodNodes and mergedGraph * structures. */ private class GraphUpdateListener implements GraphListener<ExecutionContext, ContextEdge> { @Override public void edgeAdded(GraphEdgeChangeEvent<ExecutionContext, ContextEdge> e) { if (mergedCallGraph != null) { addMergedGraphEdge(e.getEdge()); } // graph may not be acyclic anymore, need to check again acyclic = Ternary.UNKNOWN; } @Override public void edgeRemoved(GraphEdgeChangeEvent<ExecutionContext, ContextEdge> e) { if (mergedCallGraph != null) { removeMergedGraphEdge(e.getEdge()); } } @Override public void vertexAdded(GraphVertexChangeEvent<ExecutionContext> e) { // we always call this because we need to update mergedNodes onAddExecutionContext(e.getVertex()); } @Override public void vertexRemoved(GraphVertexChangeEvent<ExecutionContext> e) { // we always call this because we need to update mergedNodes onRemoveExecutionContext(e.getVertex()); } } /** * This graph is attached to the main full callgraph for every subgraph, and propagates * changes to the main graph to the given subgraph. */ @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) private class SubgraphUpdateListener implements GraphListener<ExecutionContext, ContextEdge> { private final CallGraph subgraph; private SubgraphUpdateListener(CallGraph subgraph) { this.subgraph = subgraph; } @Override public void edgeAdded(GraphEdgeChangeEvent<ExecutionContext, ContextEdge> e) { // first we check if the target is already in the subgraph // if no new nodes required, does not change connectivity, simply add edge ExecutionContext target = e.getEdge().getTarget(); if (!subgraph.callGraph.containsVertex(target)) { // target must be added first, so we need to clone everything which is reachable from the target subgraph.cloneReachableGraph(Collections.singleton(target)); } subgraph.callGraph.addEdge(e.getEdge().getSource(), target); } @Override public void edgeRemoved(GraphEdgeChangeEvent<ExecutionContext, ContextEdge> e) { subgraph.removeEdge(e.getEdge(), true); } @Override public void vertexAdded(GraphVertexChangeEvent<ExecutionContext> e) { // No need to do anything yet.. since the vertex was just added, there are no edges to it, // so it is not reachable } @Override public void vertexRemoved(GraphVertexChangeEvent<ExecutionContext> e) { // checks if subgraph contains vertex itself, edges to the node are removed automatically subgraph.callGraph.removeVertex(e.getVertex()); } } private class ContextFilter implements MaskFunctor<ExecutionContext,ContextEdge> { private boolean skipIsolated; private boolean skipNoim; private ContextFilter(boolean skipIsolated, boolean skipNoim) { this.skipIsolated = skipIsolated; this.skipNoim = skipNoim; } @Override public boolean isEdgeMasked(ContextEdge edge) { // Edges from masked nodes are masked automatically return false; } @Override public boolean isVertexMasked(ExecutionContext vertex) { if (skipIsolated && callGraph.inDegreeOf(vertex) == 0 && callGraph.outDegreeOf(vertex) == 0) { return true; } if (skipNoim && callGraph.inDegreeOf(vertex) == 0 && callGraph.outDegreeOf(vertex) == 1) { ExecutionContext target = callGraph.outgoingEdgesOf(vertex).iterator().next().getTarget(); if ("com.jopdesign.sys.JVMHelp".equals(target.getMethodInfo().getClassName()) && "noim".equals(target.getMethodInfo().getShortName())) { return true; } } return false; } } private class MethodFilter implements MaskFunctor<MethodContainer,Object> { private final DirectedGraph<MethodContainer, Object> graph; private boolean skipIsolated; private boolean skipNoim; private MethodFilter(DirectedGraph<MethodContainer,Object> graph, boolean skipIsolated, boolean skipNoim) { this.graph = graph; this.skipIsolated = skipIsolated; this.skipNoim = skipNoim; } @Override public boolean isEdgeMasked(Object edge) { // Edges from masked nodes are masked automatically return false; } @Override public boolean isVertexMasked(MethodContainer vertex) { if (skipIsolated && graph.inDegreeOf(vertex) == 0 && graph.outDegreeOf(vertex) == 0) { return true; } if (skipNoim && graph.inDegreeOf(vertex) == 0 && graph.outDegreeOf(vertex) == 1) { Object edge = graph.outgoingEdgesOf(vertex).iterator().next(); MethodContainer target = graph.getEdgeTarget(edge); // TODO make this check less .. hardcoded if ("com.jopdesign.sys.JVMHelp".equals(target.getMethodInfo().getClassName()) && "noim".equals(target.getMethodInfo().getShortName())) { return true; } } return false; } } // // Fields // ~~~~~~ private final Set<ExecutionContext> rootNodes; private final CallgraphBuilder builder; private ListenableDirectedGraph<ExecutionContext, ContextEdge> callGraph; private DirectedGraph<MethodNode, InvokeEdge> mergedCallGraph; private Map<CallGraph, SubgraphUpdateListener> subgraphs; private CallGraph parent; private Set<ClassInfo> classInfos; private Map<MethodInfo,MethodNode> methodNodes; private Ternary acyclic = Ternary.UNKNOWN; // // Caching Fields // ~~~~~~~~~~~~~~ private Map<ExecutionContext, Integer> maxDistanceToRoot = null; private Map<ExecutionContext, ExecutionContext> maxCallstackDAG = null; private Map<ExecutionContext, Integer> subgraphHeight = null; private ExecutionContext maxCallStackLeaf = null; private Map<MethodInfo,Boolean> leafNodeCache; /*---------------------------------------------------------------------------* * Constructor methods *---------------------------------------------------------------------------*/ /** * Build a callgraph rooted at the given method * * @see AppInfo#getMethodInfo(String, String) * @param appInfo The application (with classes loaded) * @param className The class where the root method of the callgraph is located * @param methodSig The root method of the call graph. Either a plain method name * (e.g. "measure"), if unique, or a method with signature (e.g. "measure()Z") * @param builder the builder class to use to build this graph * @return a freshly constructed callgraph. * @throws MethodNotFoundException if the referenced main method was not found */ public static CallGraph buildCallGraph(AppInfo appInfo, String className, String methodSig, CallgraphBuilder builder) throws MethodNotFoundException { MethodInfo rootMethod = appInfo.getMethodInfo(className,methodSig); return buildCallGraph(rootMethod, builder); } /** * Build a callgraph rooted at the given method * * @see AppInfo#getMethodInfo(String, String) * @param rootMethod The root method of the callgraph * @param builder the builder class to use to build this graph * @return a freshly constructed callgraph. */ public static CallGraph buildCallGraph(MethodInfo rootMethod, CallgraphBuilder builder) { return buildCallGraph(Collections.singleton(rootMethod), builder); } /** * Build a callgraph rooted at the given set of methods * * @param rootMethods The root methods of the callgraph * @param builder the builder class to use to build this graph * @return a freshly constructed callgraph. */ public static CallGraph buildCallGraph(Collection<MethodInfo> rootMethods, CallgraphBuilder builder) { List<ExecutionContext> roots = new ArrayList<ExecutionContext>(rootMethods.size()); for (MethodInfo method : rootMethods) { roots.add(new ExecutionContext(method)); } CallGraph cg = new CallGraph(roots, builder); cg.build(); return cg; } /** * Build a callgraph with all root methods of AppInfo. * This also adds all static initializers of all classes and all Runnable.run() methods * to the callgraph roots. * * @see AppInfo#getRootMethods() * @param appInfo the AppInfo to use * @param builder the builder class to use to build this graph * @return a freshly constructed callgraph. */ public static CallGraph buildCallGraph(AppInfo appInfo, CallgraphBuilder builder) { Collection<MethodInfo> rootMethods = appInfo.getRootMethods(); Set<ExecutionContext> roots = new LinkedHashSet<ExecutionContext>(rootMethods.size()); for (MethodInfo m : rootMethods) { roots.add(new ExecutionContext(m)); } for (MethodInfo m : appInfo.getClinitMethods()) { roots.add(new ExecutionContext(m)); } for (MethodInfo m : appInfo.getThreadRootMethods(false)) { roots.add(new ExecutionContext(m)); } CallGraph cg = new CallGraph(roots, builder); cg.build(); return cg; } /*---------------------------------------------------------------------------* * Init and build callgraph (private) *---------------------------------------------------------------------------*/ /** * Initialize a CallGraph object. * @param rootMethods The root methods of the callgraph (not abstract). * @param builder the builder class to use to build this graph */ protected CallGraph(Collection<ExecutionContext> rootMethods, CallgraphBuilder builder) { this.rootNodes = new LinkedHashSet<ExecutionContext>(rootMethods); this.builder = builder; this.subgraphs = new LinkedHashMap<CallGraph,SubgraphUpdateListener>(1); // We need a custom ContextEdge here to keep the references to the vertices for the removeEdge listener this.callGraph = new ListenableDirectedGraph<ExecutionContext,ContextEdge>( new DefaultDirectedGraph<ExecutionContext,ContextEdge>( new EdgeFactory<ExecutionContext,ContextEdge>() { @Override public ContextEdge createEdge(ExecutionContext sourceVertex, ExecutionContext targetVertex) { return new ContextEdge(sourceVertex,targetVertex); } }) ); this.methodNodes = new LinkedHashMap<MethodInfo, MethodNode>(); this.classInfos = new LinkedHashSet<ClassInfo>(); } protected CallGraph(CallGraph parent, Collection<ExecutionContext> rootNodes, CallgraphBuilder builder) { this(rootNodes, builder); this.parent = parent; } /** * Build and initialize everything, perform checks */ private void build() { logger.debug("Starting construction of callgraph with roots " + MiscUtils.toString(rootNodes, 3)); this.buildGraph(); logger.debug("Finished constructing callgraph"); invalidate(); } public void checkAcyclicity() throws AppInfoException { /* Check the callgraph is cycle free */ for (ExecutionContext rootNode : rootNodes) { logger.debug("Checking for loops in callgraph starting at "+rootNode); Pair<List<ExecutionContext>,List<ExecutionContext>> cycle = DirectedCycleDetector.findCycle(callGraph,rootNode); if(cycle != null) { // maybe make dumping the whole graph optional :) /* for(DefaultEdge e : callGraph.edgeSet()) { ExecutionContext src = callGraph.getEdgeSource(e); ExecutionContext target = callGraph.getEdgeTarget(e); System.err.println(""+src+" --> "+target); } */ acyclic = Ternary.FALSE; throw new AppInfoException(cyclicCallGraphMsg(cycle)); } } acyclic = Ternary.TRUE; logger.debug("No loops found in callgraph"); } public void setAcyclicity(boolean acyclic) { this.acyclic = Ternary.valueOf(acyclic); } public Ternary getAcyclicity() { return acyclic; } public boolean isAcyclic() { if (acyclic == Ternary.UNKNOWN) { try { checkAcyclicity(); } catch (AppInfoException e) { logger.debug("Found loop in callgraph: "+e.getMessage()); } } return acyclic == Ternary.TRUE; } /** * Build the callgraph. * * <p>NEW: now we also use callstrings to get a more precise call graph model</p> */ private void buildGraph() { // Note that updating methodNodes and classInfos is now done by the GraphUpdateListener callGraph.addGraphListener(new GraphUpdateListener()); if (parent == null) { /* Initialize DFS data structures and lookup maps */ Stack<ExecutionContext> todo = new Stack<ExecutionContext>(); for (ExecutionContext rootNode : rootNodes) { callGraph.addVertex(rootNode); todo.push(rootNode); } while(! todo.empty()) { ExecutionContext current = todo.pop(); if (logger.isTraceEnabled()) { logger.trace("Processing " +current); } Set<ExecutionContext> invoked = builder.getInvokedMethods(current); for (ExecutionContext cgn : invoked) { if (!callGraph.containsVertex(cgn)) { callGraph.addVertex(cgn); todo.push(cgn); } if (logger.isTraceEnabled()) { logger.trace(" - found invoke of " +cgn); } callGraph.addEdge(current, cgn); } } /* end while */ } else { // build the the graph using all reachable nodes from the parent graph cloneReachableGraph(rootNodes); } } /** * Copy all reachable nodes (and edges) from the parent graph, starting at the root * @param roots where to start cloning */ @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) private void cloneReachableGraph(Collection<ExecutionContext> roots) { List<ExecutionContext> newNodes = new LinkedList<ExecutionContext>(); for (ExecutionContext root : roots) { DepthFirstIterator<ExecutionContext, ContextEdge> dfs = new DepthFirstIterator<ExecutionContext, ContextEdge>(parent.callGraph, root); while (dfs.hasNext()) { ExecutionContext ec = dfs.next(); if (callGraph.addVertex(ec)) { // TODO if a node is already in the subgraph (ie. reachable), skip its children // (must all be here already or if it is a circle they will be added later) newNodes.add(ec); } } } // add all edges between new nodes and edges to existing nodes. for (ExecutionContext ec : newNodes) { // all outgoing edges of new nodes are new (we do not care about ingoing edges) for (ContextEdge e : parent.callGraph.outgoingEdgesOf(ec)) { // source and target both must exist now callGraph.addEdge(e.getSource(), e.getTarget()); } } } /*---------------------------------------------------------------------------* * Various getters, access to nodes *---------------------------------------------------------------------------*/ public EdgeFactory<ExecutionContext,ContextEdge> getEdgeFactory() { return callGraph.getEdgeFactory(); } public DirectedGraph<ExecutionContext,ContextEdge> getGraph() { return new UnmodifiableDirectedGraph<ExecutionContext, ContextEdge>(callGraph); } /** * Get node for a method info and call context * @param m the method * @param cs the call string to use (if null, {@link CallString#EMPTY} is used). * @return a new execution context. */ public ExecutionContext getNode(MethodInfo m, CallString cs) { return new ExecutionContext(m,cs == null ? CallString.EMPTY : cs); } /** * Check if the callgraph contains a given method with a given callstring. * @param m the method * @param cs the call string to use (if null, {@link CallString#EMPTY} is used). * @return true if the given call graph node is present in the call graph */ public boolean hasNode(MethodInfo m, CallString cs) { return callGraph.containsVertex(new ExecutionContext(m,cs == null ? CallString.EMPTY : cs)); } public boolean containsMethod(MethodInfo m) { return methodNodes.containsKey(m); } /** * Get all nodes matching the given method info. * * @see #getMethodNode(MethodInfo) * @param m the method to check. * @return a set of execution contexts of this method in the callgraph, or an empty set if this method has no nodes. */ public Set<ExecutionContext> getNodes(MethodInfo m) { if (!methodNodes.containsKey(m)) { return Collections.emptySet(); } return methodNodes.get(m).getInstances(); } public Set<ExecutionContext> getNodes() { return callGraph.vertexSet(); } /** * Get all matching nodes for a context in the graph. * @param context an execution context, not necessarily a node in the graph. * @return all nodes of the same method with matching callstrings. */ public Set<ExecutionContext> getNodes(ExecutionContext context) { return getNodes(context.getCallString(), context.getMethodInfo()); } public Set<ExecutionContext> getNodes(CallString cs, MethodInfo m) { Set<ExecutionContext> nodes = new LinkedHashSet<ExecutionContext>(); for (ExecutionContext ec : methodNodes.get(m).getInstances()) { if (ec.getCallString().matches(cs)) { nodes.add(ec); } } return nodes; } /** * Get a MethodNode for a given methodInfo. * @param m the method to check. * @return the methodNode containing a set of all execution contexts of the method, or null if not found. */ public MethodNode getMethodNode(MethodInfo m) { return methodNodes.get(m); } public Collection<ContextEdge> getOutgoingEdges(ExecutionContext node) { return callGraph.outgoingEdgesOf(node); } /** * Get a list of all nodes which are direct successors of a given node. * @param node the parent node. * @return all its direct successors. */ public List<ExecutionContext> getChildren(ExecutionContext node) { Set<ContextEdge> out = callGraph.outgoingEdgesOf(node); List<ExecutionContext> childs = new ArrayList<ExecutionContext>(out.size()); for (ContextEdge e : out) { childs.add(e.getTarget()); } return childs; } /** * @param node the invoker * @return the keys are the invoke sites in the invoker, the values the set of successors of the invoker node * than can be invoked by that invoke site. If the callgraph is context sensitive, the map might not contain * all invoke sites for which there are no invokees. */ public Map<InvokeSite, Set<ExecutionContext>> getChildsPerInvokeSite(ExecutionContext node) { Map<InvokeSite,Set<ExecutionContext>> map = new LinkedHashMap<InvokeSite, Set<ExecutionContext>>(); List<ExecutionContext> emptyCSNodes = new LinkedList<ExecutionContext>(); for (ContextEdge edge : callGraph.outgoingEdgesOf(node)) { long count; ExecutionContext child = edge.getTarget(); if (!child.getCallString().isEmpty()) { // simple case: if we have a callstring, the top entry is the invokesite in the invoker Set<ExecutionContext> childs = map.get(child.getCallString().top()); if (childs == null) { childs = new LinkedHashSet<ExecutionContext>(); map.put(child.getCallString().top(), childs); } childs.add(child); } else { // tricky case: no callstring, we need to find all invokesites in the invoker emptyCSNodes.add(child); } } if (emptyCSNodes.isEmpty()) return map; for (InvokeSite invokeSite : node.getMethodInfo().getCode().getInvokeSites()) { Set<ExecutionContext> childs = map.get(invokeSite); if (childs == null) { childs = new LinkedHashSet<ExecutionContext>(); map.put(invokeSite, childs); } for (ExecutionContext child : emptyCSNodes) { if (invokeSite.canInvoke(child.getMethodInfo()) != Ternary.FALSE) { childs.add(child); } } } return map; } public List<ExecutionContext> getParents(ExecutionContext node) { Set<ContextEdge> in = callGraph.incomingEdgesOf(node); List<ExecutionContext> parents = new ArrayList<ExecutionContext>(in.size()); for (ContextEdge e : in) { parents.add(e.getSource()); } return parents; } public Set<ClassInfo> getRootClasses() { Set<ClassInfo> classes = new LinkedHashSet<ClassInfo>(2); for (ExecutionContext root : rootNodes) { classes.add(root.getMethodInfo().getClassInfo()); } return classes; } /** * Get a set of all root methods which were used to construct the callgraph. * Note that a root method does not necessarily need to be a root of the graph, even * if it is acyclic, and there might be nodes in the graph for this method which are not * root nodes. * * @see #getRootNodes() * @return The methods of all root nodes. */ public Set<MethodInfo> getRootMethods() { Set<MethodInfo> methods = new LinkedHashSet<MethodInfo>(); for (ExecutionContext root : rootNodes) { methods.add(root.getMethodInfo()); } return methods; } /** * Get a set of all root nodes which were used to construct the callgraph. * Note that a root node does not necessarily need to be a root of the graph, even * if it is acyclic! * * @return All (initial) roots of the callgraph. */ public Set<ExecutionContext> getRootNodes() { return Collections.unmodifiableSet(rootNodes); } public Set<ClassInfo> getClassInfos() { return classInfos; } public Set<MethodInfo> getMethodInfos() { return methodNodes.keySet(); } /*---------------------------------------------------------------------------* * Modify the graph *---------------------------------------------------------------------------*/ public ContextEdge addEdge(ExecutionContext source, ExecutionContext target) { return callGraph.addEdge(source, target); } /** * Remove an edge from the graph. * * @param source source of the edge * @param target target of the edge * @param removeUnreachable if true, remove all nodes which are no longer reachable from the root * @return true if the edge existed */ public boolean removeEdge(ExecutionContext source, ExecutionContext target, boolean removeUnreachable) { ContextEdge edge = callGraph.getEdge(source, target); if (edge == null) return false; return removeEdge(edge, removeUnreachable); } /** * Remove an edge from the graph. * * @param edge the edge to remove. * @param removeUnreachable if true, remove all nodes which are no longer reachable from the root * @return true if the edge existed */ public boolean removeEdge(ContextEdge edge, boolean removeUnreachable) { // Edge equals is defined over the adjacent execution contexts, so we can do this here // without worrying about same instances boolean exists = callGraph.removeEdge(edge); if (exists && removeUnreachable) { // do we need to worry about loosing connectivity? ExecutionContext target = edge.getTarget(); Set<ExecutionContext> queue = new LinkedHashSet<ExecutionContext>(); queue.add(target); while (!queue.isEmpty()) { // take one down .. ExecutionContext ec = queue.iterator().next(); queue.remove(ec); if (callGraph.incomingEdgesOf(ec).isEmpty()) { // this node has now become disconnected.. remove it, queue all children queue.addAll(getChildren(ec)); // Note that we do not need to worry about any onRemove methods, they are called by the listeners callGraph.removeVertex(ec); } } } return exists; } public boolean removeEdges(MethodInfo invoker, MethodInfo invokee, boolean removeUnreachable) { MethodNode node = methodNodes.get(invoker); if (node == null) return false; List<ContextEdge> remove = new LinkedList<ContextEdge>(); for (ExecutionContext ec : node.getInstances()) { for (ContextEdge edge : callGraph.outgoingEdgesOf(ec)) { if (edge.getTarget().getMethodInfo().equals(invokee)) { remove.add(edge); } } } for (ContextEdge edge : remove) { removeEdge(edge, removeUnreachable); } return !remove.isEmpty(); } public boolean removeNodes(InvokeSite invokeSite, MethodInfo invokee, boolean removeUnreachable) { return removeNodes(new CallString(invokeSite), invokee, removeUnreachable); } /** * Remove all nodes from the graph which are invoked in a certain context, i.e. all execution contexts which * have the callstring as suffix. This does not remove contexts which have a context callstring shorter than the * callstring. * * @param callstring the callstring leading to the invokee * @param invokee the method for which contexts should be removed * @param removeUnreachable remove all newly unreachable methods from the callgraph too. * @return false if no nodes have been removed. */ public boolean removeNodes(CallString callstring, MethodInfo invokee, boolean removeUnreachable) { // find all edges to remove MethodNode node = methodNodes.get(invokee); if (node == null) return false; // need to save nodes to remove into a temp list, because removing them from the graph would modify the instance list List<ExecutionContext> remove = new ArrayList<ExecutionContext>(node.getInstances().size()); for (ExecutionContext ec : node.getInstances()) { if (ec.getCallString().hasSuffix(callstring)) { // if the node has the given callstring as suffix (i.e. is reached via the callstring), remove it remove.add(ec); } } for (ExecutionContext ec : remove) { removeNode(ec, removeUnreachable); } return !remove.isEmpty(); } public boolean removeNode(ExecutionContext context, boolean removeUnreachable) { if (removeUnreachable) { // we only need to do this if we want to remove unreachable nodes, since removing a vertex // also removes its edges List<ContextEdge> remove = new ArrayList<ContextEdge>(callGraph.outgoingEdgesOf(context)); for (ContextEdge e : remove) { removeEdge(e, removeUnreachable); } } // we do not need to worry about any onRemove methods, they are called by the listeners return callGraph.removeVertex(context); } public boolean removeMethod(MethodInfo method, boolean removeUnreachable) { MethodNode node = methodNodes.get(method); if (node == null) return false; // need to copy the list here, since it will be modified when we remove the nodes List<ExecutionContext> contexts = new ArrayList<ExecutionContext>(node.getInstances()); for (ExecutionContext c : contexts) { // simply remove all nodes, the rest will be updated by the listeners removeNode(c, removeUnreachable); } return true; } /** * Copy an existing node, and replace the callstring of the node with a new callstring. * All reachable nodes are also copied using the new callstring if they do not exist. * * @param source the node to copy * @param newContext the new callstring for the new node * @param callstringLength the maximum callstring length for new nodes. * @return the new node */ public ExecutionContext copyNodeRecursive(ExecutionContext source, CallString newContext, int callstringLength) { ExecutionContext root = new ExecutionContext(source.getMethodInfo(), newContext); if (callGraph.containsVertex(root)) { return root; } callGraph.addVertex(root); List<ExecutionContext> newQueue = new LinkedList<ExecutionContext>(); List<ExecutionContext> oldQueue = new LinkedList<ExecutionContext>(); oldQueue.add(source); newQueue.add(root); while (!newQueue.isEmpty()) { ExecutionContext newNode = newQueue.remove(0); ExecutionContext oldNode = oldQueue.remove(0); // create a copy for every child of oldNode, construct a new callstring for (ExecutionContext oldChild : getChildren(oldNode)) { CallString newString; if (oldChild.getCallString().isEmpty()) { newString = CallString.EMPTY; } else { newString = newNode.getCallString().push(oldChild.getCallString().top(), callstringLength); } ExecutionContext newChild = new ExecutionContext(oldChild.getMethodInfo(), newString); if (!callGraph.containsVertex(newChild)) { callGraph.addVertex(newChild); oldQueue.add(oldChild); newQueue.add(newChild); } callGraph.addEdge(newNode, newChild); } } return root; } /* public void merge(MethodInfo target, Set<CallString> remove, Map<CallString, InvokeSite> invokeMap) { int len = AppInfo.getSingleton().getCallstringLength(); merge(target, remove, invokeMap, new DefaultCallgraphBuilder(len), len); } public void merge(MethodInfo target, Set<CallString> remove, Map<CallString, InvokeSite> invokeMap, CallgraphBuilder builder) { merge(target, remove, invokeMap, builder, AppInfo.getSingleton().getCallstringLength()); } public void merge(MethodInfo target, Set<CallString> remove, Map<CallString, InvokeSite> invokeMap, CallgraphBuilder builder, int callstringLength) { for (ExecutionContext context : getNodes(target)) { merge(context, remove, invokeMap, builder, callstringLength); } } */ /** * Merge nodes in the callgraph. * <p> * This assumes that callstrings never decrease in length along any path in this callgraph. * </p> * * TODO implement. For now we just make sure that all optimizations which modify the callgraph * (permanently) do not rely on an updated callgraph and we rebuild the callgraph and all * analysis-data after the optimization is complete. * * @param target the node to merge other nodes into * @param remove a set of callstrings from the target to the nodes to remove. First item in a * callstring must be an invokeSite in the target method, last entry is the invokeSite * of the node to remove. * @param invokeMap map merged invokes to new invokesites. The keys are callstrings from the target to * the invokesites to replace, the values the new invokesites to use instead. The * keyset must be a superset of callstrings to all invokesites directly reachable from * the 'remove' set. * @param builder the builder to use to check if an edge is still needed after merging if the node has * callstring-length 0. * @param callstringLength the maximum callstring length for new nodes. */ /* public void merge(ExecutionContext target, Set<CallString> remove, Map<CallString, InvokeSite> invokeMap, CallgraphBuilder builder, int callstringLength) { // first we add the new edges so that nodes do not get disconnected to avoid unnecessary // graph updates // - for all nodes directly reachable from 'removed': // - clone them, create a context with callstring of the target + the new invokesite (retrieved // from invokeMap), add edges from the target to the new nodes // use callstringLength param to limit length // - for all reachable nodes from the cloned nodes: // clone those nodes with new callstrings (callstring of the new node + invokesite of cloned node) // recurse down until no new callstrings are created, limit length by callstringLength // Now we need to remove old edges and we remove all new roots which have not been added // as roots // for all outgoing edges of 'target' and nodes in 'remove' set, check: // - if callstring length of edge-target = 0: we do not know if the target is still used // (e.g if one invokesite with this invokee has been inlined and another invokesite with same // invokee has not), so we need to use the builder to create all invokee nodes for 'target' // and check if the edge-target is still in the created set. If so, keep the edge. // Outgoing edges of 'target' and of nodes in the 'remove' set need to be handled differently.. // - else: check the invokesites of the targets along the path from 'target' to the edge-target // if this path is contained in 'remove', if so we can remove the edge. // - for all removed edges: check if edge-target is now has no ingoing edges, if so // check if edge-target is not in the callgraph-roots, and if so remove the node and all // edges, recurse down with this check. Subgraphs and merged-graph are automatically updated. // TODO we could also allow registering of callback handlers which will be notified // when callstrings change due to merging. This could be used to update callstrings in // the DFA results etc. so that we do not need to rerun the DFA after inlining } */ /*---------------------------------------------------------------------------* * Merge graph, subgraphs *---------------------------------------------------------------------------*/ /** * Build a second graph with a single node per method to get a less precise call graph model. * Used to get all execution contexts per method and all invoked methods per InvokeSite. * This graph is backed by the main graph, so all changes to the main graph are reflected in this graph. */ public void buildMergedGraph() { if (mergedCallGraph != null) return; this.mergedCallGraph = new DefaultDirectedGraph<MethodNode, InvokeEdge>( new EdgeFactory<MethodNode, InvokeEdge>() { @Override public InvokeEdge createEdge(MethodNode source, MethodNode target) { return new InvokeEdge(); } }); // nodes are already uptodate, add them to the graph for (MethodNode node : methodNodes.values()) { mergedCallGraph.addVertex(node); } // for all edges in callGraph, add or update the edge in this graph for (ContextEdge edge : callGraph.edgeSet()) { addMergedGraphEdge(edge); } } public DirectedGraph<MethodNode,InvokeEdge> getMergedCallGraph(boolean reversed) { buildMergedGraph(); return reversed ? new EdgeReversedGraph<MethodNode, InvokeEdge>(mergedCallGraph) : mergedCallGraph; } public SimpleDirectedGraph<MethodNode,InvokeEdge> getAcyclicMergedGraph(boolean reversed) { return GraphUtils.createAcyclicGraph(getMergedCallGraph(reversed)); } public CallGraph getSubGraph(MethodInfo rootMethod) { MethodNode node = getMethodNode(rootMethod); return getSubGraph(node.getInstances()); } /** * Get a subgraph starting at a given node which contains all reachable nodes and edges. * The subgraph is backed by this graph, so any modifications to it will be reflected * in the subgraph (but not the other way round!) * * @param roots root nodes of the new graph. * @return a new subgraph, or an existing subgraph which starts at the same root */ public CallGraph getSubGraph(Set<ExecutionContext> roots) { // first check if we already have this graph.. for (CallGraph subgraph : subgraphs.keySet()) { if (subgraph.getRootNodes().equals(roots)) { return subgraph; } } CallGraph subGraph = new CallGraph(this, roots, builder); subGraph.build(); SubgraphUpdateListener listener = new SubgraphUpdateListener(subGraph); callGraph.addGraphListener(listener); // we use a map here to keep the listener, needed for remove // TODO maybe add a listener to subgraph which updates the parent graph when it is modified too? subgraphs.put(subGraph,listener); return subGraph; } public void removeSubGraph(CallGraph subgraph) { SubgraphUpdateListener listener = subgraphs.get(subgraph); callGraph.removeGraphListener(listener); subgraphs.remove(subgraph); } public DirectedGraph<ExecutionContext, ContextEdge> getReversedGraph() { return new EdgeReversedGraph<ExecutionContext, ContextEdge>(callGraph); } public DirectedGraph<ExecutionContext, ContextEdge> getAcyclicGraph(boolean reversed) { DirectedGraph<ExecutionContext, ContextEdge> graph = reversed ? getReversedGraph() : getGraph(); if (acyclic == Ternary.TRUE) { return new UnmodifiableDirectedGraph<ExecutionContext, ContextEdge>(graph); } return GraphUtils.createAcyclicGraph(graph); } public DirectedGraph<ExecutionContext, ContextEdge> createInvokeGraph(Collection<ExecutionContext> roots, boolean reversed) { // TODO this method is a hotspot, but what can you do? The TopologicalOrderIterator only works on // on full graphs, it does not allow to "start in the middle, because it does not make // much sense". One could implement an own TopOrderIterator, but finding out which nodes are // the 'real' roots (i.e. eliminating all roots which have a path to other roots) may not be much // faster than this code. // A better approach would simply be to 'compress' the callgraph once. DirectedGraph<ExecutionContext, ContextEdge> invokeGraph = new DefaultDirectedGraph<ExecutionContext,ContextEdge>( new EdgeFactory<ExecutionContext,ContextEdge>() { @Override public ContextEdge createEdge(ExecutionContext sourceVertex, ExecutionContext targetVertex) { return new ContextEdge(sourceVertex,targetVertex); } }); LinkedList<ExecutionContext> queue = new LinkedList<ExecutionContext>(roots); for (ExecutionContext root : roots) { invokeGraph.addVertex(root); } // add all incoming edges to the new graph, add new nodes to the queue while (!queue.isEmpty()) { ExecutionContext node = queue.removeFirst(); // add all incoming edges for (ContextEdge edge : callGraph.incomingEdgesOf(node)) { ExecutionContext source = edge.getSource(); // new node, not yet in queue if (!invokeGraph.containsVertex(source)) { invokeGraph.addVertex(source); queue.add(source); } // add edge if (reversed) { invokeGraph.addEdge(node, source); } else { invokeGraph.addEdge(source, node); } } } return invokeGraph; } /*---------------------------------------------------------------------------* * Various lookup methods *---------------------------------------------------------------------------*/ /** * @param invokee the invoked method * @return a set of all methods which may directly invoke this method */ public Set<MethodInfo> getDirectInvokers(MethodInfo invokee) { MethodNode node = getMethodNode(invokee); Set<MethodInfo> invokers = new LinkedHashSet<MethodInfo>(); for (ExecutionContext ec : node.getInstances()) { for (ContextEdge edge : callGraph.incomingEdgesOf(ec)) { invokers.add( edge.getSource().getMethodInfo() ); } } return invokers; } public Set<InvokeSite> getInvokeSites(MethodInfo invokee) { MethodNode node = getMethodNode(invokee); Set<InvokeSite> invokeSites = new LinkedHashSet<InvokeSite>(); for (ExecutionContext ec : node.getInstances()) { if (ec.getCallString().isEmpty()) { // no callstring, need to search the invoker 'manually' for invokesites which may invoke this method AppInfo appInfo = AppInfo.getSingleton(); for (ContextEdge edge : callGraph.incomingEdgesOf(ec)) { for (InvokeSite invokeSite : edge.getSource().getMethodInfo().getCode().getInvokeSites()) { if (invokeSite.canInvoke(invokee) != Ternary.FALSE) { invokeSites.add(invokeSite); } } } } else { invokeSites.add(ec.getCallString().top()); } } return invokeSites; } public List<ExecutionContext> getInvokedNodes(ExecutionContext node, InvokeSite invokeSite, MethodInfo invokee) { List<ExecutionContext> invoked = new ArrayList<ExecutionContext>(1); for (ContextEdge edge : callGraph.outgoingEdgesOf(node)) { if (!edge.getTarget().getMethodInfo().equals(invokee)) { continue; } if (edge.getTarget().getCallString().isEmpty() || edge.getTarget().getCallString().top().equals(invokeSite)) { invoked.add(edge.getTarget()); } } return invoked; } public Set<MethodInfo> getInvokedMethods(MethodInfo method) { MethodNode node = getMethodNode(method); Set<MethodInfo> invokees = new LinkedHashSet<MethodInfo>(); if (mergedCallGraph != null) { for (InvokeEdge edge : mergedCallGraph.outgoingEdgesOf(node)) { MethodNode invokee = mergedCallGraph.getEdgeTarget(edge); invokees.add(invokee.getMethodInfo()); } } else { for (ExecutionContext context : node.getInstances()) { for (ContextEdge edge : callGraph.outgoingEdgesOf(context)) { invokees.add(edge.getTarget().getMethodInfo()); } } } return invokees; } public Collection<ContextEdge> getInvokeEdges(MethodInfo invoker, MethodInfo invokee) { MethodNode node = getMethodNode(invoker); List<ContextEdge> edges = new ArrayList<ContextEdge>(); for (ExecutionContext ec : node.getInstances()) { for (ContextEdge edge : callGraph.outgoingEdgesOf(ec)) { if (edge.getTarget().getMethodInfo().equals(invokee)) { edges.add(edge); } } } return edges; } public BackEdgeFinder<ExecutionContext,ContextEdge> getBackEdgeFinder() { return new BackEdgeFinder<ExecutionContext, ContextEdge>(callGraph); } /** * Return a top-down (topological) iterator for the callgraph * @return A topological order iterator */ public TopologicalOrderIterator<ExecutionContext, ContextEdge> topDownIterator() { return new TopologicalOrderIterator<ExecutionContext, ContextEdge>(callGraph); } public TopologicalOrderIterator<ExecutionContext, ContextEdge> reverseTopologicalOrder() { return new TopologicalOrderIterator<ExecutionContext, ContextEdge>( getReversedGraph() ); } /** * Get non-abstract methods, in topological order. * * Requires an acyclic callgraph. * @param rootMethod start with this method * @return a list of all non-abstract reachable methods, in topological order. */ public List<MethodInfo> getReachableImplementations(MethodInfo rootMethod) { List<MethodInfo> implemented = new ArrayList<MethodInfo>(); Set<MethodInfo> reachable = getReachableImplementationsSet(rootMethod); TopologicalOrderIterator<ExecutionContext, ContextEdge> ti = topDownIterator(); while(ti.hasNext()) { MethodInfo m = ti.next().getMethodInfo(); if(m != null && reachable.contains(m)) implemented.add(m); } return implemented; } /** * Retrieve non-abstract methods reachable from the given method. * All callgraph nodes reachable from nodes representing the given a method are collected * * @param rootMethod start method * @return a list of all reachable methods, sorted in topological order */ public Set<MethodInfo> getReachableImplementationsSet(MethodInfo rootMethod) { Set<MethodInfo> implemented = new LinkedHashSet<MethodInfo>(); for(ExecutionContext cgNode : methodNodes.get(rootMethod).getInstances()) { DepthFirstIterator<ExecutionContext, ContextEdge> ti = new DepthFirstIterator<ExecutionContext, ContextEdge>(callGraph,cgNode); ti.setCrossComponentTraversal(false); while(ti.hasNext()) { MethodInfo m = ti.next().getMethodInfo(); if(m == null) throw new AssertionError("Abstract method in callgraph"); implemented.add(m); } } return implemented; } public List<MethodInfo> getReachableImplementations(MethodInfo rootMethod, CallString cs) { if(! this.hasNode(rootMethod, cs)) { throw new AssertionError("CallGraph#getReachableImplementations: no such node: "+ new ExecutionContext(rootMethod, cs)); } ExecutionContext cgNode = this.getNode(rootMethod, cs); return getReachableImplementations(cgNode); } public List<MethodInfo> getReachableImplementations(ExecutionContext cgNode) { final List<MethodInfo> implemented = new ArrayList<MethodInfo>(); final Set<MethodInfo> visited = new LinkedHashSet<MethodInfo>(); DepthFirstIterator<ExecutionContext, ContextEdge> ti = new DepthFirstIterator<ExecutionContext, ContextEdge>(callGraph,cgNode); ti.setCrossComponentTraversal(false); ti.addTraversalListener(new TraversalListenerAdapter<ExecutionContext,ContextEdge>() { @Override public void vertexFinished(VertexTraversalEvent<ExecutionContext> e) { MethodInfo m = e.getVertex().getMethodInfo(); if (m == null) throw new AssertionError("Abstract method in callgraph"); if (visited.add(m)) { implemented.add(m); } } }); while(ti.hasNext()) { ti.next(); } Collections.reverse(implemented); return implemented; } /** * Retrieve non-abstract methods reachable from the given call graph node. * All callgraph nodes reachable from nodes representing the given a method are collected * @param rootMethod where to start * @param cs callstring of the invocation of the method, a node with this callstring (same length!) must exist. * @return a list of all reachable implementations, sorted in DFS order */ public Set<MethodInfo> getReachableImplementationsSet(MethodInfo rootMethod, CallString cs) { if(! this.hasNode(rootMethod, cs)) { throw new AssertionError("CallGraph#getReachableImplementations: no such node: "+ new ExecutionContext(rootMethod, cs)); } ExecutionContext cgNode = this.getNode(rootMethod, cs); return getReachableImplementationsSet(cgNode); } /** * Retrieve non-abstract methods reachable from the given call graph node. * All callgraph nodes reachable from nodes representing the given a method are collected * @param cgNode where to start * @return a list of all reachable implementations, sorted in DFS order */ public Set<MethodInfo> getReachableImplementationsSet(ExecutionContext cgNode) { Set<MethodInfo> implemented = new LinkedHashSet<MethodInfo>(); DepthFirstIterator<ExecutionContext, ContextEdge> ti = new DepthFirstIterator<ExecutionContext, ContextEdge>(callGraph,cgNode); ti.setCrossComponentTraversal(false); while(ti.hasNext()) { MethodInfo m = ti.next().getMethodInfo(); if (m == null) throw new AssertionError("Abstract method in callgraph"); implemented.add(m); } return implemented; } /** * Find all implementing methods for a given non-empty callstring. * This method is similar to {@link AppInfo#findImplementations(CallString)}, but is based * solemnly on this callgraph, even for non-virtual invokes. * * @see AppInfo#findImplementations(CallString) * @see #findImplementationContexts(CallString) * @param cs the non-empty callstring, top element represents the invocation. * @return a set of implementing methods of the invokee. */ public Set<MethodInfo> findImplementations(CallString cs) { Collection<ExecutionContext> nodes = findImplementationContexts(cs); Set<MethodInfo> methods = new LinkedHashSet<MethodInfo>(nodes.size()); for (ExecutionContext node : nodes) { methods.add(node.getMethodInfo()); } return methods; } /** * For a given non-empty callstring, find all implementations which might get called by the last * invocation in the callstring, i.e. find all methods which might appear in the next entry of the * callstring. * <p> * This is only a lookup in the callgraph, and does not check if the invocation is a special invoke, * so if callstring length of the callgraph is zero, the results are not correct. Instead use * {@link AppInfo#findImplementations(CallString)} which handles all special cases and falls back * to the default callgraph. * </p> * * @param cs callstring of the invocation, must contain at least one invokesite. * @return a list of all methods which might get invoked by the top invocation of the callstring, * with their callstrings. */ public Set<ExecutionContext> findImplementationContexts(CallString cs) { if (cs.length() == 0) { throw new AssertionError("findImplementationContexts: callstring must not be empty!"); } InvokeSite invoke = cs.top(); Set<ExecutionContext> methods = new LinkedHashSet<ExecutionContext>(); MethodRef invokeeRef = invoke.getInvokeeRef(); // if the invoke is not virtual, should we look it up in the graph anyway? // We could just return new ExecutionContext(invokee, cs); // But that's what AppInfo#findImplementations() is for, here we only lookup the callgraph, // but then we cannot have different callgraphs and perform precise lookups on all of them, // since that method only looks into the default callgraph (but why would we want multiple // graphs anyway?) // find all instances of possible invokers Set<ExecutionContext> invoker = getNodes(invoke.getInvoker()); for (ExecutionContext invokeNode : invoker) { // TODO filter out nodes which do not match the callstring, to speed up things a bit for (ContextEdge outEdge : callGraph.outgoingEdgesOf(invokeNode)) { CallString cgString = outEdge.getTarget().getCallString(); // check if the target callstring matches the given callstring if (cgString.isEmpty()) { // target has no callstring, must at least override the invoked method if (invokeeRef.isInterfaceMethod() == Ternary.TRUE) { // for interface methods we have a problem, because we only have the // implementations in the call graph, not the receivers, we cannot // check if the receiver implements the interface.. if (outEdge.getTarget().getMethodInfo().implementsMethod(invokeeRef)) { methods.add(outEdge.getTarget()); } } else { if (outEdge.getTarget().getMethodInfo().overrides(invokeeRef, true)) { methods.add(outEdge.getTarget()); } } } else { // if one of the callstrings is a suffix of the other, this context is a possible invocation if (cs.matches(cgString)) { methods.add(outEdge.getTarget()); } } } } return methods; } /** * @param m invoker method * @return methods possibly directly invoked from the given method */ public Set<ExecutionContext> getReferencedMethods(MethodInfo m) { Set<ExecutionContext> nodes = getNodes(m); Set<ExecutionContext> succs = new LinkedHashSet<ExecutionContext>(); for(ExecutionContext node : nodes) { for(ContextEdge e : callGraph.outgoingEdgesOf(node)) { succs.add(callGraph.getEdgeTarget(e)); } } return succs; } /** * @param node a callgraph node * @return true when the given method does not invoke any other methods */ public boolean isLeafNode(ExecutionContext node) { return callGraph.outDegreeOf(node) == 0; } public boolean isLeafMethod(MethodInfo mi) { /* Using caching, as this method is used quite often */ Boolean isLeafNode = leafNodeCache.get(mi); if(isLeafNode != null) return isLeafNode; isLeafNode = true; for(ExecutionContext node : getNodes(mi)) { if(! isLeafNode(node)) { isLeafNode = false; break; } } leafNodeCache.put(mi,isLeafNode); return isLeafNode; } /** * Get the maximum height of the call stack. * <p>A leaf method has height 1, an abstract method's height is the * maximum height of its children, and the height of an implemented method * is the maximum height of its children + 1. <p> * @return the maximum call stack */ public List<MethodInfo> getMaximalCallStack() { if(maxCallStackLeaf == null) calculateDepthAndHeight(); ExecutionContext n = this.maxCallStackLeaf; List<MethodInfo> maxCallStack = new ArrayList<MethodInfo>(); maxCallStack.add(n.getMethodInfo()); while(maxCallstackDAG.containsKey(n)) { n = maxCallstackDAG.get(n); maxCallStack.add(n.getMethodInfo()); } Collections.reverse(maxCallStack); return maxCallStack; } public int getMaxHeight() { calculateDepthAndHeight(); int maxHeight = 0; for (ExecutionContext rootNode : rootNodes) { maxHeight = Math.max(maxHeight, this.subgraphHeight.get(rootNode)); } return maxHeight; } public ControlFlowGraph getLargestMethod() { ControlFlowGraph largest = null; int maxBytes = 0; for (ExecutionContext rootNode : rootNodes) { for(MethodInfo mi : this.getReachableImplementationsSet(rootNode.getMethodInfo())) { int bytes = mi.getCode().getNumberOfBytes(); if(bytes > maxBytes) { largest = mi.getCode().getControlFlowGraph(false); maxBytes = bytes; } } } return largest; } public int getTotalSizeInBytes() { int bytes = 0; for (ExecutionContext rootNode : rootNodes) { for (MethodInfo mi : this.getReachableImplementationsSet(rootNode.getMethodInfo())) { bytes += mi.getCode().getNumberOfBytes(); } } return bytes; } /*---------------------------------------------------------------------------* * Export methods *---------------------------------------------------------------------------*/ public void dumpCallgraph(Config config, String graphName, DUMPTYPE type, boolean skipNoim) { try { dumpCallgraph(config, graphName, null, null, type, skipNoim); } catch (IOException e) { logger.warn("Could not dump callgraph '"+graphName+"': "+e.getMessage(), e); } } /** * Dump this callgraph or a subgraph to a file and create a png image. * If you use this method, add {@link #CALLGRAPH_DIR} to the options. * * @param config the config containing options for InvokeDot and {@link #CALLGRAPH_DIR} * @param graphName the name of the graph, will be used to construct the file name. * @param suffix a suffix for the graph name, e.g. to distinguish between various subgraphs * @param roots The roots of the subgraph to dump. If null, dump the whole graph. If roots is empty, do nothing. * @param type dump the complete graph, dump only the merged graph, or dump both or nothing. * @param skipNoim if true, do not include methods which have a single edge to the JVM.noim method. * @throws IOException if exporting the file fails. */ public void dumpCallgraph(Config config, String graphName, String suffix, Set<ExecutionContext> roots, DUMPTYPE type, boolean skipNoim) throws IOException { if (type == DUMPTYPE.off) return; if (roots != null && roots.isEmpty()) return; File outDir; try { outDir = config.getOutDir(config.getDebugGroup(), CallGraph.CALLGRAPH_DIR); } catch (BadConfigurationException e) { throw new BadConfigurationError("Could not create output dir "+ config.getDebugGroup().getOption(CallGraph.CALLGRAPH_DIR), e); } CallGraph subGraph = roots == null ? this : getSubGraph(roots); if (type == CallGraph.DUMPTYPE.merged || type == CallGraph.DUMPTYPE.both) { dumpCallgraph(config, outDir, graphName, suffix, subGraph, true, skipNoim); } if (type == CallGraph.DUMPTYPE.full || type == CallGraph.DUMPTYPE.both) { dumpCallgraph(config, outDir, graphName, suffix, subGraph, false, skipNoim); } if (roots != null) { removeSubGraph(subGraph); } } /** * Export the complete callgraph as .dot file * @param w Write the graph to this writer. To improve performance, use a buffered writer. * @throws IOException if writing fails */ public void exportDOT(Writer w) throws IOException { exportDOT(w, false, false, false); } /** * Export the callgraph as a .dot file. * * @param w Write the graph to this writer. To improve performance, use a buffered writer. * @param merged if true, export the merged callgraph instead of the full graph. * @param skipIsolated if true, do not export isolated nodes. * @param skipNoImp if true, do not export roots with only one edge to com.jopdesign.sys.JVMHelp.noim(). * @throws IOException if writing fails. */ public void exportDOT(Writer w, boolean merged, final boolean skipIsolated, final boolean skipNoImp) throws IOException { AdvancedDOTExporter<MethodContainer, Object> exporter = new AdvancedDOTExporter<MethodContainer, Object>(); exporter.setGraphAttribute("rankdir", "LR"); if (merged && mergedCallGraph == null) { buildMergedGraph(); } // Why is this an unchecked statement?? @SuppressWarnings({"unchecked"}) DirectedGraph<MethodContainer, Object> graph = (DirectedGraph<MethodContainer,Object>) (merged ? mergedCallGraph : callGraph); if (skipIsolated || skipNoImp) { graph = new DirectedMaskSubgraph<MethodContainer, Object>(graph, new MethodFilter(graph, skipIsolated, skipNoImp)); } exporter.exportDOT(w, graph); } /*---------------------------------------------------------------------------* * Private methods *---------------------------------------------------------------------------*/ private void dumpCallgraph(Config config, File outDir, String graphName, String type, CallGraph graph, boolean merged, boolean skipNoim) throws IOException { String suffix = type != null ? type + "-" : ""; suffix += (merged) ? "merged" : "full"; File dotFile = new File(outDir, graphName+"-"+suffix+".dot"); File pngFile = new File(outDir, graphName+"-"+suffix+".png"); logger.info("Dumping "+suffix+" callgraph to "+dotFile); FileWriter writer = new FileWriter(dotFile); graph.exportDOT(writer, merged, false, skipNoim); writer.close(); InvokeDot.invokeDot(config, dotFile, pngFile); } /** * calculate the depth of each node, the height of the subgraph * rooted at that node, and a maximum call-stack tree. */ private void calculateDepthAndHeight() { if(this.maxDistanceToRoot != null) return; // caching if (acyclic != Ternary.TRUE) { throw new AssertionError("Callgraph needs to be checked for acyclicity first."); } if (rootNodes.size() != 1) { // TODO The problem here is the calculation of maxDistanceToRoot and maxCallStack // The algorithms to calculate them need to be checked/updated to work with multiple roots // The various getters above should already work for multiple roots // Also, it may be nice to know the root of the max callstack... throw new AssertionError("Calculating max stack for callgraphs with multiple roots is not supported!"); } ExecutionContext rootNode = rootNodes.iterator().next(); this.maxDistanceToRoot = new LinkedHashMap<ExecutionContext,Integer>(); this.maxCallStackLeaf = rootNode; this.maxCallstackDAG = new LinkedHashMap<ExecutionContext,ExecutionContext>(); this.subgraphHeight = new LinkedHashMap<ExecutionContext, Integer>(); /* calculate longest distance to root and max call stack DAG */ List<ExecutionContext> toList = new ArrayList<ExecutionContext>(); TopologicalOrderIterator<ExecutionContext, ContextEdge> toIter = new TopologicalOrderIterator<ExecutionContext, ContextEdge>(callGraph); int globalMaxDist = 0; while(toIter.hasNext()) { ExecutionContext node = toIter.next(); toList.add(node); int maxDist = 0; ExecutionContext maxCallStackPred = null; for(ContextEdge e : callGraph.incomingEdgesOf(node)) { ExecutionContext pred = callGraph.getEdgeSource(e); int distViaPred = maxDistanceToRoot.get(pred) + 1; if(distViaPred > maxDist) { maxDist = distViaPred; maxCallStackPred = pred; } } this.maxDistanceToRoot.put(node,maxDist); if(maxCallStackPred != null) this.maxCallstackDAG.put(node,maxCallStackPred); if(maxDist > globalMaxDist) this.maxCallStackLeaf = node; } /* calculate subgraph height */ Collections.reverse(toList); for(ExecutionContext n : toList) { int maxHeight = 0; for(ContextEdge e : callGraph.outgoingEdgesOf(n)) { int predHeight = subgraphHeight.get(callGraph.getEdgeTarget(e)); maxHeight = Math.max(maxHeight, predHeight + 1); } subgraphHeight.put(n, maxHeight); } } private void invalidate() { maxCallStackLeaf = null; maxDistanceToRoot = null; maxCallstackDAG = null; subgraphHeight = null; leafNodeCache = new LinkedHashMap<MethodInfo, Boolean>(); } /** * Add a context to the callGraph as vertex and add it to the methodNodes map. * @param context the context to add. */ private void onAddExecutionContext(ExecutionContext context) { MethodNode node = methodNodes.get(context.getMethodInfo()); if (node == null) { node = new MethodNode(context.getMethodInfo()); methodNodes.put(context.getMethodInfo(), node); } node.addInstance(context); // doing this here will call add(ClassInfo) more often than iterating over all // method nodes, but doing it here it is more robust when the graph is modified classInfos.add(node.getMethodInfo().getClassInfo()); if (mergedCallGraph != null) { mergedCallGraph.addVertex(node); } } private void onRemoveExecutionContext(ExecutionContext context) { MethodNode node = methodNodes.get(context.getMethodInfo()); node.removeInstance(context); if (node.getInstances().isEmpty()) { methodNodes.remove(context.getMethodInfo()); if (mergedCallGraph != null) { mergedCallGraph.removeVertex(node); } // TODO we might need to remove the class of the method from classInfos too! } rootNodes.remove(context); } private void addMergedGraphEdge(ContextEdge edge) { MethodNode invoker = methodNodes.get(edge.getSource().getMethodInfo()); MethodNode invokee = methodNodes.get(edge.getTarget().getMethodInfo()); InvokeEdge invoke = mergedCallGraph.getEdge(invoker, invokee); if (invoke == null) { invoke = mergedCallGraph.addEdge(invoker, invokee); } if (edge.getTarget().getCallString().length() > 0) { invoke.addInvokeSite(edge.getTarget().getCallString().top()); } } private void removeMergedGraphEdge(ContextEdge edge) { MethodNode invoker = methodNodes.get(edge.getSource().getMethodInfo()); MethodNode invokee = methodNodes.get(edge.getTarget().getMethodInfo()); // check all incoming edges if there is an edge to the invoker left boolean remove = true; for (ExecutionContext ctx : invokee.getInstances()) { for (ContextEdge srcEdge : callGraph.incomingEdgesOf(ctx)) { if (srcEdge.getSource().getMethodInfo().equals(invoker.getMethodInfo())) { remove = false; break; } } } if (remove) { mergedCallGraph.removeEdge(invoker, invokee); } else { InvokeEdge invoke = mergedCallGraph.getEdge(invoker, invokee); if (edge.getTarget().getCallString().length() > 0) { invoke.removeInvokeSite(edge.getTarget().getCallString().top()); } } } private String cyclicCallGraphMsg(Pair<List<ExecutionContext>, List<ExecutionContext>> cycleWithPrefix) { List<ExecutionContext> cycle = cycleWithPrefix.second(); List<ExecutionContext> prefix = cycleWithPrefix.first(); StringBuffer sb = new StringBuffer(); sb.append("Cyclic Callgraph !\n"); sb.append("One cycle is:\n"); for(ExecutionContext cn : cycle) sb.append(" ").append(cn).append("\n"); sb.append("Reachable via:\n"); for(ExecutionContext cn : prefix) sb.append(" ").append(cn).append("\n"); return sb.toString(); } }