diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ConcreteMemory.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ConcreteMemory.scala index d9c756e..3620806 100644 --- a/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ConcreteMemory.scala +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ConcreteMemory.scala @@ -1,10 +1,52 @@ package edu.colorado.plv.cuanto.scoot.concrete_interpreter +import com.sun.org.glassfish.external.amx.MBeanListener.CallbackImpl +import edu.colorado.plv.cuanto.scoot.concrete_interpreter + /** * @author Shawn Meier * Created on 9/29/17. */ -object ConcreteMemory {} +object ConcreteMemory { + def div(v1 : CValue, v2: CValue) = (v1,v2) match{ + case (CInteger(i1), CInteger(i2)) => CInteger(i1/i2) + case _ => ??? + } + def mul(v1: CValue, v2: CValue) = (v1,v2) match{ + case (CInteger(i1), CInteger(i2)) => CInteger(i1*i2) + case _ => ??? + } + + def add(v1 : CValue, v2 : CValue) = (v1,v2) match{ + case (CInteger(i1), CInteger(i2)) => CInteger(i1+i2) + case _ => ??? + } + def sub(v1 :CValue, v2 :CValue) = (v1,v2) match{ + case (CInteger(i1), CInteger(i2)) => CInteger(i1 - i2) + } + def neg(v1 : CValue) = v1 match{ + case CInteger(i1) => CInteger(-i1) + case _ => ??? + } + def equ(v1 :CValue, v2 :CValue) = (v1,v2) match{ + case (CInteger(i1),CInteger(i2)) => booleanToInteger(i1 == i2) + case _ => ??? + } + def nequ(v1 :CValue, v2: CValue) = (v1,v2) match{ + case (CInteger(i1), CInteger(i2)) => booleanToInteger(i1 != i2) + case _ => ??? + } + def ge(v1 :CValue, v2: CValue) = (v1,v2) match{ + case (CInteger(i1), CInteger(i2)) => booleanToInteger(i1 >= i2) + case _ => ??? + } + def booleanToInteger(b : Boolean): CInteger = if(b) CInteger(1) else CInteger(0) + def isZero(a: CValue): Boolean = a match{ + case CInteger(0) => true + case CInteger(i) => false + case _ => ??? + } +} sealed trait CValue sealed trait CPrimitive extends CValue diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/Interpreter.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/Interpreter.scala index 6cb9e2e..b8f629a 100644 --- a/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/Interpreter.scala +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/Interpreter.scala @@ -13,46 +13,57 @@ import scala.util.Try */ object Interpreter { + case class StackFrame(returnLocation: Option[(Body, Stmt)], locals : Map[String, CValue], returnValueLocation : Option[Local]) + /** An "execution environment" or state, mapping variables (of type * `Local`) to integer values */ - type Env = Map[String,Int] + //type Env = Map[String,CValue] + private val emptyLocals = new HashMap[String, CValue]() /** An environment with no assigned variables */ - private val emptyEnv: Env = new HashMap[String,Int]() //TODO: update environment + private val emptyEnv: StackFrame = StackFrame(None, emptyLocals, None) //TODO: update environment + def emptyEnv(body : Body, stmt : Stmt, returnValueLocation : Option[Local]) = + StackFrame(Some((body,stmt)), emptyLocals, returnValueLocation) /** Interpret arithmetic expressions encoded as a single `Value` */ - def evaluate_expr(v: Value, env: Env = emptyEnv): Option[Int] = v match { //TODO: update denote - case Local(s) => Some(env.getOrElse(s, throw new RuntimeException(s"Variable $s not found, malformed jimple"))) + def evaluate_expr(v: Value, env: StackFrame): Option[CValue] = v match { //TODO: update denote + case Local(s) => { + Some(getEnv(env,s)) + } case IntConstant(v) => { - Some(v) + Some(CInteger(v)) } case AddExpr(e1, e2) => for { arg1 <- evaluate_expr(e1, env) arg2 <- evaluate_expr(e2, env) - } yield arg1 + arg2 + } yield ConcreteMemory.add(arg1, arg2) case SubExpr(e1, e2) => for { arg1 <- evaluate_expr(e1, env) arg2 <- evaluate_expr(e2, env) - } yield arg1 - arg2 + } yield ConcreteMemory.sub(arg1,arg2) case MulExpr(e1, e2) => for { arg1 <- evaluate_expr(e1, env) arg2 <- evaluate_expr(e2, env) - } yield arg1 * arg2 + } yield ConcreteMemory.mul(arg1,arg2) case DivExpr(e1, e2) => for { arg1 <- evaluate_expr(e1, env) arg2 <- {val res = evaluate_expr(e2, env); if(res != 0) res else None} - } yield arg1 / arg2 + } yield ConcreteMemory.div(arg1,arg2) case NegExpr(e) => for { arg <- evaluate_expr(e, env) - } yield -arg + } yield ConcreteMemory.neg(arg) case EqExpr(e1,e2) => for{ arg1 <- evaluate_expr(e1,env) arg2 <- evaluate_expr(e2,env) - } yield if(arg1 == arg2) 1 else 0 + } yield ConcreteMemory.equ(arg1,arg2) case NeExpr(e1,e2) => for{ arg1 <- evaluate_expr(e1,env) arg2 <- evaluate_expr(e2,env) - }yield if(arg1 != arg2) 1 else 0 + }yield ConcreteMemory.nequ(arg1,arg2) + case GeExpr(e1,e2) => for{ + arg1 <- evaluate_expr(e1,env) + arg2 <- evaluate_expr(e2,env) + }yield ConcreteMemory.ge(arg1,arg2) case _ => { ??? } @@ -61,20 +72,38 @@ object Interpreter { Try(internal_interpretBody(List(emptyEnv), b.getFirstNonIdentityStmt, b).getOrElse(throw new RuntimeException("interpreter exception"))) } //TODO: update environment and update stack - private def updateEnv(env: Env, varname: String, value: Int): Env = { - env + (varname -> value) + private def updateEnv(env: StackFrame, varname: String, value: CValue): StackFrame = env match{ + case StackFrame(loc, env, rvl) => StackFrame(loc, env + (varname -> value), rvl) + } + private def getEnv(env: StackFrame, varname : String) : CValue = env match { + case StackFrame(_, env, _) => env.getOrElse (varname, throw new RuntimeException (s"Variable $varname not found, malformed jimple") ) } private def malformedJimple(): Nothing = throw new RuntimeException("malformed jimple") @tailrec - private def internal_interpretBody(stack : List[Env], loc: Stmt, b : Body): Option[CValue] = { + private def internal_interpretBody(stack : List[StackFrame], loc: Stmt, b : Body): Option[CValue] = { //normal successor, conditional successor TODO: exceptional successor - val successor = b.getSuccessors(loc) + val successor: (Option[Stmt], Option[Stmt]) = b.getSuccessors(loc) interpret_stmt(stack.head, loc) match { case InterpretNext(env) => internal_interpretBody(env :: stack.tail, successor._1.getOrElse(malformedJimple()), b) case InterpretConditionalJump(env) => internal_interpretBody(env :: stack.tail, successor._2.getOrElse(malformedJimple()),b) - case ReturnFromBody(returnValue) => returnValue + case ReturnFromBody(returnValue) => stack match{ + case h :: Nil => returnValue + case StackFrame(Some((body,stmt)), _, Some(Local(varname))) :: (us@StackFrame(r,e,l)) :: tail => { + val newEnv = returnValue.map(updateEnv(us,varname,_)).getOrElse(us) + val newFrame = StackFrame(r,newEnv.locals,l) + internal_interpretBody(newFrame :: tail, stmt, body) + } + case _ => + throw new RuntimeException("malformed stack exception") + } + case Invoke(method, args, returnValueLocation) => + val body = new Body(method.getActiveBody) + val newEnviornment = emptyEnv(b, successor._1.getOrElse(throw new RuntimeException("malformed jimple")), returnValueLocation) +// val newEnvironment = emptyEnv(b,successor._1.) + internal_interpretBody( newEnviornment :: + stack, body.getFirstNonIdentityStmt(), body) case ExecutionExceptionDivideByZero(stmt) => ??? } } @@ -84,9 +113,10 @@ object Interpreter { */ private trait StmtResult private trait NormalControlFlow extends StmtResult - private sealed case class InterpretNext(newEnvironment : Env) extends NormalControlFlow - private sealed case class InterpretConditionalJump(newEnvironment: Env) extends NormalControlFlow + private sealed case class InterpretNext(newEnvironment : StackFrame) extends NormalControlFlow + private sealed case class InterpretConditionalJump(newEnvironment: StackFrame) extends NormalControlFlow private sealed case class ReturnFromBody(result: Option[CValue]) extends NormalControlFlow + private sealed case class Invoke(method: soot.SootMethod, arguments : List[Value], returnValueLocation : Option[Local]) extends NormalControlFlow //TODO: talk about design philosophy, I believe its easier to throw when we encounter malformed jimple //The design philosophy of java is that there are caught exceptions for places where you need to react to a failure, // there are also uncaught exceptions which indicate something unexpected happened @@ -102,9 +132,9 @@ object Interpreter { - private def wrapExprEvaluationException(exprResult: Option[Int], + private def wrapExprEvaluationException(exprResult: Option[CValue], stmt: Stmt, - successCondition: Int => NormalControlFlow): StmtResult = exprResult match{ + successCondition: CValue => NormalControlFlow): StmtResult = exprResult match{ case Some(v) => successCondition(v) case None => ExecutionExceptionDivideByZero(stmt) } @@ -114,12 +144,13 @@ object Interpreter { * @param stmt stmt to interpret * @return StmtResult conveys what control flow action needs to be taken as well as the information needed */ - private def interpret_stmt(env: Env, stmt: Stmt): StmtResult = stmt match{ - case ReturnStmt(op) => ReturnFromBody(evaluate_expr(op, env).map(a => CInteger(a))) + private def interpret_stmt(env: StackFrame, stmt: Stmt): StmtResult = stmt match{ + case ReturnStmt(op) => ReturnFromBody(evaluate_expr(op, env).map(a => a)) + case AssignStmt(l@Local(varname), StaticInvokeExpr(method, args)) => Invoke(method, args, Some(l)) case AssignStmt(Local(varname),rval) => wrapExprEvaluationException( evaluate_expr(rval,env), stmt, a => InterpretNext(updateEnv(env,varname,a))) case IfStmt(condition,_) => wrapExprEvaluationException( - evaluate_expr(condition,env), stmt, a => if (a == 0) InterpretNext(env) else InterpretConditionalJump(env)) + evaluate_expr(condition,env), stmt, a => if (ConcreteMemory.isZero(a)) InterpretNext(env) else InterpretConditionalJump(env)) case GotoStmt(_) => InterpretConditionalJump(env) case _ => { ??? diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/GeExpr.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/GeExpr.scala new file mode 100644 index 0000000..b6085ec --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/GeExpr.scala @@ -0,0 +1,11 @@ +package edu.colorado.plv.cuanto.scoot.jimple + +/** + * @author Shawn Meier + * Created on 10/16/17. + */ +class GeExpr private[jimple] (private val dt: soot.jimple.GeExpr) extends BinopExpr + +object GeExpr { + def unapply(arg: GeExpr): Option[(Value,Value)] = Some(arg.dt.getOp1, arg.dt.getOp2) +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/Local.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/Local.scala index 0bac265..2ea1651 100644 --- a/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/Local.scala +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/Local.scala @@ -1,10 +1,12 @@ package edu.colorado.plv.cuanto.scoot.jimple +import soot.Type + /** * @author Jared Wright */ class Local private[jimple] (private val dt: soot.Local) extends Value object Local { - def unapply(l: Local): Option[String] = Some(l.dt.getName()) + def unapply(l: Local): Option[(String)] = Some(l.dt.getName) } \ No newline at end of file diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/StaticInvokeExpr.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/StaticInvokeExpr.scala new file mode 100644 index 0000000..7794c6a --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/StaticInvokeExpr.scala @@ -0,0 +1,19 @@ +package edu.colorado.plv.cuanto.scoot.jimple + +import soot.SootMethod + +import scala.collection.JavaConverters._ +/** + * @author Shawn Meier + * Created on 10/19/17. + */ +class StaticInvokeExpr private[jimple] (private val dt: soot.jimple.StaticInvokeExpr) extends Expr + +object StaticInvokeExpr { + def unapply(arg: StaticInvokeExpr) = { + val method: SootMethod = arg.dt.getMethod + val methodref = arg.dt.getMethodRef + + Some(arg.dt.getMethod, arg.dt.getArgs.asScala.toList.map( convertValue )) + } +} \ No newline at end of file diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/package.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/package.scala index dd47b3c..8bb3475 100644 --- a/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/package.scala +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/jimple/package.scala @@ -1,7 +1,6 @@ package edu.colorado.plv.cuanto.scoot import scala.language.reflectiveCalls -import soot.jimple._ /** * @author Jared Wright @@ -23,46 +22,46 @@ package object jimple { // implicit def convertReturnStmt(dt: soot.jimple.ReturnStmt) implicit def convertUnit(dt: soot.Unit): edu.colorado.plv.cuanto.scoot.jimple.Stmt = { - val s = new StmtSwitch { + val s = new soot.jimple.StmtSwitch { var retValue : Option[edu.colorado.plv.cuanto.scoot.jimple.Stmt] = None - override def caseIdentityStmt(stmt: IdentityStmt): Unit = ??? + override def caseIdentityStmt(stmt: soot.jimple.IdentityStmt): Unit = ??? override def caseAssignStmt(stmt: soot.jimple.AssignStmt): Unit = retValue = Some(new AssignStmt(stmt)) - override def caseRetStmt(stmt: RetStmt): Unit = ??? + override def caseRetStmt(stmt: soot.jimple.RetStmt): Unit = ??? - override def caseInvokeStmt(stmt: InvokeStmt): Unit = ??? + override def caseInvokeStmt(stmt: soot.jimple.InvokeStmt): Unit = ??? override def caseGotoStmt(stmt: soot.jimple.GotoStmt): Unit = retValue = Some(new GotoStmt(stmt)) - override def caseReturnVoidStmt(stmt: ReturnVoidStmt): Unit = ??? + override def caseReturnVoidStmt(stmt: soot.jimple.ReturnVoidStmt): Unit = ??? - override def caseExitMonitorStmt(stmt: ExitMonitorStmt): Unit = ??? + override def caseExitMonitorStmt(stmt: soot.jimple.ExitMonitorStmt): Unit = ??? - override def caseNopStmt(stmt: NopStmt): Unit = ??? + override def caseNopStmt(stmt: soot.jimple.NopStmt): Unit = ??? override def caseReturnStmt(stmt: soot.jimple.ReturnStmt): Unit = retValue = Some(new ReturnStmt(stmt)) - override def caseLookupSwitchStmt(stmt: LookupSwitchStmt): Unit = ??? + override def caseLookupSwitchStmt(stmt: soot.jimple.LookupSwitchStmt): Unit = ??? override def caseIfStmt(stmt: soot.jimple.IfStmt): Unit = retValue = Some(new IfStmt(stmt)) - override def caseThrowStmt(stmt: ThrowStmt): Unit = ??? + override def caseThrowStmt(stmt: soot.jimple.ThrowStmt): Unit = ??? - override def caseTableSwitchStmt(stmt: TableSwitchStmt): Unit = ??? + override def caseTableSwitchStmt(stmt: soot.jimple.TableSwitchStmt): Unit = ??? - override def caseEnterMonitorStmt(stmt: EnterMonitorStmt): Unit = ??? + override def caseEnterMonitorStmt(stmt: soot.jimple.EnterMonitorStmt): Unit = ??? override def defaultCase(obj: scala.Any): Unit = ??? - override def caseBreakpointStmt(stmt: BreakpointStmt): Unit = ??? + override def caseBreakpointStmt(stmt: soot.jimple.BreakpointStmt): Unit = ??? } dt.apply(s) s.retValue.getOrElse(???) } implicit def convertValue(dt: soot.Value) : Value = { - val s = new JimpleValueSwitch() { + val s = new soot.jimple.JimpleValueSwitch() { var retValue : Option[Value] = None //ExprSwitch methods @@ -84,7 +83,7 @@ package object jimple { override def caseEqExpr(eqExpr: soot.jimple.EqExpr): Unit = retValue = Some(new EqExpr(eqExpr)) - override def caseGeExpr(geExpr: soot.jimple.GeExpr): Unit = ??? + override def caseGeExpr(geExpr: soot.jimple.GeExpr): Unit = retValue = Some(new GeExpr(geExpr)) override def caseGtExpr(gtExpr: soot.jimple.GtExpr): Unit = ??? @@ -120,7 +119,7 @@ package object jimple { override def caseSpecialInvokeExpr(specialInvokeExpr: soot.jimple.SpecialInvokeExpr): Unit = ??? - override def caseStaticInvokeExpr(staticInvokeExpr: soot.jimple.StaticInvokeExpr): Unit = ??? + override def caseStaticInvokeExpr(staticInvokeExpr: soot.jimple.StaticInvokeExpr): Unit = retValue = Some(new StaticInvokeExpr(staticInvokeExpr)) override def caseSubExpr(subExpr: soot.jimple.SubExpr): Unit = retValue = Some(convertSubExpr(subExpr)) diff --git a/src/test/resources/test_files/InterpreterTests/ControlFlowTest.java b/src/test/resources/test_files/InterpreterTests/ControlFlowTest.java index 6a747f7..2b43b53 100644 --- a/src/test/resources/test_files/InterpreterTests/ControlFlowTest.java +++ b/src/test/resources/test_files/InterpreterTests/ControlFlowTest.java @@ -1,5 +1,13 @@ class ControlFlowTest{ public static void main(String[] args){ - + test1(); + } + public static int test1(){ + int foo = 3; + if(foo < 4){ + return foo+5; + }else{ + return 0; + } } } \ No newline at end of file diff --git a/src/test/resources/test_files/InterpreterTests/FunctionTest.java b/src/test/resources/test_files/InterpreterTests/FunctionTest.java new file mode 100644 index 0000000..ec27c54 --- /dev/null +++ b/src/test/resources/test_files/InterpreterTests/FunctionTest.java @@ -0,0 +1,22 @@ +class FunctionTest{ + public static void main(String[] args){ + test1(); + test2(); + } + public static int test1(){ + return inner(); + + } + public static int inner(){ + return 3; + } + public static int test2(){ + return fact(3); + } + public static int fact(int in){ + if(in == 0) + return 1; + else + return fact(in-1); + } +} \ No newline at end of file diff --git a/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ArithmeticInterpretMethodSpec.scala b/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ArithmeticInterpretMethodSpec.scala index 6c8001b..e1d0a9f 100644 --- a/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ArithmeticInterpretMethodSpec.scala +++ b/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/ArithmeticInterpretMethodSpec.scala @@ -5,6 +5,7 @@ import org.scalatest.{FlatSpec, Matchers} import org.scalatest.prop.PropertyChecks import edu.colorado.plv.cuanto.scoot.jimple._ +import scala.collection.immutable.HashMap import scala.util.{Failure, Try} class ArithmeticInterpretMethodSpec extends FlatSpec with Matchers with PropertyChecks { @@ -14,16 +15,16 @@ class ArithmeticInterpretMethodSpec extends FlatSpec with Matchers with Property val va = local("va") val vb = local("vb") - val testEnv : Map[String, Int] = Map(("va",3),("vb",15)) + val testEnv = StackFrame(None, Map(("va",CInteger(3)),("vb",CInteger(15))), None) val exprTests = Table( "expression" -> "denotation", - int(1) -> 1, - neg(int(1)) -> -1, - add(int(1))(int(1)) -> 2, - sub(int(3))(int(1)) -> 2, - mul(int(3))(int(2)) -> 6, - div(int(8))(int(4)) -> 2 + int(1) -> CInteger(1), + neg(int(1)) -> CInteger(-1), + add(int(1))(int(1)) -> CInteger(2), + sub(int(3))(int(1)) -> CInteger(2), + mul(int(3))(int(2)) -> CInteger(6), + div(int(8))(int(4)) -> CInteger(2) ) val e1 : AddExpr = add(va)(int(1)) @@ -31,23 +32,24 @@ class ArithmeticInterpretMethodSpec extends FlatSpec with Matchers with Property val exprLocalTests = Table( "expression" -> "denotation", - e1 -> 4, - e2 -> 5 + e1 -> CInteger(4), + e2 -> CInteger(5) ) "The Scoot interpreter" should "interpret stateless Values" in { forAll (exprTests) { (e, n) => - evaluate_expr(e,testEnv) should equal (Some(n)) + evaluate_expr(e, testEnv) should equal (Some(n)) } } it should "interpret Values containing in-scope Locals" in { forAll (exprLocalTests) { (e, n) => - evaluate_expr(e,testEnv) should equal (Some(n)) + val maybeValue = evaluate_expr(e, testEnv) + maybeValue should equal (Some(n)) } } it should "give None when asked to interpret undefined Locals" in { - forAll (exprLocalTests) { (e, n) => assert(Try(evaluate_expr(e)).isInstanceOf[Failure[RuntimeException]]) } + forAll (exprLocalTests) { (e, n) => assert(Try(evaluate_expr(e,StackFrame(None,new HashMap[String,CValue](), None))).isInstanceOf[Failure[RuntimeException]]) } } } diff --git a/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/InterpretBodyBehavior.scala b/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/InterpretBodyBehavior.scala index 6278ef2..1fb5e7f 100644 --- a/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/InterpretBodyBehavior.scala +++ b/src/test/scala/edu/colorado/plv/cuanto/scoot/concrete_interpreter/InterpretBodyBehavior.scala @@ -26,7 +26,9 @@ trait InterpretBodyBehavior { self: CuantoSpec => ("ArithmeticTest","int test12") -> CInteger(2), ("BooleanTest", "boolean test1") -> CInteger(1), ("BooleanTest", "boolean test2") -> CInteger(1), - ("BooleanTest", "boolean test3") -> CInteger(0) + ("BooleanTest", "boolean test3") -> CInteger(0), + ("ControlFlowTest", "int test1") -> CInteger(8), + ("FunctionTest", "int test1") -> CInteger(3) ) val interpretBoolBodyTests = Table( "methodname" -> "result",