diff --git a/build.xml b/build.xml index 36a4dbc404c1892df434c4ec60151e729dcd8d8e..9d8f971443070bf2469c6faf788309d8eec99da3 100644 --- a/build.xml +++ b/build.xml @@ -60,8 +60,9 @@ <path id="build.path"> <path refid="scalac.class.path"/> - <path refid="funcheck.lib.path"/> - <pathelement location="${build.plugin.funcheck.dir}" /> + <!-- <path refid="funcheck.lib.path"/> --> + <pathelement location="${lib.dir}/z3.jar"/> + <!-- <pathelement location="${build.plugin.funcheck.dir}" /> --> </path> <!-- @@ -105,7 +106,7 @@ <mkdir dir="${build.plugin.funcheck.dir}" /> <scalac srcdir="${sources.dir}" destdir="${build.plugin.funcheck.dir}" force="changed" addparams="${scalac.default.params}"> <classpath> - <path refid="scalac.class.path" /> + <path refid="build.path" /> </classpath> <exclude name="scala/collection/**/*"/> <exclude name="funcheck/lib/**/*"/> @@ -127,7 +128,7 @@ <fileset file="./scalac-plugin.xml" /> </jar> - <echo file="${script.file}" message="#!/bin/sh${line.separator}scalac -Xplugin:${dist.jar} $@${line.separator}" /> + <echo file="${script.file}" message="#!/bin/sh${line.separator}scalac -cp ./lib/z3.jar -Xplugin:${dist.jar} $@${line.separator}" /> <chmod file="${script.file}" perm="u+x" /> </target> diff --git a/lib/z3.jar b/lib/z3.jar new file mode 100644 index 0000000000000000000000000000000000000000..2fd797b68a3ae3d5a17bad12f77b857b7e2b4ef3 Binary files /dev/null and b/lib/z3.jar differ diff --git a/s-f b/s-f new file mode 100755 index 0000000000000000000000000000000000000000..0e04980aa1736203249654a48f26a1ca5dd54a08 --- /dev/null +++ b/s-f @@ -0,0 +1,6 @@ +#!/bin/sh +SCALALIBDIR=/home/psuter/scala/current/lib +DISTDIR=/home/psuter/guru-svn/funcheck/dist +LIBDIR=/home/psuter/guru-svn/funcheck/lib +SCALAHOME=/home/psuter/scala/current +java -Dscala.home=${SCALAHOME} -classpath ${SCALALIBDIR}/scala-compiler.jar:${SCALALIBDIR}/scala-library.jar:${DISTDIR}/funcheck-plugin.jar:${LIBDIR}/z3.jar scala.tools.nsc.Main -Xplugin:${DISTDIR}/funcheck-plugin.jar $@ diff --git a/src/purescala/Analysis.scala b/src/purescala/Analysis.scala index e889f1f3f97ef736a8cbfdfb3861b5ae5691c689..f01145dae44bf5317dfdb24210130dd78f7a387e 100644 --- a/src/purescala/Analysis.scala +++ b/src/purescala/Analysis.scala @@ -1,7 +1,10 @@ package purescala +import Common._ import Definitions._ import Trees._ +import TypeTrees._ +import z3.scala._ object Analysis { // Analysis should check that: @@ -13,12 +16,19 @@ object Analysis { // - injective functions are injective // - all global invariants are satisfied def analyze(program: Program) : Unit = { + val z3cfg = new Z3Config() + //z3cfg.setParamValue("MODEL", "TRUE") + val z3 = new Z3Context(z3cfg) + program.mainObject.defs.filter(_.isInstanceOf[FunDef]).foreach(df => { val funDef = df.asInstanceOf[FunDef] val vc = postconditionVC(funDef) if(vc != BooleanLiteral(true)) { println("Verification condition for " + funDef.id + ":") println(vc) + val (z3f,stupidMap) = toZ3Formula(z3, vc) + z3.assertCnstr(z3.mkNot(z3f)) + println("Valid? " + z3.check()) } }) } @@ -30,7 +40,7 @@ object Analysis { if(post.isEmpty) { BooleanLiteral(true) } else { - BooleanLiteral(false) + replaceInExpr(Map(ResultVariable() -> functionDefinition.body), post.get) } } @@ -41,6 +51,60 @@ object Analysis { case _ => throw new Exception("ah ha !") } + (expr, Nil) + } + + def replaceInExpr(substs: Map[Expr,Expr], expr: Expr) : Expr = { + def rec(ex: Expr) : Expr = ex match { + case _ if (substs.get(ex).isDefined) => substs(ex) + case FunctionInvocation(fd, args) => FunctionInvocation(fd, args.map(rec(_))) + case IfExpr(t1,t2,t3) => IfExpr(rec(t1),rec(t2),rec(t3)) + case MatchExpr(_,_) => ex + case And(exs) => And(exs.map(rec(_))) + case Or(exs) => Or(exs.map(rec(_))) + case Not(e) => Not(rec(e)) + case BinaryOperator(t1,t2,recons) => recons(rec(t1),rec(t2)) + case _ => ex + } + + rec(expr) } + def toZ3Formula(z3: Z3Context, expr: Expr) : (Z3AST,Map[Identifier,Z3AST]) = { + val intSort = z3.mkIntSort() + var varMap: Map[Identifier,Z3AST] = Map.empty + + def rec(ex: Expr) : Z3AST = ex match { + case v @ Variable(id) => varMap.get(id) match { + case Some(ast) => ast + case None => { + assert(v.getType == Int32Type) + val newAST = z3.mkConst(z3.mkStringSymbol(id.name), intSort) + varMap = varMap + (id -> newAST) + newAST + } + } + case IfExpr(c,t,e) => z3.mkITE(rec(c), rec(t), rec(e)) + case And(exs) => z3.mkAnd(exs.map(rec(_)) : _*) + case Or(exs) => z3.mkOr(exs.map(rec(_)) : _*) + case Not(Equals(l,r)) => z3.mkDistinct(rec(l),rec(r)) + case Not(e) => z3.mkNot(rec(e)) + case IntLiteral(v) => z3.mkInt(v, intSort) + case BooleanLiteral(v) => if (v) z3.mkTrue() else z3.mkFalse() + case Equals(l,r) => z3.mkEq(rec(l),rec(r)) + case Plus(l,r) => z3.mkAdd(rec(l), rec(r)) + case Minus(l,r) => z3.mkSub(rec(l), rec(r)) + case Times(l,r) => z3.mkMul(rec(l), rec(r)) + case Division(l,r) => z3.mkDiv(rec(l), rec(r)) + case UMinus(e) => z3.mkUnaryMinus(rec(e)) + case LessThan(l,r) => z3.mkLT(rec(l), rec(r)) + case LessEquals(l,r) => z3.mkLE(rec(l), rec(r)) + case GreaterThan(l,r) => z3.mkGT(rec(l), rec(r)) + case GreaterEquals(l,r) => z3.mkGE(rec(l), rec(r)) + case _ => scala.Predef.error("Can't handle this in translation to Z3: " + ex) + } + + val res = rec(expr) + (res,varMap) + } } diff --git a/src/purescala/Trees.scala b/src/purescala/Trees.scala index 17883d57b8e43da5c970f61f148ec2eb51440ec3..e6bbcaf74cda077edf521b91e7de37918e4a8105 100644 --- a/src/purescala/Trees.scala +++ b/src/purescala/Trees.scala @@ -40,26 +40,36 @@ object Trees { // We don't handle Seq stars for now. /* Propositional logic */ - case object And { + object And { + def apply(exprs: Seq[Expr]) : And = new And(exprs) + def apply(l: Expr, r: Expr): Expr = (l,r) match { case (And(exs1), And(exs2)) => And(exs1 ++ exs2) case (And(exs1), ex2) => And(exs1 :+ ex2) case (ex1, And(exs2)) => And(exs2 :+ ex1) case (ex1, ex2) => And(List(ex1, ex2)) } + + def unapply(and: And) : Option[Seq[Expr]] = + if(and == null) None else Some(and.exprs) } - case object Or { + object Or { + def apply(exprs: Seq[Expr]) : Or = new Or(exprs) + def apply(l: Expr, r: Expr): Expr = (l,r) match { case (Or(exs1), Or(exs2)) => Or(exs1 ++ exs2) case (Or(exs1), ex2) => Or(exs1 :+ ex2) case (ex1, Or(exs2)) => Or(exs2 :+ ex1) case (ex1, ex2) => Or(List(ex1, ex2)) } + + def unapply(or: Or) : Option[Seq[Expr]] = + if(or == null) None else Some(or.exprs) } - case class And(exprs: Seq[Expr]) extends Expr - case class Or(exprs: Seq[Expr]) extends Expr + class And(val exprs: Seq[Expr]) extends Expr + class Or(val exprs: Seq[Expr]) extends Expr case class Not(expr: Expr) extends Expr /* Maybe we should split this one depending on types? */ @@ -141,4 +151,25 @@ object Trees { case class Cdr(list: Expr) extends Expr case class Concat(list1: Expr, list2: Expr) extends Expr case class ListAt(list: Expr, index: Expr) extends Expr + + object BinaryOperator { + def unapply(expr: Expr) : Option[(Expr,Expr,(Expr,Expr)=>Expr)] = expr match { + case Equals(t1,t2) => Some((t1,t2,Equals)) + case Plus(t1,t2) => Some((t1,t2,Plus)) + case Minus(t1,t2) => Some((t1,t2,Minus)) + case Times(t1,t2) => Some((t1,t2,Times)) + case Division(t1,t2) => Some((t1,t2,Division)) + case LessThan(t1,t2) => Some((t1,t2,LessThan)) + case GreaterThan(t1,t2) => Some((t1,t2,GreaterThan)) + case LessEquals(t1,t2) => Some((t1,t2,LessEquals)) + case GreaterEquals(t1,t2) => Some((t1,t2,GreaterEquals)) + case ElementOfSet(t1,t2) => Some((t1,t2,ElementOfSet)) + case SetEquals(t1,t2) => Some((t1,t2,SetEquals)) + case SubsetOf(t1,t2) => Some((t1,t2,SubsetOf)) + case SetIntersection(t1,t2) => Some((t1,t2,SetIntersection)) + case SetUnion(t1,t2) => Some((t1,t2,SetUnion)) + case SetDifference(t1,t2) => Some((t1,t2,SetDifference)) + case _ => None + } + } } diff --git a/testcases/IntOperations.scala b/testcases/IntOperations.scala new file mode 100644 index 0000000000000000000000000000000000000000..03256597c78dc64b191e21b30c4f80d48e36557e --- /dev/null +++ b/testcases/IntOperations.scala @@ -0,0 +1,6 @@ +object IntOperations { + def sum(a: Int, b: Int) : Int = { + a + (if(b < 0) -b else b) + } ensuring(_ >= a) + +}