diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/arithmetic/Interpreter.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/arithmetic/Interpreter.scala deleted file mode 100644 index 2c6c882..0000000 --- a/src/main/scala/edu/colorado/plv/cuanto/scoot/arithmetic/Interpreter.scala +++ /dev/null @@ -1,77 +0,0 @@ -package edu.colorado.plv.cuanto.scoot -package arithmetic - -import soot._ -import soot.jimple._ - -import scala.collection.immutable.HashMap - -/** Implement an interpreter for sequences of Jimple assignment - * statements that represent integer arithmetic programs - */ -object Interpreter { - - /** An "execution environment" or state, mapping variables (of type - * `Local`) to integer values */ - type Env = Map[Local,Int] - - /** An environment with no assigned variables */ - val emptyEnv: Env = new HashMap[Local,Int]() - - private def some[A](a: A): Option[A] = Some(a) - - /** Step an environment forward over a single statement */ - def step(stmt: AssignStmt)(env: Env): Option[Env] = { - val varNameO: Option[Local] = stmt.getLeftOp() match { - case l: Local => Some(l) - case _ => None - } - val newValueO: Option[Int] = denote(stmt.getRightOp(), env) - for { - varName <- varNameO - newValue <- newValueO - } yield env + (varName -> newValue) - } - - - /** Interpret the integer value of a variable mutated over a sequence - * of assignment statements */ - def denote(ss: Traversable[AssignStmt], v: Local): Option[Int] = - denote(ss).flatMap(_ get v) - - def denote(ss: Traversable[AssignStmt]): Option[Env] = - ss.foldLeft(some(emptyEnv))((env,stmt) => env.flatMap(step(stmt))) - - /** Interpret arithmetic expressions encoded as a single `Value` */ - def denote(v: Value, env: Env = emptyEnv): Option[Int] = v match { - case v: Local => env get v - case v: IntConstant => Some(v.value) - case v: BinopExpr => for { - op <- bop(v) - arg1 <- denote(v.getOp1(), env) - arg2 <- denote(v.getOp2(), env) - } yield op(arg1, arg2) - case v: UnopExpr => for { - op <- uop(v) - arg <- denote(v.getOp(), env) - } yield op(arg) - } - - /** Interpret an arithmetic unary operator node, getting back a - * function that performs the operation */ - def uop(op: UnopExpr): Option[Int => Int] = op match { - case _: NegExpr => Some(_ * -1) - case _ => None - } - - /** Interpret an arithemetic binary operator node, getting back a - * function that performs the operation */ - def bop(op: BinopExpr): Option[(Int, Int) => Int] = op match { - case _: AddExpr => Some(_ + _) - case _: SubExpr => Some(_ - _) - case _: DivExpr => Some(_ / _) - case _: MulExpr => Some(_ * _) - case _ => None - } - -} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/ArithDom.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/ArithDom.scala new file mode 100644 index 0000000..25f7d9c --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/ArithDom.scala @@ -0,0 +1,15 @@ +package edu.colorado.plv.cuanto +package scoot.domains + +/** Generic abstraction of numeric types. + */ +trait ArithDom { + type D + + def neg(e: D): D + + def add(e1: D)(e2: D): D + def sub(e1: D)(e2: D): D + def mul(e1: D)(e2: D): D + def div(e1: D)(e2: D): D +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/IntDom.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/IntDom.scala new file mode 100644 index 0000000..5dea1bd --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/IntDom.scala @@ -0,0 +1,34 @@ +package edu.colorado.plv.cuanto +package scoot.domains + +import abstracting._ + +/** Concrete domain of integer values. + */ +case class IntDom(i: Int) extends ArithDom + with Abstractable[Int,IntDom] { + override type D = IntDom + + override def represent(i: Int): IntDom = IntDom(i) + + override def neg(e: IntDom): IntDom = e match { + case IntDom(i) => i * -1 + } + + override def add(e1: IntDom)(e2: IntDom): IntDom = + (e1,e2) match { + case (IntDom(i1),IntDom(i2)) => i1 + i2 + } + override def sub(e1: IntDom)(e2: IntDom): IntDom = + (e1,e2) match { + case (IntDom(i1),IntDom(i2)) => i1 - i2 + } + override def mul(e1: IntDom)(e2: IntDom): IntDom = + (e1,e2) match { + case (IntDom(i1),IntDom(i2)) => i1 * i2 + } + override def div(e1: IntDom)(e2: IntDom): IntDom = + (e1,e2) match { + case (IntDom(i1),IntDom(i2)) => i1 / i2 + } +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/package.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/package.scala new file mode 100644 index 0000000..9597f98 --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/domains/package.scala @@ -0,0 +1,6 @@ +package edu.colorado.plv.cuanto.scoot + +package domains { + trait Result[A <: ArithDom] + case class Arith[A <: ArithDom](a: A) extends Result[A] +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/control/package.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/control/package.scala new file mode 100644 index 0000000..d5539b6 --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/control/package.scala @@ -0,0 +1,18 @@ +package edu.colorado.plv.cuanto.scoot.interpreter +package control + +import soot.{Unit => SootUnit} +import soot.toolkits.graph.UnitGraph + +import expression.Env + +/** Interpret soot Units that affect control-flow */ +package object control { + + /** Find the next Unit, given an environment */ + def succ(graph: UnitGraph)(env: Env, unit: SootUnit): + Option[SootUnit] = ??? + + def next[D](graph: UnitGraph, unit: SootUnit): Traversable[(Unit,D)] = ??? + +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/Arithmetic.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/Arithmetic.scala new file mode 100644 index 0000000..05b6146 --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/Arithmetic.scala @@ -0,0 +1,67 @@ +package edu.colorado.plv.cuanto.scoot +package interpreter.expression + +import soot._ +import soot.jimple._ + +import domains._ +import domains.IntDom + +/** Sub-interpreter for arithmetic nodes */ +object Arithmetic { + + private def omerge[A,B](t: (Option[A],Option[B])): Option[(A,B)] = + for (a <- t._1; b <- t._2) yield (a,b) + + + /** Interpret an arithmetic operator node */ + def interpNode(v: Value)(r: Value => Option[RFun]): + Option[RFun] = v match { + // case v: Local => env get v + case v: IntConstant => Some(_ => Some(Arith(IntDom(v.value)))) + case v: BinopExpr => for { + op <- bop(v) + arg1 <- r(v.getOp1()) + arg2 <- r(v.getOp2()) + } yield { e: Env => op(arg1(e), arg2(e)) } + case v: UnopExpr => for { + op <- uop(v) + arg <- r(v.getOp()) + } yield { e: Env => op(arg(e)) } + case _ => None + } + + /** Interpret an arithmetic unary operator node, getting back a + * function that performs the operation */ + private def uop(op: UnopExpr): + Option[Option[R] => Option[R]] = { + def tryOp(f: IntDom => IntDom)(a: Option[R]): Option[R] = a match { + case Some(Arith(a)) => Some(Arith(f(a))) + case _ => None + } + op match { + case _: NegExpr => Some(tryOp((i: IntDom) => i.neg(i))) + case _ => None + } + } + + /** Interpret an arithemetic binary operator node, getting back a + * function that performs the operation */ + private def bop(op: BinopExpr): + Option[(Option[R], Option[R]) => Option[R]] = { + def tryOp(f: (IntDom,IntDom) => IntDom)(a: Option[R], b: Option[R]): + Option[R] = + (a,b) match { + case (Some(Arith(a)),(Some(Arith(b)))) => Some(Arith(f(a,b))) + case _ => None + } + op match { + case _: AddExpr => Some(tryOp((i1: IntDom,i2: IntDom) => i1.add(i1)(i2))) + case _: SubExpr => Some(tryOp((i1: IntDom,i2: IntDom) => i1.sub(i1)(i2))) + case _: DivExpr => Some(tryOp((i1: IntDom,i2: IntDom) => i1.div(i1)(i2))) + case _: MulExpr => Some(tryOp((i1: IntDom,i2: IntDom) => i1.mul(i1)(i2))) + case _ => None + } + } + +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/arithmetic/Builder.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/Builder.scala similarity index 96% rename from src/main/scala/edu/colorado/plv/cuanto/scoot/arithmetic/Builder.scala rename to src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/Builder.scala index 98fcdf1..e633482 100644 --- a/src/main/scala/edu/colorado/plv/cuanto/scoot/arithmetic/Builder.scala +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/Builder.scala @@ -1,5 +1,5 @@ -package edu.colorado.plv.cuanto.scoot -package arithmetic +package edu.colorado.plv.cuanto.scoot.interpreter +package expression import soot.{Value, IntType, Local, Immediate} import soot.jimple._ diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/package.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/package.scala new file mode 100644 index 0000000..fb44b21 --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/package.scala @@ -0,0 +1,43 @@ +package edu.colorado.plv.cuanto +package scoot.interpreter + +import scala.collection.immutable.{Map, HashMap} +import soot._ + +import scoot.domains._ + +/** Interpreter for expressions (soot Values). This depends on + * expression evaluation not having side-effects. + */ +package object expression { + /** A sum type of possible types that an expression can evaluate to */ + type R = Result[IntDom] + + /** Evaluation environment */ + type Env = Map[String,R] + type RFun = Env => Option[R] + + /** The empty environment */ + val emptyEnv: Env = new HashMap[String,R]() + + /** Interpret a value under a particular environment */ + def interpret(v: Value, env: Env = emptyEnv): Option[R] = (for { + rfun <- interpR(v) + } yield rfun(env)).flatten + + private def interpR(v: Value): Option[RFun] = + anyOf(Seq(Arithmetic.interpNode(v)(interpR), Locals.interpNode(v)(interpR))) + + private def anyOf(is: Traversable[Option[RFun]]): Option[RFun] = + is.flatten.headOption + + /** Sub-interpreter for looking up Local values in the environment */ + object Locals { + /** Interpret a Local node */ + def interpNode(v: Value)(r: Value => Option[RFun]): + Option[RFun] = v match { + case v: Local => Some(_ get v.getName()) + case _ => None + } + } +} diff --git a/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/mutation/package.scala b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/mutation/package.scala new file mode 100644 index 0000000..0082fc2 --- /dev/null +++ b/src/main/scala/edu/colorado/plv/cuanto/scoot/interpreter/mutation/package.scala @@ -0,0 +1,35 @@ +package edu.colorado.plv.cuanto.scoot.interpreter +package mutation + +import soot._ +import soot.jimple._ + +import expression._ + +/** Interpreter for soot Units that modify the evaluation + * environment */ +package object mutation { + + private def some[A](a: A): Option[A] = Some(a) + + /** Step an environment forward over a single statement */ + def step(stmt: AssignStmt)(env: Env): Option[Env] = { + val varNameO: Option[Local] = stmt.getLeftOp() match { + case l: Local => Some(l) + case _ => None + } + val newValueO: Option[R] = expression.interpret(stmt.getRightOp(), env) + for { + varName <- varNameO + newValue <- newValueO + } yield env + (varName.getName() -> newValue) + } + + /** Interpret the integer value of a variable mutated over a sequence + * of assignment statements */ + def interpret(ss: Traversable[AssignStmt], v: String): Option[R] = + interpret(ss).flatMap(_ get v) + + def interpret(ss: Traversable[AssignStmt]): Option[Env] = + ss.foldLeft(some(emptyEnv))((env,stmt) => env.flatMap(step(stmt))) +} diff --git a/src/test/scala/edu/colorado/plv/cuanto/scoot/arithmetic/ArithmeticInterpreterSpec.scala b/src/test/scala/edu/colorado/plv/cuanto/scoot/arithmetic/ArithmeticInterpreterSpec.scala deleted file mode 100644 index e93d96d..0000000 --- a/src/test/scala/edu/colorado/plv/cuanto/scoot/arithmetic/ArithmeticInterpreterSpec.scala +++ /dev/null @@ -1,78 +0,0 @@ -package edu.colorado.plv.cuanto.scoot -package arithmetic - -import org.scalatest.{FlatSpec, Matchers} -import org.scalatest.prop.PropertyChecks - -class ArithmeticInterpreterSpec extends FlatSpec with Matchers with PropertyChecks { - import Builder._ - import Interpreter._ - - val va = local("va") - val vb = local("vb") - - val testEnv = Map((va,3),(vb,15)) - - 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 - ) - - val exprLocalTests = Table( - "expression" -> "denotation", - add(va)(int(1)) -> 4, - div(vb)(va) -> 5 - ) - - "The Scoot interpreter" should "interpret stateless Values" in { - forAll (exprTests) { (e, n) => - denote(e,testEnv) should equal (Some(n)) - } - } - - it should "interpret Values containing in-scope Locals" in { - forAll (exprLocalTests) { (e, n) => - denote(e,testEnv) should equal (Some(n)) - } - } - - it should "give None when asked to interpret undefined Locals" in { - forAll (exprLocalTests) { (e, n) => denote(e) should equal (None) } - } - - val other = local("other") - - val stmtTests = Table( - "program" -> "denotation", - Seq(assign(add(int(1))(int(1)))) -> 2, - Seq(assign(int(0)), assign(int(5))) -> 5, - Seq(assign(int(4)), subs(int(8)), muls(int(2))) -> -8, - Seq(assign(int(1)), negs()) -> -1, - Seq(assign(int(1), other), - assign(add(int(1))(other))) -> 2 - ) - - val stmtFailTests = Table( - "bad program", - Seq(adds(int(1)), assign(int(2))), - Seq(assign(int(2),other)), - Seq() - ) - - it should "interpret a variable mutated through a sequence of AssignStmts" in { - forAll (stmtTests) { (e, n) => denote(e,acc) should equal (Some(n)) } - } - - /** Here, "bad program" means that either the variable being tracked - * is never assigned, or one or more expressions in the program - * could not be interpreted */ - it should "give None when asked to interpret a bad program" in { - forAll (stmtFailTests) { e => denote(e,acc) should equal (None) } - } - -} diff --git a/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/control/ControlInterpreterSpec.scala b/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/control/ControlInterpreterSpec.scala new file mode 100644 index 0000000..b686884 --- /dev/null +++ b/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/control/ControlInterpreterSpec.scala @@ -0,0 +1,12 @@ +package edu.colorado.plv.cuanto.scoot.interpreter +package control + +import org.scalatest.{FlatSpec, Matchers} +import org.scalatest.prop.PropertyChecks + +//////////////////////////////////////////////////////////////////////// + +class ControlInterpreterSpec extends FlatSpec with Matchers + with PropertyChecks { + // import Builder._ +} diff --git a/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/ExpressionInterpreterSpec.scala b/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/ExpressionInterpreterSpec.scala new file mode 100644 index 0000000..31adf19 --- /dev/null +++ b/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/expression/ExpressionInterpreterSpec.scala @@ -0,0 +1,53 @@ +package edu.colorado.plv.cuanto.scoot +package interpreter.expression + +import org.scalatest.{FlatSpec, Matchers} +import org.scalatest.prop.PropertyChecks + +import domains._ + +//////////////////////////////////////////////////////////////////////// + +class ExpressionInterpreterSpec extends FlatSpec with Matchers + with PropertyChecks { + import Builder._ + + val va = local("va") + val vb = local("vb") + + val testEnv = Map(("va",Arith[IntDom](IntDom(3))),("vb",Arith[IntDom](IntDom(15)))) + + 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 + ) + + val exprLocalTests = Table( + "expression" -> "denotation", + add(va)(int(1)) -> 4, + div(vb)(va) -> 5 + ) + + "The Scoot interpreter" should "interpret stateless Values" in { + forAll (exprTests) { (e, n) => + interpret(e,testEnv) should equal (Some(Arith[IntDom](IntDom(n)))) + } + } + + it should "interpret Values containing in-scope Locals" in { + forAll (exprLocalTests) { (e, n) => + interpret(e,testEnv) should equal (Some(Arith[IntDom](IntDom(n)))) + } + } + + it should "give None when asked to interpret undefined Locals" in { + forAll (exprLocalTests) { (e, n) => interpret(e) should equal (None) } + } + + +} diff --git a/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/mutation/MutationInterpreterSpec.scala b/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/mutation/MutationInterpreterSpec.scala new file mode 100644 index 0000000..94c8423 --- /dev/null +++ b/src/test/scala/edu/colorado/plv/cuanto/scoot/interpreter/mutation/MutationInterpreterSpec.scala @@ -0,0 +1,45 @@ +package edu.colorado.plv.cuanto.scoot +package interpreter.mutation + +import org.scalatest.{FlatSpec, Matchers} +import org.scalatest.prop.PropertyChecks + +import domains._ + +//////////////////////////////////////////////////////////////////////// + +class MutationInterpreterSpec extends FlatSpec with Matchers + with PropertyChecks { + import interpreter.expression.Builder._ + + val other = local("other") + + val stmtTests = Table( + "program" -> "denotation", + Seq(assign(add(int(1))(int(1)))) -> 2, + Seq(assign(int(0)), assign(int(5))) -> 5, + Seq(assign(int(4)), subs(int(8)), muls(int(2))) -> -8, + Seq(assign(int(1)), negs()) -> -1, + Seq(assign(int(1), other), + assign(add(int(1))(other))) -> 2 + ) + + val stmtFailTests = Table( + "bad program", + Seq(adds(int(1)), assign(int(2))), + Seq(assign(int(2),other)), + Seq() + ) + + it should "interpret a variable mutated through a sequence of AssignStmts" in { + forAll (stmtTests) { (e, n) => + mutation.interpret(e,acc.getName()) should equal (Some(Arith[IntDom](IntDom(n)))) } + } + + /** Here, "bad program" means that either the variable being tracked + * is never assigned, or one or more expressions in the program + * could not be interpreted */ + it should "give None when asked to interpret a bad program" in { + forAll (stmtFailTests) { e => mutation.interpret(e,acc.getName()) should equal (None) } + } +}