1081 lines
36 KiB
Java
1081 lines
36 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2002 - 2006 IBM Corporation.
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial API and implementation
|
|
*******************************************************************************/
|
|
package com.ibm.wala.dataflow.IFDS;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
import java.util.TreeSet;
|
|
|
|
import com.ibm.wala.cfg.IBasicBlock;
|
|
import com.ibm.wala.util.CancelException;
|
|
import com.ibm.wala.util.CancelRuntimeException;
|
|
import com.ibm.wala.util.MonitorUtil;
|
|
import com.ibm.wala.util.MonitorUtil.IProgressMonitor;
|
|
import com.ibm.wala.util.collections.HashMapFactory;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.collections.Heap;
|
|
import com.ibm.wala.util.collections.Iterator2Collection;
|
|
import com.ibm.wala.util.collections.Iterator2Iterable;
|
|
import com.ibm.wala.util.collections.MapUtil;
|
|
import com.ibm.wala.util.collections.ToStringComparator;
|
|
import com.ibm.wala.util.heapTrace.HeapTracer;
|
|
import com.ibm.wala.util.intset.IntIterator;
|
|
import com.ibm.wala.util.intset.IntSet;
|
|
import com.ibm.wala.util.intset.IntSetAction;
|
|
import com.ibm.wala.util.intset.MutableIntSet;
|
|
import com.ibm.wala.util.intset.MutableSparseIntSet;
|
|
import com.ibm.wala.util.ref.ReferenceCleanser;
|
|
|
|
/**
|
|
* A precise interprocedural tabulation solver.
|
|
* <p>
|
|
* See Reps, Horwitz, Sagiv POPL 95.
|
|
* <p>
|
|
* This version differs in some ways from the POPL algorithm. In particular ...
|
|
* <ul>
|
|
* <li>to support exceptional control flow ... there may be several return sites for each call site.
|
|
* <li>it supports an optional merge operator, useful for non-IFDS problems and widening.
|
|
* <li>it stores summary edges at each callee instead of at each call site.
|
|
* </ul>
|
|
* <p>
|
|
*
|
|
* @param <T> type of node in the supergraph
|
|
* @param <P> type of a procedure (like a box in an RSM)
|
|
* @param <F> type of factoids propagated when solving this problem
|
|
*/
|
|
public class TabulationSolver<T, P, F> {
|
|
|
|
/**
|
|
* DEBUG_LEVEL:
|
|
* <ul>
|
|
* <li>0 No output
|
|
* <li>1 Print some simple stats and warning information
|
|
* <li>2 Detailed debugging
|
|
* <li>3 Also print worklists
|
|
* </ul>
|
|
*/
|
|
protected static final int DEBUG_LEVEL = 0;
|
|
|
|
static protected final boolean verbose = true && ("true".equals(System.getProperty("com.ibm.wala.fixedpoint.impl.verbose")) ? true
|
|
: false);
|
|
|
|
static final int VERBOSE_INTERVAL = 1000;
|
|
|
|
static final boolean VERBOSE_TRACE_MEMORY = false;
|
|
|
|
private static int verboseCounter = 0;
|
|
|
|
/**
|
|
* Should we periodically clear out soft reference caches in an attempt to help the GC?
|
|
*/
|
|
final protected static boolean PERIODIC_WIPE_SOFT_CACHES = true;
|
|
|
|
/**
|
|
* Interval which defines the period to clear soft reference caches
|
|
*/
|
|
private final static int WIPE_SOFT_CACHE_INTERVAL = 1000000;
|
|
|
|
/**
|
|
* Counter for wiping soft caches
|
|
*/
|
|
private static int wipeCount = WIPE_SOFT_CACHE_INTERVAL;
|
|
|
|
/**
|
|
* The supergraph which induces this dataflow problem
|
|
*/
|
|
protected final ISupergraph<T, P> supergraph;
|
|
|
|
/**
|
|
* A map from an edge in a supergraph to a flow function
|
|
*/
|
|
protected final IFlowFunctionMap<T> flowFunctionMap;
|
|
|
|
/**
|
|
* The problem being solved.
|
|
*/
|
|
private final TabulationProblem<T, P, F> problem;
|
|
|
|
/**
|
|
* A map from Object (entry node in supergraph) -> LocalPathEdges.
|
|
*
|
|
* Logically, this represents a set of edges (s_p,d_i) -> (n, d_j). The data structure is chosen to attempt to save space over
|
|
* representing each edge explicitly.
|
|
*/
|
|
final private Map<T, LocalPathEdges> pathEdges = HashMapFactory.make();
|
|
|
|
/**
|
|
* A map from Object (entry node in supergraph) -> CallFlowEdges.
|
|
*
|
|
* Logically, this represents a set of edges (c,d_i) -> (s_p, d_j). The data structure is chosen to attempt to save space over
|
|
* representing each edge explicitly.
|
|
*/
|
|
final private Map<T, CallFlowEdges> callFlowEdges = HashMapFactory.make();
|
|
|
|
/**
|
|
* A map from Object (procedure) -> LocalSummaryEdges.
|
|
*
|
|
*/
|
|
final protected Map<P, LocalSummaryEdges> summaryEdges = HashMapFactory.make();
|
|
|
|
/**
|
|
* the set of all {@link PathEdge}s that were used as seeds during the tabulation, grouped by procedure.
|
|
*/
|
|
private final Map<P, Set<PathEdge<T>>> seeds = HashMapFactory.make();
|
|
|
|
/**
|
|
* All seeds, stored redundantly for quick access.
|
|
*/
|
|
private final Set<PathEdge<T>> allSeeds = HashSetFactory.make();
|
|
|
|
/**
|
|
* The worklist
|
|
*/
|
|
private ITabulationWorklist<T> worklist;
|
|
|
|
/**
|
|
* A progress monitor. can be null.
|
|
*/
|
|
protected final IProgressMonitor progressMonitor;
|
|
|
|
/**
|
|
* the path edge currently being processed in the main loop of {@link #forwardTabulateSLRPs()}; <code>null</code> if
|
|
* {@link #forwardTabulateSLRPs()} is not currently running. Note that if we are applying a summary edge in
|
|
* {@link #processExit(PathEdge)}, curPathEdge is modified to be the path edge terminating at the call node in the caller, to
|
|
* match the behavior in {@link #processCall(PathEdge)}.
|
|
*/
|
|
private PathEdge<T> curPathEdge;
|
|
|
|
/**
|
|
* the summary edge currently being applied in {@link #processCall(PathEdge)} or {@link #processExit(PathEdge)}, or
|
|
* <code>null</code> if summary edges are not currently being processed.
|
|
*/
|
|
private PathEdge<T> curSummaryEdge;
|
|
|
|
/**
|
|
* @param p a description of the dataflow problem to solve
|
|
* @throws IllegalArgumentException if p is null
|
|
*/
|
|
protected TabulationSolver(TabulationProblem<T, P, F> p, IProgressMonitor monitor) {
|
|
if (p == null) {
|
|
throw new IllegalArgumentException("p is null");
|
|
}
|
|
this.supergraph = p.getSupergraph();
|
|
this.flowFunctionMap = p.getFunctionMap();
|
|
this.problem = p;
|
|
this.progressMonitor = monitor;
|
|
}
|
|
|
|
/**
|
|
* Subclasses can override this to plug in a different worklist implementation.
|
|
*/
|
|
protected ITabulationWorklist<T> makeWorklist() {
|
|
return new Worklist();
|
|
}
|
|
|
|
/**
|
|
* @param p a description of the dataflow problem to solve
|
|
* @throws IllegalArgumentException if p is null
|
|
*/
|
|
public static <T, P, F> TabulationSolver<T, P, F> make(TabulationProblem<T, P, F> p) {
|
|
return new TabulationSolver<>(p, null);
|
|
}
|
|
|
|
/**
|
|
* Solve the dataflow problem.
|
|
*
|
|
* @return a representation of the result
|
|
*/
|
|
public TabulationResult<T, P, F> solve() throws CancelException {
|
|
|
|
try {
|
|
initialize();
|
|
forwardTabulateSLRPs();
|
|
Result r = new Result();
|
|
return r;
|
|
} catch (CancelException e) {
|
|
// store a partially-tabulated result in the thrown exception.
|
|
Result r = new Result();
|
|
throw new TabulationCancelException(e, r);
|
|
} catch (CancelRuntimeException e) {
|
|
// store a partially-tabulated result in the thrown exception.
|
|
Result r = new Result();
|
|
throw new TabulationCancelException(e, r);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start tabulation with the initial seeds.
|
|
*/
|
|
protected void initialize() {
|
|
for (PathEdge<T> seed : problem.initialSeeds()) {
|
|
addSeed(seed);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restart tabulation from a particular path edge. Use with care.
|
|
*/
|
|
public void addSeed(PathEdge<T> seed) {
|
|
Set<PathEdge<T>> s = MapUtil.findOrCreateSet(seeds, supergraph.getProcOf(seed.entry));
|
|
s.add(seed);
|
|
allSeeds.add(seed);
|
|
propagate(seed.entry, seed.d1, seed.target, seed.d2);
|
|
}
|
|
|
|
/**
|
|
* See POPL 95 paper for this algorithm, Figure 3
|
|
*
|
|
* @throws CancelException
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private void forwardTabulateSLRPs() throws CancelException {
|
|
assert curPathEdge == null : "curPathEdge should not be non-null here";
|
|
if (worklist == null) {
|
|
worklist = makeWorklist();
|
|
}
|
|
while (worklist.size() > 0) {
|
|
MonitorUtil.throwExceptionIfCanceled(progressMonitor);
|
|
if (verbose) {
|
|
performVerboseAction();
|
|
}
|
|
if (PERIODIC_WIPE_SOFT_CACHES) {
|
|
tendToSoftCaches();
|
|
}
|
|
|
|
final PathEdge<T> edge = popFromWorkList();
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("TABULATE " + edge);
|
|
}
|
|
curPathEdge = edge;
|
|
int j = merge(edge.entry, edge.d1, edge.target, edge.d2);
|
|
if (j == -1 && DEBUG_LEVEL > 0) {
|
|
System.err.println("merge -1: DROPPING");
|
|
}
|
|
if (j != -1) {
|
|
if (j != edge.d2) {
|
|
// this means that we don't want to push the edge. instead,
|
|
// we'll push the merged fact. a little tricky, but i think should
|
|
// work.
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("propagating merged fact " + j);
|
|
}
|
|
propagate(edge.entry, edge.d1, edge.target, j);
|
|
} else {
|
|
if (supergraph.isCall(edge.target)) {
|
|
// [13]
|
|
processCall(edge);
|
|
} else if (supergraph.isExit(edge.target)) {
|
|
// [21]
|
|
processExit(edge);
|
|
} else {
|
|
// [33]
|
|
processNormal(edge);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
curPathEdge = null;
|
|
}
|
|
|
|
/**
|
|
* For some reason (either a bug in our code that defeats soft references, or a bad policy in the GC), leaving soft reference
|
|
* caches to clear themselves out doesn't work. Help it out.
|
|
*
|
|
* It's unfortunate that this method exits.
|
|
*/
|
|
protected void tendToSoftCaches() {
|
|
wipeCount++;
|
|
if (wipeCount > WIPE_SOFT_CACHE_INTERVAL) {
|
|
wipeCount = 0;
|
|
ReferenceCleanser.clearSoftCaches();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
protected final void performVerboseAction() {
|
|
verboseCounter++;
|
|
if (verboseCounter % VERBOSE_INTERVAL == 0) {
|
|
System.err.println("Tabulation Solver " + verboseCounter);
|
|
System.err.println(" " + peekFromWorkList());
|
|
if (VERBOSE_TRACE_MEMORY) {
|
|
ReferenceCleanser.clearSoftCaches();
|
|
System.err.println("Analyze leaks..");
|
|
HeapTracer.traceHeap(Collections.singleton(this), true);
|
|
System.err.println("done analyzing leaks");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle lines [33-37] of the algorithm
|
|
*
|
|
* @param edge
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private void processNormal(final PathEdge<T> edge) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("process normal: " + edge);
|
|
}
|
|
for (T m : Iterator2Iterable.make(supergraph.getSuccNodes(edge.target))) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("normal successor: " + m);
|
|
}
|
|
IUnaryFlowFunction f = flowFunctionMap.getNormalFlowFunction(edge.target, m);
|
|
IntSet D3 = computeFlow(edge.d2, f);
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println(" reached: " + D3);
|
|
}
|
|
if (D3 != null) {
|
|
D3.foreach(d3 -> {
|
|
newNormalExplodedEdge(edge, m, d3);
|
|
propagate(edge.entry, edge.d1, m, d3);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle lines [21 - 32] of the algorithm, propagating information from an exit node.
|
|
*
|
|
* Note that we've changed the way we record summary edges. Summary edges are now associated with a callee (s_p,exit), where the
|
|
* original algorithm used a call, return pair in the caller.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected void processExit(final PathEdge<T> edge) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("process exit: " + edge);
|
|
}
|
|
|
|
final LocalSummaryEdges summaries = findOrCreateLocalSummaryEdges(supergraph.getProcOf(edge.target));
|
|
int s_p_n = supergraph.getLocalBlockNumber(edge.entry);
|
|
int x = supergraph.getLocalBlockNumber(edge.target);
|
|
if (!summaries.contains(s_p_n, x, edge.d1, edge.d2)) {
|
|
summaries.insertSummaryEdge(s_p_n, x, edge.d1, edge.d2);
|
|
}
|
|
assert curSummaryEdge == null : "curSummaryEdge should be null here";
|
|
curSummaryEdge = edge;
|
|
|
|
final CallFlowEdges callFlow = findOrCreateCallFlowEdges(edge.entry);
|
|
|
|
// [22] for each c /in callers(p)
|
|
IntSet callFlowSourceNodes = callFlow.getCallFlowSourceNodes(edge.d1);
|
|
if (callFlowSourceNodes != null) {
|
|
for (IntIterator it = callFlowSourceNodes.intIterator(); it.hasNext();) {
|
|
// [23] for each d4 s.t. <c,d4> -> <s_p,d1> occurred earlier
|
|
int globalC = it.next();
|
|
final IntSet D4 = callFlow.getCallFlowSources(globalC, edge.d1);
|
|
|
|
// [23] for each d5 s.t. <e_p,d2> -> <returnSite(c),d5> ...
|
|
propagateToReturnSites(edge, supergraph.getNode(globalC), D4);
|
|
}
|
|
}
|
|
curSummaryEdge = null;
|
|
}
|
|
|
|
/**
|
|
* Propagate information for an "exit" edge to the appropriate return sites
|
|
*
|
|
* [23] for each d5 s.t. {@literal <s_p,d2> -> <returnSite(c),d5>} ..
|
|
*
|
|
* @param edge the edge being processed
|
|
* @param succ numbers of the nodes that are successors of edge.n (the return block in the callee) in the call graph.
|
|
* @param c a call site of edge.s_p
|
|
* @param D4 set of d1 s.t. {@literal <c, d1> -> <edge.s_p, edge.d2>} was recorded as call flow
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private void propagateToReturnSites(final PathEdge<T> edge, final T c, final IntSet D4) {
|
|
P proc = supergraph.getProcOf(c);
|
|
final T[] entries = supergraph.getEntriesForProcedure(proc);
|
|
|
|
// we iterate over each potential return site;
|
|
// we might have multiple return sites due to exceptions
|
|
// note that we might have different summary edges for each
|
|
// potential return site, and different flow functions from this
|
|
// exit block to each return site.
|
|
for (T retSite : Iterator2Iterable.make(supergraph.getReturnSites(c, supergraph.getProcOf(edge.target)))) {
|
|
if (DEBUG_LEVEL > 1) {
|
|
System.err.println("candidate return site: " + retSite + " " + supergraph.getNumber(retSite));
|
|
}
|
|
// note: since we might have multiple exit nodes for the callee, (to handle exceptional returns)
|
|
// not every return site might be valid for this exit node (edge.n).
|
|
// so, we'll filter the logic by checking that we only process reachable return sites.
|
|
// the supergraph carries the information regarding the legal successors
|
|
// of the exit node
|
|
if (!supergraph.hasEdge(edge.target, retSite)) {
|
|
continue;
|
|
}
|
|
if (DEBUG_LEVEL > 1) {
|
|
System.err.println("feasible return site: " + retSite);
|
|
}
|
|
final IFlowFunction retf = flowFunctionMap.getReturnFlowFunction(c, edge.target, retSite);
|
|
if (retf instanceof IBinaryReturnFlowFunction) {
|
|
propagateToReturnSiteWithBinaryFlowFunction(edge, c, D4, entries, retSite, retf);
|
|
} else {
|
|
final IntSet D5 = computeFlow(edge.d2, (IUnaryFlowFunction) retf);
|
|
if (DEBUG_LEVEL > 1) {
|
|
System.err.println("D4" + D4);
|
|
System.err.println("D5 " + D5);
|
|
}
|
|
IntSetAction action = d4 -> propToReturnSite(c, entries, retSite, d4, D5, edge);
|
|
D4.foreach(action);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Propagate information for an "exit" edge to a caller return site
|
|
*
|
|
* [23] for each d5 s.t. {@literal <s_p,d2> -> <returnSite(c),d5>} ..
|
|
*
|
|
* @param edge the edge being processed
|
|
* @param c a call site of edge.s_p
|
|
* @param D4 set of d1 s.t. {@literal <c, d1> -> <edge.s_p, edge.d2>} was recorded as call flow
|
|
* @param entries the blocks in the supergraph that are entries for the procedure of c
|
|
* @param retSite the return site being propagated to
|
|
* @param retf the flow function
|
|
*/
|
|
private void propagateToReturnSiteWithBinaryFlowFunction(final PathEdge<T> edge, final T c, final IntSet D4, final T[] entries,
|
|
final T retSite, final IFlowFunction retf) {
|
|
D4.foreach(d4 -> {
|
|
final IntSet D5 = computeBinaryFlow(d4, edge.d2, (IBinaryReturnFlowFunction) retf);
|
|
propToReturnSite(c, entries, retSite, d4, D5, edge);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Propagate information to a particular return site.
|
|
*
|
|
* @param c the corresponding call site
|
|
* @param entries entry nodes in the caller
|
|
* @param retSite the return site
|
|
* @param d4 a fact s.t. {@literal <c, d4> -> <callee, d2>} was
|
|
* recorded as call flow and {@literal <callee, d2>} is the
|
|
* source of the summary edge being applied
|
|
* @param D5 facts to propagate to return site
|
|
* @param edge the path edge ending at the exit site of the callee
|
|
*/
|
|
private void propToReturnSite(final T c, final T[] entries, final T retSite, final int d4, final IntSet D5, final PathEdge<T> edge) {
|
|
if (D5 != null) {
|
|
D5.foreach(new IntSetAction() {
|
|
@SuppressWarnings("unused")
|
|
@Override
|
|
public void act(final int d5) {
|
|
// [26 - 28]
|
|
// note that we've modified the algorithm here to account
|
|
// for potential
|
|
// multiple entry nodes. Instead of propagating the new
|
|
// summary edge
|
|
// with respect to one s_profOf(c), we have to propagate
|
|
// for each
|
|
// potential entry node s_p /in s_procof(c)
|
|
for (final T s_p : entries) {
|
|
if (DEBUG_LEVEL > 1) {
|
|
System.err.println(" do entry " + s_p);
|
|
}
|
|
IntSet D3 = getInversePathEdges(s_p, c, d4);
|
|
if (DEBUG_LEVEL > 1) {
|
|
System.err.println("D3" + D3);
|
|
}
|
|
if (D3 != null) {
|
|
D3.foreach(d3 -> {
|
|
// set curPathEdge to be consistent with its setting in processCall() when applying a summary edge
|
|
curPathEdge = PathEdge.createPathEdge(s_p, d3, c, d4);
|
|
newSummaryEdge(curPathEdge, edge, retSite, d5);
|
|
propagate(s_p, d3, retSite, d5);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param s_p
|
|
* @param n
|
|
* @param d2 note that s_p must be an entry for procof(n)
|
|
* @return set of d1 s.t. {@literal <s_p, d1> -> <n, d2>} is a path edge, or null if none found
|
|
*/
|
|
protected IntSet getInversePathEdges(T s_p, T n, int d2) {
|
|
int number = supergraph.getLocalBlockNumber(n);
|
|
LocalPathEdges lp = pathEdges.get(s_p);
|
|
if (lp == null) {
|
|
return null;
|
|
}
|
|
return lp.getInverse(number, d2);
|
|
}
|
|
|
|
/**
|
|
* Handle lines [14 - 19] of the algorithm, propagating information into and across a call site.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected void processCall(final PathEdge<T> edge) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("process call: " + edge);
|
|
}
|
|
|
|
// c:= number of the call node
|
|
final int c = supergraph.getNumber(edge.target);
|
|
|
|
Collection<T> allReturnSites = HashSetFactory.make();
|
|
// populate allReturnSites with return sites for missing calls.
|
|
for (T retSite : Iterator2Iterable.make(supergraph.getReturnSites(edge.target, null))) {
|
|
allReturnSites.add(retSite);
|
|
}
|
|
// [14 - 16]
|
|
boolean hasCallee = false;
|
|
for (T callee : Iterator2Iterable.make(supergraph.getCalledNodes(edge.target))) {
|
|
hasCallee = true;
|
|
processParticularCallee(edge, c, allReturnSites, callee);
|
|
}
|
|
// special logic: in backwards problems, a "call" node can have
|
|
// "normal" successors as well. deal with these.
|
|
for (T m : Iterator2Iterable.make(supergraph.getNormalSuccessors(edge.target))) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("normal successor: " + m);
|
|
}
|
|
IUnaryFlowFunction f = flowFunctionMap.getNormalFlowFunction(edge.target, m);
|
|
IntSet D3 = computeFlow(edge.d2, f);
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("normal successor reached: " + D3);
|
|
}
|
|
if (D3 != null) {
|
|
D3.foreach(d3 -> {
|
|
newNormalExplodedEdge(edge, m, d3);
|
|
propagate(edge.entry, edge.d1, m, d3);
|
|
});
|
|
}
|
|
}
|
|
|
|
// [17 - 19]
|
|
// we modify this to handle each return site individually
|
|
for (final T returnSite : allReturnSites) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println(" process return site: " + returnSite);
|
|
}
|
|
IUnaryFlowFunction f = null;
|
|
if (hasCallee) {
|
|
f = flowFunctionMap.getCallToReturnFlowFunction(edge.target, returnSite);
|
|
} else {
|
|
f = flowFunctionMap.getCallNoneToReturnFlowFunction(edge.target, returnSite);
|
|
}
|
|
IntSet reached = computeFlow(edge.d2, f);
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("reached: " + reached);
|
|
}
|
|
if (reached != null) {
|
|
reached.foreach(x -> {
|
|
assert x >= 0;
|
|
assert edge.d1 >= 0;
|
|
newNormalExplodedEdge(edge, returnSite, x);
|
|
propagate(edge.entry, edge.d1, returnSite, x);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* handle a particular callee for some call node.
|
|
*
|
|
* @param edge the path edge being processed
|
|
* @param callNodeNum the number of the call node in the supergraph
|
|
* @param allReturnSites a set collecting return sites for the call. This set is mutated with the return sites for this callee.
|
|
* @param calleeEntry the entry node of the callee in question
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected void processParticularCallee(final PathEdge<T> edge, final int callNodeNum, Collection<T> allReturnSites, final T calleeEntry) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println(" process callee: " + calleeEntry);
|
|
}
|
|
// reached := {d1} that reach the callee
|
|
MutableSparseIntSet reached = MutableSparseIntSet.makeEmpty();
|
|
final Collection<T> returnSitesForCallee = Iterator2Collection.toSet(supergraph.getReturnSites(edge.target, supergraph
|
|
.getProcOf(calleeEntry)));
|
|
allReturnSites.addAll(returnSitesForCallee);
|
|
// we modify this to handle each return site individually. Some types of problems
|
|
// compute different flow functions for each return site.
|
|
for (final T returnSite : returnSitesForCallee) {
|
|
IUnaryFlowFunction f = flowFunctionMap.getCallFlowFunction(edge.target, calleeEntry, returnSite);
|
|
IntSet r = computeFlow(edge.d2, f);
|
|
if (r != null) {
|
|
reached.addAll(r);
|
|
}
|
|
}
|
|
// in some problems, we also want to consider flow into a callee that can never flow out
|
|
// via a return. in this case, the return site is null.
|
|
IUnaryFlowFunction f = flowFunctionMap.getCallFlowFunction(edge.target, calleeEntry, null);
|
|
IntSet r = computeFlow(edge.d2, f);
|
|
if (r != null) {
|
|
reached.addAll(r);
|
|
}
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println(" reached: " + reached);
|
|
}
|
|
if (reached != null) {
|
|
final LocalSummaryEdges summaries = summaryEdges.get(supergraph.getProcOf(calleeEntry));
|
|
final CallFlowEdges callFlow = findOrCreateCallFlowEdges(calleeEntry);
|
|
final int s_p_num = supergraph.getLocalBlockNumber(calleeEntry);
|
|
|
|
reached.foreach(d1 -> {
|
|
// we get reuse if we _don't_ propagate a new fact to the callee entry
|
|
final boolean gotReuse = !propagate(calleeEntry, d1, calleeEntry, d1);
|
|
recordCall(edge.target, calleeEntry, d1, gotReuse);
|
|
newCallExplodedEdge(edge, calleeEntry, d1);
|
|
// cache the fact that we've flowed <c, d2> -> <callee, d1> by a
|
|
// call flow
|
|
callFlow.addCallEdge(callNodeNum, edge.d2, d1);
|
|
// handle summary edges now as well. this is different from the PoPL
|
|
// 95 paper.
|
|
if (summaries != null) {
|
|
// for each exit from the callee
|
|
P p = supergraph.getProcOf(calleeEntry);
|
|
T[] exits = supergraph.getExitsForProcedure(p);
|
|
for (final T exit : exits) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
assert supergraph.containsNode(exit);
|
|
}
|
|
int x_num = supergraph.getLocalBlockNumber(exit);
|
|
// reachedBySummary := {d2} s.t. <callee,d1> -> <exit,d2>
|
|
// was recorded as a summary edge
|
|
IntSet reachedBySummary = summaries.getSummaryEdges(s_p_num, x_num, d1);
|
|
if (reachedBySummary != null) {
|
|
for (final T returnSite : returnSitesForCallee) {
|
|
// if "exit" is a valid exit from the callee to the return
|
|
// site being processed
|
|
if (supergraph.hasEdge(exit, returnSite)) {
|
|
final IFlowFunction retf = flowFunctionMap.getReturnFlowFunction(edge.target, exit, returnSite);
|
|
reachedBySummary.foreach(d2 -> {
|
|
assert curSummaryEdge == null : "curSummaryEdge should be null here";
|
|
curSummaryEdge = PathEdge.createPathEdge(calleeEntry, d1, exit, d2);
|
|
if (retf instanceof IBinaryReturnFlowFunction) {
|
|
final IntSet D51 = computeBinaryFlow(edge.d2, d2, (IBinaryReturnFlowFunction) retf);
|
|
if (D51 != null) {
|
|
D51.foreach(d5 -> {
|
|
newSummaryEdge(edge, curSummaryEdge, returnSite, d5);
|
|
propagate(edge.entry, edge.d1, returnSite, d5);
|
|
});
|
|
}
|
|
} else {
|
|
final IntSet D52 = computeFlow(d2, (IUnaryFlowFunction) retf);
|
|
if (D52 != null) {
|
|
D52.foreach(d5 -> {
|
|
newSummaryEdge(edge, curSummaryEdge, returnSite, d5);
|
|
propagate(edge.entry, edge.d1, returnSite, d5);
|
|
});
|
|
}
|
|
}
|
|
curSummaryEdge = null;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* invoked when a callee is processed with a particular entry fact
|
|
*
|
|
* @param callNode
|
|
* @param callee
|
|
* @param d1 the entry fact
|
|
* @param gotReuse whether existing summary edges were applied
|
|
*/
|
|
protected void recordCall(T callNode, T callee, int d1, boolean gotReuse) {
|
|
}
|
|
|
|
/**
|
|
* @return f(call_d, exit_d);
|
|
*
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected IntSet computeBinaryFlow(int call_d, int exit_d, IBinaryReturnFlowFunction f) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("got binary flow function " + f);
|
|
}
|
|
IntSet result = f.getTargets(call_d, exit_d);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @return f(d1)
|
|
*
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected IntSet computeFlow(int d1, IUnaryFlowFunction f) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("got flow function " + f);
|
|
}
|
|
IntSet result = f.getTargets(d1);
|
|
|
|
if (result == null) {
|
|
return null;
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return f^{-1}(d2)
|
|
*/
|
|
protected IntSet computeInverseFlow(int d2, IReversibleFlowFunction f) {
|
|
return f.getSources(d2);
|
|
}
|
|
|
|
protected PathEdge<T> popFromWorkList() {
|
|
assert worklist != null;
|
|
return worklist.take();
|
|
}
|
|
|
|
private PathEdge<T> peekFromWorkList() {
|
|
// horrible. don't use in performance-critical
|
|
assert worklist != null;
|
|
PathEdge<T> result = worklist.take();
|
|
worklist.insert(result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Propagate the fact <s_p,i> -> <n, j> has arisen as a path edge. Returns <code>true</code> iff the path edge was not previously
|
|
* observed.
|
|
*
|
|
* @param s_p entry block
|
|
* @param i dataflow fact on entry
|
|
* @param n reached block
|
|
* @param j dataflow fact reached
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected boolean propagate(T s_p, int i, T n, int j) {
|
|
int number = supergraph.getLocalBlockNumber(n);
|
|
if (number < 0) {
|
|
System.err.println("BOOM " + n);
|
|
supergraph.getLocalBlockNumber(n);
|
|
}
|
|
assert number >= 0;
|
|
|
|
LocalPathEdges pLocal = findOrCreateLocalPathEdges(s_p);
|
|
|
|
assert j >= 0;
|
|
|
|
if (!pLocal.contains(i, number, j)) {
|
|
if (DEBUG_LEVEL > 0) {
|
|
System.err.println("propagate " + s_p + " " + i + " " + number + " " + j);
|
|
}
|
|
pLocal.addPathEdge(i, number, j);
|
|
addToWorkList(s_p, i, n, j);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public LocalPathEdges getLocalPathEdges(T s_p) {
|
|
return pathEdges.get(s_p);
|
|
}
|
|
|
|
/**
|
|
* Merging: suppose we're doing propagate <s_p,i> -> <n,j> but we already have path edges <s_p,i> -> <n, x>, <s_p,i> -> <n,y>, and
|
|
* <s_p,i> -><n, z>.
|
|
*
|
|
* let \alpha be the merge function. then instead of <s_p,i> -> <n,j>, we propagate <s_p,i> -> <n, \alpha(j,x,y,z) > !!!
|
|
*
|
|
* return -1 if no fact should be propagated
|
|
*/
|
|
private int merge(T s_p, int i, T n, int j) {
|
|
assert j >= 0;
|
|
IMergeFunction alpha = problem.getMergeFunction();
|
|
if (alpha != null) {
|
|
LocalPathEdges lp = pathEdges.get(s_p);
|
|
IntSet preExistFacts = lp.getReachable(supergraph.getLocalBlockNumber(n), i);
|
|
if (preExistFacts == null) {
|
|
return j;
|
|
} else {
|
|
int size = preExistFacts.size();
|
|
if ((size == 0) || ((size == 1) && preExistFacts.contains(j))) {
|
|
return j;
|
|
} else {
|
|
int result = alpha.merge(preExistFacts, j);
|
|
return result;
|
|
}
|
|
}
|
|
} else {
|
|
return j;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
protected void addToWorkList(T s_p, int i, T n, int j) {
|
|
if (worklist == null) {
|
|
worklist = makeWorklist();
|
|
}
|
|
worklist.insert(PathEdge.createPathEdge(s_p, i, n, j));
|
|
if (DEBUG_LEVEL >= 3) {
|
|
System.err.println("WORKLIST: " + worklist);
|
|
}
|
|
}
|
|
|
|
protected LocalPathEdges findOrCreateLocalPathEdges(T s_p) {
|
|
LocalPathEdges result = pathEdges.get(s_p);
|
|
if (result == null) {
|
|
result = makeLocalPathEdges();
|
|
pathEdges.put(s_p, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private LocalPathEdges makeLocalPathEdges() {
|
|
return problem.getMergeFunction() == null ? new LocalPathEdges(false) : new LocalPathEdges(true);
|
|
}
|
|
|
|
protected LocalSummaryEdges findOrCreateLocalSummaryEdges(P proc) {
|
|
LocalSummaryEdges result = summaryEdges.get(proc);
|
|
if (result == null) {
|
|
result = new LocalSummaryEdges();
|
|
summaryEdges.put(proc, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
protected CallFlowEdges findOrCreateCallFlowEdges(T s_p) {
|
|
CallFlowEdges result = callFlowEdges.get(s_p);
|
|
if (result == null) {
|
|
result = new CallFlowEdges();
|
|
callFlowEdges.put(s_p, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* get the bitvector of facts that hold at the entry to a given node
|
|
*
|
|
* @return IntSet representing the bitvector
|
|
*/
|
|
public IntSet getResult(T node) {
|
|
P proc = supergraph.getProcOf(node);
|
|
int n = supergraph.getLocalBlockNumber(node);
|
|
T[] entries = supergraph.getEntriesForProcedure(proc);
|
|
MutableIntSet result = MutableSparseIntSet.makeEmpty();
|
|
|
|
Set<T> allEntries = HashSetFactory.make(Arrays.asList(entries));
|
|
Set<PathEdge<T>> pSeeds = seeds.get(proc);
|
|
if (pSeeds != null) {
|
|
for (PathEdge<T> seed : pSeeds) {
|
|
allEntries.add(seed.entry);
|
|
}
|
|
}
|
|
|
|
for (T entry : allEntries){
|
|
LocalPathEdges lp = pathEdges.get(entry);
|
|
if (lp != null) {
|
|
result.addAll(lp.getReachable(n));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public class Result implements TabulationResult<T, P, F> {
|
|
|
|
/**
|
|
* get the bitvector of facts that hold at the entry to a given node
|
|
*
|
|
* @return IntSet representing the bitvector
|
|
*/
|
|
@Override
|
|
public IntSet getResult(T node) {
|
|
return TabulationSolver.this.getResult(node);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
|
|
StringBuffer result = new StringBuffer();
|
|
TreeMap<Object, TreeSet<T>> map = new TreeMap<>(ToStringComparator.instance());
|
|
|
|
Comparator<Object> c = (o1, o2) -> {
|
|
if (!(o1 instanceof IBasicBlock)) {
|
|
return -1;
|
|
}
|
|
IBasicBlock bb1 = (IBasicBlock) o1;
|
|
IBasicBlock bb2 = (IBasicBlock) o2;
|
|
return bb1.getNumber() - bb2.getNumber();
|
|
};
|
|
for (T n : supergraph) {
|
|
P proc = supergraph.getProcOf(n);
|
|
TreeSet<T> s = map.get(proc);
|
|
if (s == null) {
|
|
s = new TreeSet<>(c);
|
|
map.put(proc, s);
|
|
}
|
|
s.add(n);
|
|
}
|
|
|
|
for (Entry<Object, TreeSet<T>> e : map.entrySet()) {
|
|
Set<T> s = e.getValue();
|
|
for (T o : s) {
|
|
result.append(o + " : " + getResult(o) + "\n");
|
|
}
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.dataflow.IFDS.TabulationResult#getProblem()
|
|
*/
|
|
@Override
|
|
public TabulationProblem<T, P, F> getProblem() {
|
|
return problem;
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.dataflow.IFDS.TabulationResult#getSupergraphNodesReached()
|
|
*/
|
|
@Override
|
|
public Collection<T> getSupergraphNodesReached() {
|
|
Collection<T> result = HashSetFactory.make();
|
|
for (Entry<T, LocalPathEdges> e : pathEdges.entrySet()) {
|
|
T key = e.getKey();
|
|
P proc = supergraph.getProcOf(key);
|
|
IntSet reached = e.getValue().getReachedNodeNumbers();
|
|
for (IntIterator ii = reached.intIterator(); ii.hasNext();) {
|
|
result.add(supergraph.getLocalBlock(proc, ii.next()));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @param n1
|
|
* @param d1
|
|
* @param n2
|
|
* @return set of d2 s.t. (n1,d1) -> (n2,d2) is recorded as a summary edge, or null if none found
|
|
*/
|
|
@Override
|
|
public IntSet getSummaryTargets(T n1, int d1, T n2) {
|
|
LocalSummaryEdges summaries = summaryEdges.get(supergraph.getProcOf(n1));
|
|
if (summaries == null) {
|
|
return null;
|
|
}
|
|
int num1 = supergraph.getLocalBlockNumber(n1);
|
|
int num2 = supergraph.getLocalBlockNumber(n2);
|
|
return summaries.getSummaryEdges(num1, num2, d1);
|
|
}
|
|
|
|
@Override
|
|
public Collection<PathEdge<T>> getSeeds() {
|
|
return TabulationSolver.this.getSeeds();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Returns the supergraph.
|
|
*/
|
|
public ISupergraph<T, P> getSupergraph() {
|
|
return supergraph;
|
|
}
|
|
|
|
protected class Worklist extends Heap<PathEdge<T>> implements ITabulationWorklist<T> {
|
|
|
|
Worklist() {
|
|
super(100);
|
|
}
|
|
|
|
@Override
|
|
protected boolean compareElements(PathEdge<T> p1, PathEdge<T> p2) {
|
|
return problem.getDomain().hasPriorityOver(p1, p2);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @return set of d1 s.t. (n1,d1) -> (n2,d2) is recorded as a summary edge, or null if none found
|
|
* @throws UnsupportedOperationException unconditionally
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
public IntSet getSummarySources(T n2, int d2, T n1) throws UnsupportedOperationException {
|
|
throw new UnsupportedOperationException("not currently supported. be careful");
|
|
// LocalSummaryEdges summaries = summaryEdges.get(supergraph.getProcOf(n1));
|
|
// if (summaries == null) {
|
|
// return null;
|
|
// }
|
|
// int num1 = supergraph.getLocalBlockNumber(n1);
|
|
// int num2 = supergraph.getLocalBlockNumber(n2);
|
|
// return summaries.getInvertedSummaryEdgesForTarget(num1, num2, d2);
|
|
}
|
|
|
|
public TabulationProblem<T, P, F> getProblem() {
|
|
return problem;
|
|
}
|
|
|
|
public Collection<PathEdge<T>> getSeeds() {
|
|
return Collections.unmodifiableCollection(allSeeds);
|
|
}
|
|
|
|
public IProgressMonitor getProgressMonitor() {
|
|
return progressMonitor;
|
|
}
|
|
|
|
protected PathEdge<T> getCurPathEdge() {
|
|
return curPathEdge;
|
|
}
|
|
|
|
protected PathEdge<T> getCurSummaryEdge() {
|
|
return curSummaryEdge;
|
|
}
|
|
|
|
/**
|
|
* Indicates that due to a path edge <s_p, d1> -> <n, d2> (the 'edge'
|
|
* parameter) and a normal flow function application, a new path edge <s_p,
|
|
* d1> -> <m, d3> was created. To be overridden in subclasses. We also use
|
|
* this function to record call-to-return flow.
|
|
*
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected void newNormalExplodedEdge(PathEdge<T> edge, T m, int d3) {
|
|
|
|
}
|
|
|
|
/**
|
|
* Indicates that due to a path edge 'edge' <s_p, d1> -> <n, d2> and
|
|
* application of a call flow function, a new path edge <calleeEntry, d3> ->
|
|
* <calleeEntry, d3> was created. To be overridden in subclasses.
|
|
*
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected void newCallExplodedEdge(PathEdge<T> edge, T calleeEntry, int d3) {
|
|
|
|
}
|
|
|
|
/**
|
|
* Combines [25] and [26-28]. In the caller we have a path edge
|
|
* 'edgeToCallSite' <s_c, d3> -> <c, d4>, where c is the call site. In the
|
|
* callee, we have path edge 'calleeSummaryEdge' <s_p, d1> -> <e_p, d2>. Of
|
|
* course, there is a call edge <c, d4> -> <s_p, d1>. Finally, we have a
|
|
* return edge <e_p, d2> -> <returnSite, d5>.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
protected void newSummaryEdge(PathEdge<T> edgeToCallSite, PathEdge<T> calleeSummaryEdge, T returnSite, int d5) {
|
|
|
|
}
|
|
|
|
|
|
}
|