Skip to content
Snippets Groups Projects
Commit 011c63af authored by Manos Koukoutos's avatar Manos Koukoutos
Browse files

Refactor quantified solvers. smt-z3-q is now sound

parent f3f28f6e
No related branches found
No related tags found
No related merge requests found
...@@ -3,40 +3,30 @@ ...@@ -3,40 +3,30 @@
package leon package leon
package solvers.smtlib package solvers.smtlib
import leon.purescala.Common.Identifier
import purescala._ import purescala._
import Expressions._ import Expressions._
import ExprOps._
import Definitions._ import Definitions._
import Constructors._ import Constructors._
import DefOps.typedTransitiveCallees import DefOps.typedTransitiveCallees
import leon.verification.VC
import smtlib.parser.Commands.{Assert => SMTAssert, FunDef => _, _} import smtlib.parser.Commands.{Assert => SMTAssert, FunDef => _, _}
import smtlib.parser.Terms.{Exists => SMTExists, ForAll => SMTForall, _ } import smtlib.parser.Terms.{Exists => SMTExists, ForAll => SMTForall, _ }
import smtlib.theories.Core.Equals import smtlib.theories.Core.Equals
// This solver utilizes the define-funs-rec command of SMTLIB-2.5 to define mutually recursive functions. // 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. // 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" 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 = { override def declareFunction(tfd: TypedFunDef): SSymbol = {
val (funs, exploredAll) = typedTransitiveCallees(Set(tfd), Some(typedFunDefExplorationLimit)) val (funs, exploredAll) = typedTransitiveCallees(Set(tfd), Some(typedFunDefExplorationLimit))
if (!exploredAll) { if (!exploredAll) {
reporter.warning( 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 ...@@ -44,6 +34,7 @@ abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program
// we declare-fun each one and assert it is equal to its body // we declare-fun each one and assert it is equal to its body
val (withParams, withoutParams) = funs.toSeq filterNot functions.containsA partition(_.params.nonEmpty) val (withParams, withoutParams) = funs.toSeq filterNot functions.containsA partition(_.params.nonEmpty)
// FIXME this may introduce dependency errors
val parameterlessAssertions = withoutParams flatMap { tfd => val parameterlessAssertions = withoutParams flatMap { tfd =>
val s = super.declareFunction(tfd) val s = super.declareFunction(tfd)
...@@ -119,42 +110,6 @@ abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program ...@@ -119,42 +110,6 @@ abstract class SMTLIBCVC4QuantifiedSolver(context: LeonContext, program: Program
parameterlessAssertions foreach sendCommand parameterlessAssertions foreach sendCommand
functions.toB(tfd) 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)
} }
} }
/* 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)
}
}
...@@ -13,11 +13,14 @@ import smtlib.parser.Terms.{ForAll => SMTForall, SSymbol} ...@@ -13,11 +13,14 @@ import smtlib.parser.Terms.{ForAll => SMTForall, SSymbol}
* This solver models function definitions as universally quantified formulas. * This solver models function definitions as universally quantified formulas.
* It is not meant as an underlying solver to UnrollingSolver, and does not handle HOFs. * 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 = { override def declareFunction(tfd: TypedFunDef): SSymbol = {
...@@ -28,10 +31,10 @@ class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends S ...@@ -28,10 +31,10 @@ class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends S
) )
} }
val smtFunDecls = funs.toSeq.collect { val notSeen = funs.toSeq filterNot functions.containsA
case tfd if !functions.containsA(tfd) =>
super.declareFunction(tfd) val smtFunDecls = notSeen map super.declareFunction
}
smtFunDecls foreach { sym => smtFunDecls foreach { sym =>
val tfd = functions.toA(sym) val tfd = functions.toA(sym)
val term = quantifiedTerm( val term = quantifiedTerm(
...@@ -43,21 +46,28 @@ class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends S ...@@ -43,21 +46,28 @@ class SMTLIBZ3QuantifiedSolver(context: LeonContext, program: Program) extends S
) )
) )
sendCommand(SMTAssert(term)) sendCommand(SMTAssert(term))
}
tfd.postcondition foreach { post => // If we encounter a function that does not refer to the current function,
val axiom = implies( // it is sound to assume its contracts for all inputs
tfd.precondition getOrElse BooleanLiteral(true), if (allowQuantifiedAssersions) for {
application( tfd <- notSeen if !refersToCurrent(tfd.fd)
post, post <- tfd.postcondition
Seq(FunctionInvocation(tfd, tfd.params map { _.toVariable })) } {
) val term = implies(
) tfd.precondition getOrElse BooleanLiteral(true),
sendCommand(SMTAssert(quantifiedTerm(SMTForall, axiom))) application(post, Seq(tfd.applied))
)
try {
sendCommand(SMTAssert(quantifiedTerm(SMTForall, term)))
} catch {
case _ : IllegalArgumentException =>
addError()
} }
} }
functions.toB(tfd) functions.toB(tfd)
}
}
} }
...@@ -25,9 +25,23 @@ class NewSolversSuite extends VerificationSuite { ...@@ -25,9 +25,23 @@ class NewSolversSuite extends VerificationSuite {
false false
} }
if (isCVC4Available) val isZ3Available = try {
List(List("--solvers=smt-cvc4-cex,smt-cvc4-proof,ground", "--timeout=5")) Z3Interpreter.buildDefault.free()
else Nil 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
)
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment