Program Verification

• Weakest Preconditions – most permissive assumptions to ensure postcondition is CS 326 satisfied.

Formal Reasoning: Loops • Verifying functional correctness

Stephen Freund // requires P // ensures Q Prove: method test(...) { P => wp(S, Q) S }

1

Example in Loops method Test(x : int, y : int) returns (c :Point?) { n ≥ 0 } requires P; i = 0; ensures c != null; { y = 0; ∧ ∧ c := null; { P: n ≥ 0 i = 0 y = 0 } var z; while(i != n) { if y < 0 { i = i+1; z := -2 * y; Prove: P => wp(..., c != null) y = y+i; } else { } z := x; { Q: y = sum(1,n) } } if z > 10 { c := new Point(z,y); } }

1 {P} Loops Loops in Hoare while(B) { { n ≥ 0 } S { n ≥ 0 } i = 0; } i = 0; y = 0; {Q} y = 0; { P: n ≥ 0 ∧ i = 0 ∧ y = 0 } { P: n ≥0 ∧ i = 0 ∧ y = 0 } { : y = sum(1,i) } Pick invariant I such that: { invariant I : ... } while(i != n) { while(i != n) { { y = sum(1,i) ∧ i != n } 1. P => I i = i+1; i = i+1; 2. {I ∧ B}S{I} { y = sum(1,i-1) } y = y+i; 3. (I ∧ !B) => Q y = y+i; } { y = sum(1,i-1) + i = sum(1,i) } { Q: y = sum(1,n) } } { y = sum(1,i) ∧ i = n } => { Q: y = sum(1,n) }

{P} {P} Version 2 while(B) { Version 2 while(B) { { n ≥ 0 } S { n ≥ 0 } S i = 1; } i = 1; } y = 0; {Q} y = 0; {Q} { P: n ≥ 0 ∧ i = 1 ∧ y = 0 } { P: n ≥ 0 ∧ i = 1 ∧ y = 0 } { invariant: y = sum(1,i) } { invariant: y = sum(1,i-1) } while(i != n) { Pick invariant I such that: while(i != n) { Pick invariant I such that: { y = sum(1,i) ∧ i != n } 1. P => I { y = sum(1,i-1) ∧ i != n } 1. P => I i = i+1; 2. {I ∧ B}S{I} i = i+1; 2. {I ∧ B}S{I} { y = sum(1,i-1) } { y = sum(1,i-2) } 3. (I ∧ !B) => Q 3. (I ∧ !B) => Q y = y+i; y = y+i; { y = sum(1,i-1) + i = sum(1,i) } { y = sum(1,i-2) + i = sum(1,i-1) } } } { y = sum(1,i) ∧ i = n } => { y = sum(1,i-1) ∧ i = n } => { Q: y = sum(1,n) } { Q: y = sum(1,n) }

2 {P} {P} Version 3 while(B) { Version 4: Self Check while(B) { { n ≥ 0 } S { n ≥ 0 } S i = 1; } i = 1; } y = 0; {Q} y = 0; {Q} { P: n ≥ 0 ∧ i = 1 ∧ y = 0 } { P: n ≥ 0 ∧ i = 1 ∧ y = 0 } { invariant: y = sum(1,i-1) } { invariant: y = sum(1,i-1) } while(i != n + 1) { Pick invariant I such that: while(i != n + 1) { Pick invariant I such that: { y = sum(1,i-1) ∧ i != n + 1 } 1. P => I { y = sum(1,i-1) ∧ i != n } 1. P => I i = i+1; 2. {I ∧ B}S{I} y = y+i; 2. {I ∧ B}S{I} { y = sum(1,i-2) } { y = sum(1,i-2) } 3. (I ∧ !B) => Q 3. (I ∧ !B) => Q y = y+i; i = i+1;

{ y = sum(1,i-2) + i = sum(1,i-1) } This line is wr ong! { y = sum(1,i-2) + i = sum(1,i-1) } } To f ix , we need to flip the two } { y = sum(1,i-1) ∧ i = n + 1 } => statements in the loop –see { y = sum(1,i-1) ∧ i = n } => { Q: y = sum(1,n) } Version 4 next. { Q: y = sum(1,n) }

Too Strong? Too Weak? Just Right? Methodology

is too strong: 1. Decide on the invariant first – may not hold on entry. – What describes the milestone of each iteration? – may not be preserved by body 2. Write a loop body to maintain the invariant • Loop invariant is too weak: 3. Write the loop test so "false implies – can't prove what you want after the loop postcondition" • No automatic procedure for conjuring a loop- 4. Write initialization code to establish invariant invariant... – Think about invariant while writing the code – If proof doesn’t work, invariant or code or both may need work

3 Methodology Example

Set max to hold the largest value in array items Set max to hold the largest value in array items

1. Decide loop invariant first: 2. Write a loop body to maintain the invariant // I: max holds largest value in items[0..k-1] max holds largest value in range 0..k-1 of items while( ) { // I holds if(max < items[k]) { max = items[k]; // breaks I temporarily } // max holds largest value in items[0..k] k = k+1; // I holds again }

Example Example

Set max to hold the largest value in array items Set max to hold the largest value in array items

3. Write the loop test so false-implies-postcondition 4. Write initialization code to establish invariant // I: max holds largest value in items[0..k-1] k = 1; max = items[0]; while(k != items.count) { // I: max holds largest value in items[0..k-1] // I holds while(k != items.count) { if(max < items[k]) { // I holds max = items[k]; // breaks I temporarily if(max < items[k]) { } max = items[k]; // breaks I temporarily // max holds largest value in items[0..k] } k = k+1; // I holds again // max holds largest value in items[0..k] } k = k+1; // I holds again // max holds largest value in items[0..items.count-1] } // max holds largest value in items[0..items.count-1]

4 Example Quotient and Remainder

Set q to be the quotient of x/y and r to be the remainder Edge case!

// pre: items.count > 0 {Pre: x > 0 ∧ y > 0} k = 1; max = items[0]; q = 0; r = x; // I: max holds largest value in items[0..k-1] ∧ while(k != items.count) { {Invariant: q*y + r = x 0 ≤ r } // I holds while (y <= r) { if(max < items[k]) { q = q + 1; max = items[k]; // breaks I temporarily } r = r – y; // max holds largest value in items[0..k] } k = k+1; // I holds again ∧ } {Post: x = q*y + r 0 ≤ r < y } // max holds largest value in items[0..items.count-1]

Dutch National Flag (classic) Pre- and Post-conditions Given an array of red, white, and blue pebbles, Precondition: Any mix of red, white, and blue sort the array so the red pebbles are at the front, Mixed colors: red, white, blue white are in the middle, and blue are at the end – [Can only swap pebbles, not count them...]

Postcondition: – Red, then white, then blue – Number of each color same as in original array

Red White Blue Edsgar Dijkstra

5 Red White Mixed Blue Some Potential Invariants More Precise i j k • Precondition: a contains red,white,blue • Postcondition:

Red White Blue Mixed 0 <= i <= j < a.count ∧ a[0..i-1] is red

Red White Mixed Blue ∧ a[i..j-1] is white ∧ a[j..a.count-1] is blue Red Mixed White Blue • Invariant: 0 <= i <= j <= k <= a.count Mixed Red White Blue ∧ a[0..i-1] is red ∧ a[i..j-1] is white ∧ a[k..a.count-1] is blue

The Code Termination

i = 0; Red White Mixed Blue j = 0; k = a.count; i j k • Quotient-and-remainder while (j!=k) { – r (starts positive, gets strictly smaller) if(a[j] == White) { j = j+1; • Binary search } else if (a[j] == Blue) { – size of range still considered swap(a,j,k-1); k = k-1; • Dutch-national-flag } else { // a[j] == Red – size of range not yet partitioned (k-j) swap(a,i,j) i = i+1; • Search in a linked list j = j+1; – length of list not yet considered } }

6 From last time: Recap • weakest == most { P } permissive Point c = null; • strongest == most int z; restrictive if (y < 0) { z = -2*y; } else { What is the weakest z = x; precondition P that ensures postcondition Q? } if (z > 10) { c = new Point(z,y); } { Q: c != null }

When to use proofs for loops Termination

• Overkill for “obvious” loops: • Two kinds of loops – for (name in friends) {…} – Those we want to always terminate (normal case) – Those that may conceptually run forever (e.g., web-server) • Use logical reasoning: • – So, proving a loop correct usually also requires proving When intermediate state (invariant) is unclear or edge cases termination are tricky or you need inspiration, etc. – We haven’t been proving this: might just preserve invariant – As an intellectual debugging tool forever without test ever becoming false • What exactly is the invariant? – Our Hoare triples say if loop terminates, postcondition holds • Is it satisfied on every iteration? • How to prove termination (variants exist): • Are you sure? Write code to check? – Map state to a natural number somehow (just “in the proof”) • Did you check all the edge cases? – Prove the natural number goes down on every iteration • Are there preconditions you did not make explicit? – Prove test is false by the time natural number gets to 0

7 Why Reason About Programs? Our Approach

• Essential complement to testing • Hoare Logic, an approach developed in the 70’s – Testing shows specific result for a specific input • Rarely use Hoare logic explicitly – often overkill for simple code • Proof shows general result for all inputs – shines for developing code with subtle invariants – Can only prove correct code, proving uncovers bugs – Provides deeper understanding of why code is correct • Ideal for introducing program reasoning foundations • Precisely stating assumptions is essence of spec – How does logic “talk about” program states? – “Callers must not pass null as an argument” – – “Callee will always return an unaliased object” How can program execution “change what’s true”? – What do “weaker” and “stronger” mean in logic?

Weakest Precondition

wp(x = e, Q) Q[x := e] wp(S1;S2, Q) wp(S1,wp(S2,Q)) wp(if b S1 else S2, Q) (b ∧ wp(S1,Q)) ∨ (!b ∧ wp(S2,Q))

8