/**
*
*/
package org.joogie.soot;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.joogie.GlobalsCache;
import org.joogie.util.Log;
import org.joogie.util.TranslationHelpers;
import soot.Immediate;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Trap;
import soot.Unit;
import soot.Value;
import soot.jimple.DynamicInvokeExpr;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.VirtualInvokeExpr;
import boogie.ProgramFactory;
import boogie.ast.Attribute;
import boogie.ast.VarList;
import boogie.ast.expression.Expression;
import boogie.ast.expression.IdentifierExpression;
import boogie.ast.statement.Statement;
import boogie.enums.BinaryOperator;
/**
* @author schaef
*
*/
public class InvokeTranslation {
public static void translateInvokeAssignment(SootStmtSwitch ss, Value lhs,
InvokeExpr ivk, Unit statement) {
if (specialCaseInvoke(ss, lhs, ivk))
return;
SootValueSwitch valueswitch = ss.getValueSwitch();
SootProcedureInfo procInfo = ss.getProcInfo();
// translate the left-hand side of the assignment (if there is one).
LinkedList<IdentifierExpression> lefts = new LinkedList<IdentifierExpression>();
IdentifierExpression stubbedvar = null;
Expression left = null;
if (lhs != null) {
lhs.apply(valueswitch);
left = valueswitch.getExpression();
if (left instanceof IdentifierExpression) {
lefts.add((IdentifierExpression) left);
} else {
/*
* boogie doesn't allow you to put an array access as left hand
* side for a function call. So, if this happens, we add a fake
* local and assign it back after the call statement.
*/
stubbedvar = procInfo.createLocalVariable(left.getType());
lefts.add(stubbedvar);
}
}
SootMethod m = ivk.getMethod();
// translate the call args
LinkedList<Expression> args = new LinkedList<Expression>();
for (int i = 0; i < ivk.getArgs().size(); i++) {
ivk.getArg(i).apply(valueswitch);
args.add(valueswitch.getExpression());
}
// Translate base, if necessary.
// SootClass baseClass = null;
if (ivk instanceof InstanceInvokeExpr) {
// this include Interface-, Virtual, and SpecialInvokeExpr
InstanceInvokeExpr iivk = (InstanceInvokeExpr) ivk;
// baseClass = getBaseClass(iivk.getBase().getType());
iivk.getBase().apply(valueswitch);
Expression base = valueswitch.getExpression();
// add the "this" variable to the list of args
args.addFirst(base);
try {
if (iivk.getBase() instanceof Immediate
&& procInfo.getNullnessAnalysis().isAlwaysNonNullBefore(
statement, (Immediate) iivk.getBase())) {
// do not check
} else {
ss.getErrorModel().createNonNullViolationException(base);
}
} catch (Throwable e) {
}
} else if (ivk instanceof StaticInvokeExpr) {
// Do nothing
} else if (ivk instanceof DynamicInvokeExpr) {
DynamicInvokeExpr divk = (DynamicInvokeExpr)ivk;
Log.info("Ignoring dynamic invoke: "+divk.toString());
} else {
throw new RuntimeException(
"Cannot compute instance for " + ivk.getClass().toString());
}
ss.addStatement(createCallStatement(ss, m, lefts,
args.toArray(new Expression[args.size()])));
Expression constructorInstance = null;
if (m.isConstructor()) {
// if it is a call to a constructor, we have to add
// the condition that the object which is being constructed
// will be set to null if the constructor terminates
// with an exception.
// That is, for: $exception = Object$init(base)
// we create
// if ($exception!=null && base!=this) base=null;
constructorInstance = args.getFirst(); // the first arg must be the
// pointer to that object
}
// now check if the procedure returned exceptional
// and jump to the appropriate location
translateCalleeExceptions(ss, statement, constructorInstance, m);
/*
* if the left-hand side was an array access and we introduced a helper
* variable, we create and assignment here that assigns this variable to
* the original LHS.
*/
if (stubbedvar != null) {
AssignmentTranslation.translateAssignment(ss, left, stubbedvar);
}
}
// static private SootClass getBaseClass(Type type) {
// SootClass c = null;
// if (type instanceof RefType) {
// RefType rt = (RefType) type;
// c = rt.getSootClass();
// } else if (type instanceof ArrayType) {
// c = Scene.v().loadClass("java.lang.reflect.Array",
// SootClass.SIGNATURES);
// } else {
// throw new RuntimeException(
// "Something wrong in translateInvokeAssignment: Expected RefType or ArrayType but found "
// + type.getClass().toString());
// }
// return c;
// }
static private boolean specialCaseInvoke(SootStmtSwitch ss, Value lhs,
InvokeExpr ivk) {
SootValueSwitch valueswitch = ss.getValueSwitch();
ProgramFactory pf = GlobalsCache.v().getPf();
// java.lang.String.length is treated as a special case:
if (ivk.getMethod().getSignature()
.contains("<java.lang.String: int length()>")
&& lhs != null) {
if (ivk instanceof SpecialInvokeExpr) {
((SpecialInvokeExpr) ivk).getBase().apply(valueswitch);
} else if (ivk instanceof VirtualInvokeExpr) {
((VirtualInvokeExpr) ivk).getBase().apply(valueswitch);
} else {
throw new RuntimeException("Bad usage of String.length?");
}
Expression[] indices = { valueswitch.getExpression() };
Expression right = pf.mkArrayAccessExpression(pf.getIntType(),
SootPrelude.v().getStringSizeHeapVariable(), indices);
lhs.apply(valueswitch);
Expression left = valueswitch.getExpression();
AssignmentTranslation.translateAssignment(ss, left, right);
return true;
}
if (ivk.getMethod().getSignature()
.contains("<java.lang.System: void exit(int)>")) {
Log.info("Surppressing false positive from call to System.exit");
// this is not a return statement, it actually ends the application.
// ss.addStatement(pf.mkAssumeStatement(new
// Attribute[]{pf.mkNoVerifyAttribute()},
// pf.mkBooleanLiteral(false)));
ss.addStatement(pf.mkReturnStatement());
return true;
}
if (ivk.getMethod()
.getSignature()
.contains(
"java.lang.Throwable: void addSuppressed(java.lang.Throwable)")) {
ss.addStatement(TranslationHelpers.mkLocationAssertion(
ss.getCurrentStatement(), true));
return true;
}
if (ivk.getMethod().getSignature().contains("addSuppressed")) {
System.err.println(ivk.getMethod().getSignature());
}
return false;
}
/**
* Create a Boogie call statement. The trick is, that, given a call f(x) we
* may have to create stubs for the return value of f and the exception
* thrown by f because in Boogie the number of lhs variables has to match
* the number of return variables. E.g. if f returns an int and throws
* exceptions, the call above would be translated to call ret, exc := f(x);
* where ret and exc are fresh local variables.
*
* @param ss
* @param m
* @param throwsclauses
* @param lefts
* @param args
* @return
*/
static private Statement createCallStatement(SootStmtSwitch ss,
SootMethod m, List<IdentifierExpression> lefts, Expression[] args) {
ProgramFactory pf = GlobalsCache.v().getPf();
// this is the procInfo for the procedure from which m is called
SootProcedureInfo procInfo = ss.getProcInfo();
// this is the procInfo for the called procedure m
SootProcedureInfo calleeInfo = GlobalsCache.v().lookupProcedure(m);
Attribute[] attributes = TranslationHelpers.javaLocation2Attribute(ss
.getCurrentStatement());
// we have to clone the lefts because we may add thing to it here.
List<IdentifierExpression> lefts_clone = new LinkedList<IdentifierExpression>();
for (IdentifierExpression ide : lefts) {
lefts_clone.add(ide);
}
if (calleeInfo.getReturnVariable() != null && lefts_clone.size() == 0) {
lefts_clone.add(procInfo.createLocalVariable(calleeInfo
.getReturnVariable().getType()));
}
/*
* now add a fake local if the callee may throw an exception
*/
if (calleeInfo.getExceptionVariable() != null) {
lefts_clone.add(procInfo.getExceptionVariable());
}
HashMap<String, Expression> substitutes = new HashMap<String, Expression>();
for (int i = 0; i < calleeInfo.getProcedureDeclaration().getInParams().length; i++) {
VarList vl = calleeInfo.getProcedureDeclaration().getInParams()[i];
Expression arg = args[i];
if (vl.getIdentifiers().length != 1) {
throw new RuntimeException("That aint right!");
}
substitutes.put(vl.getIdentifiers()[0], arg);
}
Statement s = pf.mkCallStatement(attributes, false, lefts_clone
.toArray(new IdentifierExpression[lefts_clone.size()]),
calleeInfo.getBoogieName(), args);
return s;
}
static private void translateCalleeExceptions(SootStmtSwitch ss,
Unit statement, Expression constructorInstance,
SootMethod calledMethod) {
SootValueSwitch valueswitch = ss.getValueSwitch();
ProgramFactory pf = GlobalsCache.v().getPf();
SootProcedureInfo procInfo = ss.getProcInfo();
// first we collect all possible exceptions.
// then we create one statement if($exception!=null) statements
LinkedList<Statement> statements = new LinkedList<Statement>();
List<Trap> traps = new LinkedList<Trap>();
List<Trap> finally_traps = new LinkedList<Trap>(); // TODO: do we have
// to use them here?
TranslationHelpers.getReachableTraps(statement,
procInfo.getSootMethod(), traps, finally_traps);
List<SootClass> possibleExceptions = sortExceptions(calledMethod
.getExceptions());
// in case the method throws something unexpected, we
// add Throwable to the list of possible exceptions.
// NOTE: this causes a ridiculous blow-up of the boogie program
// and we don't gain anything for the infeasible code detection,
// so we're not doing it for now.
// SootClass throwableException =
// Scene.v().loadClass("java.lang.Throwable",
// SootClass.SIGNATURES);
// if (!possibleExceptions.contains(throwableException)) {
// possibleExceptions.add(throwableException);
// }
SootClass interuptException = Scene.v().loadClass(
"java.lang.InterruptedException", SootClass.SIGNATURES);
SootClass largestCaughtException = null;
LinkedList<Trap> usedTraps = new LinkedList<Trap>();
for (SootClass c : possibleExceptions) {
String transferlabel = null;
// for each possible exception, check if there is a catch block.
for (Trap trap : new LinkedList<Trap>(traps)) {
if (GlobalsCache.v().isSubTypeOrEqual(c, trap.getException())) {
transferlabel = GlobalsCache.v().getUnitLabel(
(Stmt) trap.getHandlerUnit());
// mark that we used that trap already, so we don't
// add another transition for it later.
// System.err.println("Exception "+c.getName() +
// " is caught by "+trap.getException());
usedTraps.add(trap);
break;
}
}
if (largestCaughtException == null
|| GlobalsCache.v().isProperSubType(largestCaughtException,
c)) {
largestCaughtException = c;
} else {
// System.err.println("Not catching "+c.getName() +
// " because we already caught "+largestCaughtException.getName());
continue;
}
Statement transferStatement;
if (transferlabel == null) {
// if the exception is not caught, leave the procedure
// that is, re-throw.
transferStatement = pf.mkReturnStatement();
} else {
// if the exception is caught, create a goto
transferStatement = pf.mkGotoStatement(transferlabel);
}
// now make a statement of the form
// if($exception<:c) transferStatement
// and add it to the list statements
Expression condition = pf.mkBinaryExpression(
pf.getBoolType(),
BinaryOperator.COMPPO,
valueswitch.getClassTypeFromExpression(
procInfo.getExceptionVariable(), false),
GlobalsCache.v().lookupClassVariable(c));
LinkedList<Statement> then = new LinkedList<Statement>();
// Small hack to avoid false positives form
// code that is only reachable if an interleaving happens
if (interuptException == c
&& !org.joogie.Options.v().useSoundThreads()) {
then.add(TranslationHelpers.havocEverything(procInfo,
valueswitch));
} else {
if (constructorInstance != null
&& constructorInstance != procInfo.getThisReference()) {
then.add(pf.mkAssignmentStatement(constructorInstance, SootPrelude.v()
.getNullConstant()));
}
}
then.add(transferStatement);
Statement[] thenPart = then.toArray(new Statement[then.size()]);
// Statement[] thenPart = { transferStatement };
// //Small hack to avoid false positives form
// //code that is only reachable if an interleaving happens
// if (interuptException==c && !org.joogie.Options.v().useSoundThreads()) {
// thenPart = new Statement[]{TranslationHelpers.havocEverything(procInfo, valueswitch), transferStatement};
// }
Statement[] elsePart = { TranslationHelpers
.createClonedAttribAssert()};
statements.add(pf.mkIfStatement(condition, thenPart, elsePart));
}
// now remove all the traps that we have already used
// and generate transitions for the remaining ones if
// necessary.
traps.removeAll(usedTraps);
// now check if there are traps of type Exception or Throwable left, or
// if we might not know the throws clause
// then we have to create an edge to them as well. Otherwise
// we might create unreachable catch blocks
// also check if runtime exceptions are caught. In that case,
// we have to allow a transition to them as well
SootClass exception = Scene.v().loadClass("java.lang.Exception",
SootClass.SIGNATURES);
SootClass throwable = Scene.v().loadClass("java.lang.Throwable",
SootClass.SIGNATURES);
SootClass runtimeexception = Scene.v().loadClass(
"java.lang.RuntimeException", SootClass.SIGNATURES);
for (Trap trap : traps) {
if (trap.getException() == exception
|| trap.getException() == throwable
|| !calledMethod.hasActiveBody()
|| GlobalsCache.v().isSubTypeOrEqual(trap.getException(),
runtimeexception)) {
if (largestCaughtException == null
|| GlobalsCache.v().isProperSubType(
largestCaughtException, trap.getException())) {
largestCaughtException = trap.getException();
} else {
// System.err.println("Not catching "+trap.getException().getName()
// +
// " because we already caught "+largestCaughtException.getName());
continue;
}
Expression condition = pf.mkBinaryExpression(
pf.getBoolType(),
BinaryOperator.COMPPO,
valueswitch.getClassTypeFromExpression(
procInfo.getExceptionVariable(), false),
GlobalsCache.v().lookupClassVariable(
trap.getException()));
Statement transferStatement = pf.mkGotoStatement(GlobalsCache.v().getUnitLabel(
(Stmt) trap.getHandlerUnit()));
Statement[] thenPart = {
TranslationHelpers.createClonedAttribAssert(),
transferStatement };
if (constructorInstance != null
&& constructorInstance != procInfo.getThisReference()) {
thenPart = new Statement[]{
TranslationHelpers.createClonedAttribAssert(),
pf.mkAssignmentStatement(constructorInstance, SootPrelude.v()
.getNullConstant()),
transferStatement };
}
Statement[] elsePart = { TranslationHelpers
.createClonedAttribAssert() };
statements.add(pf.mkIfStatement(condition, thenPart, elsePart));
}
}
// finally check if there is a finally_trap that we didn't account for
// and create an unconditional goto.
if (finally_traps.size() > 0) {
if (finally_traps.size() > 1) {
Log.error("more than one finally trap for "
+ procInfo.getBoogieName());
}
Trap trap = finally_traps.get(0);
if (largestCaughtException == null
|| GlobalsCache.v().isProperSubType(largestCaughtException,
trap.getException())) {
if (constructorInstance != null
&& constructorInstance != procInfo.getThisReference()) {
statements.addFirst(pf.mkAssignmentStatement(
constructorInstance, SootPrelude.v()
.getNullConstant()));
}
largestCaughtException = trap.getException();
statements.add(pf.mkGotoStatement(GlobalsCache.v()
.getUnitLabel((Stmt) trap.getHandlerUnit())));
} else {
// System.err.println("Not catching finally "+trap.getException().getName()
// +
// " because we already caught "+largestCaughtException.getName());
}
}
// if (statements.size() == 0)
// return;
// // if the call was a constructor, add an assignment that sets
// // the created variable back to null
// if (constructorInstance != null
// && constructorInstance != procInfo.getThisReference()) {
// statements.addFirst(pf.mkAssignmentStatement(constructorInstance,
// SootPrelude.v().getNullConstant()));
// }
// now add all the exceptional checks in a block where
// we ensure that $excpeiton was not null
if (statements.size()>0) {
Expression condition = pf.mkBinaryExpression(pf.getBoolType(),
BinaryOperator.COMPNEQ, procInfo.getExceptionVariable(),
SootPrelude.v().getNullConstant());
ss.addStatement(pf.mkIfStatement(condition,
statements.toArray(new Statement[statements.size()]),
new Statement[] {}));
}
}
static public void translateCalleeExceptions2(SootStmtSwitch ss,
Unit statement, Expression constructorInstance,
SootMethod calledMethod) {
SootValueSwitch valueswitch = ss.getValueSwitch();
ProgramFactory pf = GlobalsCache.v().getPf();
SootProcedureInfo procInfo = ss.getProcInfo();
// first we collect all possible exceptions.
// then we create one statement if($exception!=null) statements
LinkedList<Statement> statements = new LinkedList<Statement>();
List<Trap> traps = new LinkedList<Trap>();
List<Trap> finally_traps = new LinkedList<Trap>(); // TODO: do we have
// to use them here?
TranslationHelpers.getReachableTraps(statement,
procInfo.getSootMethod(), traps, finally_traps);
List<SootClass> possibleExceptions = calledMethod.getExceptions();
SootClass interuptException = Scene.v().loadClass(
"java.lang.InterruptedException", SootClass.SIGNATURES);
SootClass exception = Scene.v().loadClass("java.lang.Exception",
SootClass.SIGNATURES);
SootClass throwable = Scene.v().loadClass("java.lang.Throwable",
SootClass.SIGNATURES);
SootClass runtimeexception = Scene.v().loadClass(
"java.lang.RuntimeException", SootClass.SIGNATURES);
LinkedList<SootClass> largestCaughtExceptions = new LinkedList<SootClass>();
LinkedList<Trap> usedTraps = new LinkedList<Trap>();
for (SootClass c : possibleExceptions) {
String transferlabel = null;
// for each possible exception, check if there is a catch block.
for (Trap trap : new LinkedList<Trap>(traps)) {
if (GlobalsCache.v().isSubTypeOrEqual(c, trap.getException())) {
transferlabel = GlobalsCache.v().getUnitLabel(
(Stmt) trap.getHandlerUnit());
// mark that we used that trap already, so we don't
// add another transition for it later.
usedTraps.add(trap);
break;
}
}
if (subsumedByLargestKnonwExceptions(largestCaughtExceptions, c)) {
continue;
}
Statement transferStatement;
if (transferlabel == null) {
// if the exception is not caught, leave the procedure
// that is, re-throw.
transferStatement = pf.mkReturnStatement();
} else {
// if the exception is caught, create a goto
transferStatement = pf.mkGotoStatement(transferlabel);
}
// now make a statement of the form
// if($exception<:c) transferStatement
// and add it to the list statements
Expression condition = pf.mkBinaryExpression(
pf.getBoolType(),
BinaryOperator.COMPPO,
valueswitch.getClassTypeFromExpression(
procInfo.getExceptionVariable(), false),
GlobalsCache.v().lookupClassVariable(c));
Statement[] thenPart = { transferStatement };
// Small hack to avoid false positives form
// code that is only reachable if an interleaving happens
if (interuptException == c
&& !org.joogie.Options.v().useSoundThreads()) {
thenPart = new Statement[] {
TranslationHelpers.havocEverything(procInfo,
valueswitch), transferStatement };
}
Statement[] elsePart = {};
statements.add(pf.mkIfStatement(condition, thenPart, elsePart));
}
// now remove all the traps that we have already used
// and generate transitions for the remaining ones if
// necessary.
traps.removeAll(usedTraps);
// now check if there are traps of type Exception or Throwable left, or
// if we might not know the throws clause
// then we have to create an edge to them as well. Otherwise
// we might create unreachable catch blocks
// also check if runtime exceptions are caught. In that case,
// we have to allow a transition to them as well
for (Trap trap : traps) {
if (trap.getException() == exception
|| trap.getException() == throwable
|| !calledMethod.hasActiveBody()
|| GlobalsCache.v().isSubTypeOrEqual(trap.getException(),
runtimeexception)) {
if (subsumedByLargestKnonwExceptions(largestCaughtExceptions,
trap.getException())) {
continue;
}
Expression condition = pf.mkBinaryExpression(
pf.getBoolType(),
BinaryOperator.COMPPO,
valueswitch.getClassTypeFromExpression(
procInfo.getExceptionVariable(), false),
GlobalsCache.v().lookupClassVariable(
trap.getException()));
Statement[] thenPart = {
TranslationHelpers.createClonedAttribAssert(),
pf.mkGotoStatement(GlobalsCache.v().getUnitLabel(
(Stmt) trap.getHandlerUnit())) };
Statement[] elsePart = { TranslationHelpers
.createClonedAttribAssert() };
statements.add(pf.mkIfStatement(condition, thenPart, elsePart));
}
}
// finally check if there is a finally_trap that we didn't account for
// and create an unconditional goto.
if (finally_traps.size() > 0) {
if (finally_traps.size() > 1) {
Log.error("more than one finally trap for "
+ procInfo.getBoogieName());
}
Trap trap = finally_traps.get(0);
if (!subsumedByLargestKnonwExceptions(largestCaughtExceptions,
trap.getException())) {
statements.add(pf.mkGotoStatement(GlobalsCache.v()
.getUnitLabel((Stmt) trap.getHandlerUnit())));
}
}
if (statements.size() == 0)
return;
// if the call was a constructor, add an assignment that sets
// the created variable back to null
if (constructorInstance != null
&& constructorInstance != procInfo.getThisReference()) {
statements.addFirst(pf.mkAssignmentStatement(constructorInstance,
SootPrelude.v().getNullConstant()));
}
// now add all the exceptional checks in a block where
// we ensure that $excpeiton was not null
Expression condition = pf.mkBinaryExpression(pf.getBoolType(),
BinaryOperator.COMPNEQ, procInfo.getExceptionVariable(),
SootPrelude.v().getNullConstant());
ss.addStatement(pf.mkIfStatement(condition,
statements.toArray(new Statement[statements.size()]),
new Statement[] {}));
}
/**
* Updates the list of largest known exceptions. It scans through the list
* and checks if it already contains a superclass of c. If so, it returns
* true. Otherwise, it adds c to the list and removes all sublcasses of c
* from the list, and returns false
*
* @param largestCaughtExceptions
* @param c
*/
private static boolean subsumedByLargestKnonwExceptions(
List<SootClass> largestCaughtExceptions, SootClass c) {
if (largestCaughtExceptions.isEmpty()) {
largestCaughtExceptions.add(c);
} else {
SootClass largerKnown = null;
boolean replacedOld = false;
for (SootClass large : new LinkedList<SootClass>(
largestCaughtExceptions)) {
if (GlobalsCache.v().isProperSubType(c, large)) {
largerKnown = large;
break;
} else if (GlobalsCache.v().isProperSubType(large, c)) {
largestCaughtExceptions.remove(large);
replacedOld = true;
}
}
if (largerKnown != null) {
// System.err.println("Not catching "+c.getName() +
// " because we already caught "+largerKnown.getName());
return true;
}
if (replacedOld) {
largestCaughtExceptions.add(c);
}
}
return false;
}
/**
* Sort a list of exceptions such that a class always occurs before its
* superclass.
*
* @param excpetions
* @return
*/
private static List<SootClass> sortExceptions(List<SootClass> exceptions) {
LinkedList<SootClass> sorted = new LinkedList<SootClass>();
LinkedList<SootClass> todo = new LinkedList<SootClass>(exceptions);
while (!todo.isEmpty()) {
SootClass largest = null;
for (SootClass c : todo) {
if (largest == null) {
largest = c;
continue;
}
if (GlobalsCache.v().isProperSubType(largest, c)) {
largest = c;
}
}
todo.remove(largest);
sorted.addFirst(largest);
}
// System.err.print("unsorted: " );
// for (SootClass c : exceptions) System.err.print(c.getName()+", ");
// System.err.println();
// System.err.print("sorted: " );
// for (SootClass c : sorted) System.err.print(c.getName()+", ");
// System.err.println();
return sorted;
}
}