/**
*
*/
package soottocfg.soot.transformers;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.Verify;
import soot.ArrayType;
import soot.Body;
import soot.BooleanType;
import soot.Hierarchy;
import soot.Local;
import soot.NullType;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.jimple.AssignStmt;
import soot.jimple.IdentityStmt;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.JimpleBody;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.Stmt;
import soot.tagkit.Host;
import soot.toolkits.graph.CompleteUnitGraph;
import soottocfg.soot.util.LocalTypeFinder;
import soottocfg.util.Pair;
/**
* @author schaef
*
*/
public class VirtualCallResolver extends AbstractSceneTransformer {
private final Hierarchy hierarchy;
private LocalTypeFinder ltf;
/**
*
*/
public VirtualCallResolver() {
hierarchy = Scene.v().getActiveHierarchy();
}
public void applyTransformation() {
for (JimpleBody body : this.getSceneBodies()) {
transform(body);
}
}
private void transform(Body body) {
ltf = new LocalTypeFinder(new CompleteUnitGraph(body), body);
Map<Unit, Pair<InstanceInvokeExpr, List<SootMethod>>> callsToResolve = new HashMap<Unit, Pair<InstanceInvokeExpr, List<SootMethod>>>();
for (Unit u : body.getUnits()) {
Stmt s = (Stmt) u;
if (s.containsInvokeExpr()) {
InvokeExpr ie = s.getInvokeExpr();
if (ie instanceof InstanceInvokeExpr) {
List<SootMethod> callees = getPossibleCallees(body, s, (InstanceInvokeExpr) ie);
if (callees.isEmpty()) {
throw new RuntimeException("Failed to resolve virutal call " + ie);
} else if (callees.size() == 1 && callees.get(0).equals(ie.getMethod())) {
// if the only callee is the method itself, we don't
// have to
// do anything.
} else {
callsToResolve.put(s,
new Pair<InstanceInvokeExpr, List<SootMethod>>((InstanceInvokeExpr) ie, callees));
}
}
}
}
for (Entry<Unit, Pair<InstanceInvokeExpr, List<SootMethod>>> entry : callsToResolve.entrySet()) {
Unit originalCall = entry.getKey();
InstanceInvokeExpr ivk = entry.getValue().getFirst();
List<SootMethod> callees = entry.getValue().getSecond();
int counter = 0;
for (SootMethod callee : callees) {
counter++;
List<Unit> vcall = createVirtualCall(body, callee, originalCall, ivk);
if (counter < callees.size()) {
body.getUnits().addAll(vcall);
Local l = getFreshLocal(body, BooleanType.v());
List<Unit> stmts = new LinkedList<Unit>();
stmts.add(assignStmtFor(l,
Jimple.v().newInstanceOfExpr(ivk.getBase(), callee.getDeclaringClass().getType()),
originalCall));
stmts.add(ifStmtFor(jimpleNeZero(l), vcall.get(0), originalCall));
body.getUnits().insertBefore(stmts, originalCall);
} else {
// To keep the bytecode verifier happy,
// we don't guard the call for the last case
// to avoid uninitialized variables.
body.getUnits().insertBefore(vcall, originalCall);
}
}
// if the originalCall is an assignment,
// TODO: removing stuff break the bytecode verifier
// if its not run form within eclipse ... dig into that.
body.getUnits().remove(originalCall);
}
body.validate();
}
private List<Unit> createVirtualCall(Body body, SootMethod callee, Unit originalCall, InstanceInvokeExpr ivk) {
List<Unit> units = new LinkedList<Unit>();
Local l = getFreshLocal(body, callee.getDeclaringClass().getType());
// cast the base to the corresponding type.
units.add(assignStmtFor(l, Jimple.v().newCastExpr(ivk.getBase(), callee.getDeclaringClass().getType()),
originalCall));
if (originalCall instanceof InvokeStmt) {
// make the call statement
units.add(invokeStmtFor(l, callee.makeRef(), ivk.getArgs(), originalCall));
} else if (originalCall instanceof AssignStmt) {
AssignStmt s = (AssignStmt) originalCall;
// make the call statement
Unit newAssign = assignStmtFor(s.getLeftOp(),
Jimple.v().newVirtualInvokeExpr(l, callee.makeRef(), ivk.getArgs()), s);
units.add(newAssign);
} else if (originalCall instanceof IdentityStmt) {
throw new RuntimeException("Not imeplemented " + originalCall);
}
// jump back to the statement after the original call.
Unit succ = body.getUnits().getSuccOf(originalCall);
if (succ != null) {
units.add(gotoStmtFor(succ, originalCall));
}
return units;
}
private Unit invokeStmtFor(Local base, SootMethodRef method, List<? extends Value> args, Host createdFrom) {
InvokeStmt stmt = Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(base, method, args));
stmt.addAllTagsOf(createdFrom);
return stmt;
}
private List<SootMethod> getPossibleCallees(Body body, Unit u, InstanceInvokeExpr call) {
List<SootMethod> res = new LinkedList<SootMethod>();
SootMethod callee = call.getMethod();
Collection<SootClass> possibleClasses = new HashSet<SootClass>();
/*
* Special invoke is either a call to a constructor
* or to a super class method.
*/
if (call instanceof SpecialInvokeExpr) {
// TODO: is this correct?
SpecialInvokeExpr sivk = (SpecialInvokeExpr) call;
if (sivk.getMethod().isConstructor()) {
res.add(callee);
return res;
} else {
// Call to superclass method.
possibleClasses.add(call.getMethod().getDeclaringClass());
}
} else {
for (Type t : ltf.getLocalTypesBefore(u, (Local) call.getBase())) {
if (t instanceof RefType) {
SootClass subClass = ((RefType) t).getSootClass();
if (!subClass.isAbstract() && !subClass.isInterface()
&& subClass.declaresMethod(callee.getSubSignature())) {
possibleClasses.add(subClass);
}
} else if (t instanceof NullType) {
/*This case can be ignored since we insert a runtime
* assertion that the base is non-null anyway. So the
* assertion will fail before we resolve the call.
*/
} else {
Verify.verify(t instanceof ArrayType, "possible callee of type " + t.getClass() + " not expected");
/* This is sth like
* int[] a = ...;
* a.clone();
* Here, we don't have to distinguish subtypes.
*/
}
}
}
for (SootClass sub : possibleClasses) {
if (sub.resolvingLevel() < SootClass.SIGNATURES) {
// Log.error("Not checking subtypes of " + sub.getName());
// Then we probably really don't care.
} else {
if (sub.declaresMethod(callee.getName(), callee.getParameterTypes(), callee.getReturnType())) {
// if (callee.hasActiveBody()) {
// TODO: does it make sense to only add methods
// that have an active body?
res.add(sub.getMethod(callee.getName(), callee.getParameterTypes(), callee.getReturnType()));
// }
}
}
}
if (res.isEmpty()) {
/* TODO check when this happens... usually when we have no
* body for any version of this method.
*
* Another case is when the base is an array. E.g.,
* int[] arr;
* arr.clone();
* For arrays, we do not have to distinguish virtual calls since
* we cannot overload the array class anyway.
*/
res.add(callee);
}
if (res.size() == 1) {
return res;
}
// magic constant to keep the class file small.
if (res.size() > 30) {
System.err.println("Ignoring " + res.size() + " cases for " + u);
res.clear();
res.add(callee);
return res;
}
// we have to sort the methods by type.
Collections.sort(res, new Comparator<SootMethod>() {
@Override
public int compare(final SootMethod a, final SootMethod b) {
if (a == b || a.getDeclaringClass() == b.getDeclaringClass())
return 0;
if (hierarchy.isClassSubclassOf(a.getDeclaringClass(), b.getDeclaringClass()))
return -1;
if (hierarchy.isClassSuperclassOf(a.getDeclaringClass(), b.getDeclaringClass()))
return 1;
return 0;
}
});
return res;
}
}