Introduction: what is Hoare logic?

First steps

According to wikipedia:

Hoare logic (also known as Floyd–Hoare logic or Hoare rules) is a formal system with a set of logical rules for reasoning rigorously about the correctness of computer programs.

So this is a powerful system that let us reason formally about the correctness of our program. The basic structure of Hoare logic is the Hoare triple. A triple describes how some properties of the state are modified by the execution of a program. A triple has the form of

\[ \{P\}C\{Q\}\]

where \(P\) and \(Q\) are assertions and \(C\) is a program. \(P\) is called precondition and \(Q\) is called postcondition. A triple is said valid if, for each states that satisfies the precondition, the program terminates and the resulting state satisfies the postcondition.

I use a custom language for writing the program, that supports only empty statements, assignments, composition, if statemets and while statement. I'm sure you will have no problem following the syntax of this language as it is fairly standard.

Examples of valid triples are:

\[\{x=0\}x:=x+1\{x=1\}\]

This triple is valid because if before the execution it holds that \(x=0\), then after the execution of the assignment command the assertion \(x=1\) will hold as well. An important condition for the validity of a triple is the termination, but since the command is an assignment, it always terminates (except for when we have an undefined expression on the right hand side, like \(x/0\), but for simplicity we will just assume that the program we will write will never try to evaluate an undefined expression).

\[\{ x=A \land y=B\}z:=x; x:=y; y:=z\{x=B \land y=A\}\]

This is a variable swap, notice that this triple is completely parametric, as it uses two constants (\(A\) and \(B\)) that stand for arbitrary values. This is a very powerful thing you can do in Hoare logic, as this triple is valid for all values of \(x\) and \(y\).

But we can have even more complicated triples, such as

\[\{s=\sum_{k=0}^i k^2\}i:=i+1;s:=s+i*i\{s=\sum_{k=0}^i k^2\}\]

or

\[\{n \geq 1\}\\\text{while}(n

eq 1) \text{do if} (n \text{ mod } 2 = 0) \text{ then } n:=n/2\text{ else } n:= 3n+1 \text{ fi endw}\\\{n=1\}\]

you may have recognized this triple, as it is valid iff the Collatz conjecture is true. Also this is an example where proving just the termination of the command is very important and not trivial at all (in fact, proving the termination is sufficient for proving the entire triple).

What is an inference rule?

I'm sure if you are reading this, that you will be familiar with the concept of inference, but just to make sure we are all on the same page and to make clear the notation used, it's best to discuss this important concept.

An inference rule has the form

\[\frac{\phi_1 \quad \phi_2 \quad \phi_3 \quad \dots \quad \phi_n}{\phi}\]

where \(\phi_1 \dots \phi_n\) are the premises and \(\phi\) is the conclusion. This can be read in two ways:

if \(\phi_1 \dots \phi_n\) are all valid formulas, then we can infer (or derive) the formula \(\phi\) to prove \(\phi\) it is sufficient to prove that \(\phi_1 \dots \phi_n\) are all valid formulas.

For our purpose, the second interpretetion is what fits the best, so keep in mind this intuition behind inference rules.

An axiom is just an inference rule with no premises, so that if \(\psi\) is an axiom we will write

\[\frac{}{\psi}\]

Examples of inference rules in classical propositional calculus are

\[\frac{A \land B}{A}\quad (\land \text{ elimination})\]

\[\frac{A \to B \quad

eg B }{

eg A}\quad \text{(Modus Tollens)}\]

For example, the last one in our interpretation is read "to prove that \(

eg A\) is valid it is sufficient to prove that \(A \to B\) and \(

eg B\) are both valid".

A proof system for Hoare triples

Axioms

Now let's discuss about how to formally prove that a triple is valid. Let's start with some axioms: these are basic triples that are always valid.

The first axiom is the empty command axiom (or skip axiom):

\[\frac{}{\{P\}\text{skip}\{P\}}\quad (\text{SKIP-AX})\]

where \(P\) is an arbitrary assertion and \(\text{skip}\) is the empty command. The intuition behind this axiom is that if a certain condition holds before the skip statement, then it will also hold after the execution, because skip does not modify the state and always terminates.

The second axiom is the assignment axiom:

\[\frac{}{\{P[x \mapsto E]\}x:=E\{P\}}\quad (\text{ASSIGNMENT-AX})\]

where \(P[x \mapsto E]\) means the assertion \(P\) with all the occurrences of \(x\) replaced by \(E\). The intuition behind this axiom is that if an assertion must hold true after the assignment statement, then it must hold also before the statement, but with the identifier substituted by the expression.

Basic inference rules

Now, let's talk about some serious stuff: inference rules. The first one is pretty straight forward, and it is the pre-post rule:

\[\frac{P\to P' \quad \{P'\}C\{Q'\} \quad Q' \to Q}{\{P\}C\{Q\}}\quad \text{(PRE-POST)}\]

The intuition behind this rule is that we can strengthen the precondition and we can weaken the postcondition at our favor.

This rule is really important because with this and the two axioms we can derive the second and the third inference rules:

\[\frac{P \to Q}{\{P\}\text{skip}\{Q\}}\quad (\text{SKIP})\]

\[\frac{P \to Q[x \mapsto E]}{\{P\}x:=E\{Q\}}\quad (\text{ASSIGNMENT})\]

The next rule is the composition rule (or sequence rule):

\[\frac{\{P\}C_1\{R\} \quad \{R\}C_2\{Q\}}{\{P\}C_1;C_2\{Q\}}\quad (\text{COMPOSITION})\]

The intuition behind this rule is that if it can be found an intermediate assertion (in this case \(R\)) so that the two single triples are valid, then the whole triple is also valid.

Contditional and while rules

Now let's see the most difficult rules: the if and the while rules. Let's start with the conditional one:

\[\frac{\{P \land E\}C_1\{Q\} \quad \{P \land

eg E\}C_2\{Q\}}{\{P\}\text{if } E \text{ then } C_1 \text{ else } C_2\{Q\}}\quad (\text{IF})\]

The intuition behind this rule is that we have to prove the triple both in the case that the guard is satisfied and in the case that it is not satisfied, so that in the general case the triple holds. This rule remembers me the proof by cases in propositional calculus, that is:

\[\frac{P \to Q \quad

eg P \to Q}{Q}\quad \text{Proof by cases}\]

but I'm digressing.

Let's talk about the while rule. I'm ging to present this monstruosity so that we can then break it down piece by piece:

\[\frac{\begin{gathered} P \to inv \quad inv \to Q \quad inv \to t \geq 0 \\\{inv \land E\}C\{inv\} \quad \{inv \land E \land t=V\}C\{ t \lt V \} \end{gathered}}{\{P\}\text{while }E \text{ do } C \text{ endw}\{Q\}}\quad \text{(WHILE)}\]

Ok, so, take a deep breath, because this will be kind of a long one. The semantics of the while command is just a composition of the same command (that is the body of the while). So the semantics of the while can be expanded as follows:

\[\overbrace{C;C;C;C;C \dots C}^{n \text{ times}}\]

where \(n\) is the number of times the body of the while will be executed. Notice that the guard may never become false, in that case the sequence of commands is infinite. To prove that this command terminates and the sequence of commands is finite, we assign a natural number (which is identified by the expression \(t\)) to each cycle, so that we create a sequence. Now to prove that this sequence terminates we just have to prove that this sequence is strictly decreasing (we use a theorem that states that every strictly decreasing sequence of natural numbers is finite).

To do this we can use a thing called loop invariant (often called only \(inv\)), that is an assertion that is true at the beginning of every iteration. With this we can prove the termination and also we can be sure that the precondition and postcondition are satisfied.

Now, with everithig settled in, let's discuss in some detail the 5 premises of the while rule:

\(\{inv \land E\}C\{inv\}\) : this is called the invariance premise, as it makes sure that the \(inv\) assertion is true at the beginning of each iteration. \(P \to inv\) : this is called the pre premise, as is simply make sure that the precondition is stronger than the invariant, so that the first time we enter the loop, the invariant holds. \( inv \to Q\) : this is called post premis, and as the name sugest make sure that the if the invariant is true, then also the postcondition is true. This is done so that at the end of the execution we can make sure that the postcondition holds true, since we know that the invariant holds true. \(\{inv \land E \land t=V\}C\{ t \lt V \}\) : this is called progress premise, as it make sure that each term of the sequence is strictly decreasing. \(inv \to t\geq 0\) : last but not least the termination premise, that make sure that our sequence identified by the expression \(t\) has a lower bound, in this case 0, so that we can make sure it is finite.

Whoa! That was quite a jurney! But now let's stop talking about theory and let's jump into the code!

The automatic theorem prover

Premises

For this project I choose python as the main language because I was not aiming at a full-fledged and efficient prover, but more at a proof-of-concept or prototype program, in which python is really great. In particular I used z3 theorem prover for proving the implications, and lark for parsing the input triple. The whole project is on github, pull requests are welcome!

The parser

Using lark, parsing was surprisingly easy. I just defined the grammar into a big string and lark taked care of everything. The grammar can be seen in the source code for the parser. An example triple that can be accepted by the parser is:

[x, A] {x==A} x := x-3; if (x >= 0) then x := x + 5 else x := -x + 5 fi {x>A}

In the first line we declare all the identifiers that we are going to use in the triple. Then we state the precondition, the command and the postcondition. The triple procesed by the parser produces a parse tree, which is perfect because we will do a lot of induction on the structure of the program.

The prover

The basic building block for the theorem prover is a function that takes in input a z3 formula and tries to prove the validity of if. This is implemented as follows.

def prove_formula ( self , what_to_prove ) : s = z3 . Solver ( ) s . add ( z3 . Not ( what_to_prove ) ) res = s . check ( ) if str ( res ) == " unsat " : print ( " proved " , what_to_prove ) return True else : print ( " could not prove " , what_to_prove ) return False

The z3's check function can only give you 2 results: sat , which means that the formula is satisfiable, and unsat , which means the formula is unsatisfiable. So there is no result for valid, but we can use a well known fact in logic: a formula \(\phi\) is valis iff \(

eg \phi\) is unsatisfiable. So we negate the formula and if it is unsatisfiable, then the formula is considered proved.

Now let's start implementing the main protagonist of this jurney: the prove_triple() function, that takes in input a precondition as a z3 formula, a command as a parse tree and a postcondition as a z3 formula and returns True if the triple is valid, False otherwise. The first inference rule we will implement is the simplest one: the skip rule.

\[\frac{P \to Q}{\{P\}\text{skip}\{Q\}}\quad (\text{SKIP})\]

def prove_triple ( self , precond , command , postcond ) : if command . data == " skip " : formula_to_prove = z3 . Implies ( precond , postcond ) print ( " found skip statement, trying to prove " , formula_to_prove ) res = self . prove_formula ( formula_to_prove ) return res

Here we state that for proving a skip triple it is sufficient to prove that the precondition implies the postcondition.

Now let's implement the assignment inference rule.

\[\frac{P \to Q[x \mapsto E]}{\{P\}x:=E\{Q\}}\quad (\text{ASSIGNMENT})\]

elif command . data == " assignment " : ide , exp = command . children formula_to_prove = z3 . Implies ( precond , z3 . substitute ( postcond , ( self . env [ ide ] , self . expr_to_z3_formula ( exp ) ) ) ) print ( " found assignment statement, trying to prove " , formula_to_prove ) res = self . prove_formula ( formula_to_prove ) return res

Now let's go on and implement the conditional inference rule. This is when prove_triple becomes inductive, as it calls itself on a subtree of the parse tree.

\[\frac{\{P \land E\}C_1\{Q\} \quad \{P \land

eg E\}C_2\{Q\}}{\{P\}\text{if } E \text{ then } C_1 \text{ else } C_2\{Q\}}\quad (\text{IF})\]

elif command . data == " if " : guard , s1 , s2 = command . children print ( " found if statement, trying to prove the first alternative " ) res_1 = self . prove_triple ( z3 . And ( precond , self . expr_to_z3_formula ( guard ) ) , s1 , postcond ) if res_1 : print ( " now trying to prove the second alternative " ) res_2 = self . prove_triple ( z3 . And ( precond , z3 . Not ( self . expr_to_z3_formula ( guard ) ) ) , s2 , postcond ) return res_2 else : return False

There is nothing magical in the code yet: these three cases are the direct translation of the inference rule.

Now let's talk about composition. From the inference rule we know we have to search for an intermediate assertion, so that both intermediate triples are valid.

\[\frac{\{P\}C_1\{R\} \quad \{R\}C_2\{Q\}}{\{P\}C_1;C_2\{Q\}}\quad (\text{COMPOSITION})\]

There is no rule on how to find it, but we can use some heuristics to find it. Basically we try to find an assertion \(R\) so that \(\{R\}C_2\{Q\}\) is an axiom (so is valid in our proof system) and then we delegate the validity of the whole triple to the validity of \(\{P\}C_1\{R\}\). To do this we need a new function called find_axiom that takes in input a command as a parse tree and a postondition as a z3 formula and tries to find a precondition so that the triple is an axiom. Remember that there are only two axioms in our system, so we can implement only them.

\[\frac{}{\{P\}\text{skip}\{P\}}\quad (\text{SKIP-AX})\]

\[\frac{}{\{P[x \mapsto E]\}x:=E\{P\}}\quad (\text{ASSIGNMENT-AX})\]

def find_axiom ( self , command , postcond ) : if command . data == " skip " : print ( " found axiom for skip statement: " , postcond ) return ( True , postcond ) elif command . data == " assignment " : ide , exp = command . children axiom = z3 . substitute ( postcond , ( self . env [ ide ] , self . expr_to_z3_formula ( exp ) ) ) print ( " found axiom for assignment statement: " , axiom ) return ( True , axiom ) else : print ( " could not find axiom " ) return ( False , None )

Now we are all set for implementing the composition rule.

\[\frac{\{P\}C_1\{R\} \quad \{R\}C_2\{Q\}}{\{P\}C_1;C_2\{Q\}}\quad (\text{COMPOSITION})\]

elif command . data == " composition " : c1 , c2 = command . children print ( " found composition, trying to find axiom for the right side " ) ( res , axiom ) = self . find_axiom ( c2 , postcond ) if res : return self . prove_triple ( precond , c1 , axiom ) else : return False

With theese simple inference rules implemented, we can try our prover on a variable swap. So the input triple looks like this:

[x, y, z, A, B] {x==A and y==B} z := x; x := y; y := z {x==B and y==A}

The output of our prover is:

What to prove: [x, y, z, A, B] {x==A and y==B} z := x; x := y; y := z {x==B and y==A} found composition, trying to find axiom for the right side found axiom for assignment statement: And(x == B, z == A) found composition, trying to find axiom for the right side found axiom for assignment statement: And(y == B, z == A) found assignment statement, trying to prove Implies(And(x == A, y == B), And(y == B, x == A)) proved Implies(And(x == A, y == B), And(y == B, x == A)) The triple is valid

Yay! So the verdict is that this triple is valid! That means that so far our little prover works! The great thing is that you can see the process of the program by looking at the output, and we can see that the steps made by our little program are exactly what we expected.

Now we just have to implement the scary one: the while inference rule. Luckly we got the hang of it, and now the code is pretty much a direct application of the inference rule.

\[\frac{\begin{gathered} P \to inv \quad inv \to Q \quad inv \to t \geq 0 \\\{inv \land E\}C\{inv\} \quad \{inv \land E \land t=V\}C\{ t \lt V \} \end{gathered}}{\{P\}\text{while }E \text{ do } C \text{ endw}\{Q\}}\quad \text{(WHILE)}\]

elif command . data == " while " : e , t_expr , inv , c = command . children print ( " found while statement, trying to prove: " ) invariant = self . expr_to_z3_formula ( inv ) t = self . expr_to_z3_formula ( t_expr ) guard = self . expr_to_z3_formula ( e ) pre = z3 . Implies ( precond , invariant ) post = z3 . Implies ( z3 . And ( precond , z3 . Not ( guard ) ) , postcond ) term = z3 . Implies ( z3 . And ( invariant , self . env [ " t " ] == t ) , t > = 0 ) print ( " 1) [pre] " , pre ) res_pre = self . prove_formula ( pre ) if res_pre : print ( " 2) [post] " , post ) res_post = self . prove_formula ( post ) if res_post : print ( " 3) [term] " , term ) res_term = self . prove_formula ( term ) if res_term : print ( " 4) [invariance] " , term ) res_inv = self . prove_triple ( z3 . And ( invariant , guard ) , c , invariant ) if res_inv : print ( " 5) [progress] " , term ) v = z3 . Int ( " V " ) res = self . prove_triple ( z3 . And ( z3 . And ( invariant , guard ) , t == v ) , c , t < v ) return res return False

So now we can finally prove some simple while triples! I have added some syntax to the while command to code the loop invariant and the expression for \(t\). Here's an example triple that calculates \(5 \cdot 10\) in a very stupid way:

[i, s, t, r] {i==0 and r==0} while (i<10) where t is (10-i); inv is (r==5*i and 10-i>=0) do i := i+1; r := r+5 endw {r==5*10}

This time the output is a bit more cluttered:

What to prove: [i, s, t, r] {i==0 and r==0} while (i<10) where t is (10-i); inv is (r==5*i and 10-i>=0) do i := i+1; r := r+5 endw {r==5*10} found while statement, trying to prove: 1) [pre] Implies(And(0 == i, 0 == r), And(r == 5*i, 0 <= 10 - i)) proved Implies(And(0 == i, 0 == r), And(r == 5*i, 0 <= 10 - i)) 2) [post] Implies(And(And(0 == i, 0 == r), Not(10 > i)), r == 5*10) proved Implies(And(And(0 == i, 0 == r), Not(10 > i)), r == 5*10) 3) [term] Implies(And(And(r == 5*i, 0 <= 10 - i), t == 10 - i), 10 - i >= 0) proved Implies(And(And(r == 5*i, 0 <= 10 - i), t == 10 - i), 10 - i >= 0) 4) [invariance] Implies(And(And(r == 5*i, 0 <= 10 - i), t == 10 - i), 10 - i >= 0) found composition, trying to find axiom for the right side found axiom for assignment statement: And(r + 5 == 5*i, 0 <= 10 - i) found assignment statement, trying to prove Implies(And(And(r == 5*i, 0 <= 10 - i), 10 > i), And(r + 5 == 5*(i + 1), 0 <= 10 - (i + 1))) proved Implies(And(And(r == 5*i, 0 <= 10 - i), 10 > i), And(r + 5 == 5*(i + 1), 0 <= 10 - (i + 1))) 5) [progress] Implies(And(And(r == 5*i, 0 <= 10 - i), t == 10 - i), 10 - i >= 0) found composition, trying to find axiom for the right side found axiom for assignment statement: 10 - i < V found assignment statement, trying to prove Implies(And(And(And(r == 5*i, 0 <= 10 - i), 10 > i), 10 - i == V), 10 - (i + 1) < V) proved Implies(And(And(And(r == 5*i, 0 <= 10 - i), 10 > i), 10 - i == V), 10 - (i + 1) < V) The triple is valid

Great! We can see exactly that all 5 while premises have been proved, so it is fair to say that this loop has been proven formally that it terminates and r will contain the result of \(5 \cdot 10\).

To experiment with this simple system (and maybe contribute to it) you can clone the repository, and you can run an example proof (for example the variable swap one) by:

$ python src/lpp_prover.py examples/var_swap.lpp

In the examples folder on github there are a bunch of triples that should give you the feeling of the capability of this system.

Conclusions

This was a very interesting project for me, because it game me the possibility to apply all this theoretical stuff I learned at university. This is not, and does not aim to be a professional formal prover, it is just a little program written by a bored cs student.