/* * 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 soottocfg.soot.visitors; import java.util.LinkedList; import java.util.List; import soot.ArrayType; import soot.ByteType; import soot.CharType; import soot.DoubleType; import soot.FloatType; import soot.IntType; import soot.Local; import soot.LongType; import soot.NullType; import soot.PrimType; import soot.RefType; import soot.ShortType; import soot.SootMethodRef; import soot.Type; import soot.Value; import soot.jimple.AddExpr; import soot.jimple.AndExpr; import soot.jimple.ArrayRef; import soot.jimple.BinopExpr; import soot.jimple.CastExpr; import soot.jimple.CaughtExceptionRef; import soot.jimple.ClassConstant; import soot.jimple.CmpExpr; import soot.jimple.CmpgExpr; import soot.jimple.CmplExpr; import soot.jimple.DivExpr; import soot.jimple.DoubleConstant; import soot.jimple.DynamicInvokeExpr; import soot.jimple.EqExpr; import soot.jimple.FloatConstant; import soot.jimple.GeExpr; import soot.jimple.GtExpr; import soot.jimple.InstanceFieldRef; import soot.jimple.InstanceOfExpr; import soot.jimple.IntConstant; import soot.jimple.InterfaceInvokeExpr; import soot.jimple.Jimple; import soot.jimple.JimpleValueSwitch; import soot.jimple.LeExpr; import soot.jimple.LengthExpr; import soot.jimple.LongConstant; import soot.jimple.LtExpr; import soot.jimple.MethodHandle; import soot.jimple.MulExpr; import soot.jimple.NeExpr; import soot.jimple.NegExpr; import soot.jimple.NewArrayExpr; import soot.jimple.NewExpr; import soot.jimple.NewMultiArrayExpr; import soot.jimple.NullConstant; import soot.jimple.OrExpr; import soot.jimple.ParameterRef; import soot.jimple.RemExpr; import soot.jimple.ShlExpr; import soot.jimple.ShrExpr; import soot.jimple.SpecialInvokeExpr; import soot.jimple.StaticFieldRef; import soot.jimple.StaticInvokeExpr; import soot.jimple.StringConstant; import soot.jimple.SubExpr; import soot.jimple.ThisRef; import soot.jimple.UshrExpr; import soot.jimple.VirtualInvokeExpr; import soot.jimple.XorExpr; 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.IteExpression; import soottocfg.cfg.expression.UnaryExpression; import soottocfg.cfg.expression.UnaryExpression.UnaryOperator; import soottocfg.cfg.expression.literal.BooleanLiteral; import soottocfg.cfg.expression.literal.IntegerLiteral; import soottocfg.cfg.variable.ClassVariable; import soottocfg.soot.memory_model.MemoryModel; import soottocfg.soot.util.MethodInfo; import soottocfg.soot.util.SootTranslationHelpers; /** * @author schaef */ public class SootValueSwitch implements JimpleValueSwitch { private final List<Expression> expressionStack = new LinkedList<Expression>(); private final SootStmtSwitch statementSwitch; private final MethodInfo methodInfo; private final MemoryModel memoryModel; // private boolean isLeftHandSide = false; public SootValueSwitch(SootStmtSwitch ss) { this.statementSwitch = ss; this.memoryModel = SootTranslationHelpers.v().getMemoryModel(); this.memoryModel.setStmtSwitch(this.statementSwitch); this.memoryModel.setValueSwitch(this); this.methodInfo = this.statementSwitch.getMethodInfo(); } public Expression popExpression() { return this.expressionStack.remove(this.expressionStack.size() - 1); } protected void translateBinOp(BinopExpr arg0) { // this.isLeftHandSide = false; arg0.getOp1().apply(this); Expression lhs = popExpression(); arg0.getOp2().apply(this); Expression rhs = popExpression(); String op = arg0.getSymbol().trim(); if (op.compareTo("+") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Plus, lhs, rhs)); return; } else if (op.compareTo("-") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Minus, lhs, rhs)); return; } else if (op.compareTo("*") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Mul, lhs, rhs)); return; } else if (op.compareTo("/") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Div, lhs, rhs)); return; } else if (op.compareTo("%") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Mod, lhs, rhs)); return; } else if (op.compareTo("cmp") == 0 || op.compareTo("cmpl") == 0 || op.compareTo("cmpg") == 0) { /* * Returns 0 if lhs==rhs -1 if lhs <rhs 1 if lhs >rhs We model that * using ITE expressions as: (lhs<=rhs)?((lhs==rhs)?0:-1):1 */ Expression ite = new IteExpression(statementSwitch.getCurrentLoc(), new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Le, lhs, rhs), new IteExpression(statementSwitch.getCurrentLoc(), new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Eq, lhs, rhs), IntegerLiteral.zero(), new UnaryExpression(statementSwitch.getCurrentLoc(), UnaryOperator.Neg, IntegerLiteral.one())), IntegerLiteral.one()); this.expressionStack.add(ite); return; } else if (op.compareTo("==") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Eq, lhs, rhs)); return; } else if (op.compareTo("<") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Lt, lhs, rhs)); return; } else if (op.compareTo(">") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Gt, lhs, rhs)); return; } else if (op.compareTo("<=") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Le, lhs, rhs)); return; } else if (op.compareTo(">=") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Ge, lhs, rhs)); return; } else if (op.compareTo("!=") == 0) { this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Ne, lhs, rhs)); return; } else if (op.compareTo("&") == 0) { // bit-and this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.BAnd, lhs, rhs)); return; } else if (op.compareTo("|") == 0) { // bit-or this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.BOr, lhs, rhs)); return; } else if (op.compareTo("<<") == 0) { // Shiftl this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Shl, lhs, rhs)); return; } else if (op.compareTo(">>") == 0) { // Shiftr this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Shr, lhs, rhs)); return; } else if (op.compareTo(">>>") == 0) { // UShiftr this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Ushr, lhs, rhs)); return; } else if (op.compareTo("^") == 0) { // XOR this.expressionStack .add(new BinaryExpression(statementSwitch.getCurrentLoc(), BinaryOperator.Xor, lhs, rhs)); return; } else { throw new RuntimeException("UNKNOWN Jimple operator " + op); } } @Override public void caseClassConstant(ClassConstant arg0) { this.expressionStack.add( new IdentifierExpression(statementSwitch.getCurrentLoc(), this.memoryModel.lookupClassVariable(arg0))); } @Override public void caseDoubleConstant(DoubleConstant arg0) { this.expressionStack.add(this.memoryModel.mkDoubleConstant(arg0)); } @Override public void caseFloatConstant(FloatConstant arg0) { this.expressionStack.add(this.memoryModel.mkFloatConstant(arg0)); } @Override public void caseIntConstant(IntConstant arg0) { this.expressionStack.add(new IntegerLiteral(statementSwitch.getCurrentLoc(), arg0.value)); } @Override public void caseLongConstant(LongConstant arg0) { this.expressionStack.add(new IntegerLiteral(statementSwitch.getCurrentLoc(), arg0.value)); } @Override public void caseMethodHandle(MethodHandle arg0) { throw new RuntimeException( "We currently do not handle MethodHandle instructions. Please file an issue with your example.\nhttps://github.com/jayhorn/jayhorn/issues"); } @Override public void caseNullConstant(NullConstant arg0) { this.expressionStack.add(this.memoryModel.mkNullConstant()); } @Override public void caseStringConstant(StringConstant arg0) { this.expressionStack.add(this.memoryModel.mkStringConstant(arg0)); } @Override public void defaultCase(Object arg0) { throw new RuntimeException("Not implemented " + arg0); } @Override public void caseAddExpr(AddExpr arg0) { translateBinOp(arg0); } @Override public void caseAndExpr(AndExpr arg0) { translateBinOp(arg0); } @Override public void caseCastExpr(CastExpr arg0) { if (isPrimitiveNarrowing(arg0)) { // if we down-cast a numeric type, translate a havoc instead of the // actual cast. // TODO: this should me implemented in a more flexible way. createHavocLocal(arg0.getCastType()); } else { arg0.getOp().apply(this); } } /** * Creates a fresh local of type t, adds a statement * that assigns a non-det value to this local, and puts * this local on the stack. * * @param t * Type of the local that should be generated */ private void createHavocLocal(Type t) { // create a fresh local variable final String localName = "$ndet" + this.statementSwitch.getMethod().getActiveBody().getLocals().size(); Local freshLocal = Jimple.v().newLocal(localName, t); this.statementSwitch.getMethod().getActiveBody().getLocals().add(freshLocal); // add a statement that assigns a non-det value to this variable. SootMethodRef smr = SootTranslationHelpers.v().getHavocMethod(t).makeRef(); Jimple.v().newAssignStmt(freshLocal, Jimple.v().newStaticInvokeExpr(smr)).apply(this.statementSwitch); // push this fresh local on the expression stack. freshLocal.apply(this); } /** * Check if the cast narrows a numeric type. * E.g., down-casts a long into int. * * @param ce * Cast Expression * @return true if the cast narrows a numeric type, false otherwise. */ protected boolean isPrimitiveNarrowing(CastExpr ce) { int target = primitiveTypeToNumber(ce.getCastType()); int inner = primitiveTypeToNumber(ce.getOp().getType()); if (inner < 0 || target < 0) return false; return target < inner; } /** * Check if the cast widens a numeric type. * E.g., down-casts a int into long. * * @param ce * Cast Expression * @return true if the cast widens a numeric type, false otherwise. */ protected boolean isPrimitiveWidening(CastExpr ce) { int target = primitiveTypeToNumber(ce.getCastType()); int inner = primitiveTypeToNumber(ce.getOp().getType()); if (inner < 0 || target < 0) return false; return target > inner; } /** * Map numeric types to a number indicating a partial order of * their size. With double being the largest and byte being the * smallest. Following the partial order defined here: * https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html * * @param t * @return */ private int primitiveTypeToNumber(Type t) { if (t instanceof ByteType) return 0; if (t instanceof ShortType) return 1; if (t instanceof CharType) return 1; if (t instanceof IntType) return 2; if (t instanceof LongType) return 3; if (t instanceof FloatType) return 4; if (t instanceof DoubleType) return 5; return -1; } @Override public void caseCmpExpr(CmpExpr arg0) { translateBinOp(arg0); } @Override public void caseCmpgExpr(CmpgExpr arg0) { translateBinOp(arg0); } @Override public void caseCmplExpr(CmplExpr arg0) { translateBinOp(arg0); } @Override public void caseDivExpr(DivExpr arg0) { translateBinOp(arg0); } @Override public void caseDynamicInvokeExpr(DynamicInvokeExpr arg0) { throw new RuntimeException("Not implemented here."); } @Override public void caseEqExpr(EqExpr arg0) { translateBinOp(arg0); } @Override public void caseGeExpr(GeExpr arg0) { translateBinOp(arg0); } @Override public void caseGtExpr(GtExpr arg0) { translateBinOp(arg0); } @Override public void caseInstanceOfExpr(InstanceOfExpr arg0) { Value left = arg0.getOp(); soot.Type t = left.getType(); if (t instanceof RefType) { //first make a heap-read of the type filed. // SootField typeField = SootTranslationHelpers.getTypeField(((RefType)t).getSootClass()); // final String localName = "$instof"+this.statementSwitch.getMethod().getActiveBody().getLocals().size(); // Local freshLocal = Jimple.v().newLocal(localName, typeField.getType()); // this.statementSwitch.getMethod().getActiveBody().getLocals().add(freshLocal); // freshLocal.apply(this); // Expression instofLocal = this.popExpression(); // // FieldRef fieldRef = Jimple.v().newInstanceFieldRef(left, typeField.makeRef()); // memoryModel.mkHeapReadStatement(this.statementSwitch.getCurrentStmt(), fieldRef, freshLocal); // // //now make the bla <: blub expression // ClassVariable cv = SootTranslationHelpers.v().getClassVariable(arg0.getCheckType()); // BinaryExpression instof = SootTranslationHelpers.createInstanceOfExpression(instofLocal, cv); // this.expressionStack.add(instof); left.apply(this); IdentifierExpression leftId = (IdentifierExpression)this.popExpression(); ClassVariable cv = SootTranslationHelpers.v().getClassVariable(arg0.getCheckType()); Expression instof = SootTranslationHelpers.createInstanceOfExpression(this.statementSwitch.getCurrentLoc(), leftId.getVariable(), cv); this.expressionStack.add(instof); } else if (t instanceof ArrayType) { throw new RuntimeException("Remove arrays first"); } else if (t instanceof NullType) { //TODO: Warn? this.expressionStack.add(BooleanLiteral.falseLiteral()); return; } else if (t instanceof PrimType) { if (arg0.getCheckType() instanceof PrimType) { throw new RuntimeException("Not implemented. "+ arg0 + ", "+t.getClass()); } else { //TODO: Warn? this.expressionStack.add(BooleanLiteral.falseLiteral()); return; } } else { throw new RuntimeException("Not implemented. "+ arg0 + ", "+t.getClass()); } } @Override public void caseInterfaceInvokeExpr(InterfaceInvokeExpr arg0) { throw new RuntimeException("Not implemented here."); } @Override public void caseLeExpr(LeExpr arg0) { translateBinOp(arg0); } @Override public void caseLengthExpr(LengthExpr arg0) { throw new RuntimeException("Arrays have to be removed first."); } @Override public void caseLtExpr(LtExpr arg0) { translateBinOp(arg0); } @Override public void caseMulExpr(MulExpr arg0) { translateBinOp(arg0); } @Override public void caseNeExpr(NeExpr arg0) { translateBinOp(arg0); } @Override public void caseNegExpr(NegExpr arg0) { arg0.getOp().apply(this); Expression expr = popExpression(); this.expressionStack .add(new UnaryExpression(statementSwitch.getCurrentLoc(), UnaryOperator.Neg /* LNot */, expr)); } @Override public void caseNewArrayExpr(NewArrayExpr arg0) { this.expressionStack.add(this.memoryModel.mkNewArrayExpr(arg0)); } @Override public void caseNewExpr(NewExpr arg0) { throw new RuntimeException("Should be handeled in SootStmtSwitch"); } @Override public void caseNewMultiArrayExpr(NewMultiArrayExpr arg0) { this.expressionStack.add(this.memoryModel.mkNewMultiArrayExpr(arg0)); } @Override public void caseOrExpr(OrExpr arg0) { translateBinOp(arg0); } @Override public void caseRemExpr(RemExpr arg0) { translateBinOp(arg0); } @Override public void caseShlExpr(ShlExpr arg0) { translateBinOp(arg0); } @Override public void caseShrExpr(ShrExpr arg0) { translateBinOp(arg0); } @Override public void caseSpecialInvokeExpr(SpecialInvokeExpr arg0) { throw new RuntimeException("Not implemented here."); } @Override public void caseStaticInvokeExpr(StaticInvokeExpr arg0) { throw new RuntimeException("Not implemented here."); } @Override public void caseSubExpr(SubExpr arg0) { translateBinOp(arg0); } @Override public void caseUshrExpr(UshrExpr arg0) { translateBinOp(arg0); } @Override public void caseVirtualInvokeExpr(VirtualInvokeExpr arg0) { throw new RuntimeException("Not implemented here."); } @Override public void caseXorExpr(XorExpr arg0) { translateBinOp(arg0); } @Override public void caseArrayRef(ArrayRef arg0) { // this.expressionStack.add(this.memoryModel.mkArrayRefExpr(arg0)); throw new RuntimeException("Should be handled in statement switch."); } @Override public void caseCaughtExceptionRef(CaughtExceptionRef arg0) { // This should have been eliminated by the exception translation. throw new UnsupportedOperationException( "CaughtExceptionRef should have been eliminated earlier! This is a bug!"); } @Override public void caseInstanceFieldRef(InstanceFieldRef arg0) { throw new RuntimeException("must not be called"); } @Override public void caseParameterRef(ParameterRef arg0) { this.expressionStack.add(methodInfo.lookupParameterRef(arg0)); } @Override public void caseStaticFieldRef(StaticFieldRef arg0) { throw new RuntimeException("must not be called"); } @Override public void caseThisRef(ThisRef arg0) { this.expressionStack.add(methodInfo.getThisVariable()); } @Override public void caseLocal(Local arg0) { this.expressionStack.add(methodInfo.lookupLocal(arg0)); } }