/* * jimple2boogie - Translates Jimple (or Java) Programs to Boogie * Copyright (C) 2013 Martin Schaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaef and Stephan Arlt * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.joogie.soot; import java.util.LinkedList; import java.util.List; import org.joogie.GlobalsCache; import org.joogie.errormodel.AbstractErrorModel; import org.joogie.errormodel.AssertionErrorModel; import org.joogie.errormodel.ExceptionErrorModel; import org.joogie.util.Log; import org.joogie.util.TranslationHelpers; import soot.ArrayType; import soot.Local; import soot.NullType; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.Trap; import soot.Type; import soot.Value; import soot.jimple.AssignStmt; import soot.jimple.BinopExpr; import soot.jimple.BreakpointStmt; import soot.jimple.EnterMonitorStmt; import soot.jimple.ExitMonitorStmt; import soot.jimple.GotoStmt; import soot.jimple.IdentityStmt; import soot.jimple.IfStmt; import soot.jimple.InvokeStmt; import soot.jimple.LookupSwitchStmt; import soot.jimple.NewExpr; import soot.jimple.NopStmt; import soot.jimple.NullConstant; import soot.jimple.RetStmt; import soot.jimple.ReturnStmt; import soot.jimple.ReturnVoidStmt; import soot.jimple.Stmt; import soot.jimple.StmtSwitch; import soot.jimple.TableSwitchStmt; import soot.jimple.ThrowStmt; import boogie.ProgramFactory; import boogie.ast.Attribute; import boogie.ast.expression.Expression; import boogie.ast.expression.IdentifierExpression; import boogie.ast.statement.Statement; import boogie.enums.BinaryOperator; /** * @author schaef */ public class SootStmtSwitch implements StmtSwitch { private SootProcedureInfo procInfo; private SootValueSwitch valueswitch; private ProgramFactory pf; private Stmt currentStatement = null; // needed to identify throw targets of // expressions private AbstractErrorModel errorModel; private boolean inMonitor = false; public SootStmtSwitch(SootProcedureInfo pinfo) { this.procInfo = pinfo; this.pf = GlobalsCache.v().getPf(); this.valueswitch = new SootValueSwitch(this.procInfo, this); if (org.joogie.Options.v().isExceptionErrorModel()) { this.errorModel = new ExceptionErrorModel(this.procInfo, this); } else { this.errorModel = new AssertionErrorModel(this.procInfo, this); } } public SootProcedureInfo getProcInfo() { return this.procInfo; } public LinkedList<Statement> popAll() { LinkedList<Statement> ret = new LinkedList<Statement>(); ret.addAll(this.boogieStatements); this.boogieStatements.clear(); return ret; } public SootValueSwitch getValueSwitch() { return this.valueswitch; } public AbstractErrorModel getErrorModel() { return this.errorModel; } /** * Returns true if we are in a monitor or if the whole method is * synchronized. * * @return */ public boolean isInMonitor() { return this.inMonitor || this.procInfo.getSootMethod().isSynchronized(); } private LinkedList<Statement> boogieStatements = new LinkedList<Statement>(); /** * this should only be used by the SootValueSwitch if extra guards have to * be created * * @param guard */ public void addStatement(Statement guard) { this.boogieStatements.add(guard); } public Stmt getCurrentStatement() { return this.currentStatement; } private void injectLabelStatements(Stmt arg0) { this.currentStatement = arg0; if (arg0.getBoxesPointingToThis().size() > 0) { String label = GlobalsCache.v().getUnitLabel(arg0); this.boogieStatements.add(this.pf.mkLabel(label)); } this.boogieStatements.add(TranslationHelpers.mkLocationAssertion(arg0)); } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseAssignStmt(soot.jimple.AssignStmt) */ @Override public void caseAssignStmt(AssignStmt arg0) { injectLabelStatements(arg0); AssignmentTranslation.translateAssignment(this, arg0.getLeftOp(), arg0.getRightOp(), arg0); } public IdentifierExpression createAllocatedVariable(Type sootType) { // create fresh local variable for "right" Attribute[] attributes = {}; if (this.currentStatement != null) { TranslationHelpers.javaLocation2Attribute(this.currentStatement); } IdentifierExpression newexpr = this.procInfo .createLocalVariable(SootPrelude.v().getReferenceType()); Expression obj_type; if (sootType instanceof RefType) { obj_type = GlobalsCache.v().lookupClassVariable( ((RefType) sootType).getSootClass()); if (obj_type == null) { throw new RuntimeException("Not a class variable: " + ((RefType) sootType).getSootClass()); } } else if (sootType instanceof ArrayType) { obj_type = GlobalsCache.v().lookupArrayType((ArrayType) sootType); if (obj_type == null) { throw new RuntimeException("Not a type: " + (ArrayType) sootType); } } else { throw new RuntimeException("Translation of Array Access failed!"); } this.boogieStatements.add(SootPrelude.v().newObject(attributes, newexpr, obj_type)); return newexpr; } /* * (non-Javadoc) * * @see * soot.jimple.StmtSwitch#caseBreakpointStmt(soot.jimple.BreakpointStmt) */ @Override public void caseBreakpointStmt(BreakpointStmt arg0) { injectLabelStatements(arg0); Log.info("Joogie does not translate BreakpointStmt"); } /* * (non-Javadoc) * * @see * soot.jimple.StmtSwitch#caseEnterMonitorStmt(soot.jimple.EnterMonitorStmt) * If this is only for synchronization, we don't need to translate it */ @Override public void caseEnterMonitorStmt(EnterMonitorStmt arg0) { injectLabelStatements(arg0); arg0.getOp().apply(this.valueswitch); this.valueswitch.getExpression(); this.inMonitor = true; // TODO: this is a very aggressive hack // to avoid false positives that we encountered in Tomcat. // For example: if (A) synchronized() { if (A) ... // havoc everything. this.boogieStatements.add(TranslationHelpers.havocEverything(this.getProcInfo(), this.valueswitch)); } /* * (non-Javadoc) * * @see * soot.jimple.StmtSwitch#caseExitMonitorStmt(soot.jimple.ExitMonitorStmt) * If this is only for synchronization, we don't need to translate it */ @Override public void caseExitMonitorStmt(ExitMonitorStmt arg0) { injectLabelStatements(arg0); arg0.getOp().apply(this.valueswitch); this.valueswitch.getExpression(); // TODO: this.inMonitor = false; } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseGotoStmt(soot.jimple.GotoStmt) */ @Override public void caseGotoStmt(GotoStmt arg0) { injectLabelStatements(arg0); String labelName = GlobalsCache.v().getUnitLabel( (Stmt) arg0.getTarget()); this.boogieStatements.add(this.pf.mkGotoStatement(labelName)); // if (labelName.contains("block324")) throw new // RuntimeException("there it is!"); } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseIdentityStmt(soot.jimple.IdentityStmt) */ @Override public void caseIdentityStmt(IdentityStmt arg0) { injectLabelStatements(arg0); AssignmentTranslation.translateAssignment(this, arg0.getLeftOp(), arg0.getRightOp(), arg0); } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseIfStmt(soot.jimple.IfStmt) */ @Override public void caseIfStmt(IfStmt arg0) { injectLabelStatements(arg0); boolean forceCloneAttibute = false; if (TranslationHelpers.clonedFinallyBlocks.contains(arg0)) { forceCloneAttibute = true; } else if (this.procInfo.duplicatedIfStatement.contains(arg0)) { forceCloneAttibute = true; } Statement[] thenPart = { TranslationHelpers.mkLocationAssertion(arg0, forceCloneAttibute, "thenblock"), this.pf.mkGotoStatement(GlobalsCache.v().getUnitLabel( arg0.getTarget())) }; Statement[] elsePart = {TranslationHelpers.mkLocationAssertion(arg0, forceCloneAttibute, "elseblock")}; // now check if we can find a source location for the else block. // Stmt else_loc = findSuccessorStatement(arg0); // if (else_loc != null) { // // elsePart = new Statement[] { TranslationHelpers // // .mkLocationAssertion(else_loc, forceCloneAttibute) }; // // elsePart = new Statement[] {}; // TODO: test // } arg0.getCondition().apply(this.valueswitch); Expression cond = TranslationHelpers.castBoogieTypes( this.valueswitch.getExpression(), this.pf.getBoolType()); if (isTrivialNullCheck(arg0.getCondition())) { Log.debug("Ignore trivial check "+arg0); for (Statement s : thenPart) { this.boogieStatements.add(s); } } else { this.boogieStatements.add(this.pf.mkIfStatement(cond, thenPart, elsePart)); } // this.boogieStatements.add(TranslationHelpers.mkLocationAssertion(arg0.getTarget(), // forceCloneAttibute, "elseblock")); } /** * This is a helper function to suppress false positives: Sometimes, * when try-catch with resources is being used * @param v * @return */ private boolean isTrivialNullCheck(Value v) { if (v instanceof BinopExpr) { BinopExpr bo = (BinopExpr)v; if (bo.getOp2() instanceof NullConstant && bo.getSymbol().equals(" == ")) { //now it gets itchy. We want to catch only that case //where the bytecode introduces a renaming of null and //does this unreachable null check. if (bo.getOp1() instanceof Local) { Local l = (Local)bo.getOp1(); if (l.getType() instanceof NullType) { return true; } } } } return false; } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseInvokeStmt(soot.jimple.InvokeStmt) */ @Override public void caseInvokeStmt(InvokeStmt arg0) { injectLabelStatements(arg0); AssignmentTranslation.translateAssignment(this, null, arg0.getInvokeExpr(), arg0); } /* * (non-Javadoc) * * @see * soot.jimple.StmtSwitch#caseLookupSwitchStmt(soot.jimple.LookupSwitchStmt) */ @Override public void caseLookupSwitchStmt(LookupSwitchStmt arg0) { injectLabelStatements(arg0); LinkedList<Expression> cases = new LinkedList<Expression>(); LinkedList<Statement[]> targets = new LinkedList<Statement[]>(); arg0.getKey().apply(this.valueswitch); Expression key = this.valueswitch.getExpression(); for (int i = 0; i < arg0.getTargetCount(); i++) { Expression cond = this.pf.mkBinaryExpression( this.pf.getBoolType(), BinaryOperator.COMPEQ, key, this.pf .mkIntLiteral(Integer.toString(arg0.getLookupValue(i)))); cases.add(cond); Statement[] gototarget = { this.pf.mkGotoStatement(GlobalsCache.v() .getUnitLabel((Stmt) arg0.getTarget(i))) }; targets.add(gototarget); } { Statement[] gototarget = { this.pf.mkGotoStatement( GlobalsCache.v().getUnitLabel((Stmt) arg0.getDefaultTarget())) }; targets.add(gototarget); } translateSwitch(cases, targets); } /** * note that there is one more target than cases because of the default * cases * * @param cases * @param targets */ private void translateSwitch(LinkedList<Expression> cases, LinkedList<Statement[]> targets) { Statement[] elseblock = targets.getLast(); Statement ifstatement = null; int max = cases.size() - 1; for (int i = max; i >= 0; i--) { Statement[] thenblock = targets.get(i); ifstatement = this.pf.mkIfStatement(cases.get(i), thenblock, elseblock); elseblock = new Statement[1]; elseblock[0] = ifstatement; } if (ifstatement != null) { this.boogieStatements.add(ifstatement); } else { Log.info("Warning: Found empty switch statement (or only default case)."); for (int i = 0; i < elseblock.length; i++) { this.boogieStatements.add(elseblock[i]); } } } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseNopStmt(soot.jimple.NopStmt) */ @Override public void caseNopStmt(NopStmt arg0) { injectLabelStatements(arg0); // Log.error("NopStmt: " + arg0.toString()); // assert (false); } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseRetStmt(soot.jimple.RetStmt) */ @Override public void caseRetStmt(RetStmt arg0) { injectLabelStatements(arg0); Log.error("This is deprecated: " + arg0.toString()); throw new RuntimeException( "caseRetStmt is not implemented. Contact developers!"); } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseReturnStmt(soot.jimple.ReturnStmt) */ @Override public void caseReturnStmt(ReturnStmt arg0) { injectLabelStatements(arg0); if (this.procInfo.getReturnVariable() != null) { Expression lhs = this.procInfo.getReturnVariable(); arg0.getOp().apply(this.valueswitch); Expression rhs = this.valueswitch.getExpression(); AssignmentTranslation.translateAssignment(this, lhs, rhs); } this.boogieStatements.add(this.pf.mkReturnStatement()); } /* * (non-Javadoc) * * @see * soot.jimple.StmtSwitch#caseReturnVoidStmt(soot.jimple.ReturnVoidStmt) */ @Override public void caseReturnVoidStmt(ReturnVoidStmt arg0) { injectLabelStatements(arg0); this.boogieStatements.add(this.pf.mkReturnStatement()); } /* * (non-Javadoc) * * @see * soot.jimple.StmtSwitch#caseTableSwitchStmt(soot.jimple.TableSwitchStmt) * The TableSwitch is a special case of the LookupSwitch, where all cases * are consecutive. */ @Override public void caseTableSwitchStmt(TableSwitchStmt arg0) { injectLabelStatements(arg0); LinkedList<Expression> cases = new LinkedList<Expression>(); LinkedList<Statement[]> targets = new LinkedList<Statement[]>(); arg0.getKey().apply(this.valueswitch); Expression key = this.valueswitch.getExpression(); int counter = 0; for (int i = arg0.getLowIndex(); i <= arg0.getHighIndex(); i++) { Expression cond = this.pf.mkBinaryExpression(this.pf.getBoolType(), BinaryOperator.COMPEQ, key, this.pf.mkIntLiteral(Integer.toString(i))); cases.add(cond); Statement[] gototarget = { this.pf.mkGotoStatement( GlobalsCache.v().getUnitLabel((Stmt) arg0.getTarget(counter))) }; targets.add(gototarget); counter++; } { Statement[] gototarget = { this.pf.mkGotoStatement( GlobalsCache.v().getUnitLabel((Stmt) arg0.getDefaultTarget())) }; targets.add(gototarget); } translateSwitch(cases, targets); } private SootClass findExceptionType(ThrowStmt s) { if (s.getOp() instanceof NewExpr) { NewExpr ne = (NewExpr) s.getOp(); return ne.getBaseType().getSootClass(); } else if (s.getOp() instanceof Local) { Local l = (Local) s.getOp(); if (l.getType() instanceof RefType) { return ((RefType) l.getType()).getSootClass(); } } System.err.println("Unexpected value in throw stmt " + s.getOp()); return Scene.v().loadClass("java.lang.Throwable", SootClass.SIGNATURES); } /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#caseThrowStmt(soot.jimple.ThrowStmt) */ @Override public void caseThrowStmt(ThrowStmt arg0) { injectLabelStatements(arg0); // find the type of the exception that is thrown. SootClass c = findExceptionType(arg0); arg0.getOp().apply(this.valueswitch); Expression right = this.valueswitch.getExpression(); // assign the value from arg0.getOp() to the $exception variable of // the current procedure. // Note that this only works because soot moves the "new" statement // to a new local variable. AssignmentTranslation.translateAssignment(this, this.procInfo.getExceptionVariable(), right); // Add a goto statement to the exceptional successors. List<Trap> traps = new LinkedList<Trap>(); List<Trap> finally_traps = new LinkedList<Trap>(); // TODO: do we have TranslationHelpers.getReachableTraps(arg0, this.procInfo.getSootMethod(), traps, finally_traps); // TODO, maybe we need to consider the case that // we don't know the exact type of arg0.getOp at this point? for (Trap trap : traps) { if (GlobalsCache.v().isSubTypeOrEqual(c, trap.getException())) { this.boogieStatements.add(this.pf.mkGotoStatement(GlobalsCache .v().getUnitLabel((Stmt) trap.getHandlerUnit()))); return; } } this.boogieStatements.add(this.pf.mkReturnStatement()); } // public void caseThrowStmt(ThrowStmt arg0) { // injectLabelStatements(arg0); // arg0.getOp().apply(this.valueswitch); // Expression right = this.valueswitch.getExpression(); // // assign the value from arg0.getOp() to the $exception variable of // // the current procedure. // // Note that this only works because soot moves the "new" statement // // to a new local variable. // AssignmentTranslation.translateAssignment(this, // this.procInfo.getExceptionVariable(), right); // // Add a goto statement to the exceptional successors. // List<Unit> exc_succ = procInfo.getExceptionalUnitGraph() // .getExceptionalSuccsOf((Unit) arg0); // String[] labels = new String[exc_succ.size()]; // if (exc_succ.size() > 0) { // for (int i = 0; i < exc_succ.size(); i++) { // labels[i] = GlobalsCache.v().getUnitLabel( // (Stmt) exc_succ.get(i)); // } // if (exc_succ.size() > 1) { // // for (int i = 0; i < exc_succ.size(); i++) { // Unit u = exc_succ.get(i); // // if (u instanceof IdentityStmt) { // // IdentityStmt istmt = (IdentityStmt) u; // if (istmt.getRightOp() instanceof CaughtExceptionRef) { // // sb.append("... catches exception! " + // // istmt.getLeftOp().getType()+"\n"); // Type caughttype = istmt.getLeftOp().getType(); // if (!(caughttype instanceof RefType)) { // throw new RuntimeException( // "Bug in translation of ThrowStmt!"); // } // RefType caught = (RefType) caughttype; // Expression cond = GlobalsCache // .v() // .compareTypeExpressions( // this.valueswitch.getClassTypeFromExpression( // right, false), // GlobalsCache // .v() // .lookupClassVariable( // caught.getSootClass())); // Statement[] thenPart = new Statement[] { this.pf // .mkGotoStatement(labels[i]) }; // Statement ifstmt = this.pf.mkIfStatement(cond, // thenPart, new Statement[0]); // // sb.append("created choice: "+ifstmt+"\n"); // this.boogieStatements.add(ifstmt); // } else { // throw new RuntimeException( // "Bug in translation of ThrowStmt!"); // } // } else if (u instanceof NopStmt) { // String filename = ""; // int startln = -1; // for (Tag tag : u.getTags()) { // if (tag instanceof LineNumberTag) { // if (GlobalsCache.v().currentMethod != null) { // filename = GlobalsCache.v().currentMethod // .getDeclaringClass().getName(); // } // startln = ((LineNumberTag) tag).getLineNumber(); // break; // } else if (tag instanceof SourceLnNamePosTag) { // startln = ((SourceLnNamePosTag) tag).startLn(); // filename = ((SourceLnNamePosTag) tag) // .getFileName(); // break; // } else if (tag instanceof SourceFileTag) { // filename = ((SourceFileTag) tag) // .getSourceFile(); // break; // } // } // Log.error("Catch block starts with Nop instead of assignment. Maybe unsound. " // + filename + ": " + startln); // } else { // throw new RuntimeException( // "Bug in translation of ThrowStmt! " // + u.getClass().toString()); // } // } // // throw new RuntimeException(sb.toString()); // // Log.error(sb); // // Make sure that the execution does not continue after the // // throw statement // this.boogieStatements.add(this.pf.mkReturnStatement()); // } else { // this.boogieStatements.add(this.pf.mkGotoStatement(labels[0])); // } // } else { // // this.boogieStatements.add(this.pf.mkReturnStatement()); // } // // } // /* * (non-Javadoc) * * @see soot.jimple.StmtSwitch#defaultCase(java.lang.Object) */ @Override public void defaultCase(Object arg0) { assert (false); } }