/**
* 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.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;
/**
* A generic forward-flow inter-procedural analysis which is fully context-sensitive.
*
* <p>
* This class essentially captures a forward data flow problem which can be
* solved using the context-sensitive inter-procedural analysis framework as
* described in {@link InterProceduralAnalysis}.
* </p>
*
* <p>
* This is the class that client analyses will extend in order to perform
* forward-flow inter-procedural analysis.
* </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
*
* @deprecated This is the old API from the initial SOAP '13 submission without call/return flow functions.
* It is only here for a temporary period while the {@link vasco.callgraph.PointsToAnalysis PointsToAnalysis} class is migrated to the new API.
* After that work is done, this class will be permanently removed from VASCO.
*/
public abstract class OldForwardInterProceduralAnalysis<M,N,A> extends InterProceduralAnalysis<M,N,A> {
/** Constructs a new forward-flow inter-procedural analysis. */
public OldForwardInterProceduralAnalysis() {
// Kick-up to the super with the FORWARD direction.
super(false);
analysisStack = new Stack<Context<M,N,A>>();
}
protected Stack<Context<M,N,A>> analysisStack;
/**
* {@inheritDoc}
*/
@Override
public void doAnalysis() {
// Initialise the MAIN context
for (M entryPoint : programRepresentation().getEntryPoints()) {
Context<M,N,A> context = new Context<M,N,A>(entryPoint, programRepresentation().getControlFlowGraph(entryPoint), false);
A boundaryInformation = boundaryValue(entryPoint);
initContext(context, boundaryInformation);
}
// Stack-of-work-lists data flow analysis.
while (!analysisStack.isEmpty()) {
// Get the context at the top of the stack.
Context<M,N,A> context = analysisStack.peek();
// Either analyse the next pending unit or pop out of the method
if (!context.getWorkList().isEmpty()) {
// work-list contains items; So the next unit to analyse.
N unit = context.getWorkList().pollFirst();
if (unit != null) {
// Compute the IN data flow value (only for non-entry units).
List<N> predecessors = context.getControlFlowGraph().getPredsOf(unit);
if (predecessors.size() != 0) {
// Merge all the OUT values of the predecessors
Iterator<N> predIterator = predecessors.iterator();
// Initialise IN to the OUT value of the first predecessor
A in = context.getValueAfter(predIterator.next());
// Then, merge OUT of remaining predecessors with the
// intermediate IN value
while (predIterator.hasNext()) {
A predOut = context.getValueAfter(predIterator.next());
in = meet(in, predOut);
}
// Set the IN value at the context
context.setValueBefore(unit, in);
}
// Store the value of OUT before the flow function is processed.
A prevOut = context.getValueAfter(unit);
// Get the value of IN
A in = context.getValueBefore(unit);
// Now perform the flow function.
A out = flowFunction(context, unit, in);
// If the result is null, then no change
if (out == null)
out = prevOut;
// Set the OUT value
context.setValueAfter(unit, out);
// If the flow function was applied successfully and the OUT changed...
if (out.equals(prevOut) == false) {
// Then add successors to the work-list.
for (N successor : context.getControlFlowGraph().getSuccsOf(unit)) {
context.getWorkList().add(successor);
}
// If the unit is in TAILS, then we have at least one
// path to the end of the method, so add the NULL unit
if (context.getControlFlowGraph().getTails().contains(unit)) {
context.getWorkList().add(null);
}
}
} else {
// NULL unit, which means the end of the method.
assert (context.getWorkList().isEmpty());
// Exit flow value is the merge of the OUTs of the tail nodes.
A exitFlow = topValue();
for (N tail : context.getControlFlowGraph().getTails()) {
A tailOut = context.getValueAfter(tail);
exitFlow = meet(exitFlow, tailOut);
}
// Set the exit flow of the context.
context.setExitValue(exitFlow);
// Mark this context as analysed at least once.
context.markAnalysed();
// Add return nodes to stack (only if there were callers).
Set<CallSite<M,N,A>> callersSet = contextTransitions.getCallers(context);
if (callersSet != null) {
List<CallSite<M,N,A>> callers = new LinkedList<CallSite<M,N,A>>(callersSet);
// Sort the callers in ascending order of their ID so that
// the largest ID is on top of the stack
Collections.sort(callers);
for (CallSite<M,N,A> callSite : callers) {
// Extract the calling context and unit from the caller site.
Context<M,N,A> callingContext = callSite.getCallingContext();
N callingNode = callSite.getCallNode();
// Add the calling unit to the calling context's work-list.
callingContext.getWorkList().add(callingNode);
// Ensure that the calling context is on the analysis stack,
// and if not, push it on to the stack.
if (!analysisStack.contains(callingContext)) {
analysisStack.push(callingContext);
}
}
}
// Free memory on-the-fly if not needed
if (freeResultsOnTheFly) {
Set<Context<M,N,A>> reachableContexts = contextTransitions.reachableSet(context, true);
// If any reachable contexts exist on the stack, then we cannot free memory
boolean canFree = true;
for (Context<M,N,A> reachableContext : reachableContexts) {
if (analysisStack.contains(reachableContext)) {
canFree = false;
break;
}
}
// If no reachable contexts on the stack, then free memory associated
// with this context
if (canFree) {
for (Context<M,N,A> reachableContext : reachableContexts) {
reachableContext.freeMemory();
}
}
}
}
} else {
// If work-list is empty, then remove it from the analysis.
analysisStack.remove(context);
}
}
// Sanity check
for (List<Context<M,N,A>> contextList : contexts.values()) {
for (Context<M,N,A> context : contextList) {
if (context.isAnalysed() == false) {
System.err.println("*** ATTENTION ***: Only partial analysis of X" + context +
" " + context.getMethod());
}
}
}
}
/**
* Creates a new context and initialises data flow values.
*
* <p>
* The following steps are performed:
* <ol>
* <li>Initialise all nodes to default flow value (lattice top).</li>
* <li>Initialise the entry nodes (heads) with a copy of the entry value.</li>
* <li>Add entry points to work-list.</li>
* <li>Push this context on the top of the analysis stack.</li>
* </ol>
* </p>
*
* @param context the context to initialise
* @param entryValue the data flow value at the entry of this method
*/
protected void initContext(Context<M,N,A> context, A entryValue) {
// Get the method
M method = context.getMethod();
// First initialise all points to default flow value.
for (N unit : context.getControlFlowGraph()) {
context.setValueBefore(unit, topValue());
context.setValueAfter(unit, topValue());
}
// Now, initialise entry points with a copy of the given entry flow.
context.setEntryValue(copy(entryValue));
for (N unit : context.getControlFlowGraph().getHeads()) {
context.setValueBefore(unit, copy(entryValue));
// Add entry points to work-list
context.getWorkList().add(unit);
}
// Add this new context to the given method's mapping.
if (!contexts.containsKey(method)) {
contexts.put(method, new LinkedList<Context<M,N,A>>());
}
contexts.get(method).add(context);
// Push this context on the top of the analysis stack.
analysisStack.add(context);
}
/**
* Processes a call statement.
*
* <p>
* Retrieves a value context for the callee if one exists with the given
* entry value, or else creates a new one and adds the transition to
* the context transition table.
* </p>
*
* <p>
* If the callee context has already been analysed, returns the resulting
* exit value. For newly created contexts the result would be <tt>null</tt>,
* as they are obviously not analysed even once.
* </p>
*
* <p>
* Note that this method is not directly called by {@link #doAnalysis() doAnalysis}, but
* is instead called by {@link #flowFunction(Context, Object, Object) flowFunction} when a method
* call statement is encountered. The reason for this design decision is
* that client analyses may want their own setup and tear down sequences
* before a call is made (similar to edge flow functions at the call and
* return site). Also, analyses may want to choose which method call to
* process at an invoke statement in the case of virtual calls (e.g. a
* points-to analysis may build the call-graph on-the-fly).
* </p>
*
* <p>Therefore, it is the responsibility of the client analysis to detect
* an invoke expression when implementing {@link #flowFunction(Context, Object, Object) flowFunction},
* and suitably invoke {@link #processCall(Context, Object, Object, Object) processCall} with
* the input data flow value which may be different from the IN/OUT value of the node due to
* handling of arguments, etc. Similarly, the result of {@link #processCall(Context, Object, Object, Object) processCall}
* may be modified by the client to handle return values, etc. before returning from {@link #flowFunction(Context, Object, Object) flowFunction}.
* Ideally, {@link #flowFunction(Context, Object, Object) flowFunction} should return
* <tt>null</tt> if and only if {@link #processCall(Context, Object, Object, Object) processCall}
* returns <tt>null</tt>.
*
* @param callerContext the analysis context at the call-site
* @param callNode the calling statement
* @param method the method being called
* @param entryValue the data flow value at the entry of the called method.
* @return the data flow value at the exit of the called method,
* if available, or <tt>null</tt> if unavailable.
*/
protected A processCall(Context<M,N,A> callerContext, N callNode, M method, A entryValue) {
CallSite<M,N,A> callSite = new CallSite<M,N,A>(callerContext, callNode);
// Check if the called method has a context associated with this entry flow:
Context<M,N,A> calleeContext = getContext(method, entryValue);
// If not, then set 'calleeContext' to a new context with the given entry flow.
if (calleeContext == null) {
calleeContext = new Context<M,N,A>(method, programRepresentation().getControlFlowGraph(method), false);
initContext(calleeContext, entryValue);
if (verbose) {
System.out.println("[NEW] X" + callerContext + " -> X" + calleeContext + " " + method + " ");
}
}
// Store the transition from the calling context and site to the called context.
contextTransitions.addTransition(callSite, calleeContext);
// Check if 'caleeContext' has been analysed (surely not if it is just newly made):
if (calleeContext.isAnalysed()) {
if (verbose) {
System.out.println("[HIT] X" + callerContext + " -> X" + calleeContext + " " + method + " ");
}
// If yes, then return the 'exitFlow' of the 'calleeContext'.
return calleeContext.getExitValue();
} else {
// If not, then return 'null'.
return null;
}
}
protected abstract A flowFunction(Context<M,N,A> context, N unit, A in);
}