/* * jimple2boogie - Translates Jimple (or Java) Programs to Boogie * Copyright (C) 2013 Martin Schaef 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 soottocfg.soot.visitors; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.google.common.base.Preconditions; import com.google.common.base.Verify; import soot.ArrayType; import soot.Body; import soot.Local; import soot.PatchingChain; import soot.RefType; import soot.SootClass; import soot.SootMethod; import soot.Unit; import soot.Value; import soot.jimple.AnyNewExpr; import soot.jimple.AssignStmt; import soot.jimple.BreakpointStmt; import soot.jimple.DefinitionStmt; import soot.jimple.DynamicInvokeExpr; import soot.jimple.EnterMonitorStmt; import soot.jimple.ExitMonitorStmt; import soot.jimple.FieldRef; import soot.jimple.GotoStmt; import soot.jimple.IdentityStmt; import soot.jimple.IfStmt; import soot.jimple.InstanceInvokeExpr; import soot.jimple.InvokeExpr; import soot.jimple.InvokeStmt; import soot.jimple.LengthExpr; import soot.jimple.LookupSwitchStmt; import soot.jimple.NopStmt; import soot.jimple.RetStmt; import soot.jimple.ReturnStmt; import soot.jimple.ReturnVoidStmt; import soot.jimple.SpecialInvokeExpr; import soot.jimple.StaticInvokeExpr; import soot.jimple.Stmt; import soot.jimple.StmtSwitch; import soot.jimple.TableSwitchStmt; import soot.jimple.ThrowStmt; import soot.toolkits.graph.CompleteUnitGraph; import soottocfg.cfg.SourceLocation; import soottocfg.cfg.expression.BinaryExpression; import soottocfg.cfg.expression.BinaryExpression.BinaryOperator; import soottocfg.cfg.expression.Expression; import soottocfg.cfg.expression.IdentifierExpression; import soottocfg.cfg.expression.TupleAccessExpression; import soottocfg.cfg.expression.UnaryExpression; import soottocfg.cfg.expression.UnaryExpression.UnaryOperator; import soottocfg.cfg.method.CfgBlock; import soottocfg.cfg.method.Method; import soottocfg.cfg.statement.AssertStatement; import soottocfg.cfg.statement.AssignStatement; import soottocfg.cfg.statement.CallStatement; import soottocfg.cfg.statement.NewStatement; import soottocfg.cfg.statement.Statement; import soottocfg.cfg.type.ReferenceType; import soottocfg.cfg.variable.ClassVariable; import soottocfg.cfg.variable.Variable; import soottocfg.soot.util.MethodInfo; import soottocfg.soot.util.SootTranslationHelpers; /** * @author schaef */ public class SootStmtSwitch implements StmtSwitch { private final SootMethod sootMethod; private final Body sootBody; private final MethodInfo methodInfo; private final SootValueSwitch valueSwitch; private final PatchingChain<Unit> units; private final CompleteUnitGraph unitGraph; private CfgBlock currentBlock, entryBlock, exitBlock; private boolean insideMonitor = false; private Stmt currentStmt; protected SourceLocation loc; public SootStmtSwitch(Body body, MethodInfo mi) { this.methodInfo = mi; this.sootBody = body; this.sootMethod = sootBody.getMethod(); this.valueSwitch = new SootValueSwitch(this); units = body.getUnits(); Unit head = units.getFirst(); unitGraph = new CompleteUnitGraph(sootBody); // check if the block is empty. if (head != null) { this.entryBlock = methodInfo.lookupCfgBlock(head); this.currentBlock = this.entryBlock; Iterator<Unit> iterator = units.iterator(); while (iterator.hasNext()) { Unit u = iterator.next(); u.apply(this); } } else { if (methodInfo.getMethod().getSource()==null) { methodInfo.getMethod().setSource(new CfgBlock(methodInfo.getMethod())); } // this.entryBlock = new CfgBlock(methodInfo.getMethod()); this.entryBlock = methodInfo.getMethod().getSource(); this.currentBlock = this.entryBlock; } if (this.currentBlock != null) { this.exitBlock = this.currentBlock; } else { this.exitBlock = null; } // TODO: connect stuff to exit. } public CfgBlock getEntryBlock() { return this.entryBlock; } public CfgBlock getExitBlock() { return this.exitBlock; } public MethodInfo getMethodInfo() { return this.methodInfo; } public SootMethod getMethod() { return this.sootMethod; } public Stmt getCurrentStmt() { return this.currentStmt; } public SourceLocation getCurrentLoc() { return this.loc; } /** * Checks if the current statement is synchronized or inside a monitor * * @return True if the current statement is inside a monitor or synchronized * and false, otherwise. */ public boolean isSynchronizedOrInsideMonitor() { return this.insideMonitor || this.sootMethod.isSynchronized(); } public void push(Statement stmt) { this.currentBlock.addStatement(stmt); } private void connectBlocks(CfgBlock from, CfgBlock to) { Preconditions.checkArgument(!methodInfo.getMethod().containsEdge(from, to)); this.methodInfo.getMethod().addEdge(from, to); } private void connectBlocks(CfgBlock from, CfgBlock to, Expression label) { Preconditions.checkArgument(!methodInfo.getMethod().containsEdge(from, to)); this.methodInfo.getMethod().addEdge(from, to).setLabel(label); } private void precheck(Stmt st) { this.currentStmt = st; loc = SootTranslationHelpers.v().getSourceLocation(currentStmt); if (currentBlock != null) { // first check if we already created a block // for this statement. CfgBlock block = methodInfo.findBlock(st); if (block != null) { if (block != currentBlock) { connectBlocks(currentBlock, block); currentBlock = block; } else { // do nothing. } } else { if (unitGraph.getPredsOf(st).size() > 1) { // then this statement might be reachable via a back edge // and we have to create a new block for it. CfgBlock newBlock = methodInfo.lookupCfgBlock(st); connectBlocks(currentBlock, newBlock); currentBlock = newBlock; } else { // do nothing. } } } else { // If not, and we currently don't have a block, // create a new one. currentBlock = methodInfo.lookupCfgBlock(st); } } /* * Below follow the visitor methods from StmtSwitch * */ @Override public void caseAssignStmt(AssignStmt arg0) { precheck(arg0); translateDefinitionStmt(arg0); } @Override public void caseBreakpointStmt(BreakpointStmt arg0) { precheck(arg0); } @Override public void caseEnterMonitorStmt(EnterMonitorStmt arg0) { precheck(arg0); arg0.getOp().apply(this.valueSwitch); this.valueSwitch.popExpression(); this.insideMonitor = true; // TODO Havoc stuff } @Override public void caseExitMonitorStmt(ExitMonitorStmt arg0) { precheck(arg0); arg0.getOp().apply(this.valueSwitch); this.valueSwitch.popExpression(); this.insideMonitor = false; // TODO: } @Override public void caseGotoStmt(GotoStmt arg0) { precheck(arg0); CfgBlock target = this.methodInfo.lookupCfgBlock(arg0.getTarget()); connectBlocks(currentBlock, target); this.currentBlock = null; } @Override public void caseIdentityStmt(IdentityStmt arg0) { precheck(arg0); translateDefinitionStmt(arg0); } @Override public void caseIfStmt(IfStmt arg0) { precheck(arg0); arg0.getCondition().apply(valueSwitch); Expression cond = valueSwitch.popExpression(); // apply the switch twice. Otherwise the conditional and its negation // are aliased. arg0.getCondition().apply(valueSwitch); Expression negCond = new UnaryExpression(loc, UnaryOperator.LNot, valueSwitch.popExpression()); // create a new (empty) block for the fan out // CfgBlock block = methodInfo.lookupCfgBlock(arg0); // if (currentBlock!=null) { // connectBlocks(currentBlock, block); // } // currentBlock = block; /* * In jimple, conditionals are of the form if (x) goto y; So we end the * current block and create two new blocks for then and else branch. The * new currenBlock becomes the else branch. */ Unit next = units.getSuccOf(arg0); /* * In rare cases of empty If- and Else- blocks, next and * arg0.getTraget() are the same. For these cases, we do not generate an * If statement, but still translate the conditional in case it may * throw an exception. */ if (next == arg0.getTarget()) { // ignore the IfStmt. return; } CfgBlock thenBlock = methodInfo.lookupCfgBlock(arg0.getTarget()); connectBlocks(currentBlock, thenBlock, cond); if (next != null) { CfgBlock elseBlock = methodInfo.lookupCfgBlock(next); connectBlocks(currentBlock, elseBlock, negCond); this.currentBlock = elseBlock; } else { connectBlocks(currentBlock, methodInfo.getSink(), negCond); this.currentBlock = null; } } @Override public void caseInvokeStmt(InvokeStmt arg0) { precheck(arg0); translateMethodInvokation(arg0, null, arg0.getInvokeExpr()); } @Override public void caseLookupSwitchStmt(LookupSwitchStmt arg0) { throw new RuntimeException("Should have been eliminated by SwitchStatementRemover"); } @Override public void caseNopStmt(NopStmt arg0) { precheck(arg0); } @Override public void caseRetStmt(RetStmt arg0) { throw new RuntimeException("Not implemented " + arg0); } @Override public void caseReturnStmt(ReturnStmt arg0) { precheck(arg0); arg0.getOp().apply(valueSwitch); Expression returnValue = valueSwitch.popExpression(); currentBlock.addStatement(new AssignStatement(SootTranslationHelpers.v().getSourceLocation(arg0), methodInfo.getReturnVariable(), returnValue)); connectBlocks(currentBlock, methodInfo.getSink()); currentBlock = null; } @Override public void caseReturnVoidStmt(ReturnVoidStmt arg0) { precheck(arg0); // if (sootMethod.isConstructor()) { // SourceLocation loc = getCurrentLoc(); // SootClass currentClass = // SootTranslationHelpers.v().getCurrentMethod().getDeclaringClass(); // List<SootField> fields = // SootTranslationHelpers.findFieldsRecursively(currentClass); // JimpleBody jb = (JimpleBody)this.sootMethod.getActiveBody(); // // for (int i=1; i<methodInfo.getOutVariables().size();i++) { // Variable outVar = methodInfo.getOutVariables().get(i); // Variable tmp = methodInfo.createFreshLocal("afdafd", // outVar.getType(), false, false); // // AssignStatement as = new AssignStatement(loc, // new IdentifierExpression(loc, outVar), // new IdentifierExpression(loc, tmp)); // currentBlock.addStatement(as); // } // } connectBlocks(currentBlock, methodInfo.getSink()); currentBlock = null; } @Override public void caseTableSwitchStmt(TableSwitchStmt arg0) { throw new RuntimeException("Should have been eliminated by SwitchStatementRemover"); } @Override public void caseThrowStmt(ThrowStmt arg0) { precheck(arg0); throw new RuntimeException("Apply the ExceptionRemover first."); // arg0.getOp().apply(valueSwitch); // Expression exception = valueSwitch.popExpression(); // currentBlock.addStatement(new // AssignStatement(SootTranslationHelpers.v().getSourceLocation(arg0), // methodInfo.getExceptionVariable(), exception)); // connectBlocks(currentBlock, methodInfo.getSink()); // currentBlock = null; } @Override public void defaultCase(Object arg0) { throw new RuntimeException("Case not implemented"); } /** * Translate method invokation. This assumes that exceptions and virtual * calls have already been removed. * * @param u * @param optionalLhs * @param call */ private void translateMethodInvokation(Unit u, Value optionalLhs, InvokeExpr call) { if (isHandledAsSpecialCase(u, optionalLhs, call)) { return; } // translate the expressions in the arguments first. LinkedList<Expression> args = new LinkedList<Expression>(); for (int i = 0; i < call.getArgs().size(); i++) { call.getArg(i).apply(valueSwitch); args.add(valueSwitch.popExpression()); } Expression baseExpression = null; // List of possible virtual methods that can be called at this point. // Order matters here. if (call instanceof InstanceInvokeExpr) { InstanceInvokeExpr iivk = (InstanceInvokeExpr) call; iivk.getBase().apply(valueSwitch); baseExpression = valueSwitch.popExpression(); // add the "this" variable to the list of args args.addFirst(baseExpression); // this include Interface-, Virtual, and SpecialInvokeExpr } else if (call instanceof StaticInvokeExpr) { // no need to handle the base. } else if (call instanceof DynamicInvokeExpr) { // DynamicInvokeExpr divk = (DynamicInvokeExpr) call; System.err.println("Dynamic invoke translation is only a stub. Will be unsound!"); } else { throw new RuntimeException("Cannot compute instance for " + call.getClass().toString()); } List<Expression> receiver = new LinkedList<Expression>(); receiver.add(methodInfo.getExceptionVariable()); if (optionalLhs != null) { optionalLhs.apply(valueSwitch); Expression lhs = valueSwitch.popExpression(); receiver.add(lhs); } // System.err.println(call); if (call.getMethod().isConstructor() && call instanceof SpecialInvokeExpr) { /* * For our new memory model, we need special treatment of * constructor invoke */ SootTranslationHelpers.v().getMemoryModel().mkConstructorCall(u, call.getMethod(), args); } else { Method method = SootTranslationHelpers.v().lookupOrCreateMethod(call.getMethod()); // if (optionalLhs!=null) { // System.err.println("Method " +method.getMethodName()); // List<Type> rtypes = new LinkedList<Type>(); // for (Expression e : receiver) // rtypes.add(e.getType()); // System.err.println("Receiver " +rtypes.toString()); // System.err.println("Out " +method.getReturnType().toString()); // Type receiverType = receiver.get(receiver.size()-1).getType(); // Type returnType = // method.getReturnType().get(method.getReturnType().size()-1); // if (!receiverType.equals(returnType)) { // System.err.println(receiverType); // System.err.println(returnType); // throw new RuntimeException(""); // } // } CallStatement stmt = new CallStatement(SootTranslationHelpers.v().getSourceLocation(u), method, args, receiver); this.currentBlock.addStatement(stmt); } } /** * Check if the call is a special case such as System.exit. If so, translate * it and return true. Otherwise, ignore it and return false. * * @param u * @param optionalLhs * @param call * @return true, if its a special method that is handled by the procedure, * and false, otherwise. */ private boolean isHandledAsSpecialCase(Unit u, Value optionalLhs, InvokeExpr call) { if (call.getMethod().getSignature().equals(SootTranslationHelpers.v().getAssertMethod().getSignature())) { Verify.verify(optionalLhs == null); Verify.verify(call.getArgCount() == 1); call.getArg(0).apply(valueSwitch); currentBlock.addStatement( new AssertStatement(SootTranslationHelpers.v().getSourceLocation(u), valueSwitch.popExpression())); return true; } if (call.getMethod().getSignature().contains("<java.lang.String: int length()>")) { assert (call instanceof InstanceInvokeExpr); Expression rhs = SootTranslationHelpers.v().getMemoryModel() .mkStringLengthExpr(((InstanceInvokeExpr) call).getBase()); if (optionalLhs != null) { optionalLhs.apply(valueSwitch); Expression lhs = valueSwitch.popExpression(); currentBlock .addStatement(new AssignStatement(SootTranslationHelpers.v().getSourceLocation(u), lhs, rhs)); } return true; } if (call.getMethod().getSignature().contains("<java.lang.System: void exit(int)>")) { // TODO: this is not sufficient for interprocedural analysis. currentBlock = null; return true; } if (call.getMethod().getDeclaringClass().getName().contains("org.junit.Assert")) { // TODO: this should not be hard coded! if (call.getMethod().getName().equals("fail")) { Stmt ret = SootTranslationHelpers.v().getDefaultReturnStatement(sootMethod.getReturnType(), currentStmt); ret.apply(this); return true; } } if (call.getMethod().getDeclaringClass().getName().contains("com.google.common.base.")) { if (call.getMethod().getSignature().contains("void checkArgument(boolean)")) { Preconditions.checkArgument(optionalLhs == null); call.getArg(0).apply(valueSwitch); Expression guard = valueSwitch.popExpression(); currentBlock.addStatement(new AssertStatement(SootTranslationHelpers.v().getSourceLocation(u), guard)); return true; } } if (call.getMethod().getSignature().equals("<java.lang.Class: boolean isAssignableFrom(java.lang.Class)>")) { InstanceInvokeExpr iivk = (InstanceInvokeExpr) call; Verify.verify(call.getArgCount() == 1); if (optionalLhs != null) { optionalLhs.apply(valueSwitch); Expression lhs = valueSwitch.popExpression(); iivk.getBase().apply(valueSwitch); Expression binOpRhs = valueSwitch.popExpression(); Verify.verify(binOpRhs instanceof IdentifierExpression); Variable rhsVar = ((IdentifierExpression)binOpRhs).getVariable(); Verify.verify(rhsVar instanceof ClassVariable); call.getArg(0).apply(valueSwitch); IdentifierExpression binOpLhs = (IdentifierExpression)valueSwitch.popExpression(); Expression instOf = SootTranslationHelpers.createInstanceOfExpression(getCurrentLoc(), binOpLhs.getVariable(), (ClassVariable)rhsVar); currentBlock.addStatement( new AssignStatement(SootTranslationHelpers.v().getSourceLocation(u), lhs, instOf)); return true; } // otherwise ignore. } else if (call.getMethod().getSignature().equals("<java.lang.Object: java.lang.Class getClass()>")) { InstanceInvokeExpr iivk = (InstanceInvokeExpr) call; Verify.verify(call.getArgCount() == 0); if (optionalLhs != null) { Value objectToGetClassFrom = iivk.getBase(); soot.Type t = objectToGetClassFrom.getType(); // SootField typeField = null; if (t instanceof RefType) { // first make a heap-read of the type filed. // typeField = SootTranslationHelpers.getTypeField(((RefType) t).getSootClass()); // // now get the dynamic type // SootTranslationHelpers.v().getMemoryModel().mkHeapReadStatement(getCurrentStmt(), // Jimple.v().newInstanceFieldRef(objectToGetClassFrom, typeField.makeRef()), optionalLhs); objectToGetClassFrom.apply(valueSwitch); IdentifierExpression base = (IdentifierExpression)valueSwitch.popExpression(); optionalLhs.apply(valueSwitch); Expression left = valueSwitch.popExpression(); currentBlock.addStatement(new AssignStatement(loc, left, new TupleAccessExpression(loc, base.getVariable(), ReferenceType.TypeFieldName))); } else if (t instanceof ArrayType) { // typeField = SootTranslationHelpers.getTypeField(Scene.v().getSootClass("java.lang.Object")); throw new RuntimeException("Arrays should be removed first."); } else { throw new RuntimeException("Not implemented. " + t + ", " + t.getClass()); } return true; } } else if (call.getMethod().getSignature() .equals("<java.lang.Class: java.lang.Object cast(java.lang.Object)>")) { // TODO: we have to check if we have to throw an exception or add // E.g, String.<java.lang.Class: java.lang.Object // cast(java.lang.Object)>(x); means (String)x InstanceInvokeExpr iivk = (InstanceInvokeExpr) call; Verify.verify(call.getArgCount() == 1); if (optionalLhs != null) { // TODO optionalLhs.apply(valueSwitch); Expression lhs = valueSwitch.popExpression(); iivk.getBase().apply(valueSwitch); Expression binOpRhs = valueSwitch.popExpression(); call.getArg(0).apply(valueSwitch); Expression binOpLhs = valueSwitch.popExpression(); Expression instOf = new BinaryExpression(this.getCurrentLoc(), BinaryOperator.PoLeq, binOpLhs, binOpRhs); currentBlock.addStatement(new AssertStatement(SootTranslationHelpers.v().getSourceLocation(u), instOf)); call.getArg(0).apply(valueSwitch); Expression asgnRhs = valueSwitch.popExpression(); currentBlock.addStatement( new AssignStatement(SootTranslationHelpers.v().getSourceLocation(u), lhs, asgnRhs)); return true; } } else if (call.getMethod().getSignature().equals("<java.lang.Class: boolean isInstance(java.lang.Object)>")) { /* * E.g, * $r2 = class "java/lang/String"; * $z0 = virtualinvoke $r2.<java.lang.Class: boolean * isInstance(java.lang.Object)>(r1); * checks if r1 instancof String */ InstanceInvokeExpr iivk = (InstanceInvokeExpr) call; Verify.verify(call.getArgCount() == 1); if (optionalLhs != null) { // TODO optionalLhs.apply(valueSwitch); Expression lhs = valueSwitch.popExpression(); iivk.getBase().apply(valueSwitch); Expression binOpRhs = valueSwitch.popExpression(); call.getArg(0).apply(valueSwitch); Expression binOpLhs = valueSwitch.popExpression(); Expression instOf = new BinaryExpression(this.getCurrentLoc(), BinaryOperator.PoLeq, binOpLhs, binOpRhs); currentBlock.addStatement( new AssignStatement(SootTranslationHelpers.v().getSourceLocation(u), lhs, instOf)); return true; } } return false; } private void translateDefinitionStmt(DefinitionStmt def) { if (def.containsInvokeExpr()) { translateMethodInvokation(def, def.getLeftOp(), def.getInvokeExpr()); return; } Value lhs = def.getLeftOp(); Value rhs = def.getRightOp(); if (def.containsFieldRef()) { Verify.verify(lhs instanceof FieldRef || rhs instanceof FieldRef); if (def.getFieldRef().getField().equals(SootTranslationHelpers.v().getExceptionGlobal())) { // Special treatment of the exception global. if (lhs instanceof FieldRef) { IdentifierExpression left = methodInfo.getExceptionVariable(); if (rhs instanceof AnyNewExpr) { SootClass sc = ((RefType) ((AnyNewExpr) rhs).getType()).getSootClass(); currentBlock.addStatement( new NewStatement(loc, left, SootTranslationHelpers.v().getClassVariable(sc))); } else { rhs.apply(valueSwitch); Expression right = valueSwitch.popExpression(); currentBlock.addStatement( new AssignStatement(SootTranslationHelpers.v().getSourceLocation(def), left, right)); } } else /* if (rhs instanceof FieldRef) */ { lhs.apply(valueSwitch); Expression left = valueSwitch.popExpression(); Expression right = methodInfo.getExceptionVariable(); currentBlock.addStatement( new AssignStatement(SootTranslationHelpers.v().getSourceLocation(def), left, right)); } } else { if (lhs instanceof FieldRef) { Verify.verify(!(rhs instanceof AnyNewExpr)); SootTranslationHelpers.v().getMemoryModel().mkHeapWriteStatement(def, def.getFieldRef(), rhs); } else /* if (rhs instanceof FieldRef) */ { SootTranslationHelpers.v().getMemoryModel().mkHeapReadStatement(def, def.getFieldRef(), lhs); } } } else if (def.containsArrayRef()) { throw new RuntimeException("Remove Arrays first."); } else if (rhs instanceof LengthExpr) { throw new RuntimeException("Remove Arrays first."); } else { // first tell memory model to copy all fields if (lhs instanceof Local && rhs instanceof Local) SootTranslationHelpers.v().getMemoryModel().mkCopy((Local) lhs, (Local) rhs); // local to local assignment. lhs.apply(valueSwitch); Expression left = valueSwitch.popExpression(); if (rhs instanceof AnyNewExpr) { SootClass sc = ((RefType) ((AnyNewExpr) rhs).getType()).getSootClass(); currentBlock.addStatement(new NewStatement(loc, (IdentifierExpression) left, SootTranslationHelpers.v().getClassVariable(sc))); } else { rhs.apply(valueSwitch); Expression right = valueSwitch.popExpression(); currentBlock.addStatement( new AssignStatement(SootTranslationHelpers.v().getSourceLocation(def), left, right)); } } //TODO: assume non-null is not needed because we have a NewStatement now. // if (rhs instanceof AnyNewExpr) { // // add an assume that lhs is not null. // lhs.apply(valueSwitch); // Expression left = valueSwitch.popExpression(); // currentBlock.addStatement(new AssumeStatement(getCurrentLoc(), new BinaryExpression(getCurrentLoc(), // BinaryOperator.Ne, left, SootTranslationHelpers.v().getMemoryModel().mkNullConstant()))); // } } }