diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4QuantifiedSolver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4QuantifiedSolver.scala index 93730b3c4f0c28c0e4c7623b9a999be33d0c4537..fd9085a95c2a20bfaacbd5ea180162bff5244410 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4QuantifiedSolver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4QuantifiedSolver.scala @@ -3,40 +3,30 @@ package leon package solvers.smtlib -import leon.purescala.Common.Identifier import purescala._ import Expressions._ -import ExprOps._ import Definitions._ import Constructors._ import DefOps.typedTransitiveCallees -import leon.verification.VC import smtlib.parser.Commands.{Assert => SMTAssert, FunDef => _, _} import smtlib.parser.Terms.{Exists => SMTExists, ForAll => SMTForall, _ } import smtlib.theories.Core.Equals // This solver utilizes the define-funs-rec command of SMTLIB-2.5 to define mutually recursive functions. // It is not meant as an underlying solver to UnrollingSolver, and does not handle HOFs. -abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program) extends SMTLIBCVC4Solver(context, program) { +abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program) + extends SMTLIBCVC4Solver(context, program) + with SMTLIBQuantifiedSolver +{ override def targetName = "cvc4-quantified" - private var currentFunDef: Option[FunDef] = None - def refersToCurrent(fd: FunDef) = { - (currentFunDef contains fd) || (currentFunDef exists { - program.callGraph.transitivelyCalls(fd, _) - }) - } - - private val typedFunDefExplorationLimit = 10000 - - protected val allowQuantifiedAssersions: Boolean - override def declareFunction(tfd: TypedFunDef): SSymbol = { val (funs, exploredAll) = typedTransitiveCallees(Set(tfd), Some(typedFunDefExplorationLimit)) if (!exploredAll) { reporter.warning( - s"Did not manage to explore the space of typed functions trasitively called from ${tfd.id}. The solver may fail" + "Did not manage to explore the space of typed functions " + + s"transitively called from ${tfd.id}. The solver may fail" ) } @@ -44,6 +34,7 @@ abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program // we declare-fun each one and assert it is equal to its body val (withParams, withoutParams) = funs.toSeq filterNot functions.containsA partition(_.params.nonEmpty) + // FIXME this may introduce dependency errors val parameterlessAssertions = withoutParams flatMap { tfd => val s = super.declareFunction(tfd) @@ -119,42 +110,6 @@ abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program parameterlessAssertions foreach sendCommand functions.toB(tfd) - - } - - private def withInductiveHyp(cond: Expr): Expr = { - - val inductiveHyps = for { - fi@FunctionInvocation(tfd, args) <- functionCallsOf(cond).toSeq - } yield { - val formalToRealArgs = tfd.params.map{ _.id}.zip(args).toMap - val post = tfd.postcondition map { post => - application( - replaceFromIDs(formalToRealArgs, post), - Seq(fi) - ) - } getOrElse BooleanLiteral(true) - val pre = tfd.precondition getOrElse BooleanLiteral(true) - and(pre, post) - } - - // We want to check if the negation of the vc is sat under inductive hyp. - // So we need to see if (indHyp /\ !vc) is satisfiable - liftLets(matchToIfThenElse(andJoin(inductiveHyps :+ not(cond)))) - - } - - // We need to know the function context. - // The reason is we do not want to assume postconditions of functions referring to - // the current function, as this may make the proof unsound - override def assertVC(vc: VC) = { - currentFunDef = Some(vc.fd) - assertCnstr(withInductiveHyp(vc.condition)) - } - - override def getModel: Map[Identifier, Expr] = { - val filter = currentFunDef.map{ _.params.map{_.id}.toSet }.getOrElse( (_:Identifier) => true ) - getModel(filter) } } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedSolver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedSolver.scala new file mode 100644 index 0000000000000000000000000000000000000000..47a6bed091305e1f9ef1f9a93c2c6154874d9599 --- /dev/null +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedSolver.scala @@ -0,0 +1,62 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon.solvers.smtlib + +import leon.purescala.Common.Identifier +import leon.purescala.Constructors._ +import leon.purescala.Definitions.FunDef +import leon.purescala.ExprOps._ +import leon.purescala.Expressions.{BooleanLiteral, FunctionInvocation, Expr} +import leon.verification.VC + +trait SMTLIBQuantifiedSolver extends SMTLIBSolver { + + private var currentFunDef: Option[FunDef] = None + protected def refersToCurrent(fd: FunDef) = { + (currentFunDef contains fd) || (currentFunDef exists { + program.callGraph.transitivelyCalls(fd, _) + }) + } + + protected val allowQuantifiedAssersions: Boolean + + protected val typedFunDefExplorationLimit = 10000 + + protected def withInductiveHyp(cond: Expr): Expr = { + + val inductiveHyps = for { + fi@FunctionInvocation(tfd, args) <- functionCallsOf(cond).toSeq + } yield { + val formalToRealArgs = tfd.params.map{ _.id}.zip(args).toMap + val post = tfd.postcondition map { post => + application( + replaceFromIDs(formalToRealArgs, post), + Seq(fi) + ) + } getOrElse BooleanLiteral(true) + val pre = tfd.precondition getOrElse BooleanLiteral(true) + and(pre, post) + } + + // We want to check if the negation of the vc is sat under inductive hyp. + // So we need to see if (indHyp /\ !vc) is satisfiable + liftLets(matchToIfThenElse(andJoin(inductiveHyps :+ not(cond)))) + + } + + // We need to know the function context. + // The reason is we do not want to assume postconditions of functions referring to + // the current function, as this may make the proof unsound + override def assertVC(vc: VC) = { + currentFunDef = Some(vc.fd) + assertCnstr(withInductiveHyp(vc.condition)) + } + + // Normally, UnrollingSolver tracks the input variable, but this one + // is invoked alone so we have to filter them here + override def getModel: Map[Identifier, Expr] = { + val filter = currentFunDef.map{ _.params.map{_.id}.toSet }.getOrElse( (_:Identifier) => true ) + getModel(filter) + } + +} diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3QuantifiedSolver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3QuantifiedSolver.scala index 7e8dd06733c2846871f8cacfafa9bd10a70ff9a8..bd2f3e05497a3ca3f087c714a5439686d143ee35 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3QuantifiedSolver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3QuantifiedSolver.scala @@ -13,11 +13,14 @@ import smtlib.parser.Terms.{ForAll => SMTForall, SSymbol} * This solver models function definitions as universally quantified formulas. * It is not meant as an underlying solver to UnrollingSolver, and does not handle HOFs. */ -class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends SMTLIBZ3Solver(context, program) { +class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) + extends SMTLIBZ3Solver(context, program) + with SMTLIBQuantifiedSolver +{ - private val typedFunDefExplorationLimit = 10000 + protected val allowQuantifiedAssersions: Boolean = true - override def targetName = "z3-quantified" + override def targetName = "z3-q" override def declareFunction(tfd: TypedFunDef): SSymbol = { @@ -28,10 +31,10 @@ class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends S ) } - val smtFunDecls = funs.toSeq.collect { - case tfd if !functions.containsA(tfd) => - super.declareFunction(tfd) - } + val notSeen = funs.toSeq filterNot functions.containsA + + val smtFunDecls = notSeen map super.declareFunction + smtFunDecls foreach { sym => val tfd = functions.toA(sym) val term = quantifiedTerm( @@ -43,21 +46,28 @@ class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends S ) ) sendCommand(SMTAssert(term)) + } - tfd.postcondition foreach { post => - val axiom = implies( - tfd.precondition getOrElse BooleanLiteral(true), - application( - post, - Seq(FunctionInvocation(tfd, tfd.params map { _.toVariable })) - ) - ) - sendCommand(SMTAssert(quantifiedTerm(SMTForall, axiom))) + // If we encounter a function that does not refer to the current function, + // it is sound to assume its contracts for all inputs + if (allowQuantifiedAssersions) for { + tfd <- notSeen if !refersToCurrent(tfd.fd) + post <- tfd.postcondition + } { + val term = implies( + tfd.precondition getOrElse BooleanLiteral(true), + application(post, Seq(tfd.applied)) + ) + try { + sendCommand(SMTAssert(quantifiedTerm(SMTForall, term))) + } catch { + case _ : IllegalArgumentException => + addError() } } functions.toB(tfd) - } + } } diff --git a/src/test/scala/leon/test/verification/NewSolversSuite.scala b/src/test/scala/leon/test/verification/NewSolversSuite.scala index 4a83e80cfb7dcbbeda1e5b7bd9fa150ba928a70d..93b51d436600bc22f68ab3d0215e2ca6a991e652 100644 --- a/src/test/scala/leon/test/verification/NewSolversSuite.scala +++ b/src/test/scala/leon/test/verification/NewSolversSuite.scala @@ -25,9 +25,23 @@ class NewSolversSuite extends VerificationSuite { false } - if (isCVC4Available) - List(List("--solvers=smt-cvc4-cex,smt-cvc4-proof,ground", "--timeout=5")) - else Nil + val isZ3Available = try { + Z3Interpreter.buildDefault.free() + true + } catch { + case e: java.io.IOException => + false + } + + ( + if (isCVC4Available) + List(List("--solvers=smt-cvc4-cex,smt-cvc4-proof,ground", "--timeout=5")) + else Nil + ) ++ ( + if (isZ3Available) + List(List("--solvers=smt-z3-q,ground", "--timeout=10")) + else Nil + ) } }