sootup.analysis.intraprocedural.FlowAnalysis Maven / Gradle / Ivy
package sootup.analysis.intraprocedural;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 1997 - 1999 Raja Vallee-Rai
* %%
* This program 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 program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.util.*;
import javax.annotation.Nonnull;
import sootup.core.graph.BasicBlock;
import sootup.core.graph.StmtGraph;
import sootup.core.jimple.common.stmt.JGotoStmt;
import sootup.core.jimple.common.stmt.Stmt;
/**
* An abstract class providing a framework for carrying out dataflow analysis. Subclassing either
* BackwardFlowAnalysis or ForwardFlowAnalysis and providing implementations for the abstract
* methods will allow Soot to compute the corresponding flow analysis.
*/
public abstract class FlowAnalysis extends AbstractFlowAnalysis {
public enum Flow {
IN {
@Override
F getFlow(Entry e) {
return e.inFlow;
}
},
OUT {
@Override
F getFlow(Entry e) {
return e.outFlow;
}
};
abstract F getFlow(Entry e);
}
static class Entry {
final Stmt data;
int number;
/** This Entry is part of a real scc. */
boolean isRealStronglyConnected;
Entry[] in;
Entry[] out;
F inFlow;
F outFlow;
@SuppressWarnings("unchecked")
Entry(Stmt u, Entry pred) {
in = new Entry[] {pred};
data = u;
number = Integer.MIN_VALUE;
isRealStronglyConnected = false;
}
@Override
public String toString() {
return data == null ? "" : data.toString();
}
}
static class Orderer {
/**
* Creates a new {@code Entry} graph based on a {@code DirectedGraph}. This includes pseudo
* topological order, local access for predecessors and successors, a graph entry-point, a
* {@code Numberable} interface and a real strongly connected component marker.
*
* @param g
* @param direction
* @param entryFlow
* @return
*/
static List> newUniverse(
@Nonnull StmtGraph extends BasicBlock>> g,
@Nonnull AnalysisDirection direction,
@Nonnull F entryFlow) {
final int size = g.getNodes().size();
final int n = size;
Deque> s = new ArrayDeque<>(n);
List> universe = new ArrayList<>(n);
Map> visited = new HashMap<>(((n + 1) * 4) / 3);
// out of universe node
Entry superEntry = new Entry(null, null);
List entries;
List actualEntries = direction.getEntries(g);
if (!actualEntries.isEmpty()) {
// normal cases: there is at least
// one return statement for a backward analysis
// or one entry statement for a forward analysis
entries = actualEntries;
} else {
// cases without any entry statement
if (AnalysisDirection.FORWARD == direction) {
// case of a forward flow analysis on
// a method without any entry point
throw new RuntimeException("error: no entry point for method in forward analysis");
} else {
// case of backward analysis on
// a method which potentially has
// an infinite loop and no return statement
entries = new ArrayList<>();
// a single head is expected
final Collection entrypoints = g.getEntrypoints();
assert entrypoints.size() == 1;
Stmt head = entrypoints.iterator().next();
// collect all 'goto' statements to catch the 'goto' from the infinite loop
Set visitedNodes = new HashSet<>();
List workList = new ArrayList<>();
workList.add(head);
for (Stmt currentStmt; !workList.isEmpty(); ) {
currentStmt = workList.remove(0);
visitedNodes.add(currentStmt);
// only add 'goto' statements
if (currentStmt instanceof JGotoStmt) {
entries.add(currentStmt);
}
for (Stmt successor : g.successors(currentStmt)) {
if (visitedNodes.contains(successor)) {
continue;
}
workList.add(successor);
}
}
//
if (entries.isEmpty()) {
throw new RuntimeException("error: backward analysis on an empty entry set.");
}
}
}
visitEntry(visited, superEntry, entries);
superEntry.inFlow = entryFlow;
superEntry.outFlow = entryFlow;
@SuppressWarnings("unchecked")
Entry[] sv = new Entry[size];
int[] si = new int[size];
int index = 0;
int i = 0;
Entry v = superEntry;
while (true) {
if (i < v.out.length) {
Entry w = v.out[i++];
// an unvisited child node
if (w.number == Integer.MIN_VALUE) {
w.number = s.size();
s.add(w);
visitEntry(visited, w, direction.getOut(g, w.data));
// save old
si[index] = i;
sv[index] = v;
index++;
i = 0;
v = w;
}
} else {
if (index == 0) {
assert universe.size() <= size;
Collections.reverse(universe);
return universe;
}
universe.add(v);
sccPop(s, v);
// restore old
index--;
v = sv[index];
i = si[index];
}
}
}
@Nonnull
private static Entry[] visitEntry(
Map> visited, Entry v, List out) {
final int n = out.size();
@SuppressWarnings("unchecked")
Entry[] a = new Entry[n];
assert (out instanceof RandomAccess);
for (int i = 0; i < n; i++) {
a[i] = getEntryOf(visited, out.get(i), v);
}
return v.out = a;
}
@Nonnull
private static Entry getEntryOf(
@Nonnull Map> visited, @Nonnull Stmt stmt, @Nonnull Entry v) {
// either we reach a new node or a merge node, the latter one is rare
// so put and restore should be better that a lookup
// add and restore if required
Entry newEntry = new Entry<>(stmt, v);
Entry oldEntry = visited.putIfAbsent(stmt, newEntry);
// no restore required
if (oldEntry == null) {
return newEntry;
}
// adding self ref (real strongly connected with itself)
if (oldEntry == v) {
oldEntry.isRealStronglyConnected = true;
}
// merge nodes are rare, so this is ok
int l = oldEntry.in.length;
oldEntry.in = Arrays.copyOf(oldEntry.in, l + 1);
oldEntry.in[l] = v;
return oldEntry;
}
private static void sccPop(@Nonnull Deque> s, @Nonnull Entry v) {
int min = v.number;
for (Entry e : v.out) {
assert e.number > Integer.MIN_VALUE;
min = Math.min(min, e.number);
}
// not our SCC
if (min != v.number) {
v.number = min;
return;
}
// we only want real SCCs (size > 1)
Entry w = s.removeLast();
w.number = Integer.MAX_VALUE;
if (w == v) {
return;
}
w.isRealStronglyConnected = true;
for (; ; ) {
w = s.removeLast();
assert w.number >= v.number;
w.isRealStronglyConnected = true;
w.number = Integer.MAX_VALUE;
if (w == v) {
assert w.in.length >= 2;
return;
}
}
}
}
enum AnalysisDirection {
BACKWARD {
@Override
@Nonnull
List getEntries(StmtGraph extends BasicBlock>> g) {
return g.getTails();
}
@Override
@Nonnull
List getOut(StmtGraph extends BasicBlock>> g, Stmt s) {
return g.predecessors(s);
}
},
FORWARD {
@Override
@Nonnull
List getEntries(StmtGraph extends BasicBlock>> g) {
return (List) g.getEntrypoints();
}
@Override
@Nonnull
List getOut(StmtGraph extends BasicBlock>> g, Stmt s) {
return g.successors(s);
}
};
@Nonnull
abstract List getEntries(StmtGraph extends BasicBlock>> g);
@Nonnull
abstract List getOut(StmtGraph extends BasicBlock>> g, Stmt s);
}
/** Maps graph nodes to OUT sets. */
@Nonnull protected final Map stmtToAfterFlow;
/** Filtered: Maps graph nodes to OUT sets. */
@Nonnull protected Map filterStmtToAfterFlow;
/** Constructs a flow analysis on the given DirectedGraph
. */
public FlowAnalysis(@Nonnull StmtGraph extends BasicBlock>> graph) {
super(graph);
this.stmtToAfterFlow = new IdentityHashMap<>(graph.getNodes().size() * 2 + 1);
this.filterStmtToAfterFlow = Collections.emptyMap();
}
/**
* Given the merge of the out
sets, compute the in
set for s
*
(or in to out, depending on direction).
*
* This function often causes confusion, because the same interface is used for both forward
* and backward flow analyses. The first parameter is always the argument to the flow function
* (i.e. it is the "in" set in a forward analysis and the "out" set in a backward analysis), and
* the third parameter is always the result of the flow function (i.e. it is the "out" set in a
* forward analysis and the "in" set in a backward analysis).
*
* @param in the input flow
* @param d the current node
* @param out the returned flow
*/
protected abstract void flowThrough(@Nonnull A in, Stmt d, @Nonnull A out);
/** Accessor function returning value of OUT set for s. */
public A getFlowAfter(@Nonnull Stmt s) {
A a = stmtToAfterFlow.get(s);
return a == null ? newInitialFlow() : a;
}
@Nonnull
@Override
public A getFlowBefore(@Nonnull Stmt s) {
A a = stmtToBeforeFlow.get(s);
return a == null ? newInitialFlow() : a;
}
private void initFlow(
@Nonnull Iterable> universe, @Nonnull Map in, @Nonnull Map out) {
// If a node has only a single in-flow, the in-flow is always equal
// to the out-flow if its predecessor, so we use the same object.
// this saves memory and requires less object creation and copy calls.
// Furthermore a node can be marked as omissible, this allows us to use
// the same "flow-set" for out-flow and in-flow. A merge node with within
// a real scc cannot be omitted, as it could cause endless loops within
// the fixpoint-iteration!
for (Entry n : universe) {
boolean omit = true;
if (n.in.length > 1) {
n.inFlow = newInitialFlow();
// no merge points in loops
omit = !n.isRealStronglyConnected;
} else {
assert n.in.length == 1 : "missing superhead";
n.inFlow = getFlow(n.in[0], n);
assert n.inFlow != null : "topological order is broken";
}
if (omit && omissible(n.data)) {
// We could recalculate the graph itself but thats more expensive than
// just falling through such nodes.
n.outFlow = n.inFlow;
} else {
n.outFlow = newInitialFlow();
}
// for legacy api (ms: already a soot comment)
in.put(n.data, n.inFlow);
out.put(n.data, n.outFlow);
}
}
/**
* If a flow node can be omitted return true
, otherwise false
. There is
* no guarantee a node will be omitted. A omissible node does not influence the result of an
* analysis.
*
* If you are unsure, don't overwrite this method
*
* @param stmt the node to check
* @return false
*/
protected boolean omissible(@Nonnull Stmt stmt) {
return false;
}
/**
* You can specify which flow set you would like to use of node {@code from}
*
* @param from
* @param mergeNode
* @return Flow.OUT
*/
protected Flow getFlow(@Nonnull Stmt from, @Nonnull Stmt mergeNode) {
return Flow.OUT;
}
private A getFlow(@Nonnull Entry o, @Nonnull Entry e) {
return (o.inFlow == o.outFlow) ? o.outFlow : getFlow(o.data, e.data).getFlow(o);
}
private void meetFlows(@Nonnull Entry entry) {
assert entry.in.length >= 1;
if (entry.in.length > 1) {
boolean copy = true;
for (Entry o : entry.in) {
A flow = getFlow(o, entry);
if (copy) {
copy = false;
copy(flow, entry.inFlow);
} else {
mergeInto(entry.data, entry.inFlow, flow);
}
}
}
}
final int execute(@Nonnull Map inFlow, @Nonnull Map outFlow) {
final boolean isForward = isForward();
final List> universe =
Orderer.newUniverse(
graph,
isForward ? AnalysisDirection.FORWARD : AnalysisDirection.BACKWARD,
newInitialFlow());
initFlow(universe, inFlow, outFlow);
Queue> q = UniverseSortedPriorityQueue.of(universe);
// Perform fixed point flow analysis
for (int numComputations = 0; ; numComputations++) {
Entry e = q.poll();
if (e == null) {
return numComputations;
}
meetFlows(e);
// Compute beforeFlow and store it.
// ifh.handleFlowIn(this, e.data);
boolean hasChanged = flowThrough(e);
// ifh.handleFlowOut(this, e.data);
// Update queue appropriately
if (hasChanged) {
q.addAll(Arrays.asList(e.out));
}
}
}
private boolean flowThrough(Entry d) {
// omitted, just fall through
if (d.inFlow == d.outFlow) {
assert !d.isRealStronglyConnected || d.in.length == 1;
return true;
}
if (d.isRealStronglyConnected) {
// A flow node that is influenced by at least one back-reference.
// It's essential to check if "flowThrough" changes the result.
// This requires the calculation of "equals", which itself
// can be really expensive - depending on the used flow-model.
// Depending on the "merge"+"flowThrough" costs, it can be cheaper
// to fall through. Only nodes with real back-references always
// need to be checked for changes
A out = newInitialFlow();
flowThrough(d.inFlow, d.data, out);
if (out.equals(d.outFlow)) {
return false;
}
// copy back the result, as it has changed (former: copyFreshToExisting)
copy(out, d.outFlow);
return true;
}
// no back-references, just calculate "flowThrough"
flowThrough(d.inFlow, d.data, d.outFlow);
return true;
}
/*
* Copies a *fresh* copy of in to dest. The input is not referenced somewhere else. This allows
* subclasses for a smarter and faster copying.
*
* @param in
* @param dest
*
protected void copyFreshToExisting(A in, A dest) {
if (in instanceof FlowSet && dest instanceof FlowSet) {
FlowSet> fin = (FlowSet>) in;
FlowSet fdest = (FlowSet) dest;
fin.copyFreshToExisting(fdest);
} else {
copy(in, dest);
}
}
*/
}