// verification of imperative code // imperative code goes into "method"s (even if not part of a classs) method mult(x : int, y : int) returns (r : int) requires x >= 0 requires y >= 0 { var i := 0; r := 0; while (i < y) invariant 0 <= i <= y { r := r + x; i := i + 1; } } method use_mult() { var x := mult(3, 7); // can assign the result of a method call to a variable // fails: var y := mult(3, 7) + mult(2, 3); // cannot use method calls in expressions (for various reasons) } // you can refer to the result of the method call by "r" (instead of "calling" the method) in the post-conditions method mult_specified(x : int, y : int) returns (r : int) requires x >= 0 requires y >= 0 ensures r == y * x { var i := 0; r := 0; while (i < y) invariant 0 <= i <= y invariant r == i * x // when you have loops or recursive calls, Dafny typically needs invariants to establish the postcondition { r := r + x; i := i + 1; } } // method can diverge, but they need to be marked as such: method can_diverge() decreases * { can_diverge(); } // For methods that diverge, Dafny only establishes << partial // correctness >>, which means that the post condition is guaranteed // to hold for the cases where the method terminates. For the cases // where the method does not terminate, there are no guarantees. method can_diverge'() ensures false decreases * { can_diverge'(); } // Minimize the use of "decreases *". You typically only need it on // the outermost layer (e.g., a loop reading a command and outputing // the result). If you use "decreases *" for no reason, there could // be a bug hiding in your spec. method oops() ensures 0 == 1 decreases * { can_diverge'(); } ghost predicate even(x : int) { exists y :: y * 2 == x } // Dafny has several constructions that can help the prover. // In the case below, the postcondition cannot be established. method example(a : nat) returns (b : nat) // ensures even(b) { b := 2 * a; b := b * b; } method example'(a : nat) returns (b : nat) ensures even(b) { b := 2 * a; ghost var b0 := b; // b0 is ghost, meaning it is deleted at compile // time (only used for verification purposes) and // has no impact on running time assert even(b0) by { assert b0 == a * 2; } b := b * b; assert even(b) by { calc { b; == b0 * b0; // use ghost variable to refer to previous value of b == (a * 2) * (a * 2); == (a * a * 2) * 2; } } } // Some properties cannot be stated as post-conditions of methods (or // maybe they would be difficult to prove). // A good way to go around this is to have a functional specification // for the method. function mult_spec(x : int, y : int) : int requires x >= 0 requires y >= 0 { if y == 0 then 0 else x + mult_spec(x, y - 1) } method mult'(x : int, y : int) returns (r : int) requires x >= 0 requires y >= 0 ensures r == mult_spec(x, y) { var i := 0; r := 0; while (i < y) invariant 0 <= i <= y invariant r == mult_spec(x, i) { r := r + x; i := i + 1; } } // Now we have mult' (imperative implementation) and mult_spec (functional specification). // Easy to state commutativity: lemma {:induction false} mult_comm(x : int, y : int) requires x >= 0 requires y >= 0 ensures mult_spec(x, y) == mult_spec(y, x) { if y == 0 && x == 0 { } else if x == 0 { mult_comm(x, y - 1); } else if y == 0 { mult_comm(x - 1, y); } else { calc { mult_spec(x, y); == x + mult_spec(x, y - 1); == { mult_comm(x, y - 1); } x + mult_spec(y - 1, x); == x + (y - 1) + mult_spec(y - 1, x - 1); == { mult_comm(x - 1, y - 1); } x + (y - 1) + mult_spec(x - 1, y - 1); == (x - 1) + 1 + (y - 1) + mult_spec(x - 1, y - 1); == 1 + (y - 1) + mult_spec(x - 1, y); == y + mult_spec(x - 1, y); == { mult_comm(x - 1, y); } y + mult_spec(y, x - 1); == mult_spec(y, x); } } } function mult_func(x : int, y : int) : int requires x >= 0 requires y >= 0 { if x == 0 then 0 else y + mult_func(x - 1, y) } by method { var z := 0; for i := 0 to x invariant z == mult_func(i, y) { z := z + y; } return z; } // Arrays method hello_array(a : array) // arrays are references to some heap location requires a.Length > 128 modifies a // every method needs to specify a superset of the references it changes (write frame) // (for tractability of verification) { a[7] := 42; // in-place update var x := a[3]; // access } function third(a : array) : int requires a.Length > 128 reads a // every function needs to specify the references it reads (read frame) // (for tractability of verification) { a[3] } method change(a : array) requires a.Length >= 128 modifies a ensures a[0] > old(a[0]) // use the "old" modifier to refer to the value of an expression // before the method call { a[0] := a[0] + 42; } /* Internally: each function takes an additional (hidden) parameter, a map from memory location to objects, that models the heap. The "old" modifier makes sure the parameter refers to the old heap. Postconditions can use both the current heap and the old heap, and are therefore called "two state predicates". */ method example_label(a : array) requires a.Length >= 128 modifies a ensures even(a[0]) { a[0] := a[0] * 2; label L: a[0] := a[0] * a[0]; assert even(old@L(a[0])) by { assert old@L(a[0]) == old(a[0]) * 2; } assert even(a[0]) by { calc { a[0]; == old@L(a[0]) * old@L(a[0]); // use old@L to refer to the state of the heap just after the label L == (old(a[0]) * 2) * (old(a[0]) * 2); == (old(a[0]) * old(a[0]) * 2) * 2; } } } method copy(a : array) returns (r : array) ensures r[..] == a[..] { r := new int [a.Length]; for i := 0 to a.Length invariant r[..i] == a[..i] { r[i] := a[i]; } } method change_copy(a : array) requires a.Length > 128 { var b := copy(a); // fails: b[0] := 10; // cannot assign to b because not in write frame // But b does not even exist at the point where we declare the write frame! } method copy'(a : array) returns (r : array) ensures r[..] == a[..] ensures fresh(r) { r := new int [a.Length]; for i := 0 to a.Length invariant r[..i] == a[..i] { r[i] := a[i]; } } method change_copy'(a : array) requires a.Length > 128 { var b := copy'(a); b[0] := 10; // now it works // a method can change freshly allocated memory even if not in write-frame } // fresh(o) is a two state predicate, synonymous to !old(allocated(o)) // checks that o is not allocated in the previous state twostate predicate nochange(a : array) // twostate predicates (and functions) take two hidden parameters: // the old state and the current state // onestate predicates take just one hidden parameter reads a { a[..] == old(a[..]) } method copy''(a : array) returns (r : array) ensures !old(allocated(r)) ensures nochange(a) ensures r[..] == a[..] { r := new int [a.Length]; for i := 0 to a.Length invariant r[..i] == a[..i] { r[i] := a[i]; } }