A Three-Valued Hoare Calculus

Viviana Bono

Dipartimento di Informatica
Università di Torino
Corso Svizzera 185
I-10149 Torino, Italy
http://www.di.unito.it/~bono

Manfred Kerber

School of Computer Science
The University of Birmingham
Birmingham B15 2TT
England
http://www.cs.bham.ac.uk/~mmk

If we consider programs on concrete computers then physical limitations have to be taken into account. The implemented integers, for instance, can form only a subset of all (mathematical) integers. This can be done (and is done in many programming languages) in the form that two integers MININT and MAXINT are defined and any integer computation that leads outside this range of integers leads to an error state. We will introduce a Hoare calculus that can deal with this phenomenon in an adequate way.

As soon as we consider concrete computers, functions are typically partial. Even addition is no longer total, since in a finite subset of INT, the sum of two big enough numbers will exceed any given bound (assumed addition in the subset is a restriction of standard addition). Let us assume that we have in our programming language integers which are defined as INTf =INT intersection [MININT,MAXINT], where INT is the mathematical (infinite) set of all integers. Let us define MAXINT = 32768 and MININT = -32767, for instance. With this setting + is partial, since MAXINT + 1 is not defined and leads to a crash.

If we want to reason about crash as well, we have to consider how a Hoare triple (|precondition|) P (|postcondition|) should read. We keep the original reading: If the precondition holds and the program terminates then the postcondition holds. We could also have given a Hoare triple a weaker meaning, namely: " If the precondition holds, the program does not crash and terminates, then the postcondition holds." We don't follow this possible reading, since we want to be able to prove that the program does not crash and don't want to assume it.

Our programming language is a minimal language that has two types, the usual constructs of a Turing complete language, and the possibility of crash.

An example program P is:

WHILE i < n DO
  i   := i + 1;
  sum := sum + i
OD
which assumes i, n, and sum to be defined and to contain values of type INT. With sum considered to be 0 initially, the program adds up the first n natural numbers and the result will be sum, if sum is smaller than or equal to MAXINT; otherwise the program will crash.

Logic

In order to speak about crash, we need to know when an expression is defined and when not. For instance, if we assume integer division with rest, x/ y is defined for any x and any y =/=  0. That is, the expression is defined for {(x,y) | x in INT &  y in INT &  y =/=  0}. We write Dt(x/ y) <->  x in INT &  y in INT &  y =/=  0. Dt is a predicate symbol which expects a term (indicated by the index), and gives back a truth value true or false (that is, we assume that Dt itself never crashes). Likewise we will have a construct which tells us whether a formula is undefined, e.g., whether x/ y== 0 is defined or not. We write in this case Df(x/ y== 0). Df is a connective which is true iff the formula is true or false, and false otherwise. Together with the traditional connectives ~,  & ,  | , and  -> , this constitutes the propositional logic fragment of the three-valued language. Terms are variables, constant symbols, or applications of function symbols to terms. They are interpreted in a standard non-empty domain D, or as an error element error. We assume all functions to be strict, that is, if a subterm of a term is mapped to the error element, then the whole term will be mapped to the error element.

We define the semantics of the connectives as follows:

~  
false   true
crash   crash
true   false
    
Df  
false   true
crash   false
true   true

 &    false   crash   true
false   false   false   false
crash   crash   crash   crash
true   false   crash   true

Note that the connective  &  is not symmetric (but associative). This is made in order to mirror the behaviour of the lazy boolean function and in the programming language (and the ones that can be defined like or). If the A in A and B is false the whole expression is false indiscriminately of the B. Likewise, if the evaluation of A crashes, the whole expression crashes. Only if the A evaluates to true, the second, the B, is evaluated and its value will be the value of the conjunction.

Quantification will always be guarded by a type T (or sort), FORALL xT A and EXISTS xT A. We assume a simple type system of (non-empty) disjoint types (we consider only T=INT).

We define Iphi( Q xS A) := * Q({Iphi,[a/x](A) | a in AS}) where Q in {FORALL,EXISTS} and furthermore ] From the semantics we can directly construct a tableau calculus analogously to the work in [KeKo96-KGS].

The Hoare calculus that can deal with crash makes use of the assignment schema:

(|Dt(t) &  Atx|) x:= t (|Dt(x) &  A|) with
Atx=A[x->t] replace all free x in A by t; and an axiom schema which says that we can't recover from crash: (|CRASH|) P (|CRASH|)

The rules consider the possibilities how a crash can be avoided or obtained, for instance, for an implication this means:

A ->  Df(C)          (|A &  C|) P (|B|)          (|A &  ~ C|) Q (|B|)
(|A|) IF C THEN P ELSE Q FI (|B|)
A ->  ~Df(C)
(|A|) IF C THEN P ELSE Q FI (|CRASH|)

The second rule reads: if the precondition A of a rule implies that the condition C will crash, then the whole if-then-else expression will crash. The first rule is almost the traditional rule with the extension, however, that from the precondition A follows that the condition C will not crash. Note that for the first rule the postcondition B can also be CRASH.

Likewise we can build rules which characterise the behaviour of loops.

We have proved the soundness of the rules relative to an operational semantics following the approach outlined in [Mitchell96]. Proving a relative completeness property is left as future work.

With the calculus it is possible to establish:
(|Dt(n) & Dt(i) & Dt(sum) &  0<= n<= 255 &  i== 0 &  sum== 0|) P (|Dt(n) & Dt(i) & Dt(sum) &  sum == ½ n(n+1)|)
and (|Dt(n) & Dt(i) & Dt(sum) &  n== 256 &  i== 0 &  sum== 0|) P (|CRASH|)

Summary

We have extended the traditional Hoare calculus to a three-valued setting to deal with crash. The meaning of the Hoare triple is that if the precondition holds before the execution of P and P terminates, then after the execution of P the postcondition will hold. If the postcondition says that the variables are in a particular state, then the program will not crash, if the postcondition says CRASH then the program will crash.

References


© 2004, ARW, Viviana Bono, Manfred Kerber
The URL of this page is http://www.cs.bham.ac.uk/~mmk/papers/04-ARW.html.
Also available as pdf ps.gz bib .