Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion silver
Submodule silver updated 33 files
+20 −2 src/main/scala/viper/silver/ast/Expression.scala
+2 −0 src/main/scala/viper/silver/ast/pretty/PrettyPrinter.scala
+5 −1 src/main/scala/viper/silver/frontend/SilFrontEndConfig.scala
+6 −0 src/main/scala/viper/silver/testing/BackendTypeTest.scala
+251 −0 src/main/scala/viper/silver/verifier/Counterexample.scala
+30 −0 src/test/resources/counterexample_general/field_permissions.vpr
+106 −0 src/test/resources/counterexample_general/field_permissions_qp.vpr
+34 −0 src/test/resources/counterexample_general/field_values.vpr
+42 −0 src/test/resources/counterexample_general/field_values_qp.vpr
+27 −0 src/test/resources/counterexample_general/local_values.vpr
+19 −0 src/test/resources/counterexample_general/magic_wands.vpr
+12 −0 src/test/resources/counterexample_general/maps.vpr
+12 −0 src/test/resources/counterexample_general/multisets.vpr
+22 −0 src/test/resources/counterexample_general/predicate_permissions.vpr
+30 −0 src/test/resources/counterexample_general/predicate_permissions_qp.vpr
+18 −0 src/test/resources/counterexample_general/qfield.vpr
+23 −0 src/test/resources/counterexample_general/qpred.vpr
+22 −0 src/test/resources/counterexample_general/sequences.vpr
+12 −0 src/test/resources/counterexample_general/sets.vpr
+26 −0 src/test/resources/counterexample_general/two_qp_same_field.vpr
+13 −0 src/test/resources/counterexample_mapped/cyclic-ref.vpr
+17 −0 src/test/resources/counterexample_mapped/functions.vpr
+28 −0 src/test/resources/counterexample_mapped/lseg.vpr
+19 −0 src/test/resources/counterexample_mapped/method-call.vpr
+10 −0 src/test/resources/counterexample_mapped/negative.vpr
+11 −0 src/test/resources/counterexample_mapped/permissions.vpr
+27 −0 src/test/resources/counterexample_mapped/predicate.vpr
+12 −0 src/test/resources/counterexample_mapped/ref-sequence.vpr
+14 −0 src/test/resources/counterexample_mapped/sequence.vpr
+27 −0 src/test/resources/counterexample_mapped/simple-refs-rec.vpr
+21 −0 src/test/resources/counterexample_mapped/simple-refs.vpr
+0 −21 src/test/scala/CounterexampleVariablesTests.scala
+196 −0 src/test/scala/ExpectedCounterexampleAnnotation.scala
1 change: 1 addition & 0 deletions src/main/resources/z3config.smt2
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
(set-option :smt.qi.eager_threshold 100)
(set-option :smt.arith.solver 2)
(set-option :model.v2 true)
(set-option :model.partial true)

; We want unlimited multipatterns
(set-option :smt.qi.max_multi_patterns 1000)
145 changes: 136 additions & 9 deletions src/main/scala/reporting/Converter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,20 @@ object Converter {
}
}

def evaluateTerm(term: Term, model: Model): ExtractedModelEntry = {
/**
* Evaluates a Silicon term against the given SMT model. `env` binds (quantified) variables to
* already-evaluated values, which is used e.g. to evaluate a quantified chunk's permission and
* value terms for a concrete receiver. This is the single term-evaluation facility used by the
* counterexample machinery; it handles snapshots, function applications, boolean connectives,
* comparisons, collection membership, and permission arithmetic.
*/
def evaluateTerm(term: Term, model: Model, env: Map[Var, ExtractedModelEntry] = Map.empty): ExtractedModelEntry = {
term match {
case Unit => UnprocessedModelEntry(ConstantEntry(snapUnitId))
case IntLiteral(x) => LitIntEntry(x)
case t: BooleanLiteral => LitBoolEntry(t.value)
case Null => VarEntry(model.entries(nullRefId).toString, sorts.Ref)
case v: Var if env.contains(v) => env(v)
case Var(_, sort, _) =>
val key: String = term.toString
val entry: Option[ModelEntry] = model.entries.get(key)
Expand All @@ -277,7 +285,7 @@ object Converter {
}
val toSort = app.resultSort
val argEntries: Seq[ExtractedModelEntry] = args
.map(t => evaluateTerm(t, model))
.map(t => evaluateTerm(t, model, env))

val argsFinal = argEntries.map {
case UnprocessedModelEntry(entry) => entry
Expand All @@ -286,13 +294,13 @@ object Converter {
getFunctionValue(model, fname, argsFinal, toSort)

case Combine(p0, p1) =>
val p0Eval = evaluateTerm(p0, model).asValueEntry
val p1Eval = evaluateTerm(p1, model).asValueEntry
val p0Eval = evaluateTerm(p0, model, env).asValueEntry
val p1Eval = evaluateTerm(p1, model, env).asValueEntry
val entry = ApplicationEntry("$Snap.combine", Seq(p0Eval, p1Eval))
UnprocessedModelEntry(entry)

case First(p) =>
val sub = evaluateTerm(p, model)
val sub = evaluateTerm(p, model, env)
sub match {
case UnprocessedModelEntry(ApplicationEntry(name, args)) =>
if (name == "$Snap.combine") {
Expand All @@ -305,7 +313,7 @@ object Converter {
}

case Second(p) =>
val sub = evaluateTerm(p, model)
val sub = evaluateTerm(p, model, env)
sub match {
case UnprocessedModelEntry(ApplicationEntry(name, args)) =>
if (name == "$Snap.combine") {
Expand All @@ -318,7 +326,7 @@ object Converter {
}

case SortWrapper(t, to) =>
val sub = evaluateTerm(t, model)
val sub = evaluateTerm(t, model, env)
val fromSortName: String = termconverter.convert(t.sort)
val toSortName: String = termconverter.convert(to)
val fname = s"$$SortWrappers.${fromSortName}To$toSortName"
Expand All @@ -334,14 +342,133 @@ object Converter {
case PredicateLookup(predname, psf, args) =>
val lookupFuncName: String = s"$$PSF.lookup_$predname"
val snap = toSnapTree.apply(args)
val snapVal = evaluateTerm(snap, model).asValueEntry
val psfVal = evaluateTerm(psf, model).asValueEntry
val snapVal = evaluateTerm(snap, model, env).asValueEntry
val psfVal = evaluateTerm(psf, model, env).asValueEntry
val arg = Seq(psfVal, snapVal)
getFunctionValue(model, lookupFuncName, arg, sorts.Snap)

case l: Lookup =>
val toSort = l.fvf.sort match {
case fvfSort: sorts.FieldValueFunction => fvfSort.codomainSort
case _ => sorts.Snap
}
val fvfVal = evaluateTerm(l.fvf, model, env).asValueEntry
val atVal = evaluateTerm(l.at, model, env).asValueEntry
getFunctionValue(model, s"$$FVF.lookup_${l.field}", Seq(fvfVal, atVal), toSort)

case SeqLength(s) =>
getFunctionValue(model, "Seq_length", Seq(evaluateTerm(s, model, env).asValueEntry), sorts.Int)

case SetIn(e, s) =>
getFunctionValue(model, "Set_in", Seq(evaluateTerm(e, model, env).asValueEntry, evaluateTerm(s, model, env).asValueEntry), sorts.Bool)

case Ite(cond, thn, els) =>
evaluateTerm(cond, model, env) match {
case LitBoolEntry(true) => evaluateTerm(thn, model, env)
case LitBoolEntry(false) => evaluateTerm(els, model, env)
case _ => OtherEntry(term.toString, "condition not evaluable")
}

case Not(t0) =>
evaluateTerm(t0, model, env) match {
case LitBoolEntry(b) => LitBoolEntry(!b)
case _ => OtherEntry(term.toString, "not evaluable")
}
case And(ts) =>
val rs = ts.map(t0 => evaluateTerm(t0, model, env))
if (rs.exists { case LitBoolEntry(false) => true; case _ => false }) LitBoolEntry(false)
else if (rs.forall { case LitBoolEntry(true) => true; case _ => false }) LitBoolEntry(true)
else OtherEntry(term.toString, "not evaluable")
case Or(ts) =>
val rs = ts.map(t0 => evaluateTerm(t0, model, env))
if (rs.exists { case LitBoolEntry(true) => true; case _ => false }) LitBoolEntry(true)
else if (rs.forall { case LitBoolEntry(false) => true; case _ => false }) LitBoolEntry(false)
else OtherEntry(term.toString, "not evaluable")

case AtMost(t0, t1) => intComparison(t0, t1, model, env)(_ <= _)
case AtLeast(t0, t1) => intComparison(t0, t1, model, env)(_ >= _)
case Less(t0, t1) => intComparison(t0, t1, model, env)(_ < _)
case t: Greater => intComparison(t.p0, t.p1, model, env)(_ > _)
case BuiltinEquals(t0, t1) =>
val e0 = evaluateTerm(t0, model, env)
val e1 = evaluateTerm(t1, model, env)
(e0, e1) match {
case (_: OtherEntry, _) | (_, _: OtherEntry) => OtherEntry(term.toString, "not evaluable")
case _ => LitBoolEntry(e0.toString == e1.toString)
}

case NoPerm => LitPermEntry(Rational.zero)
case FullPerm => LitPermEntry(Rational.one)
case FractionPermLiteral(r) => LitPermEntry(Rational(r.numerator, r.denominator))
case FractionPerm(t0, t1) =>
(evaluateTerm(t0, model, env), evaluateTerm(t1, model, env)) match {
case (LitIntEntry(n), LitIntEntry(d)) if d != 0 => LitPermEntry(Rational(n, d))
case _ => OtherEntry(term.toString, "not evaluable")
}
case PermPlus(t0, t1) => permArithmetic(t0, t1, model, env)(_ + _)
case PermMinus(t0, t1) => permArithmetic(t0, t1, model, env)(_ - _)
case PermTimes(t0, t1) => permArithmetic(t0, t1, model, env)(_ * _)
case PermMin(t0, t1) => permArithmetic(t0, t1, model, env)((a, b) => if (a < b) a else b)

// Integer arithmetic. Div/Mod follow the SMT-LIB `div`/`mod` (Euclidean) semantics that
// Silicon translates them to, so the results agree with the solver's model.
case t: Plus => intArithmetic(t.p0, t.p1, model, env)(_ + _)
case t: Minus => intArithmetic(t.p0, t.p1, model, env)(_ - _)
case t: Times => intArithmetic(t.p0, t.p1, model, env)(_ * _)
case t: Div => intDivMod(t.p0, t.p1, model, env)(euclideanDiv)
case t: Mod => intDivMod(t.p0, t.p1, model, env)(euclideanMod)

// Multiset containment: `e in ms` is the count of `e` in `ms`.
case t: MultisetCount =>
getFunctionValue(model, "Multiset_count",
Seq(evaluateTerm(t.p0, model, env).asValueEntry, evaluateTerm(t.p1, model, env).asValueEntry), sorts.Int)

// Map operations. `r in domain(m)` desugars to SetIn(r, MapDomain(m)), so evaluating the
// domain to its set value lets the existing SetIn case take over.
case t: MapDomain =>
getFunctionValue(model, "Map_domain", Seq(evaluateTerm(t.p, model, env).asValueEntry), t.sort)
case t: MapLookup =>
getFunctionValue(model, "Map_apply",
Seq(evaluateTerm(t.base, model, env).asValueEntry, evaluateTerm(t.key, model, env).asValueEntry), t.sort)
case t: MapCardinality =>
getFunctionValue(model, "Map_card", Seq(evaluateTerm(t.p, model, env).asValueEntry), sorts.Int)

case _ => OtherEntry(term.toString, "unhandled")
}
}

private def euclideanMod(a: BigInt, b: BigInt): BigInt = { val r = a % b; if (r < 0) r + b.abs else r }
private def euclideanDiv(a: BigInt, b: BigInt): BigInt = (a - euclideanMod(a, b)) / b

private def intArithmetic(t0: Term, t1: Term, model: Model, env: Map[Var, ExtractedModelEntry])
(op: (BigInt, BigInt) => BigInt): ExtractedModelEntry =
(evaluateTerm(t0, model, env), evaluateTerm(t1, model, env)) match {
case (LitIntEntry(a), LitIntEntry(b)) => LitIntEntry(op(a, b))
case _ => OtherEntry(s"integer arithmetic on $t0 and $t1", "not evaluable")
}

private def intDivMod(t0: Term, t1: Term, model: Model, env: Map[Var, ExtractedModelEntry])
(op: (BigInt, BigInt) => BigInt): ExtractedModelEntry =
(evaluateTerm(t0, model, env), evaluateTerm(t1, model, env)) match {
case (LitIntEntry(_), LitIntEntry(b)) if b == 0 => OtherEntry(s"division of $t0 by zero", "not evaluable")
case (LitIntEntry(a), LitIntEntry(b)) => LitIntEntry(op(a, b))
case _ => OtherEntry(s"division of $t0 by $t1", "not evaluable")
}

private def intComparison(t0: Term, t1: Term, model: Model, env: Map[Var, ExtractedModelEntry])
(op: (BigInt, BigInt) => Boolean): ExtractedModelEntry =
(evaluateTerm(t0, model, env), evaluateTerm(t1, model, env)) match {
case (LitIntEntry(a), LitIntEntry(b)) => LitBoolEntry(op(a, b))
case _ => OtherEntry(s"comparison of $t0 and $t1", "not evaluable")
}

private def permArithmetic(t0: Term, t1: Term, model: Model, env: Map[Var, ExtractedModelEntry])
(op: (Rational, Rational) => Rational): ExtractedModelEntry =
(evaluateTerm(t0, model, env), evaluateTerm(t1, model, env)) match {
case (LitPermEntry(a), LitPermEntry(b)) => LitPermEntry(op(a, b))
case _ => OtherEntry(s"permission arithmetic on $t0 and $t1", "not evaluable")
}

def extractHeap(h: Iterable[Chunk], model: Model): ExtractedHeap = {
var entries: Vector[HeapEntry] = Vector()
h foreach {
Expand Down
Loading