Class 3
class03.dfydfy
// Let's bring back this simple example method from Class 1.
method Triple(x: int) returns (r: int)
ensures r == 3 * x
{
var y := 2 * x;
r := x + y;
}
// Here is another method that calls Triple.
// Note that Dafny is able to prove the requested property about the result of
// the method call.
method Caller() {
var t := Triple(18);
assert t < 100;
}
// To help explain how Dafny reasons about method calls, let's show how to model
// the method call with more primitive commands.
method Caller_model() {
// First, we make up fresh versions of the variable names involved in Triple's
// specification. That way, we avoid getting confused across different calls
// to the same method (or just multiple methods that share parameter/return
// names).
// So, first, we assign to a fresh variable storing the parameter to the
// method.
var x' := 18;
// Then we create another variable for the (in-principle) unknown return
// value.
var r' : int;
// Here's the trippy part: we *assume* the postcondition of the method.
// That means we add an extra true fact without proving it!
// This modeling trick is *only* sound given that we really did verify the
// method against the claimed postcondition. IDEs highlight 'assume' in a
// scary way, so you think twice before using it!
assume r' == 3 * x';
// Finally, we assign the return value to the original local variable where it
// was intended to end up.
var t := r';
// Now the assertion can be proved straightforwardly.
assert t < 100;
}
// Let's repeat the kind of exercise from class 2 and show how we calculate
// what Dafny knows at each line of code, first in the forward direction.
method Caller_forward() {
var x' := 18;
assert x' == 18;
var r' : int;
assert x' == 18;
assume r' == 3 * x';
assert x' == 18 && r' == 3 * x';
// Yup, the fact that we assume just magically pops into the formula!
var t := r';
assert x' == 18 && r' == 3 * x' && t == r';
assert t < 100;
}
// We can also calculate known facts backward.
// Like last time, read the assertions here from bottom to top as we calculate.
method Caller_backward() {
// This final fact is straightforward to prove by arithmetic.
assert forall r' :: r' == 3 * 18 ==> r' < 100;
var x' := 18;
// We glossed over this point last time: we can calculate backward past a
// variable using a "for all" quantifier.
assert forall r' :: r' == 3 * x' ==> r' < 100;
var r' : int;
// Aha: backwards reasoning treats 'assume' by adding an implication!
assert r' == 3 * x' ==> r' < 100;
assume r' == 3 * x';
assert r' < 100;
var t := r';
assert t < 100;
}
// Here's another method for us to use in working through examples.
method Divide(a: int, b: int) returns (r: int)
requires b != 0
ensures r == a / b
{
return a / b;
}
// A simple call that manages to establish Divide's precondition:
method DivideCaller(x: int)
{
var t := Divide(100, 2 * x + 1);
assert t <= 100;
}
// How do we extend our modeling of method calls via more primitive constructs?
// This time, we already have the required tool in our arsenal: 'assert'.
method DivideCaller_model(x: int)
{
var a', b' := 100, 2 * x + 1;
assert b' != 0;
var r': int;
assume r' == a' / b';
var t := r';
assert t <= 100;
}
method DivideCaller_forward(x: int)
{
var a', b' := 100, 2 * x + 1;
assert a' == 100 && b' == 2 * x + 1;
// An 'assert' doesn't actually change what we know; it just triggers a check
// that a particular fact is provable, before we proceed.
assert b' != 0;
assert a' == 100 && b' == 2 * x + 1;
var r': int;
assume r' == a' / b';
assert a' == 100 && b' == 2 * x + 1 && r' == a' / b';
var t := r';
assert a' == 100 && b' == 2 * x + 1 && r' == a' / b' && t == r';
assert t <= 100;
}
method DivideCaller_backward(x: int)
{
assert (forall r' :: r' == 100 / (2 * x + 1) ==> r' <= 100) && 2 * x + 1 != 0;
var a', b' := 100, 2 * x + 1;
assert (forall r' :: r' == a' / b' ==> r' <= 100) && b' != 0;
assert b' != 0;
assert forall r' :: r' == a' / b' ==> r' <= 100;
var r': int;
assert r' == a' / b' ==> r' <= 100;
assume r' == a' / b';
assert r' <= 100;
var t := r';
assert t <= 100;
}
// Now that we understand how Danfy calculates through method calls, we're in a
// good position to verify some recursive functions. This one checks that a
// sequence is sorted.
method Sorted(s: seq<int>) returns (r: bool)
ensures r == (forall i :: 0 <= i < |s|-1 ==> s[i] <= s[i+1])
{
if |s| <= 1 {
return true;
} else {
var t := Sorted(s[1..]);
return s[0] <= s[1] && t;
}
}
// That wasn't so bad at all! Beyond normal programming, we just needed to
// explain what correctness means for this function.
// We aren't always so lucky, though.
// Maybe we want to generate a sequence of consecutive integers, sort of like
// Python range(). Note that the postcondition only records that the final
// sequence is sorted.
method Upto(n: int) returns (r: seq<int>)
requires n >= 0
ensures forall i :: 0 <= i < |r|-1 ==> r[i] <= r[i+1]
{
if n == 0 {
return [];
} else {
var t := Upto(n-1);
return t + [n-1];
}
}
// Yikes: Dafny says it can't prove the postcondition for the 'else' branch!
// To see why, let's rewrite Upto using the modeling we just practiced.
method Upto_model(n: int) returns (r: seq<int>)
requires n >= 0
ensures forall i :: 0 <= i < |r|-1 ==> r[i] <= r[i+1]
{
if n == 0 {
return [];
} else {
// Now we can see very clearly why it is important to use fresh renamings of
// the variables of the method we're calling. In a recursive call, we would
// confusingly overwrite the original variable values, without this strategy.
var n' := n - 1;
assert n' >= 0;
var r' : seq<int>;
assume forall i :: 0 <= i < |r'|-1 ==> r'[i] <= r'[i+1];
var t := r';
return t + [n-1];
}
}
// Let's do a forward calculation to figure out why Dafny can't prove the
// postcondition.
method Upto_forward(n: int) returns (r: seq<int>)
requires n >= 0
ensures forall i :: 0 <= i < |r|-1 ==> r[i] <= r[i+1]
{
if n == 0 {
return [];
} else {
assert n > 0;
var n' := n - 1;
assert n > 0 && n' == n - 1;
assert n' >= 0;
var r' : seq<int>;
assume forall i :: 0 <= i < |r'|-1 ==> r'[i] <= r'[i+1];
assert n > 0 && n' == n - 1 && (forall i :: 0 <= i < |r'|-1 ==> r'[i] <= r'[i+1]);
var t := r';
assert n > 0 && n' == n - 1 && (forall i :: 0 <= i < |r'|-1 ==> r'[i] <= r'[i+1]) && t == r';
// The postcondition is asking us to prove the following:
assert forall i :: 0 <= i < |t + [n-1]|-1 ==> (t + [n-1])[i] <= (t + [n-1])[i+1];
// But it genuinely isn't provable here! Why?
// From what we know about the recursive call, we can't be sure that n-1 is
// really greater than or equal to all elements of t!
return t + [n-1];
}
}
// Here is one way to fix the problem, by choosing a *stronger* postcondition,
// just like we often strengthen induction hypotheses (IHes) in math proofs.
method Upto_stronger(n: int) returns (r: seq<int>)
requires n >= 0
ensures |r| == n
ensures forall i :: 0 <= i < n-1 ==> r[i] <= r[i+1]
ensures forall v :: v in r ==> 0 <= v < n
{
if n == 0 {
return [];
} else {
var t := Upto_stronger(n-1);
// assert n > 1 ==> t[n-2] in t;
return t + [n-1];
}
}
// Whoa, why did that one assertion make the difference?
// Let's show more of the proof-debugging action of repeatedly asserting facts
// to figure out what Dafny is having trouble proving.
// Read this one starting from the bottom.
method Upto_stronger_missing(n: int) returns (r: seq<int>)
requires n >= 0
ensures |r| == n
ensures forall i :: 0 <= i < n-1 ==> r[i] <= r[i+1]
ensures forall v :: v in r ==> 0 <= v < n
{
if n == 0 {
return [];
} else {
var t := Upto_stronger_missing(n-1);
// Gosh, what could Dafny be missing to conclude that fact?
// Oh, it apparently isn't using the last postcondition of the recursive call.
// Let's remind Dafny that t[n-2] actually is part of the result of that call.
assert n > 1 ==> t[n-2] in t;
// That's what we were missing! And now all the other extra lines we added
// are redundant, or at least Dafny figures them out itself as needed.
// OK, what else might Dafny not know?
// Let's restate the obligation for this case, but using the equations we
// just checked.
assert n > 1 ==> t[n-2] <= n-1;
// Oh, it doesn't know this one!
// How about the other one?
assert n > 1 ==> (t + [n-1])[n-1] == n-1;
// This one is fine, too.
// Hm, why can't Dafny prove that nice, simple, quantifier-free implication?
// Let's check if it knows how to simplify each of the sequence accesses.
assert n > 1 ==> (t + [n-1])[n-2] == t[n-2];
// This one is fine.
// Sure enough, Dafny says it can't prove this version.
// (We did a little algebraic simplification, e.g. eliminating 'i'.)
assert n > 1 ==> (t + [n-1])[n-2] <= (t + [n-1])[n-1];
// OK, let's check if Dafny knows how to prove the first of the two cases.
assert forall i :: 0 <= i < n-2 ==> (t + [n-1])[i] <= (t + [n-1])[i+1];
// Yup, that goes through. So we can focus on the other case.
// Insight #1: let's split the premise of the implication into two cases:
// (1) 'i' is in-bounds for 't'.
// (2) 'i' is such that we compare the last element of 't' with the new element.
// Note this case is only possible if n > 1! Otherwise, the final array has
// length 1, and we don't actually compare any numbers.
assert forall i :: 0 <= i < n-2 || (n > 1 && i == n-2) ==> (t + [n-1])[i] <= (t + [n-1])[i+1];
// When Dafny tells us it can't prove the postcondition, we can copy that
// postcondition here, with current variable values substituted.
assert forall i :: 0 <= i < n-1 ==> (t + [n-1])[i] <= (t + [n-1])[i+1];
return t + [n-1];
}
}
// Another approach is to give the most precise specification we can, which
// leaves us with less guesswork about which properties are worth remembering.
// This style is the ultimate in "IH strengthening"!
method Upto_determined_helper(n: int) returns (r: seq<int>)
requires n >= 0
ensures |r| == n
ensures forall i :: 0 <= i < n ==> r[i] == i
{
if n == 0 {
return [];
} else {
var t := Upto_determined_helper(n-1);
return t + [n-1];
}
}
method Upto'(n: int) returns (r: seq<int>)
requires n >= 0
ensures forall i :: 0 <= i < |r|-1 ==> r[i] <= r[i+1]
{
var t := Upto_determined_helper(n);
return t;
}
// It's kind of a shame we had to introduce an extra call just to prove this
// implementation correct! We can adopt a hybrid approach, though.
// We *can* still do it in one method.
method Upto_determined(n: int) returns (r: seq<int>)
requires n >= 0
ensures |r| == n
ensures forall i :: 0 <= i < n ==> r[i] == i
ensures forall i :: 0 <= i < |r|-1 ==> r[i] <= r[i+1]
{
if n == 0 {
return [];
} else {
var t := Upto_determined(n-1);
return t + [n-1];
}
}
// The specification is still "cluttered" with other facts that we might prefer
// to think of as internal proof details, so there is a definite trade-off to
// make between these two styles.
// As another example, let's write an efficient check for whether every element
// of one sequence is also in another one. Just to mix things up, we'll work
// with strings instead of integers as data values.
// To start with, here is a version that is *not* efficient. It takes time
// (at least) quadratic in the sequence lengths, because sequence 'in' takes
// linear time!
method SequenceInclusion_slow(a: seq<string>, b: seq<string>) returns (r: bool)
ensures r == (forall x :: x in a ==> x in b)
{
if |a| == 0 {
return true;
} else {
var t := SequenceInclusion_slow(a[1..], b);
return t && a[0] in b;
}
}
// To do better, we'll write (and verify) a few helper methods.
// This first one may not quite take linear time due to our repeated
// creation of new sets, but a smart-enough compiler optimization could figure
// out the pattern and modify the set instead of making a new one, right? >:-)
method SetOfSeq(a: seq<string>) returns (r: set<string>)
ensures forall x :: x in a <==> x in r
{
if |a| == 0 {
return {};
} else {
var t := SetOfSeq(a[1..]);
return t + {a[0]};
}
}
// Now we can implement the original functionality but where the second argument
// is a set.
method SequenceInclusionWithSet(a: seq<string>, b: set<string>) returns (r: bool)
ensures r == (forall x :: x in a ==> x in b)
{
if |a| == 0 {
return true;
} else {
var t := SequenceInclusionWithSet(a[1..], b);
return t && a[0] in b;
}
}
// And we bring it all together here.
method SequenceInclusion(a: seq<string>, b: seq<string>) returns (r: bool)
ensures r == (forall x :: x in a ==> x in b)
{
var b' := SetOfSeq(b);
var t := SequenceInclusionWithSet(a, b');
return t;
}
// We can also get roughly the same performance with a version that leans more
// into set operations.
method SequenceInclusion_alt(a: seq<string>, b: seq<string>) returns (r: bool)
ensures r == (forall x :: x in a ==> x in b)
{
var a' := SetOfSeq(a);
var b' := SetOfSeq(b);
return a' <= b'; // That's a subset check!
}
// One last recursion example: counting how many times each element of a
// sequence occurs. Recall multisets from classes 1 & 2. They are like sets
// but with duplicates allowed -- perfect for expressing what it means to
// count occurrences.
method Counts(s: seq<string>) returns (r: map<string, int>)
ensures forall k :: k in r.Keys <==> k in s
ensures forall k :: k in r.Keys ==> r[k] == multiset(s)[k]
{
if |s| == 0 {
return map[];
} else {
var t := Counts(s[1..]);
// Note the need for a case split to avoid accessing the map at a key that
// it doesn't contain.
if s[0] in t {
assert s == [s[0]] + s[1..];
// This extra assertion took a bit of experimentation to concoct!
return t[s[0] := t[s[0]] + 1];
} else {
return t[s[0] := 1];
}
}
}Exercises
class03/divide.dfydfy
method Divide(a: int, b: int) returns (r: int)
requires b != 0
ensures r == a / b
{
return a / b;
}
method DivideCaller(x: int)
{
var t := Divide(100, 2 * x + 1);
assert t <= 100;
}
method DivideCaller_model(x: int)
{
var a', b' := 100, 2 * x + 1;
// TODO assert something (why?)
var r': int;
// TODO assume something (why?)
// (Dafny will give you an error and a warning about the assumption.)
var t := r';
assert t <= 100;
}
method DivideCaller_forward(x: int)
{
var a', b' := 100, 2 * x + 1;
assert a' == 100 && b' == 2 * x + 1; // (1) <- we start here
// TODO assert something (copy from above)
// (The assertion needs to hold given what we knew. We don't learn anything.)
assert a' == 100 && b' == 2 * x + 1; // (2) <- no change
var r': int;
// TODO assume something (copy from above)
assert true; // (3)
var t := r';
assert true; // (4)
assert t <= 100;
}
method DivideCaller_backward(x: int)
{
assert true; // (6)
var a', b' := 100, 2 * x + 1;
assert true; // (5)
// TODO assert something (copy from above)
assert true; // (4)
var r': int;
assert true; // (3)
// TODO assume something (copy from above)
assert r' <= 100; // (2)
var t := r';
assert t <= 100; // (1) <- remember to start here!
}class03/upto.dfydfy
method Upto_stronger(n: int) returns (r: seq<int>)
requires n >= 0
ensures |r| == n
ensures forall i :: 0 <= i < n-1 ==> r[i] <= r[i+1]
ensures forall v :: v in r ==> 0 <= v < n
{
if n == 0 {
return [];
} else {
var t := Upto_stronger(n-1);
// (3) Try to keep going!
// (2) Then work backward. Try splitting the implication into 2 cases:
// - what do we need when `i` is in bounds for `t`? (i.e. 0 <= i < n-2)
// assert forall i :: 0 <= i < n-2 ==> ???;
// - what do we need when `i == n-2`? (which is only possible when n > 1)
// assert n > 1 ==> ???;
// (1) Re-state postcondition that we couldn't prove:
// assert forall i :: 0 <= i < n-1 ==> (t + [n-1])[i] <= (t + [n-1])[i+1];
return t + [n-1];
}
}class03/sequence.dfydfy
method SetOfSeq(a: seq<string>) returns (r: set<string>)
// TODO
method SequenceInclusionWithSet(a: seq<string>, b: set<string>) returns (r: bool)
// TODO
method SequenceInclusion(a: seq<string>, b: seq<string>) returns (r: bool)
ensures r == (forall x :: x in a ==> x in b)
{
var b' := SetOfSeq(b);
var t := SequenceInclusionWithSet(a, b');
return t; // (1) Write specs above so this method verifies.
// Just write the spec, no { ... } method body.
}
method SequenceInclusion_alt(a: seq<string>, b: seq<string>) returns (r: bool)
ensures r == (forall x :: x in a ==> x in b)
{
var a' := SetOfSeq(a);
var b' := SetOfSeq(b);
return true; // (2) Lean in to set operations and implement a different way
}