Lecture notes 20210301 Hoare Logic 2
Remark. Some material in this lecture is from << Software Foundation >>
volume 2.
Like our last lecture, we need to import Imp first.
Require Import PL.Imp.
Import Assertion_S.
Import Concrete_Pretty_Printing.
Import Assertion_S.
Import Concrete_Pretty_Printing.
Examples about hoare_if
Axiom hoare_if : ∀P Q b c1 c2,
{{ P AND [[b]] }} c1 {{ Q }} ->
{{ P AND NOT [[b]] }} c2 {{ Q }} ->
{{ P }} If b Then c1 Else c2 EndIf {{ Q }} .
{{ P AND [[b]] }} c1 {{ Q }} ->
{{ P AND NOT [[b]] }} c2 {{ Q }} ->
{{ P }} If b Then c1 Else c2 EndIf {{ Q }} .
Computing difference
{{ True }}
If A ≤ B
Then C ::= B - A
Else C ::= A - B
EndIf
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
We can prove it by hoare_if and the following two facts:
If A ≤ B
Then C ::= B - A
Else C ::= A - B
EndIf
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
1. {{ True AND [[A ≤ B]] }}
C ::= B - A
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
2. {{ True AND NOT [[A ≤ B]] }}
C ::= A - B
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
C ::= B - A
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
2. {{ True AND NOT [[A ≤ B]] }}
C ::= A - B
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
Counter example revisited
{{ True }}
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ [[X]] ≤ [[Y]] }}
we can use hoare_if. Then, we need to prove:
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ [[X]] ≤ [[Y]] }}
{{ True AND [[X == 0]] }}
Y ::= 2
{{ [[X]] ≤ [[Y]] }}
and
Y ::= 2
{{ [[X]] ≤ [[Y]] }}
{{ True AND NOT [[X == 0]] }}
Y ::= X + 1
{{ [[X]] ≤ [[Y]] }} ,
which are both valid
Y ::= X + 1
{{ [[X]] ≤ [[Y]] }} ,
{{ True }}
Y ::= 2
{{ [[X]] ≤ [[Y]] }}
and
Y ::= 2
{{ [[X]] ≤ [[Y]] }}
{{ True }}
Y ::= X + 1
{{ [[X]] ≤ [[Y]] }} .
They correspond to two assumptions of hoare_if_first_try. Now, it is obvious
that the first triple here is not true.
Y ::= X + 1
{{ [[X]] ≤ [[Y]] }} .
Axiomatic semantics for loops
Axiom hoare_while : ∀P b c,
{{ P AND [[b]] }} c {{P}} ->
{{P}} While b Do c EndWhile {{ P AND NOT [[b]] }} .
{{ P AND [[b]] }} c {{P}} ->
{{P}} While b Do c EndWhile {{ P AND NOT [[b]] }} .
Here, the assertion P is called a loop invariant (循环不变量).
This program will terminate until X is zero.
For fixed positive integer m and n, the following program calculates the
integer quotient and remainder:
The most critical steps in our examples above is to find a good loop
invariant. Is there a generic method of doing that?
Let's consider a specific case in our slow division example above. Suppose
n = 3 and m = 10. Then the loop body is executed for 3 times:
Also, our loop invariant should not be too weak, or else P AND NOT [[b]]
is not strong enough to describe an ideal property. In this specific case, let's
first try setting our loop invariant as strong as possible. In other words, we
let P be
Here are some general principles.
(1) A loop invariant should not be too strong. The program states before and
after every single loop body's iteration should satisfy the invariant.
(2) A loop invariant P should not be too weak to derive meaningful
conclusion from the conjunction P AND NOT [[b]] .
The following program calculates the remainder only.
What specification shall we write? How to prove it?
We need to use existential quantifiers for help. We may write:
How to prove that the following program calculates the subtraction?
Solution: using invariant
The following program squares X by repeated addition. How to prove its
correctness?
Solution: using invariant
The following program computes the (integer) square root of X
by naive iteration. How to prove its correctness?
Solution. Using invariant
So far, we've introduced four axioms for four program constructor. They are:
Example: Reduce to Zero
{{ True }}
While !(X == 0) Do
X ::= X - 1
EndWhile
{{ [[ X ]] = 0 }}
Using True as loop invariant! [[ X ]] = 0 is equivalent to
NOT [[ !(X == 0) ]].
While !(X == 0) Do
X ::= X - 1
EndWhile
{{ [[ X ]] = 0 }}
Example: Division
{{ 0 ≤ m AND 0 < n }}
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
We write NOT [[n ≤ X]] instead of [[X]] < n in order to fit the format
of hoare_while. We can prove this triple using the following loop invariant:
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
n * [[Y]] + [[X]] = m AND 0 ≤ [[X]].
Only two things are critical:
1. {{ 0 ≤ m AND 0 < n }}
X ::= m;;
Y ::= 0
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .
2. {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[n ≤ X]] }}
X ::= X - n;;
Y ::= Y + 1
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .
X ::= m;;
Y ::= 0
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .
2. {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[n ≤ X]] }}
X ::= X - n;;
Y ::= Y + 1
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .
How to find a good loop invariant?
1. {{ [[X]] = 10 AND [[Y]] = 0 }}
X ::= X - 3;;
Y ::= Y + 1
{{ [[X]] = 7 AND [[Y]] = 1 }} ;
2. {{ [[X]] = 7 AND [[Y]] = 1 }}
X ::= X - 3;;
Y ::= Y + 1
{{ [[X]] = 4 AND [[Y]] = 2 }} ;
3. {{ [[X]] = 4 AND [[Y]] = 2 }}
X ::= X - 3;;
Y ::= Y + 1
{{ [[X]] = 1 AND [[Y]] = 3 }} .
Loop invariant should be preserved by any execution of loop body (suppose the
loop condition is true at the beginning). Thus, our loop invariant P in this
case should not be stronger than
X ::= X - 3;;
Y ::= Y + 1
{{ [[X]] = 7 AND [[Y]] = 1 }} ;
2. {{ [[X]] = 7 AND [[Y]] = 1 }}
X ::= X - 3;;
Y ::= Y + 1
{{ [[X]] = 4 AND [[Y]] = 2 }} ;
3. {{ [[X]] = 4 AND [[Y]] = 2 }}
X ::= X - 3;;
Y ::= Y + 1
{{ [[X]] = 1 AND [[Y]] = 3 }} .
([[X]] = 10 AND [[Y]] = 0) OR
([[X]] = 7 AND [[Y]] = 1) OR
([[X]] = 4 AND [[Y]] = 2) OR
([[X]] = 1 AND [[Y]] = 3),
or else
([[X]] = 7 AND [[Y]] = 1) OR
([[X]] = 4 AND [[Y]] = 2) OR
([[X]] = 1 AND [[Y]] = 3),
{{ P }} LOOP_BODY {{ P }}
LOOP SPECIFICATIONS's PRECONDITION ⊢ P
cannot be true at the same time.
LOOP SPECIFICATIONS's PRECONDITION ⊢ P
([[X]] = 10 AND [[Y]] = 0) OR
([[X]] = 7 AND [[Y]] = 1) OR
([[X]] = 4 AND [[Y]] = 2) OR
([[X]] = 1 AND [[Y]] = 3).
Thus,
([[X]] = 7 AND [[Y]] = 1) OR
([[X]] = 4 AND [[Y]] = 2) OR
([[X]] = 1 AND [[Y]] = 3).
P AND NOT [[3 ≤ X]] ⊢ [[X]] = 1 AND [[Y]] = 3.
This is already good enough. We can also restate this P in a more concise way:
n * [[Y]] + [[X]] = 10 AND 0 ≤ [[X]] AND 0 ≤ [[Y]].
BTW, a weaker loop invariant may also work (the one we used previously):
n * [[Y]] + [[X]] = 10 AND 0 ≤ [[X]].
Example: Remainder Only
X ::= m;;
While n ≤ X Do
X ::= X - n
EndWhile
While n ≤ X Do
X ::= X - n
EndWhile
{{ 0 ≤ m }}
X ::= m;;
While n ≤ X Do
X ::= X - n
EndWhile
{{ (EXISTS q, n * q + [[X]] = m) AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
and we may use the following loop invariant:
X ::= m;;
While n ≤ X Do
X ::= X - n
EndWhile
{{ (EXISTS q, n * q + [[X]] = m) AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
(EXISTS q, n * q + [[X]] = m) AND 0 ≤ [[X]]
Example: Slow subtraction
X ::= m;;
Y ::= p;;
While !(X == 0) Do
Y ::= Y - 1;;
X ::= X - 1
EndWhile
Y ::= p;;
While !(X == 0) Do
Y ::= Y - 1;;
X ::= X - 1
EndWhile
[[Y]] - [[X]] = p - m.
Example: Squaring
I ::= 0;;
RES ::= 0;;
While !(I == X) Do
RES ::= RES + X;;
I ::= I + 1
EndWhile
RES ::= 0;;
While !(I == X) Do
RES ::= RES + X;;
I ::= I + 1
EndWhile
[[RES]] = [[I]] * m AND [[X]] = m.
Example: Finding Square Roots
I ::= 0;;
While (I+1)*(I+1) ≤ X Do
I ::= I+1
EndWhile
While (I+1)*(I+1) ≤ X Do
I ::= I+1
EndWhile
[[I]] * [[I]] ≤ m AND [[X]] = m.
Program correctness proof in Coq: more examples
Module Axiomatic_semantics.
Axiom hoare_seq : ∀(P Q R: Assertion) (c1 c2: com),
{{P}} c1 {{Q}} ->
{{Q}} c2 {{R}} ->
{{P}} c1;;c2 {{R}} .
Axiom hoare_skip : ∀P,
{{P}} Skip {{P}} .
Axiom hoare_if : ∀P Q b c1 c2,
{{ P AND [[b]] }} c1 {{ Q }} ->
{{ P AND NOT [[b]] }} c2 {{ Q }} ->
{{ P }} If b Then c1 Else c2 EndIf {{ Q }} .
Axiom hoare_while : ∀P b c,
{{ P AND [[b]] }} c {{P}} ->
{{P}} While b Do c EndWhile {{ P AND NOT [[b]] }} .
End Axiomatic_semantics.
Axiom hoare_seq : ∀(P Q R: Assertion) (c1 c2: com),
{{P}} c1 {{Q}} ->
{{Q}} c2 {{R}} ->
{{P}} c1;;c2 {{R}} .
Axiom hoare_skip : ∀P,
{{P}} Skip {{P}} .
Axiom hoare_if : ∀P Q b c1 c2,
{{ P AND [[b]] }} c1 {{ Q }} ->
{{ P AND NOT [[b]] }} c2 {{ Q }} ->
{{ P }} If b Then c1 Else c2 EndIf {{ Q }} .
Axiom hoare_while : ∀P b c,
{{ P AND [[b]] }} c {{P}} ->
{{P}} While b Do c EndWhile {{ P AND NOT [[b]] }} .
End Axiomatic_semantics.
Because we use these axioms about Hoare triples to define program semantics,
such definition is called an axiomatic semantics (公理化语义). We also say that
these axioms form a proof system (推理系统), or a Hoare logic (霍尔逻辑).
Here we show how to use this logic to prove program correctness in Coq.
Example: Simple Conditionals
Module if_minus_dec.
Import Axiomatic_semantics.
Local Instance A: var := new_var().
Local Instance B: var := new_var().
Local Instance C: var := new_var().
Hypothesis triple1:
{{ True AND [[A ≤ B]] }}
C ::= B - A
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
Hypothesis triple2:
{{ True AND NOT [[A ≤ B]] }}
C ::= A - B
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
Fact if_minus_dec_correct:
{{ True }}
If A ≤ B
Then C ::= B - A
Else C ::= A - B
EndIf
{{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .
Proof.
apply hoare_if.
+ apply triple1.
+ apply triple2.
Qed.
Here, bullets (+, -, *, ++, —, **, etc.) are used to separated two proof
goals' proof scripts.
End if_minus_dec.
Module counter_example.
Import Axiomatic_semantics.
Local Instance X: var := new_var().
Local Instance Y: var := new_var().
Axiom hoare_if_first_try : ∀P Q b c1 c2,
{{P}} c1 {{Q}} ->
{{P}} c2 {{Q}} ->
{{P}} If b Then c1 Else c2 EndIf {{Q}} .
Fact sample_program_correct_fail:
{{ True }}
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ [[X]] ≤ [[Y]] }} .
Proof.
apply hoare_if_first_try.
Abort.
Hypothesis triple1:
{{ True AND [[X == 0]] }}
Y ::= 2
{{ [[X]] ≤ [[Y]] }} .
Hypothesis triple2:
{{ True AND NOT [[X == 0]] }}
Y ::= X + 1
{{ [[X]] ≤ [[Y]] }} .
Fact sample_program_correct_:
{{ True }}
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ [[X]] ≤ [[Y]] }} .
Proof.
apply hoare_if.
+ apply triple1.
+ apply triple2.
Qed.
End counter_example.
Module reduce_to_zero.
Import Axiomatic_semantics.
Local Instance X: var := new_var().
Hypothesis triple1:
{{ True AND [[!(X == 0)]] }}
X ::= X - 1
{{ True }} .
Fact reduce_to_zero_correct:
{{ True }}
While !(X == 0) Do
X ::= X - 1
EndWhile
{{ True AND NOT [[ !(X == 0) ]] }} .
Proof.
apply hoare_while.
apply triple1.
Qed.
End reduce_to_zero.
Module div_mod_dec.
Import Axiomatic_semantics.
Local Instance X: var := new_var().
Local Instance Y: var := new_var().
Hypothesis triple1: ∀m: Z,
{{ 0 ≤ m }}
X ::= m
{{ [[X]] = m AND 0 ≤ [[X]] }} .
Hypothesis triple2: ∀m n: Z,
{{ [[X]] = m AND 0 ≤ [[X]] }}
Y ::= 0
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .
Hypothesis triple3: ∀m n: Z,
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[n ≤ X]] }}
X ::= X - n
{{ n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]] }} .
Hypothesis triple4: ∀m n: Z,
{{ n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]] }}
Y ::= Y + 1
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .
Fact div_mod_dec_correct: ∀m n: Z,
{{ 0 ≤ m }}
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
Proof.
intros.
apply hoare_seq with ([[X]] = m AND 0 ≤ [[X]])%assert.
{ apply triple1. }
apply hoare_seq with (n * [[Y]] + [[X]] = m AND 0 ≤ [[X]])%assert.
{ apply triple2. }
apply hoare_while.
apply hoare_seq with (n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]])%assert.
+ apply triple3.
+ apply triple4.
Qed.
It is worth mentioning the proof style in the Coq proof script above. In
short, bullets (+, -, *, ++, —, **, etc.) and proof blocks ("{" and "}") can be
used to illustrate proof structures. Usually, they are used for two different
purposes. If two or more subgoals are equally complicated, we would prefer to
use bullets to list their proofs parallelly. If one branch among all subgoals
is the main proof target and the other proof goals are just side steps, we would
prefer to use proof blocks.
Fact div_mod_dec_correct_2: ∀m n: Z,
{{ 0 ≤ m }}
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
Proof.
intros.
{{ 0 ≤ m }}
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
Proof.
intros.
Originally, if we want to use apply to prove this Hoare triple in the
backward direction, we have to feed it an argument, indicating what the middle
condition Q in hoare_seq represents. But if we use eapply instead of
apply, we do not need to provide that argument. Mainly, eapply hoare_seq
says, we will apply this proof rule hoare_seq without knowing what Q is,
and we will know what that Q should be later.
eapply hoare_seq.
As you can see, the unknown argument is marked as ?Q which appear both in
the first proof goal and the second proof goal. Now, we use triple1 to
solve the first proof goal. It will instantiate ?Q.
{ apply triple1. }
eapply hoare_seq.
{ apply triple2 with (n := n). }
apply hoare_while.
eapply hoare_seq.
+ apply triple3.
+ apply triple4.
Qed.
Fact div_mod_dec_correct_3: ∀m n: Z,
{{ 0 ≤ m }}
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
Proof.
intros.
pose proof triple1 m.
pose proof triple2 m n.
pose proof triple3 m n.
pose proof triple4 m n.
pose proof hoare_seq _ _ _ _ _ H1 H2.
clear H1 H2.
pose proof hoare_while _ (n ≤ X) _ H3.
clear H3.
pose proof hoare_seq _ _ _ _ _ H0 H1.
pose proof hoare_seq _ _ _ _ _ H H2.
exact H3.
Qed.
End div_mod_dec.
eapply hoare_seq.
{ apply triple2 with (n := n). }
apply hoare_while.
eapply hoare_seq.
+ apply triple3.
+ apply triple4.
Qed.
Fact div_mod_dec_correct_3: ∀m n: Z,
{{ 0 ≤ m }}
X ::= m;;
Y ::= 0;;
While n ≤ X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }} .
Proof.
intros.
pose proof triple1 m.
pose proof triple2 m n.
pose proof triple3 m n.
pose proof triple4 m n.
pose proof hoare_seq _ _ _ _ _ H1 H2.
clear H1 H2.
pose proof hoare_while _ (n ≤ X) _ H3.
clear H3.
pose proof hoare_seq _ _ _ _ _ H0 H1.
pose proof hoare_seq _ _ _ _ _ H H2.
exact H3.
Qed.
End div_mod_dec.
Till now, we have learned how to prove a program correct by Hoare
logic and how to use apply in Coq to formalize those proofs.
You might have noticed two tiny problems. 1. We have proved nothing for
assignment commands. We only assume that some Hoare triples about assignment
commands are true. Coq forces us to distinguish which parts really get proved
and which parts are actually hypotheses. 2. Our postconditions look not nice.
For example, in div_mod_correct we have to write
We will introduce several more axioms in response to this concern.
We clearly see a common pattern when we try to write postconditions for
assignment commands with form X ::= E. Each of these postconditions has two
parts (1) the original value of X satisfies the precondition; (2) the current
value of X can be calculated by E using the original value of X. The
original value of X plays a very important role here! But, we cannot always
refer to the original value directly. Here is an example:
What is the best postcondition for the following command?
The best postcondition is:
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[n ≤ X]] }}
instead of
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[X]] < n }} .
Coq forces us to strictly follow axioms' statements. But we really hope that we
can at least replace an assertion with an equivalent one.
Assignment rule (forward)
{{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[X]] < n }}
Y ::= 0
{{ ??? }}
Y ::= 0
{{ ??? }}
{{ EXISTS y, n * y + [[X]] = m AND
0 ≤ [[X]] AND [[X]] < n AND [[Y]] = 0 }}
This example gives us a hint—-we do not need to refer to the old value
directly; we can talk about it using the existential quantifier. In general,
given a precondition P, a program variable X and a program expression E,
we can write the following postcondition informally:
0 ≤ [[X]] AND [[X]] < n AND [[Y]] = 0 }}
There exists an old value x of program variable X, such that (1) the precondition P would hold if the value of X would be x (2) the value of X is result of evaluating the expression E if treating all occurrences of X in E as x.
(* 2021-03-04 08:28 *)