/** * */ package soottocfg.soot.transformers; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.google.common.base.Verify; import soot.Body; import soot.BooleanType; import soot.Hierarchy; import soot.Immediate; import soot.IntType; import soot.Local; import soot.PatchingChain; import soot.PrimType; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootMethod; import soot.Trap; import soot.Unit; import soot.Value; import soot.dava.toolkits.base.misc.ConditionFlipper; import soot.jimple.AnyNewExpr; import soot.jimple.ArrayRef; import soot.jimple.BinopExpr; import soot.jimple.CastExpr; import soot.jimple.CaughtExceptionRef; import soot.jimple.ConditionExpr; import soot.jimple.DefinitionStmt; import soot.jimple.ExitMonitorStmt; import soot.jimple.IdentityRef; import soot.jimple.IfStmt; import soot.jimple.InstanceFieldRef; import soot.jimple.InstanceInvokeExpr; import soot.jimple.InstanceOfExpr; import soot.jimple.IntConstant; import soot.jimple.InvokeExpr; import soot.jimple.InvokeStmt; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.LengthExpr; import soot.jimple.NullConstant; import soot.jimple.Ref; import soot.jimple.ReturnStmt; import soot.jimple.StaticFieldRef; import soot.jimple.Stmt; import soot.jimple.SwitchStmt; import soot.jimple.ThrowStmt; import soot.jimple.UnopExpr; import soot.jimple.toolkits.annotation.nullcheck.NullnessAnalysis; import soot.tagkit.Host; import soot.toolkits.graph.CompleteUnitGraph; import soottocfg.soot.util.SootTranslationHelpers; import soottocfg.util.Pair; /** * @author schaef * TODO: we should do a fixed point iteration over all methods * and add throws clauses to all those that can throw user created * RuntimeExceptions. */ public class ExceptionTransformer extends AbstractSceneTransformer { protected final SootClass exceptionClass, runtimeExceptionClass, nullPointerExceptionClass, arrayIndexOutOfBoundsExceptionClass, classCastExceptionClass, errorExceptionClass, throwableClass; private final Hierarchy hierarchy; private boolean treatUncaughtExceptionsAsAssertions; private Body body; private NullnessAnalysis nullnessAnalysis; private Map<Unit, List<Pair<Value, SootClass>>> runtimeExceptions; private Set<Pair<Unit, InvokeExpr>> methodInvokes; private Set<Pair<Unit, Value>> throwStatements; private Map<Unit, Local> caughtExceptionLocal; private Map<SootClass, Unit> generatedThrowStatements; /** * */ public ExceptionTransformer() { this(true); } public ExceptionTransformer(boolean uncaughtAsAssertion) { treatUncaughtExceptionsAsAssertions = uncaughtAsAssertion; exceptionClass = Scene.v().getSootClass("java.lang.Exception"); throwableClass = Scene.v().getSootClass("java.lang.Throwable"); runtimeExceptionClass = Scene.v().getSootClass("java.lang.RuntimeException"); nullPointerExceptionClass = Scene.v().getSootClass("java.lang.NullPointerException"); arrayIndexOutOfBoundsExceptionClass = Scene.v().getSootClass("java.lang.ArrayIndexOutOfBoundsException"); classCastExceptionClass = Scene.v().getSootClass("java.lang.ClassCastException"); errorExceptionClass = Scene.v().getSootClass("java.lang.Error"); hierarchy = Scene.v().getActiveHierarchy(); } public void applyTransformation() { boolean tmp = treatUncaughtExceptionsAsAssertions; for (JimpleBody body : this.getSceneBodies()) { if (body.getMethod().isMain()) { treatUncaughtExceptionsAsAssertions = true; } else { treatUncaughtExceptionsAsAssertions = tmp; } transform(body); } } private void transform(Body b) { runtimeExceptions = new HashMap<Unit, List<Pair<Value, SootClass>>>(); methodInvokes = new HashSet<Pair<Unit, InvokeExpr>>(); throwStatements = new HashSet<Pair<Unit, Value>>(); caughtExceptionLocal = new HashMap<Unit, Local>(); generatedThrowStatements = new HashMap<SootClass, Unit>(); body = b; this.nullnessAnalysis = new NullnessAnalysis(new CompleteUnitGraph(body)); // first remove all the monitor related exceptions removeUnreachableTraps(body); removeMonitorTraps(body); PatchingChain<Unit> units = body.getUnits(); for (Unit u : units) { collectPossibleExceptions(u); } Set<Trap> usedTraps = new HashSet<Trap>(); usedTraps.addAll(handleRuntimeException()); usedTraps.addAll(handleMethodCalls()); usedTraps.addAll(handleThrowStatements()); // now remove the @caughtexceptionrefs Map<Unit, Unit> replacementMap = new HashMap<Unit, Unit>(); for (Trap t : usedTraps) { // Replace the caughtExceptionRef in the handler unit by // the exception local so that we can remove the traps. // For that we also need to add assignments that assign the // exception // variable to the corresponding new exception. if (t.getHandlerUnit() instanceof DefinitionStmt) { DefinitionStmt ds = (DefinitionStmt) t.getHandlerUnit(); if (ds.getRightOp() instanceof CaughtExceptionRef) { if (!replacementMap.containsKey(ds)) { Value left = ds.getLeftOp(); Value right = caughtExceptionLocal.get(t.getHandlerUnit()); if (!hierarchy.isClassSubclassOfIncluding(((RefType) right.getType()).getSootClass(), ((RefType) left.getType()).getSootClass())) { // if the left has a narrower type then we need to // add a cast. right = Jimple.v().newCastExpr(right, left.getType()); } Unit newAssign = assignStmtFor(left, right, ds); replacementMap.put(ds, newAssign); } } else { throw new RuntimeException( "Unexpected " + t.getHandlerUnit() + "\t" + caughtExceptionLocal.get(t.getHandlerUnit())); } } else { throw new RuntimeException("Unexpected " + t.getHandlerUnit() + "\n" + body); } } // now replace all @caughtexceptionrefs in on go. for (Entry<Unit, Unit> entry : replacementMap.entrySet()) { List<Unit> toInsert = new LinkedList<Unit>(); toInsert.add(entry.getValue()); // after the exception is caught, set the // $exception variable back to Null. toInsert.add(assignStmtFor(SootTranslationHelpers.v().getExceptionGlobalRef(), NullConstant.v(), entry.getKey())); body.getUnits().insertAfter(toInsert, entry.getKey()); body.getUnits().remove(entry.getKey()); } // finally, remove those traps: body.getTraps().removeAll(usedTraps); if (!body.getTraps().isEmpty()) { System.err.format("TODO: %d traps could not be removed for %s%n", body.getTraps().size(), body.getMethod().getSignature()); // TODO!!!!! body.getTraps().clear(); Set<Unit> caughtExceptionUnits = new HashSet<Unit>(); for (Unit u : body.getUnits()) { if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof CaughtExceptionRef) { caughtExceptionUnits.add(u); // deadLocals.add((Local) ((DefinitionStmt) u).getLeftOp()); } } body.getUnits().removeAll(caughtExceptionUnits); // body.getLocals().removeAll(deadLocals); } // System.err.println(body); body.validate(); } private Set<Trap> handleRuntimeException() { Set<Trap> usedTraps = new HashSet<Trap>(); // handle the runtime exceptions first. for (Entry<Unit, List<Pair<Value, SootClass>>> entry : runtimeExceptions.entrySet()) { Unit u = entry.getKey(); List<Trap> surroundingTraps = getTrapsGuardingUnit(u, body); for (Pair<Value, SootClass> pair : entry.getValue()) { Trap trap = null; for (Trap t : surroundingTraps) { if (hierarchy.isClassSubclassOfIncluding(pair.getSecond(), t.getException())) { trap = t; break; } } if (trap != null) { handleCaughtRuntimeException(u, pair.getFirst(), pair.getSecond(), trap); usedTraps.add(trap); } else { // re-throw the exception handleUncaughtRuntimeException(u, pair.getFirst(), pair.getSecond()); } } } return usedTraps; } private Set<Trap> handleMethodCalls() { Set<Trap> usedTraps = new HashSet<Trap>(); // now handle method calls. for (Pair<Unit, InvokeExpr> pair : methodInvokes) { Unit u = pair.getFirst(); InvokeExpr ivk = pair.getSecond(); List<SootClass> possibleExceptions = new LinkedList<SootClass>(); // first, add everything in the throws clause. possibleExceptions.addAll(ivk.getMethod().getExceptions()); // now get all caught exceptions of type RuntimeException or Error List<Trap> surroundingTraps = getTrapsGuardingUnit(u, body); for (Trap t : surroundingTraps) { if (hierarchy.isClassSubclassOfIncluding(t.getException(), runtimeExceptionClass) || hierarchy.isClassSubclassOfIncluding(t.getException(), errorExceptionClass)) { if (!possibleExceptions.contains(t.getException())) { possibleExceptions.add(t.getException()); } } // if there is a catch block for exception or throwable add that // as well. if (hierarchy.isClassSubclassOfIncluding(exceptionClass, t.getException())) { if (!possibleExceptions.contains(t.getException())) { possibleExceptions.add(t.getException()); } } // also add the exceptions of all catch blocks that are // sub-classes // of what is declared in the throws clause. for (SootClass sc : ivk.getMethod().getExceptions()) { if (hierarchy.isClassSubclassOfIncluding(t.getException(), sc)) { if (!possibleExceptions.contains(t.getException())) { possibleExceptions.add(t.getException()); } } } } if (mayThrowRuntimeException(ivk)) { //every method could throw a runtime exception. addRuntimExceptionIfNecessary(possibleExceptions); } // now sort the classes. sortExceptionsTightestFirst(possibleExceptions); // create the exception handling statements List<Unit> toInsert = new LinkedList<Unit>(); Local exceptionVarLocal = getFreshLocal(body, throwableClass.getType()); toInsert.add(assignStmtFor(exceptionVarLocal, SootTranslationHelpers.v().getExceptionGlobalRef(), u)); boolean throwsUncaughtException = false; for (SootClass exception : possibleExceptions) { Trap trap = null; for (Trap t : surroundingTraps) { // check if the trap is either super- or sub-class // because the procedure might throw a sub type of // what it declares. if (hierarchy.isClassSubclassOfIncluding(exception, t.getException())) { trap = t; break; } } if (trap == null) { throwsUncaughtException = true; continue; } Value instOf = Jimple.v().newInstanceOfExpr(exceptionVarLocal, RefType.v(exception)); Local l = getFreshLocal(body, BooleanType.v()); toInsert.add(assignStmtFor(l, instOf, u)); Unit target; usedTraps.add(trap); if (!caughtExceptionLocal.containsKey(trap.getHandlerUnit())) { // only create one local per trap so that we can // replace the CaughtExceptionRef later. caughtExceptionLocal.put(trap.getHandlerUnit(), getFreshLocal(body, throwableClass.getType())); } toInsert.add(assignStmtFor(caughtExceptionLocal.get(trap.getHandlerUnit()), exceptionVarLocal, u)); target = trap.getHandlerUnit(); toInsert.add(ifStmtFor(jimpleNeZero(l), target, u)); } if (throwsUncaughtException) { //check if the exception global is non-null and return. if (this.body.getMethod().isMain()) { //if this is main, we can't re-throw, so we //have to assert that the assertion is not null. Local assertionLocal = Jimple.v().newLocal("$assert_" + (body.getLocals().size()), BooleanType.v()); body.getLocals().add(assertionLocal); toInsert.add(assignStmtFor(assertionLocal, Jimple.v().newEqExpr(exceptionVarLocal, NullConstant.v()), u)); toInsert.add(SootTranslationHelpers.v().makeAssertion(assertionLocal, u)); } else { //normal case: if the exception isn't null, re-throw by //returning a default value. Unit defaultReturn = SootTranslationHelpers.v().getDefaultReturnStatement(body.getMethod().getReturnType(), u); body.getUnits().add(defaultReturn); toInsert.add(ifStmtFor(Jimple.v().newNeExpr(exceptionVarLocal, NullConstant.v()), defaultReturn, u)); } } // now insert everything after the call if (toInsert.size() > 1) { body.getUnits().insertAfter(toInsert, u); } } return usedTraps; } /** * Returns true if we assume that the invoked method may throw an * exception and false otherwise. * @param ivk * @return */ private boolean mayThrowRuntimeException(InvokeExpr ivk) { return mayThrowRuntimeException(ivk, new HashSet<InvokeExpr>()); } private boolean mayThrowRuntimeException(InvokeExpr ivk, Set<InvokeExpr> visited) { final SootMethod m = ivk.getMethod(); if (visited.contains(ivk)) { //for recursive calls, worst case assume that they may throw. mayThrowRuntimeException.put(m, true); return true; } visited.add(ivk); //TODO: this is only a stub. if (m.isStaticInitializer()) { return false; } else if (m.isJavaLibraryMethod()) { return false; } if (!mayThrowRuntimeException.containsKey(m)) { boolean mayThrow = true; if (m.hasActiveBody()) { JimpleBody jb = (JimpleBody)m.getActiveBody(); NullnessAnalysis nna = new NullnessAnalysis(new CompleteUnitGraph(jb)); mayThrow = false; for (Unit u : jb.getUnits()) { Stmt st = (Stmt)u; if (st.containsArrayRef()) { mayThrow = true; break; } else if (st.containsFieldRef() && st.getFieldRef() instanceof InstanceFieldRef) { InstanceFieldRef fr = (InstanceFieldRef)st.getFieldRef(); Immediate base = (Immediate)fr.getBase(); if (!m.isStatic() && !base.equals(jb.getThisLocal())) { // if (!nna.isAlwaysNonNullBefore(u, base)) { mayThrow = true; break; } } else if (st.containsInvokeExpr() ) { if (st.getInvokeExpr() instanceof InstanceInvokeExpr) { if (!nna.isAlwaysNonNullBefore(u, (Immediate)((InstanceInvokeExpr)st.getInvokeExpr()).getBase())) { mayThrow = true; break; } } if (mayThrowRuntimeException(st.getInvokeExpr(), visited)) { mayThrow = true; break; } } else if (st instanceof ThrowStmt) { mayThrow = true; break; } } } mayThrowRuntimeException.put(m, mayThrow); } return mayThrowRuntimeException.get(ivk.getMethod()); } Map<SootMethod, Boolean> mayThrowRuntimeException = new HashMap<SootMethod, Boolean>(); /** * Takes a list of SootClasses and adds the RuntimeException class * if the list does not contain RuntimeException or any or its * supertypes. * * @param classes */ private void addRuntimExceptionIfNecessary(List<SootClass> classes) { boolean containRteOrAbove = false; for (SootClass c : classes) { if (hierarchy.isClassSubclassOfIncluding(c, runtimeExceptionClass)) { containRteOrAbove = true; break; } } if (!containRteOrAbove) { classes.add(runtimeExceptionClass); } } private void sortExceptionsTightestFirst(List<SootClass> exceptions) { Collections.sort(exceptions, new Comparator<SootClass>() { @Override public int compare(final SootClass a, final SootClass b) { if (a == b) return 0; if (hierarchy.isClassSubclassOf(a, b)) return 1; if (hierarchy.isClassSuperclassOf(a, b)) return -1; return 0; } }); } private Set<Trap> handleThrowStatements() { Set<Trap> usedTraps = new HashSet<Trap>(); // last but not least eliminate all throw statements that are caught. Set<Unit> removeThrowStatements = new HashSet<Unit>(); for (Pair<Unit, Value> pair : throwStatements) { Unit u = pair.getFirst(); // must be a RefType RefType rt = (RefType) pair.getSecond().getType(); SootClass thrownException = rt.getSootClass(); List<Trap> surroundingTraps = getTrapsGuardingUnit(u, body); List<SootClass> possibleExceptions = new LinkedList<SootClass>(); // TODO: maybe we should treat the case where thrownException // is Throwable as a special case because then we have a // finally block. boolean catchesThrownException = false; for (Trap t : surroundingTraps) { // find any trap that is sub- or super-class if (hierarchy.isClassSubclassOfIncluding(t.getException(), thrownException) || hierarchy.isClassSubclassOfIncluding(thrownException, t.getException())) { if (!possibleExceptions.contains(t.getException())) { possibleExceptions.add(t.getException()); if (hierarchy.isClassSubclassOf(thrownException, t.getException())) { catchesThrownException = true; } } } } if (!catchesThrownException) { possibleExceptions.add(thrownException); } // now sort the classes. sortExceptionsTightestFirst(possibleExceptions); // insert a jump for each possible exception. List<Unit> toInsert = new LinkedList<Unit>(); boolean caughtThrowable = false; for (SootClass exception : possibleExceptions) { Trap trap = null; for (Trap t : surroundingTraps) { // check if the trap is either super- or sub-class // because the procedure might throw a sub type of // what it declares. if (hierarchy.isClassSubclassOfIncluding(exception, t.getException())) { trap = t; break; } } if (trap != null) { if (exception == thrownException) { caughtThrowable = true; } usedTraps.add(trap); Unit newTarget = updateExceptionVariableAndGoToTrap(u, ((ThrowStmt) u).getOp(), trap); Local l = getFreshLocal(body, BooleanType.v()); Value instOf = Jimple.v().newInstanceOfExpr(((ThrowStmt) u).getOp(), RefType.v(exception)); toInsert.add(assignStmtFor(l, instOf, u)); toInsert.add(ifStmtFor(jimpleNeZero(l), newTarget, u)); } // if we caught Throwable, we can remove the // throw statement. if (caughtThrowable) { removeThrowStatements.add(u); } } if (!removeThrowStatements.contains(u)) { // If the throw was not caught, replace it by a return. removeThrowStatements.add(u); toInsert.addAll(updateExceptionVariableAndReturn(body, ((ThrowStmt) u).getOp(), u)); } // check if toInsert only contains the assignment to the // exception local. If so, we do not need to add it. if (!toInsert.isEmpty()) { body.getUnits().insertBefore(toInsert, u); } } for (Unit u : removeThrowStatements) { body.getUnits().remove(u); } return usedTraps; } /** * Handle an exception that has a catch block * * @param b * Body of the procedure * @param u * The unit that throws the exception * @param ce * The ConditionalException * @param t * The trap that catches this exception */ protected void handleCaughtRuntimeException(Unit u, Value v, SootClass exception, Trap t) { List<Pair<Value, List<Unit>>> guards = constructGuardExpression(v, exception, true, u); Unit newTarget = createNewExceptionAndGoToTrap(u, exception, t); for (Pair<Value, List<Unit>> pair : guards) { List<Unit> toInsert = new LinkedList<Unit>(); toInsert.addAll(pair.getSecond()); toInsert.add(ifStmtFor(pair.getFirst(), newTarget, u)); body.getUnits().insertBefore(toInsert, u); } } private Unit updateExceptionVariableAndGoToTrap(Unit u, Value v, Trap t) { if (!caughtExceptionLocal.containsKey(t.getHandlerUnit())) { // only create one local per trap so that we can // replace the CaughtExceptionRef later. caughtExceptionLocal.put(t.getHandlerUnit(), getFreshLocal(body, SootTranslationHelpers.v().getExceptionGlobal().getType())); } Local execptionLocal = caughtExceptionLocal.get(t.getHandlerUnit()); List<Unit> units = new LinkedList<Unit>(); units.add(assignStmtFor(execptionLocal, v, u)); units.add(gotoStmtFor(t.getHandlerUnit(), u)); body.getUnits().addAll(units); return units.get(0); } private Unit createNewExceptionAndGoToTrap(Unit u, SootClass exception, Trap t) { // add a block that creates an exception object // and assigns it to $exception. if (!caughtExceptionLocal.containsKey(t.getHandlerUnit())) { // only create one local per trap so that we can // replace the CaughtExceptionRef later. caughtExceptionLocal.put(t.getHandlerUnit(), getFreshLocal(body, SootTranslationHelpers.v().getExceptionGlobal().getType())); } Local execptionLocal = caughtExceptionLocal.get(t.getHandlerUnit()); List<Unit> excCreation = createNewException(body, execptionLocal, exception, u); excCreation.add(gotoStmtFor(t.getHandlerUnit(), u)); body.getUnits().addAll(excCreation); return excCreation.get(0); } /** * Handle an exception that has no catch block but is declared in the * procedures throws clause. * * @param b * Body of the procedure * @param u * The unit that throws the exception * @param ce * The ConditionalException * @param tc * The class in the throws clause */ protected void handleUncaughtRuntimeException(Unit u, Value v, SootClass exception) { // runtime exceptions that also occur in the throws clause get re-thrown if (!treatUncaughtExceptionsAsAssertions) { List<Pair<Value, List<Unit>>> guards = constructGuardExpression(v, exception, true, u); Unit throwStmt = generateExceptionalReturn(u, exception); for (Pair<Value, List<Unit>> pair : guards) { List<Unit> toInsert = new LinkedList<Unit>(); toInsert.addAll(pair.getSecond()); toInsert.add(ifStmtFor(pair.getFirst(), throwStmt, u)); body.getUnits().insertBefore(toInsert, u); } } else { List<Pair<Value, List<Unit>>> guards = constructGuardExpression(v, exception, false, u); Local assertionLocal = null; if (!guards.isEmpty()) { assertionLocal = Jimple.v().newLocal("$assert_" + (body.getLocals().size()), BooleanType.v()); body.getLocals().add(assertionLocal); } for (Pair<Value, List<Unit>> pair : guards) { List<Unit> toInsert = new LinkedList<Unit>(); toInsert.addAll(pair.getSecond()); toInsert.add(assignStmtFor(assertionLocal, pair.getFirst(), u)); toInsert.add(SootTranslationHelpers.v().makeAssertion(assertionLocal, u)); body.getUnits().insertBefore(toInsert, u); } } } private Unit generateExceptionalReturn(Unit u, SootClass exception) { if (!generatedThrowStatements.containsKey(exception)) { List<Unit> units = new LinkedList<Unit>(); Local exceptionLocal = getFreshLocal(body, exception.getType()); units.addAll(createNewException(body, exceptionLocal, exception, u)); units.addAll(updateExceptionVariableAndReturn(body, exceptionLocal, u)); Unit target = units.get(0); body.getUnits().addAll(units); generatedThrowStatements.put(exception, target); } return generatedThrowStatements.get(exception); } protected List<Unit> createNewException(Body b, Local exLocal, SootClass exc, Host createdFrom) { List<Unit> result = new LinkedList<Unit>(); /* * generate l := new Exception constructor call throw l */ Local l = exLocal; // l = new Exception Unit newException = assignStmtFor(l, Jimple.v().newNewExpr(RefType.v(exc)), createdFrom); result.add(newException); // constructor call boolean foundConstructor = false; for (SootMethod sm : exc.getMethods()) { if (sm.isConstructor() && sm.getParameterCount() == 0) { // This is the constructor we are looking for. foundConstructor = true; result.add(invokeStmtFor(Jimple.v().newSpecialInvokeExpr(l, sm.makeRef()), createdFrom)); break; } } Verify.verify(foundConstructor, "Don't try to call a constructor on an abstract class or interface!"); return result; } protected List<Unit> updateExceptionVariableAndReturn(Body b, Value v, Host createdFrom) { List<Unit> result = new LinkedList<Unit>(); if (b.getMethod().isMain()) { //if it's a main we add an assert false. Local assertionLocal = Jimple.v().newLocal("$assert_" + (body.getLocals().size()), BooleanType.v()); body.getLocals().add(assertionLocal); result.add(assignStmtFor(assertionLocal, IntConstant.v(0), createdFrom)); result.add(SootTranslationHelpers.v().makeAssertion(assertionLocal, createdFrom)); } else { //otherwise we update the exception variable and return a default value. if (!SootTranslationHelpers.v().getExceptionGlobalRef().equals(v)) { result.add(assignStmtFor(SootTranslationHelpers.v().getExceptionGlobalRef(), v, createdFrom)); } } result.add(SootTranslationHelpers.v().getDefaultReturnStatement(b.getMethod().getReturnType(), createdFrom)); return result; } /** * Generates for a given value and exception a list of pairs of the Value * under which the exception occurs (or the negated version if negated is * true), and the list of supporting statements, such as temp variables. * * In most cases, the list contains only one element. Only for * IndexOutOfBoundsExceptions it returns two elements. One checking the * lower bound and one checking the upper bound. This is because Jimple does * not have disjunctions. * * @param val * @param exception * @param negated * @param createdFrom * @return */ protected List<Pair<Value, List<Unit>>> constructGuardExpression(Value val, SootClass exception, boolean negated, Host createdFrom) { List<Pair<Value, List<Unit>>> result = new LinkedList<Pair<Value, List<Unit>>>(); if (exception == nullPointerExceptionClass) { // no helper statements needed. if (negated) { result.add(new Pair<Value, List<Unit>>(Jimple.v().newEqExpr(val, NullConstant.v()), new LinkedList<Unit>())); } else { result.add(new Pair<Value, List<Unit>>(Jimple.v().newNeExpr(val, NullConstant.v()), new LinkedList<Unit>())); } return result; } else if (exception == arrayIndexOutOfBoundsExceptionClass) { ArrayRef e = (ArrayRef) val; List<Unit> helperStatements = new LinkedList<Unit>(); Value arrayLen = Jimple.v().newLengthExpr(e.getBase()); Local len = getFreshLocal(body, arrayLen.getType()); Unit helperStmt = assignStmtFor(len, arrayLen, createdFrom); helperStatements.add(helperStmt); // (index < array.length) Local left = getFreshLocal(body, IntType.v()); helperStmt = assignStmtFor(left, e.getIndex(), createdFrom); helperStatements.add(helperStmt); ConditionExpr guard = Jimple.v().newLtExpr(left, len); if (negated) { guard = ConditionFlipper.flip(guard); } result.add(new Pair<Value, List<Unit>>(guard, helperStatements)); // index >= 0 helperStatements = new LinkedList<Unit>(); guard = Jimple.v().newLeExpr(IntConstant.v(0), left); if (negated) { guard = ConditionFlipper.flip(guard); } result.add(new Pair<Value, List<Unit>>(guard, helperStatements)); return result; } else if (exception == classCastExceptionClass) { CastExpr e = (CastExpr) val; // e instanceof t /* * Since instanceof cannot be part of a UnOp, we have to create a * helper local l and a statement l = e instanceof t first. */ if (!(e.getCastType() instanceof PrimType)) { List<Unit> helperStatements = new LinkedList<Unit>(); Local helperLocal = getFreshLocal(body, BooleanType.v()); Unit helperStmt = assignStmtFor(helperLocal, Jimple.v().newInstanceOfExpr(e.getOp(), e.getCastType()), createdFrom); helperStatements.add(helperStmt); if (negated) { result.add(new Pair<Value, List<Unit>>(jimpleEqZero(helperLocal), helperStatements)); } else { result.add(new Pair<Value, List<Unit>>(jimpleNeZero(helperLocal), helperStatements)); } // } else { // System.err.println("Not guarding cast from " + e.getOp().getType() + " to " + e.getCastType() // + ". This should be done by the compiler."); } return result; } throw new RuntimeException("not implemented"); } /** * This a pre-processing hack that removes all traps that are related to * entermonitor and exitmonitor. These traps are always looping. E.g., catch * java.lang.Throwable from label07 to label08 with label07; So it is easy * to spot them. We remove these traps and their code. We also have to * remove all other traps that share the handler unit with these traps. * * @param body */ private void removeMonitorTraps(Body body) { List<Trap> monitorTraps = new LinkedList<Trap>(); Map<Trap, List<Unit>> catchBlocks = new HashMap<Trap, List<Unit>>(); // first collect all monitor traps. for (Trap t : body.getTraps()) { if (t.getBeginUnit() == t.getHandlerUnit() && t.getException() == throwableClass) { // collect the statements caught by this trap. List<Unit> catchblock = new LinkedList<Unit>(); Iterator<Unit> it = body.getUnits().iterator(t.getBeginUnit(), t.getEndUnit()); boolean containsExitMonitor = false; while (it.hasNext()) { Unit stmt = it.next(); if (stmt instanceof ExitMonitorStmt) { containsExitMonitor = true; } catchblock.add(stmt); } if (!containsExitMonitor) { // then this is not a monitor. continue; } // if there is an exit monitor, the catch block also has to end // on a throw statement. if (!(catchblock.get(catchblock.size() - 1) instanceof ThrowStmt)) { throw new RuntimeException("didn't expect that."); } catchBlocks.put(t, catchblock); monitorTraps.add(t); } } // now remove all other traps that jump into those. List<Trap> toRemove = new LinkedList<Trap>(); for (Trap t : body.getTraps()) { if (!catchBlocks.containsKey(t)) { boolean found = false; for (Trap mt : monitorTraps) { if (t.getHandlerUnit() == mt.getHandlerUnit()) { found = true; break; } } if (found) { toRemove.add(t); } } } body.getTraps().removeAll(toRemove); // //now remove all statements in the monitor traps. for (Entry<Trap, List<Unit>> entry : catchBlocks.entrySet()) { body.getUnits().removeAll(entry.getValue()); body.getTraps().remove(entry.getKey()); } } private void collectPossibleExceptions(Unit u) { if (u instanceof DefinitionStmt) { DefinitionStmt s = (DefinitionStmt) u; // precedence says left before right. collectPossibleExceptions(u, s.getLeftOp()); collectPossibleExceptions(u, s.getRightOp()); } else if (u instanceof SwitchStmt) { SwitchStmt s = (SwitchStmt) u; collectPossibleExceptions(u, s.getKey()); } else if (u instanceof IfStmt) { IfStmt s = (IfStmt) u; collectPossibleExceptions(u, s.getCondition()); } else if (u instanceof InvokeStmt) { InvokeStmt s = (InvokeStmt) u; collectPossibleExceptions(s, s.getInvokeExpr()); } else if (u instanceof ReturnStmt) { ReturnStmt s = (ReturnStmt) u; collectPossibleExceptions(u, s.getOp()); } else if (u instanceof ThrowStmt) { ThrowStmt s = (ThrowStmt) u; collectPossibleExceptions(u, s.getOp()); throwStatements.add(new Pair<Unit, Value>(u, s.getOp())); } } private boolean isAssertionMethod(SootMethod sm) { if (sm.getSignature().equals(SootTranslationHelpers.v().getAssertMethod().getSignature())) { // throw new RuntimeException("WORKS"); return true; } // TODO: do that for others as well like guava Verify, or Junit Assert return false; } private void collectPossibleExceptions(Unit u, Value v) { if (v instanceof BinopExpr) { BinopExpr e = (BinopExpr) v; // precedence says left before right. collectPossibleExceptions(u, e.getOp1()); collectPossibleExceptions(u, e.getOp2()); } else if (v instanceof UnopExpr) { UnopExpr e = (UnopExpr) v; collectPossibleExceptions(u, e.getOp()); } else if (v instanceof InvokeExpr) { InvokeExpr ivk = (InvokeExpr) v; if (isAssertionMethod(ivk.getMethod())) { // do not tinker with assertion methods because // they will be turned into assertions anyway. return; } if (ivk instanceof InstanceInvokeExpr) { // if its an instance invoke, check // if the base is null. InstanceInvokeExpr iivk = (InstanceInvokeExpr) ivk; if (iivk.getBase() instanceof Immediate && nullnessAnalysis.isAlwaysNonNullBefore(u, (Immediate) iivk.getBase())) { // do nothing. } else { registerRuntimeException(u, iivk.getBase(), nullPointerExceptionClass); } collectPossibleExceptions(u, iivk.getBase()); } // handle the args. for (Value val : ivk.getArgs()) { collectPossibleExceptions(u, val); } // add the method as a source of exceptions. methodInvokes.add(new Pair<Unit, InvokeExpr>(u, ivk)); } else if (v instanceof CastExpr) { CastExpr e = (CastExpr) v; collectPossibleExceptions(u, e.getOp()); registerRuntimeException(u, v, classCastExceptionClass); } else if (v instanceof InstanceOfExpr) { InstanceOfExpr e = (InstanceOfExpr) v; collectPossibleExceptions(u, e.getOp()); } else if (v instanceof Ref) { refMayThrowException(u, (Ref) v); } else if (v instanceof LengthExpr) { LengthExpr le = (LengthExpr)v; registerRuntimeException(u, le.getOp(), nullPointerExceptionClass); } else if (v instanceof AnyNewExpr || v instanceof Immediate) { // ignore } else { throw new RuntimeException("Not handling " + v + " of type " + v.getClass()); } } private void refMayThrowException(Unit u, Ref r) { if (r instanceof InstanceFieldRef) { InstanceFieldRef e = (InstanceFieldRef) r; collectPossibleExceptions(u, e.getBase()); if (e.getBase() instanceof Immediate && nullnessAnalysis.isAlwaysNonNullBefore(u, (Immediate) e.getBase())) { // no need to add null pointer check. } else { registerRuntimeException(u, e.getBase(), nullPointerExceptionClass); } } else if (r instanceof ArrayRef) { ArrayRef e = (ArrayRef) r; collectPossibleExceptions(u, e.getBase()); collectPossibleExceptions(u, e.getIndex()); registerRuntimeException(u, e.getBase(), nullPointerExceptionClass); registerRuntimeException(u, e, arrayIndexOutOfBoundsExceptionClass); } else if (r instanceof IdentityRef || r instanceof StaticFieldRef) { // do nothing. } } private void registerRuntimeException(Unit u, Value v, SootClass ex) { if (!runtimeExceptions.containsKey(u)) { runtimeExceptions.put(u, new LinkedList<Pair<Value, SootClass>>()); } runtimeExceptions.get(u).add(new Pair<Value, SootClass>(v, ex)); } /** * Get the list of all traps that may catch exceptions thrown by u. * * @param u * @param b * @return */ protected List<Trap> getTrapsGuardingUnit(Unit u, Body b) { List<Trap> result = new LinkedList<Trap>(); for (Trap t : b.getTraps()) { Iterator<Unit> it = b.getUnits().iterator(t.getBeginUnit(), t.getEndUnit()); while (it.hasNext()) { if (u.equals(it.next())) { result.add(t); } } } return result; } protected void removeUnreachableTraps(Body b) { List<Trap> duplicates = new LinkedList<Trap>(); for (Trap t : b.getTraps()) { Trap current = t; while (b.getTraps().getPredOf(current) != null) { current = b.getTraps().getPredOf(current); if (equalTraps(current, t)) { // System.err.println("DUPLICATE "+t); duplicates.add(t); break; } } } b.getTraps().removeAll(duplicates); } protected boolean equalTraps(Trap a, Trap b) { return a.getBeginUnit() == b.getBeginUnit() && a.getEndUnit() == b.getEndUnit() && a.getException() == b.getException(); } }