/** * Copyright (C) 2013 Rohan Padhye * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 2.1 of the * License, or (at your option) any later version. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package vasco; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.TreeSet; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import soot.toolkits.graph.DirectedGraph; import soot.toolkits.graph.SlowPseudoTopologicalOrderer; import soot.toolkits.scalar.Pair; /** * A value-based context for a context-sensitive inter-procedural data flow * analysis. * * <p> * A value-based context is identified as a pair of a method and the data * flow value at the entry of the method, for forward flows, or the data * flow value at the exit of the method, for backward flows. Thus, if * two distinct calls are made to a method and each call-site has the same * data flow value then it is considered that the target of that call is * the same context. This concept allows termination in the presence of * recursion as the number of contexts is limited by the size of the lattice * (which must be finite). * </p> * * <p> * Each value context has its own work-list of CFG nodes to analyse, and the * results of analysis are stored in a map from nodes to the data flow values * before/after the node. * </p> * * @author Rohan Padhye * * @param <M> the type of a method * @param <N> the type of a node in the CFG * @param <A> the type of a data flow value */ public class Context<M,N,A> implements soot.Context, Comparable<Context<M,N,A>> { /** A counter for global context identifiers. */ private static int count = 0; /** Debug stuff */ static java.util.Set<Object> freeContexts = new java.util.HashSet<Object>(); static int totalNodes = 0; static int liveNodes = 0; /** Whether or not this context has been fully analysed at least once. */ private boolean analysed; /** The control-flow graph of this method's body. */ private DirectedGraph<N> controlFlowGraph; /** The data flow value associated with the entry to the method. **/ private A entryValue; /** The data flow value associated with the exit of the method. */ private A exitValue; /** A globally unique identifier. */ private int id; /** The method for which this calling context context applies. */ private M method; /** The data flow values at the exit of each node. */ private Map<N,A> outValues; /** The data flow values at the entry of each node. */ private Map<N,A> inValues; private Table<N, N, A> vals = HashBasedTable.create(); /** The work-list of nodes that still need to be analysed. */ private NavigableSet<N> workList; private LinkedList<Pair<N, N>> workListOfEdges; /** * Creates a new context for phantom method * * @param method */ public Context(M method) { count++; this.id = count; this.method = method; this.inValues = new HashMap<N, A>(); this.outValues = new HashMap<N, A>(); this.analysed = false; this.workList = new TreeSet<N>(); this.workListOfEdges = new LinkedList<Pair<N, N>>(); } /** * Creates a new context for the given method. * * @param method * the method to which this value context belongs * @param cfg * the control-flow graph for the body of <tt>method</tt> * @param reverse * <tt>true</tt> if the analysis is in the reverse direction, and * <tt>false</tt> if the analysis is in the forward direction */ public Context(M method, DirectedGraph<N> cfg, boolean reverse) { // Increment count and set id. count++; this.id = count; // Initialise fields. this.method = method; this.controlFlowGraph = cfg; this.inValues = new HashMap<N,A>(); this.outValues = new HashMap<N,A>(); this.analysed = false; totalNodes = totalNodes + controlFlowGraph.size(); liveNodes = liveNodes + controlFlowGraph.size(); // Now to initialise work-list. First, let's create a total order. @SuppressWarnings("unchecked") List<N> orderedNodes = new SlowPseudoTopologicalOrderer().newList(controlFlowGraph, reverse); // Then a mapping from a N to the position in the order. final Map<N,Integer> numbers = new HashMap<N,Integer>(); int num = 1; for (N N : orderedNodes) { numbers.put(N, num); num++; } // Map the lowest priority to the null N, which is used to aggregate // ENTRY/EXIT flows. numbers.put(null, Integer.MAX_VALUE); // Now, create a sorted set with a comparator created on-the-fly using // the total order. this.workList = new TreeSet<N>(new Comparator<N>() { @Override public int compare(N u, N v) { return numbers.get(u) - numbers.get(v); } }); this.workListOfEdges = new LinkedList<Pair<N, N>>(); } /** * Compares two contexts by their globally unique IDs. * * This functionality is useful in the framework's internal methods * where ordered processing of newer contexts first helps speed up * certain operations. */ @Override public int compareTo(Context<M,N,A> other) { return this.getId() - other.getId(); } /** * Destroys all data flow information associated with the nodes * of this context. */ public void freeMemory() { liveNodes = liveNodes - controlFlowGraph.size(); inValues = null; outValues = null; controlFlowGraph = null; workList = null; freeContexts.add(this); } /** * Returns a reference to the control flow graph of this context's method. * * @return a reference to the control flow graph of this context's method */ public DirectedGraph<N> getControlFlowGraph() { return controlFlowGraph; } /** Returns the total number of contexts created so far. */ public static int getCount() { return count; } /** * Returns a reference to the data flow value at the method entry. * * @return a reference to the data flow value at the method entry */ public A getEntryValue() { return entryValue; } /** * Returns a reference to the data flow value at the method exit. * * @return a reference to the data flow value at the method exit */ public A getExitValue() { return exitValue; } /** * Returns the globally unique identifier of this context. * * @return the globally unique identifier of this context */ public int getId() { return id; } /** * Returns a reference to this context's method. * * @return a reference to this context's method */ public M getMethod() { return method; } public A getEdgeValue(N node, N succ) { return this.vals.get(node, succ); } public void setEdgeValue(N node, N succ, A val) { if (this.vals == null) { this.vals=HashBasedTable.create(); } this.vals.put(node, succ, val); } /** * Gets the data flow value at the exit of the given node. * * @param node a node in the control flow graph * @return the data flow value at the exit of the given node */ public A getValueAfter(N node) { return outValues.get(node); } /** * Gets the data flow value at the entry of the given node. * * @param node a node in the control flow graph * @return the data flow value at the entry of the given node */ public A getValueBefore(N node) { return inValues.get(node); } /** * Returns a reference to this context's work-list. * * @return a reference to this context's work-list */ public NavigableSet<N> getWorkList() { return workList; } public LinkedList<Pair<N, N>> getWorkListOfEdges() { return this.workListOfEdges; } /** * Returns whether or not this context has been analysed at least once. * * @return <tt>true</tt> if the context has been analysed at least once, * or <tt>false</tt> otherwise */ public boolean isAnalysed() { return analysed; } /** * Returns whether the information about individual CFG nodes has * been released. * * @return <tt>true</tt> if the context data has been released */ boolean isFreed() { return inValues == null && outValues == null; } /** * Marks this context as analysed. */ public void markAnalysed() { this.analysed = true; } /** * Sets the entry flow of this context. * * @param entryValue the new data flow value at the method entry */ public void setEntryValue(A entryValue) { this.entryValue = entryValue; } /** * Sets the exit flow of this context. * * @param exitValue the new data flow value at the method exit */ public void setExitValue(A exitValue) { this.exitValue = exitValue; } /** * Sets the data flow value at the exit of the given node. * * @param node a node in the control flow graph * @param value the new data flow at the node exit */ public void setValueAfter(N node, A value) { outValues.put(node, value); } /** * Sets the data flow value at the entry of the given node. * * @param node a node in the control flow graph * @param value the new data flow at the node entry */ public void setValueBefore(N node, A value) { inValues.put(node, value); } /** {@inheritDoc} */ @Override public String toString() { return Integer.toString(id); } public void unmarkAnalysed() { this.analysed = false; } }