/**
* 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.LinkedList;
import java.util.List;
import java.util.Set;
/**
* A generic backward-flow inter-procedural analysis which is fully
* context-sensitive.
*
* <p>
* This class essentially captures a backward 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
* backward-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
*/
public abstract class BackwardInterProceduralAnalysis<M,N,A> extends InterProceduralAnalysis<M,N,A> {
/** Constructs a new backward-flow inter-procedural analysis. */
public BackwardInterProceduralAnalysis() {
// Kick-up to the super with the BACKWARD direction.
super(true);
}
/**
* {@inheritDoc}
*/
@Override
public void doAnalysis() {
// Initial contexts
for (M method : programRepresentation().getEntryPoints()) {
initContext(method, boundaryValue(method));
}
// Perform work-list based analysis
while (!worklist.isEmpty()) {
// Get the newest context on the work-list
Context<M,N,A> currentContext = worklist.last();
// If this context has no more nodes to analyze, then take it out of the work-list
if (currentContext.getWorkList().isEmpty()) {
worklist.remove(currentContext);
continue;
}
// Remove the next node to process from the context's work-list
N node = currentContext.getWorkList().pollFirst();
if (node != null) {
// Compute the OUT data flow value (only for non-exit units).
List<N> successors = currentContext.getControlFlowGraph().getSuccsOf(node);
if (successors.size() != 0) {
// Initialise to the TOP value
A out = topValue();
// Merge IN values of all successors
for (N succ : successors) {
A succIn = currentContext.getValueBefore(succ);
out = meet(out, succIn);
}
// Set the OUT value at the node to the result
currentContext.setValueAfter(node, out);
}
// Store the value of IN before the flow function is processed.
A prevIn = currentContext.getValueBefore(node);
// Get the value of OUT
A out = currentContext.getValueAfter(node);
//System.out.println("OUT(" + node + ") = " + out);
// Now to compute the IN value
A in;
// Handle flow functions depending on whether this is a call statement or not
if (programRepresentation().isCall(node)) {
in = topValue();
boolean hit = false;
for (M targetMethod : programRepresentation().resolveTargets(currentContext.getMethod(), node)) {
A exitValue = callExitFlowFunction(currentContext, targetMethod, node, out);
CallSite<M,N,A> callSite = new CallSite<M,N,A>(currentContext, node);
// Check if the called method has a context associated with this exit flow:
Context<M,N,A> targetContext = getContext(targetMethod, exitValue);
// If not, then set 'targetContext' to a new context with the given exit flow.
if (targetContext == null) {
targetContext = initContext(targetMethod, exitValue);
if (verbose) {
System.out.println("[NEW] X" + currentContext + " -> X" + targetContext + " " + targetMethod + " ");
}
//System.out.println("EXIT(X"+targetContext+") = " + exitValue);
}
// Store the transition from the calling context and site to the called context.
contextTransitions.addTransition(callSite, targetContext);
// Check if the target context has been analysed (surely not if it is just newly made):
if (targetContext.isAnalysed()) {
hit = true;
if (verbose) {
System.out.println("[HIT] X" + currentContext + " -> X" + targetContext + " " + targetMethod + " ");
}
A entryValue = targetContext.getEntryValue();
//System.out.println("ENTRY(X"+targetContext+") = " + entryValue);
A callValue = callEntryFlowFunction(currentContext, targetMethod, node, entryValue);
in = meet(in, callValue);
}
}
// If there was at least one hit, continue propagation
if (hit) {
A localValue = callLocalFlowFunction(currentContext, node, out);
in = meet(in, localValue);
}
} else {
in = normalFlowFunction(currentContext, node, out);
}
//System.out.println("IN(" + node + ") = " + in);
//System.out.println("---------------------------------------");
// Merge with previous IN to force monotonicity (harmless if flow functions are monotonic)
in = meet(in, prevIn);
// Set the IN value
currentContext.setValueBefore(node, in);
// If IN has changed...
if (in.equals(prevIn) == false) {
// Then add predecessors to the work-list.
for (N predecessors : currentContext.getControlFlowGraph().getPredsOf(node)) {
currentContext.getWorkList().add(predecessors);
}
}
// If the unit is in HEADS, then we have at least one
// path to the start of the method, so add the NULL unit
if (currentContext.getControlFlowGraph().getHeads().contains(node)) {
currentContext.getWorkList().add(null);
}
} else {
// NULL unit, which means the end of the method.
assert (currentContext.getWorkList().isEmpty());
// Entry value is the merge of the INs of the head nodes.
A entryValue = topValue();
for (N headNode : currentContext.getControlFlowGraph().getHeads()) {
A headIn = currentContext.getValueBefore(headNode);
entryValue = meet(entryValue, headIn);
}
// Set the entry value of the context.
currentContext.setEntryValue(entryValue);
// Mark this context as analysed at least once.
currentContext.markAnalysed();
// Add callers to work-list, if any
Set<CallSite<M,N,A>> callers = contextTransitions.getCallers(currentContext);
if (callers != null) {
for (CallSite<M,N,A> callSite : callers) {
// Extract the calling context and node from the caller site.
Context<M,N,A> callingContext = callSite.getCallingContext();
N callNode = callSite.getCallNode();
// Add the calling unit to the calling context's node work-list.
callingContext.getWorkList().add(callNode);
// Ensure that the calling context is on the context work-list.
worklist.add(callingContext);
}
}
// Free memory on-the-fly if not needed
if (freeResultsOnTheFly) {
Set<Context<M,N,A>> reachableContexts = contextTransitions.reachableSet(currentContext, true);
// If any reachable contexts exist on the work-list, then we cannot free memory
boolean canFree = true;
for (Context<M,N,A> reachableContext : reachableContexts) {
if (worklist.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();
}
}
}
}
}
// 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 value context and initialises data flow values for its nodes.
*
* <p>
* The following steps are performed:
* <ol>
* <li>Construct the context.</li>
* <li>Initialise IN/OUT for all nodes and add them to the work-list</li>
* <li>Initialise the OUT of exit points with a copy of the given exit value.</li>
* <li>Add this new context to the given method's mapping.</li>
* <li>Add this context to the global work-list.</li>
* </ol>
* </p>
*
* @param method the method whose context to create
* @param exitValue the data flow value at the exit of this method
*/
protected Context<M,N,A> initContext(M method, A exitValue) {
// Construct the context
Context<M,N,A> context = new Context<M,N,A>(method, programRepresentation().getControlFlowGraph(method), true);
// Initialise IN/OUT for all nodes and add them to the work-list
for (N unit : context.getControlFlowGraph()) {
context.setValueBefore(unit, topValue());
context.setValueAfter(unit, topValue());
context.getWorkList().add(unit);
}
// Now, initialise the OUT of exit points with a copy of the given exit value.
context.setExitValue(copy(exitValue));
for (N unit : context.getControlFlowGraph().getTails()) {
context.setValueAfter(unit, copy(exitValue));
}
context.setEntryValue(topValue());
// 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);
// Add this context to the global work-list
worklist.add(context);
return context;
}
/**
* Processes the intra-procedural flow function of a statement that does
* not contain a method call.
*
* @param context the value context at the call-site
* @param node the statement whose flow function to process
* @param outValue the data flow value after the statement
* @return the data flow value before the statement
*/
public abstract A normalFlowFunction(Context<M,N,A> context, N node, A outValue);
/**
* Processes the inter-procedural flow function for a method call at
* the start of the call, to handle parameters.
*
* @param context the value context at the call-site
* @param targetMethod the target (or one of the targets) of this call site
* @param node the statement containing the method call
* @param entryValue the data flow value at the entry to the called procedure
* @return the data flow value before the call (call component)
*/
public abstract A callEntryFlowFunction(Context<M,N,A> context, M targetMethod, N node, A entryValue);
/**
* Processes the inter-procedural flow function for a method call at the
* end of the call, to handle return values.
*
* @param context the value context at the call-site
* @param targetMethod the target (or one of the targets) of this call site
* @param node the statement containing the method call
* @param outValue the data flow value after the call
* @return the data flow value at the exit of the called procedure
*/
public abstract A callExitFlowFunction(Context<M,N,A> context, M targetMethod, N node, A outValue);
/**
*
* Processes the intra-procedural flow function for a method call at the
* call-site itself, to handle propagation of local values that are not
* involved in the call.
*
* @param context the value context at the call-site
* @param node the statement containing the method call
* @param outValue the data flow value after the call
* @return the data flow value before the call (local component)
*/
public abstract A callLocalFlowFunction(Context<M,N,A> context, N node, A outValue);
}