/**
* 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.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
/**
* A generic inter-procedural analysis which is fully context-sensitive.
*
* <p>
* This class is a base for forward and backward inter-procedural analysis
* classes. This inter-procedural analysis framework is fully context
* sensitive even in the presence of recursion and uses data flow values
* reaching a method to distinguish contexts.
* </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
*
* @see Context
* @see ContextTransitionTable
* @see CallSite
*/
public abstract class InterProceduralAnalysis<M,N,A> {
/** A work-list of contexts to process. */
protected final NavigableSet<Context<M,N,A>> worklist;
/** A mapping from methods to a list of contexts for quick lookups. */
protected final Map<M,List<Context<M,N,A>>> contexts;
/**
* A record of transitions from calling context and call-site to
* called method and called context.
*/
protected final ContextTransitionTable<M,N,A> contextTransitions;
/**
* <tt>true</tt> if the direction of analysis is backward, or <tt>false</tt>
* if it is forward.
*/
protected final boolean reverse;
/**
* A flag, if set, directs the analysis to free memory storing
* data flow values of individual statements once a context has
* been analysed and would not be required to be re-analysed.
*
* <p>This setting is only useful for analyses that aggregate secondary
* results on the fly (e.g. call graph analysis that can do away with
* points-to information once the calls for a particular context have
* been resolved). This is not safe for use in analyses whose results
* will be directly used later (e.g. liveness analysis).</p>
*
* <p>Memory is freed when a context is removed from the work-list and no context
* reachable from it in the transition table is also on the work-list. This
* ensures that the removed context will not be added again on the work-list
* for re-analysis of any statement.</p>
*
* <p>Note that the data flow values at the entry/exit of the context are
* not freed, and hence it is still used to terminate recursion or as a cache
* hit for arbitrary call sites with same values.</p>
*
* <p>The default value for this flag is <tt>false</tt>.</p>
*
*/
protected boolean freeResultsOnTheFly;
/**
* Whether to print information about contexts.
*/
protected boolean verbose;
/**
* Constructs a new inter-procedural analysis.
*
* @param reverse <tt>true</tt> if the analysis is in the reverse direction,
* <tt>false</tt> if it is in the forward direction
*/
public InterProceduralAnalysis(boolean reverse) {
// Set direction
this.reverse = reverse;
// Initialise map of methods to contexts.
contexts = new HashMap<M,List<Context<M,N,A>>>();
// Initialise context transition table
contextTransitions = new ContextTransitionTable<M,N,A>();
// Initialise the work-list
worklist = new TreeSet<Context<M,N,A>>();
}
/**
* Returns the initial data flow value at the program entry points. For
* forward analyses this is the IN value at the ENTRY to each entry method,
* while for backward analyses this is the OUT value at the EXIT to each
* entry method.
*
* <p>Note that this method will be called exactly once per entry point
* specified by the program representation.</p>
*
* @param entryPoint an entry point specified by the program representation
* @return the data flow value at the boundary
*
* @see ProgramRepresentation#getEntryPoints()
*/
public abstract A boundaryValue(M entryPoint);
/**
* Returns a copy of the given data flow value.
*
* @param src the data flow value to copy
* @return a new data flow value which is a copy of the argument
*/
public abstract A copy(A src);
/**
* Performs the actual data flow analysis.
*
* <p>
* A work-list of contexts is maintained, each with it's own work-list of CFG nodes
* to process. For each node removed from the work-list of the newest context,
* the meet of values along incoming edges (in the direction of analysis) is computed and
* then the flow function is processed depending on whether the node contains a call
* or not. If the resulting data flow value has changed, then nodes along outgoing edges
* (in the direction of analysis) are also added to the work-list. </p>
*
* <p>
* Analysis starts with the context for the program entry points with the given
* boundary values and ends when the work-list is empty.
* </p>
*
* <p>See the SOAP '13 paper for the full algorithm in Figure 1.</p>
*
*/
public abstract void doAnalysis();
/**
* Returns the callers of a value context.
*
* @param target the value context
* @return the call-sites which transition to the value context
*/
public Set<CallSite<M,N,A>> getCallers(Context<M,N,A> target) {
return this.contextTransitions.getCallers(target);
}
/**
* Retrieves a particular value context if it has been constructed.
*
* @param method the method whose value context to find
* @param value the data flow value at the entry (forward flow) or exit
* (backward flow) of the method
* @return the value context, if one is found with the given parameters,
* or <tt>null</tt> otherwise
*/
public Context<M,N,A> getContext(M method, A value) {
// If this method does not have any contexts, then we'll have to return nothing.
if (!contexts.containsKey(method)) {
return null;
}
// Otherwise, look for a context in this method's list with the given value.
if (reverse) {
// Backward flow, so check for EXIT FLOWS
for (Context<M,N,A> context : contexts.get(method)) {
if (value.equals(context.getExitValue())) {
return context;
}
}
} else {
// Forward flow, so check for ENTRY FLOWS
for (Context<M,N,A> context : contexts.get(method)) {
if (value.equals(context.getEntryValue())) {
return context;
}
}
}
// If nothing found return null.
return null;
}
/**
* Returns a list of value contexts constructed for a given method.
*
* @param method the method whose contexts to retrieve
* @return an unmodifiable list of value contexts of the given method
*/
public List<Context<M,N,A>> getContexts(M method) {
if (contexts.containsKey(method)) {
return Collections.unmodifiableList(contexts.get(method));
} else {
return Collections.unmodifiableList(new LinkedList<Context<M,N,A>>());
}
}
/**
* Returns a reference to the context transition table used by this analysis.
*
* @return a reference to the context transition table used by this analysis
*/
public ContextTransitionTable<M,N,A> getContextTransitionTable() {
return contextTransitions;
}
/**
* Returns a meet-over-valid-paths solution by merging data flow
* values across contexts for each program point.
*
* <p>This method should not be invoked if the flag
* {@link #freeResultsOnTheFly} had been set during analysis.</p>
*
* @return a meet-over-valid-paths data flow solution
*/
public DataFlowSolution<N,A> getMeetOverValidPathsSolution() {
Map<N,A> inValues = new HashMap<N,A>();
Map<N,A> outValues = new HashMap<N,A>();
// Merge over all contexts
for (M method : contexts.keySet()) {
for (N node : programRepresentation().getControlFlowGraph(method)) {
A in = topValue();
A out = topValue();
for (Context<M,N,A> context : contexts.get(method)) {
in = meet(in, context.getValueBefore(node));
out = meet(out, context.getValueAfter(node));
}
inValues.put(node, in);
outValues.put(node, out);
}
}
// Return data flow solution
return new DataFlowSolution<N,A>(inValues, outValues);
}
/**
* Returns all methods for which at least one context was created.
* @return an unmodifiable set of analysed methods
*/
public Set<M> getMethods() {
return Collections.unmodifiableSet(contexts.keySet());
}
/**
* Returns the target of a call-site.
*
* @param callSite the call-site whose targets to retrieve
* @return a map of target methods to their respective contexts
*/
public Map<M,Context<M,N,A>> getTargets(CallSite<M,N,A> callSite) {
return this.contextTransitions.getTargets(callSite);
}
/**
* Returns the meet of two data flow values.
*
* @param op1 the first operand
* @param op2 the second operand
* @return a new data flow which is the result of the meet operation of the
* two operands
*/
public abstract A meet(A op1, A op2);
/**
* Returns a program representation on top of which the inter-procedural
* analysis runs. The program representation is used to build control
* flow graphs of individual procedures and resolve targets of virtual
* call sites.
*
* @return The program representation underlying this analysis
*/
public abstract ProgramRepresentation<M,N> programRepresentation();
/**
* Returns the default data flow value (lattice top).
*
* @return the default data flow value (lattice top)
*/
public abstract A topValue();
}