/* Soot - a J*va Optimization Framework
* Copyright (C) 2007 Patrick Lam
* Copyright (C) 2007 Eric Bodden
*
* 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 library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package soot.jimple.toolkits.pointer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.omg.CORBA.UNKNOWN;
import soot.EquivalentValue;
import soot.Local;
import soot.MethodOrMethodContext;
import soot.RefLikeType;
import soot.Scene;
import soot.SootMethod;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.CastExpr;
import soot.jimple.DefinitionStmt;
import soot.jimple.FieldRef;
import soot.jimple.IdentityRef;
import soot.jimple.ParameterRef;
import soot.jimple.Stmt;
import soot.jimple.ThisRef;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.ReachableMethods;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.ForwardFlowAnalysis;
/** LocalMustAliasAnalysis attempts to determine if two local
* variables (at two potentially different program points) must point
* to the same object.
*
* The underlying abstraction is based on global value numbering.
*
* See also {@link StrongLocalMustAliasAnalysis} for an analysis
* that soundly treats redefinitions within loops.
*
* See Sable TR 2007-8 for details.
*
* P.S. The idea behind this analysis and the way it assigns numbers is very
* similar to what is described in the paper:
* Lapkowski, C. and Hendren, L. J. 1996. Extended SSA numbering: introducing SSA properties to languages with multi-level pointers.
* In Proceedings of the 1996 Conference of the Centre For Advanced Studies on Collaborative Research (Toronto, Ontario, Canada, November 12 - 14, 1996).
* M. Bauer, K. Bennet, M. Gentleman, H. Johnson, K. Lyons, and J. Slonim, Eds. IBM Centre for Advanced Studies Conference. IBM Press, 23.
*
* Only major differences: Here we only use primary numbers, no secondary numbers. Further, we use the call graph to determine fields
* that are not written to in the transitive closure of this method's execution. A third difference is that we assign fixed values
* to {@link IdentityRef}s, because those never change during one execution.
*
* @author Patrick Lam
* @author Eric Bodden
* @see StrongLocalMustAliasAnalysis
* */
public class LocalMustAliasAnalysis extends ForwardFlowAnalysis<Unit,HashMap<Value,Object>>
{
public static final String UNKNOWN_LABEL = "UNKNOWN";
protected static final Object UNKNOWN = new Object() {
public String toString() { return UNKNOWN_LABEL; }
};
protected static final Object NOTHING = new Object() {
public String toString() { return "NOTHING"; }
};
/**
* The set of all local variables and field references that we track.
* This set contains objects of type {@link Local} and, if tryTrackFieldAssignments is
* enabled, it may also contain {@link EquivalentValue}s of {@link FieldRef}s.
* If so, these field references are to be tracked on the same way as {@link Local}s are.
*/
protected Set<Value> localsAndFieldRefs;
/** maps from right-hand side expressions (non-locals) to value numbers */
protected transient Map<Value,Integer> rhsToNumber;
/** maps from a merge point (a unit) and a value to the unique value number of that value at this point */
protected transient Map<Unit,Map<Value,Integer>> mergePointToValueToNumber;
/** the next value number */
protected int nextNumber = 1;
/** the containing method */
protected SootMethod container;
/**
* Creates a new {@link LocalMustAliasAnalysis} tracking local variables.
*/
public LocalMustAliasAnalysis(UnitGraph g) {
this(g,false);
}
/**
* Creates a new {@link LocalMustAliasAnalysis}. If tryTrackFieldAssignments,
* we run an interprocedural side-effects analysis to determine which fields
* are (transitively) written to by this method. All fields which that are not written
* to are tracked just as local variables. This semantics is sound for single-threaded programs.
*/
public LocalMustAliasAnalysis(UnitGraph g, boolean tryTrackFieldAssignments) {
super(g);
this.container = g.getBody().getMethod();
this.localsAndFieldRefs = new HashSet<Value>();
//add all locals
for (Local l : (Collection<Local>) g.getBody().getLocals()) {
if (l.getType() instanceof RefLikeType)
this.localsAndFieldRefs.add(l);
}
if(tryTrackFieldAssignments) {
this.localsAndFieldRefs.addAll(trackableFields());
}
this.rhsToNumber = new HashMap<Value, Integer>();
this.mergePointToValueToNumber = new HashMap<Unit,Map<Value,Integer>>();
doAnalysis();
//not needed any more
this.rhsToNumber = null;
this.mergePointToValueToNumber = null;
}
/**
* Computes the set of {@link EquivalentValue}s of all field references that are used
* in this method but not set by the method or any method transitively called by this method.
*/
private Set<Value> trackableFields() {
Set<Value> usedFieldRefs = new HashSet<Value>();
//add all field references that are in use boxes
for (Unit unit : this.graph) {
Stmt s = (Stmt) unit;
List<ValueBox> useBoxes = s.getUseBoxes();
for (ValueBox useBox : useBoxes) {
Value val = useBox.getValue();
if(val instanceof FieldRef) {
FieldRef fieldRef = (FieldRef) val;
if(fieldRef.getType() instanceof RefLikeType)
usedFieldRefs.add(new EquivalentValue(fieldRef));
}
}
}
//prune all fields that are written to
if(!usedFieldRefs.isEmpty()) {
if(!Scene.v().hasCallGraph()) {
throw new IllegalStateException("No call graph found!");
}
CallGraph cg = Scene.v().getCallGraph();
ReachableMethods reachableMethods = new ReachableMethods(cg,Collections.<MethodOrMethodContext>singletonList(container));
reachableMethods.update();
for (Iterator<MethodOrMethodContext> iterator = reachableMethods.listener(); iterator.hasNext();) {
SootMethod m = (SootMethod) iterator.next();
if(m.hasActiveBody() &&
//exclude static initializer of same class (assume that it has already been executed)
!(m.getName().equals(SootMethod.staticInitializerName) && m.getDeclaringClass().equals(container.getDeclaringClass()))) {
for (Unit u : m.getActiveBody().getUnits()) {
List<ValueBox> defBoxes = u.getDefBoxes();
for (ValueBox defBox : defBoxes) {
Value value = defBox.getValue();
if(value instanceof FieldRef) {
usedFieldRefs.remove(new EquivalentValue(value));
}
}
}
}
}
}
return usedFieldRefs;
}
protected void merge(Unit succUnit, HashMap<Value,Object> inMap1, HashMap<Value,Object> inMap2, HashMap<Value,Object> outMap)
{
for (Value l : localsAndFieldRefs) {
Object i1 = inMap1.get(l), i2 = inMap2.get(l);
if (i1.equals(i2))
outMap.put(l, i1);
else if (i1 == NOTHING)
outMap.put(l, i2);
else if (i2 == NOTHING)
outMap.put(l, i1);
else {
/* Merging two different values is tricky...
* A naive approach would be to assign UNKNOWN. However, that would lead to imprecision in the following case:
*
* x = null;
* if(p) x = new X();
* y = x;
* z = x;
*
* Even though it is obvious that after this block y and z are aliased, both would be UNKNOWN :-(
* Hence, when merging the numbers for the two branches (null, new X()), we assign a value number that is unique
* to that merge location. Consequently, both y and z is assigned that same number!
* In the following it is important that we use an IdentityHashSet because we want the number to be unique to the
* location. Using a normal HashSet would make it unique to the contents.
* (Eric)
*/
//retrieve the unique number for l at the merge point succUnit
//if there is no such number yet, generate one
//then assign the number to l in the outMap
Map<Value, Integer> valueToNumber = mergePointToValueToNumber.get(succUnit);
if(valueToNumber==null) {
valueToNumber = new HashMap<Value, Integer>();
mergePointToValueToNumber.put(succUnit, valueToNumber);
}
Integer number = valueToNumber.get(l);
if(number==null) {
number = nextNumber++;
valueToNumber.put(l, number);
}
outMap.put(l, number);
}
}
}
protected void flowThrough(HashMap<Value,Object> in, Unit u, HashMap<Value,Object> out) {
Stmt s = (Stmt)u;
out.clear();
out.putAll(in);
if (s instanceof DefinitionStmt) {
DefinitionStmt ds = (DefinitionStmt) s;
Value lhs = ds.getLeftOp();
Value rhs = ds.getRightOp();
if (rhs instanceof CastExpr) {
//un-box casted value
CastExpr castExpr = (CastExpr) rhs;
rhs = castExpr.getOp();
}
if ((lhs instanceof Local
|| (lhs instanceof FieldRef && this.localsAndFieldRefs.contains(new EquivalentValue(lhs))))
&& lhs.getType() instanceof RefLikeType) {
if (rhs instanceof Local) {
//local-assignment - must be aliased...
out.put(lhs, in.get(rhs));
} else if(rhs instanceof ThisRef) {
//ThisRef can never change; assign unique number
out.put(lhs, thisRefNumber());
} else if(rhs instanceof ParameterRef) {
//ParameterRef can never change; assign unique number
out.put(lhs, parameterRefNumber((ParameterRef) rhs));
} else {
//assign number for expression
out.put(lhs, numberOfRhs(rhs));
}
}
} else {
//which other kind of statement has def-boxes? hopefully none...
assert s.getDefBoxes().isEmpty();
}
}
private Object numberOfRhs(Value rhs) {
EquivalentValue equivValue = new EquivalentValue(rhs);
if(localsAndFieldRefs.contains(equivValue)){
rhs = equivValue;
}
Integer num = rhsToNumber.get(rhs);
if(num==null) {
num = nextNumber++;
rhsToNumber.put(rhs, num);
}
return num;
}
public static int thisRefNumber() {
//unique number for ThisRef (must be <1)
return 0;
}
public static int parameterRefNumber(ParameterRef r) {
//unique number for ParameterRef[i] (must be <0)
return 0 - r.getIndex();
}
protected void copy(HashMap<Value,Object> sourceMap, HashMap<Value,Object> destMap)
{
destMap.clear();
destMap.putAll(sourceMap);
}
/** Initial most conservative value: has to be {@link UNKNOWN} (top). */
protected HashMap<Value,Object> entryInitialFlow()
{
HashMap<Value,Object> m = new HashMap<Value,Object>();
for (Value l : (Collection<Value>) localsAndFieldRefs) {
m.put(l, UNKNOWN);
}
return m;
}
/** Initial bottom value: objects have no definitions. */
protected HashMap<Value,Object> newInitialFlow()
{
HashMap<Value,Object> m = new HashMap<Value,Object>();
for (Value l : (Collection<Value>) localsAndFieldRefs) {
m.put(l, NOTHING);
}
return m;
}
/**
* Returns a string (natural number) representation of the instance key associated with l
* at statement s or <code>null</code> if there is no such key associated or <code>UNKNOWN</code> if
* the value of l at s is {@link #UNKNOWN}.
* @param l any local of the associated method
* @param s the statement at which to check
*/
public String instanceKeyString(Local l, Stmt s) {
Object ln = getFlowBefore(s).get(l);
if(ln==null) {
return null;
} else if(ln==UNKNOWN) {
return UNKNOWN.toString();
}
return ln.toString();
}
/**
* Returns true if this analysis has any information about local l
* at statement s (i.e. it is not {@link #UNKNOWN}).
* In particular, it is safe to pass in locals/statements that are not
* even part of the right method. In those cases <code>false</code>
* will be returned.
* Permits s to be <code>null</code>, in which case <code>false</code> will be returned.
*/
public boolean hasInfoOn(Local l, Stmt s) {
HashMap<Value,Object> flowBefore = getFlowBefore(s);
if(flowBefore==null) {
return false;
} else {
Object info = flowBefore.get(l);
return info!=null && info!=UNKNOWN;
}
}
/**
* @return true if values of l1 (at s1) and l2 (at s2) have the
* exact same object IDs, i.e. at statement s1 the variable l1 must point to the same object
* as l2 at s2.
*/
public boolean mustAlias(Local l1, Stmt s1, Local l2, Stmt s2) {
Object l1n = getFlowBefore(s1).get(l1);
Object l2n = getFlowBefore(s2).get(l2);
if (l1n == UNKNOWN || l2n == UNKNOWN)
return false;
return l1n == l2n;
}
@Override
protected void merge(HashMap<Value, Object> in1,
HashMap<Value, Object> in2, HashMap<Value, Object> out) {
throw new UnsupportedOperationException("not implemented; use other merge method instead");
}
}