Hoare logic and program correctness How do we show that a partial correctness statement {P} c {Q} holds? We know that a triple {P} c {Q} is valid if it holds for all states and interpretations: s |=_I {P} c {Q}. Furthermore, showing that s |=_I {P} c {Q} requires reasoning about the execution of command c (that is, C[[c]]), as indicated by the definition of validity. It turns out that there is an elegant way of deriving valid partial correctness assertions, without having to reason about states, interpretations, and the execution of c. We can use a set of inference rules, called Hoare rules, to directly derive valid partial correctness assertions. The set of rules forms a proof system known as Hoare logic. The inference rules derive new valid triples provided some other partial correctness assertions hold in the premises. (skip) ------------ {P} skip {P} (assign) ------------------- {P[a/x]} x := a {P} {P} c1 {R} {R} c2 {Q} (seq) ----------------------- {P} c1; c2 {Q} {P /\ b} c1 {Q} {P /\ neg b} c2 {Q} (if) ------------------------------------- {P} if b then c1 else c2 {Q} {P /\ b} c {P} (while) ----------------------------- {P} while b do c {P /\ neg b} The assertion P in the rule for while loops is essentially a loop invariant; it is an assertion that holds before and after each iteration, as shown in the premise of the rule. Therefore, it is both a pre-condition for the loop (because it holds before the first iteration); and also a post-condition for the loop (because it holds after the last iteration). The fact that P is both a pre- and post-condition for the while loop is reflected in the conclusion of the rule. There is one more rule, the rule of consequence, which allows to strengthen pre-conditions and weaken post-conditions: |= (P => P') {P'} c {Q'} |= (Q' => Q) (consequence) --------------------------------------- {P} c {Q} Essentially, these set of Hoare rules represent an inductive definition for a set of triples {P} c {Q}. We will say that {P} c {Q} is a theorem in Hoare logic, written |- {P} c {Q}, if we can build a finite proof tree for it. Soundness and Completeness At this point we have two kinds of partial correctness assertions: a) valid assertions |= {P} c {Q}, which hold for all states and interpretations, according to the semantics of c; and b) Hoare logic theorems |- {P} c {Q}, that is, triple that can be proved using Hoare rules. The question is how do these sets relate to each other? More precisely, we have to answer two questions. First, is each Hoare logic theorem guaranteed to be valid partial correctness triple? In other words, does |- {P} c {Q} imply |= {P} c {Q}? The answer is yes, and it shows that Hoare logic is sound. Soundness is important because it says that Hoare logic doesn't allow us to derive triples that actually don't hold. The proof of soundness requires induction on the derivations in |- {P} c {Q} (but we will omit this proof). The second question refers to the expressiveness and power of Hoare rules: can we always build a Hoare logic proof for each valid assertion? In other words, does |= {P} c {Q} imply |- {P} c {Q}? The answer is yes, modulo the proofs for validity of assertions that occur in the rule of consequence (|= (P => P') and |= (Q' => Q)). This result is known as the relative completeness of Hoare logic and is due to Cook (1974); the proof is fairly complex and we will omit it (but you can find details in the Winskel book). Proofs of Program Correctness We can use the Hoare rules to prove the correctness of programs. For instance, consider a program that computes in variable y the factorial of a number initially stored in variable x: {x = n /\ n > 0} y:=1; while (x > 0) do (y:=y*x; x:=x-1) {y=n!} We can prove the correctness of this statement using Hoare logic. Since the overall proof tree is large, I will go through each piece of this tree in turn and show what rule I apply at each point. First, the program consists of a sequence of an assignment and a while loop. To be able to apply the rule for sequences, I need to find an assertion that holds after the assignment, but before the loop. Furthermore, if I look at the rule for while loops, I see that the assertion before the loop must be a loop invariant. So I must determine what the appropriate invariant is. Inspecting the execution of the loop, I see that the loop builds the factorial in y starting from n, then multiplying it with n-1, then n-2, etc. At each iteration, x is the next value that gets multiplied into y. In other words, at each iteration I have: y = n * (n-1) * ... * (x+1) I can multiply both sides of this equality by x! and re-write the equality as: x! * y = n! So this should be an invariant during the execution of the loop. Actually, I will use a slightly stronger invariant: I = ( x! * y = n! /\ x >= 0 ) So I want to prove two facts: (1) {x = n /\ n > 0} y:=1 {I} (2) {I} while (x > 0) do (y:=y*x; x:=x-1) {y=n!} If both (1) and (2) are true, I can use rule (seq) to get the desired result. To show (1) I look at the (assign) axiom and see that the following holds: {I[1/y]} y:=1 {I}. Expanding out I get: (3) {x! * 1 = n! /\ x >= 0} y:=1 { x! * y = n! /\ x >= 0} Furthermore, the following implication clearly holds: (x = n /\ n > 0) => (x! * 1 = n! /\ x >= 0) (because x = n implies x! = n!, and x = n /\ n > 0 implies x >=0). Then (1) follows from (3) using the rule of consequence. Let's turn and look at how we show (2) now. To be able to apply the (while), I need to show that I is truly a loop invariant: (4) {I /\ x > 0} y:=y*x; x:=x-1 {I} I will show this by going backwards through the sequence of assignments: The (assign) rule for x:=x-1 and y:=y*x show that: (5) {(x-1)! * y = n! /\ (x-1) >= 0} x:=x-1 {I} (6) {(x-1)! * y * x = n! /\ (x-1) >= 0} y:=y*x {(x-1)! * y = n! /\ (x-1) >= 0} Furthermore, the following implication holds: (I /\ x > 0) => {(x-1)! * y * x = n! /\ (x-1) >= 0} (because x > 0 implies (x-1) >= 0 and (x-1)! * y * x= x! * y). Then (4) follows from (5) and (6) using the rule of consequence. So I just showed that I is a loop invariant. Therefore I can apply the (while) rule and get: (7) {I} while (x > 0) do (y:=y*x; x:=x-1) {I /\ neg(x > 0)} The only piece left to complete the proof is the following implication: (I /\ neg(x > 0)) => (y = n!) which means: (x! * y = n! /\ x >= 0 /\ neg(x > 0)) => (y = n!) This is true because x >= 0 and neg(x > 0) means that x = 0, which means that x! = 0! = 1. By substituting 1 for x! in x! * y = n! we get that y = n!. Using this implication, (2) follows from (7) using the rule of consequence. And this completes the proof. So I was able to prove that program is correct -- it indeed meets its specification and computes the factorial. Note. For a more compact representation, one can write a sketch of the above proof by indicating the assertions necessary at each program point, along with the implications necessary for the rule of consequence. For instance: {x = n /\ n > 0} (x = n /\ n > 0) => (x! * 1 = n! /\ x >= 0) {x! * 1 = n! /\ x >= 0} y := 1 {x! * y = n! /\ x >= 0} while (x > 0) do {x! * y = n! /\ x >= 0 /\ x > 0} (x! * y = n! /\ x >= 0 /\ x > 0) => {(x-1)! * y * x = n! /\ (x-1) >= 0} {(x-1)! * y * x = n! /\ (x-1) >= 0} y := y * x; {(x-1)! * y = n! /\ (x-1) >= 0} x := x - 1 {x! * y = n! /\ x >= 0} {x! * y = n! /\ x >= 0 /\ neg(x > 0)} (x! * y = n! /\ x >= 0 /\ neg(x > 0)) => (y = n!) {y = n!}