Вы находитесь на странице: 1из 11

Proving Programs to be Correct

Jayalal Sarma
August 29, 2018

Abstract
This document serves as a class notes for the first week of lectures of the course
CS2700 : Programming and Data Structures course. The document borrows heavily
from notes on program correctness written by James Aspnes. The notes are being
written for consumption of the students in class and it relates to many questions
that were asked in class. The public document will be put out separately at a later
point of time. Hence the students are also requested not to publicize the notes
beyond the students within the class.

Contents
1 Introduction 1

2 How do we prove correctness? 3

3 The Hoare Triplets 3

4 Simple Start : Assignment Statement 5

5 The Next Step : If-the-Else Construct 8

6 The Fun Step : while Construct 9

7 The Fancy Step : Correctness of Recursive Programs 11

1 Introduction
Proving programs to be correct is an important aspect of being an engineer and a sci-
entist (from the name of our branch, we happen to be aspiring to be both !). In fact,
computer science and engineering has proliferated into day-to-day life so much that the
word ”important” is an understatement. Proving that the systems (could be hardware
or software) to be correct is critical since they are out there in every aspect of life now.
A technical example that we discussed (in class) is the intel FDIV bug1 . Long back,
in the year 1994, the processor manufacturers named Intel shipped out it brand new
1
Read the link from the course page for knowing more details than what we describe here.

1
processor - The Intel Pentium Processor. Very quickly they invaded markets and it
started proliferating into various systems that were built. However, a few months later,
an error was discovered by Thomas Nicely, a professor of mathematics, on 19 October
1994 and reported to Intel five days later. Then on 30 October 1994, he wrote a fateful
email to ”a number of individuals and organizations” that set the wheels in motion. The
processor floating-point divide problem was caused by a subtle but specific circuit-design
error; the flaw was easily corrected with changes to masks in the next regular production
revision of the chip, in 1994. However, intel had to withdraw the processors, fix them,
or replace them. This caused a huge financial burden on the company, which however, it
survived successfully.
The bottomline of the story is about the importance of correctness. Although the
above example is about hardware, you can imagine a similar scenario if it happens to the
JEE seat allocation software that gave you seats. It is important that we use provably
correct programs for doing the SEAT allocations.
As you might have seen in CS1100. There are different stages for developing the
program for solving a problem. The statement of the problem has two important parts.
Input Specification: How should we interpret the input give to us as. Note that
ultimately it is a string. For example, the input variable n will hold a natural
number. This has to be expressed mathematically. Example : A predicate as
P (n) : n = a where a ∈ N. An example input is often called input instance - a
terminology which we would widely use in this course.

Output Requirements: What should we produce as a result of our program? It starts


from the input specifications and output requirements specifications. This again is
a formal mathematical statement.
How did we express the program? We have a step where sometimes think vaguely of
a method, write in English, draw a flow chart, write a pseudo-code and finally convert
it to a program. Everything except the last step is done in human speakable languages
than programming languages. Even the pseudo-code although is bound by the rules of
a general programming language structure, does not add unnecessary burden of being
syntactically correct.

Proving Correctness of Psuedo-codes - the Assumptions: Hence the correctness


of a program essentially does depend on the correctness of each of the steps in design
and development of the program. However, for being mathematically precise, we need
to have mathematical objects in hand. A pseudo-code which a precise interpretation of
what each of the expression mean, program correctness can be thought of, being done at
the pseudo-code stage. However, the actual correctness of the program indeed depends
on how the final code written in the programming language actually implements the steps
in the pseudo-code and whether it then deviates from the interpretations we had while
proving correctness. For example, when we write

x=x+1

in a pseudo-code, we assume that the variable x gets incremented by 1. Our correctness


proofs will assume this interpretation. Hence, only if this is exactly what the implemen-

2
tation also implements, our correctness arguments hold waters. In this notes, we assume
semantics of the statements like assignment, if-then-else, while and for loops etc.

2 How do we prove correctness?


Let us start with the way we go about program design itself. Once the input and output
are specified normally we go through a methodical way of thinking about how we would
have solved the problem by taking specific instances of the problem. Then we abstract
out a method, from the patterns that we see, and say more general statements of the form
: Since the input is a natural number, if I do this to the input the following general prop-
erty seems to be true and that general property seems to be implying the output property.
This thought process, although vague, is an important step in automating the solution
process and hence program design and development. The program is, hence, essentially
an abstract and more general expression of the method that we used for solving specific
instances. Naturally correctness proofs should formalize this abstraction process and rea-
son out why each step of the method described achieves the above thought. Concertizing
the above thought, here is how we could go about proving correctness.
When viewed from a high enough level of abstraction, most correctness proofs look
roughly like this: define a predicate P that is true for “correct” parts of the algorithm.
Then prove:

1. P holds in the initial state because of the input specification. That is input speci-
fication interpreted as a predicate implies P .

2. P holds after step k if it holds before step k

3. If P holds when the algorithm terminates, then the output of the algorithm is correct
(that is, implies the output requirement which is a mathematical statement).

The statement P does not change from being true during the run of the algorithm.
Hence it is usually called an invariant of the algorithm. However, there is a serious
problem about how one handles conditional statements and loops etc. Moreover, this
might look a bit too strict than what we actually require. We do not require that the
predicate which implies the output requirement can start holding to be true after some
parts of the algorithm is already run. However, in the above we insist that it should true
through out the program.

3 The Hoare Triplets


A very creative idea due to Tony Hoare is to break down the task into simpler tasks.
That is, we could think of establishing a sequence of predicates P1 , P2 , P3 . . ., Pk , for a
program which has k statements such that if Pi holds before the statemnt i, then it Pi+1
holds after the ith statement in the program. Indeed, this thought need to be formalized
and needs to be usable even when more complicated programming constructs appear in
the program.

3
Definition 3.1 (Hoare Triplets) We attach to each statement of a program a precon-
dition (something that we demand is true before the statement executes) and a postcondi-
tion (something that will then be true after the statement executes). Such a precondition,
statement, and postcondition - together form a Hoare triple. That is,

{P }S{Q}

denotes such a Hoare triplet.

As for notation - the preconditions and postconditions are typically written inside curly
braces to distinguish them from program code.
Ok, so we have defined a Hoare triplet. Now how do we use it? The first idea is to use
the semantics of program execution. That is, consider a program segment P that contains
two statements S1 sqequentially followed by S2 . If a precondition P is true before the
execution of a statement S1 and it results in a postcondition Q. And if a pre-condition Q
is true before the exection of a statement S2 then the post-condition R is true after the
execution of the statement. Combining this, we can conclude that if a pre-condition P is
true before the execution of a program segment which has S1 sequentially followed by S2 ,
then the post-condition R is true. The following axiom captures this intuitive thought.
Definition 3.2 (Composition Axiom)

{P }S1 {Q} ∧ {Q}S2 {R}


{P }S1 ; S2 {R}

So if we knew Hoare triplet we know how to work through sequencing them through
repeated applications of composition axiom. Let us try on an example. Consider the
sequence of statements :
S1 : x = 2 ∗ x
S2 : x = x + 1
Let us arbitrarily try pre-conditions and post-conditions for the two statements inde-
pendently. Here is a result.

{x is a multiple of 4} x = 2 ∗ x {x is a multiple of 8}

{x is an even number} x = x + 1 {x is an odd number}

Both of them form Hoare triplets independently. However, we cannot use the composition
axiom because the post-condition of the first statement does not match with the pre-
condition of the second statement. This means that we should choose the individual
pre-conditions and post-conditions more carefully with a compatibility conditions.
Here is a more powerful idea to solve the problem. Observe that in the above example,
the postcondition of S1 in the triplet we considered was x is a multiple of 8, which actually
implies the pre-condition that we have for the statement S2 . This leads to the following
conclusion : suppose {P }S{Q} represents a Hoare triplet. Instead of Q, we just want
a post-condition which is weaker. That is, say Q → R. Hence, it is immediate that
{P }S{R} is a Hoare triplet as well. We capture this with the following two axioms.

4
Definition 3.3 (Pre-strengthening Axiom)

{Q}S{R} ∧ (P → Q)
{Q}S{R}

Definition 3.4 (Post-weakening Axiom)

{P }S{Q} ∧ (Q → R)
{P }S{R}

By applying these axioms, we can formalize the ”matching” of the pre-condition of


statement x = x + 1 we wrote down in the above example with the post-condition of the
statement x = 2 ∗ x.. Thus, by applying the Pre-strengthening axiom, we have that

{x is a multiple of 4} x = 2 ∗ x ; x = x + 1 {x is an odd number}

is a Hoare triplet.

Revised Methodology of Program Correctness Proofs: Given this framework let


us revise our methodology of proving correctness of programs. Given a program segment
S, input specifications and output requirements, to establish the correctness we do the
following:
Design two predicates P and Q and prove :

• Input specifications imply that P is true.

• Q implies the output requirements specification.

• {P }S{Q} is a Hoare triplet. This, in turn is to be done by designing P1 , P2 , . . . Pk


where k is the number of statements in the program such that Pi and Pi+1 form
Hoare triplet with the ith statement in the program.

Notice that designing P and Q such that the three proofs can be done is a challege.
Just for clarity, can we take P and Q to be a trivial tautology (a statement which is
always true)? If we do, then the first and the third are trivial to prove. Howevet, note
that the challenge then will lie in proving the second statement - in fact it is not even
true if the output specification is non-trivial. Thus, P should be weak enough for input
specification to imply it, but it should be strong enough to imply Q after the execution
of the program. Similarly, Q should be strong enough to imply the output requirement
specification, but should be weak enough to be implied by P after the execution of the
program. These thoughts demonstrate the challeges in designing the P and Q for a
program.

4 Simple Start : Assignment Statement


The above framework seems to be well set. But still the question remains, given a
program how do I start writing down P and Q? Here is a thought process: Write
down the mathematical description of the output requirement specification as your Q

5
(so that Q implies output requirement specification automatically). Now we will work
backwards statement by statement. Writing down the pre-conditions in such a way that
each statement along with the corresponding predicates written form a Hoare triplet.
Next step is to answer the question: suppose we have written the statement Q for a
program segment, and the last statement in the program was an assignment statement
x = t, how do we write a pre-condition such that it forms a Hoare triplet along with Q
as the post-condition for this assignment statement. If we want Q to be true after the
assignment statement, and Q has x in it, then we should replace the x with t and insist
that this new statement(call it Q0 ) is true before the execution of the statement x = t.
Then, Q will be true after the execution of the program. That is {Q0 } x = t {Q} forms
a Hoare triplet. Capturing this as an axiom:
Definition 4.1 (Assignment Axiom) If P is a predicate:
{P [x|t]} x = t {P }
always forms a Hoare triplet, where P [x|t] denotes the predicate obtained by replacing the
occurance of x in the predicate P by t.
Let us see a simple example :

Example: Let the assignment statement be x = x + 1 and suppose we want the post-
condition to say
{x is an even number}
Thus by the assignement axiom:
{(x + 1) is an even number} x = x + 1 {x is an even number}
forms a Hoare triplet, by replacing x on the RHS predicate by t (which in this case, is
x + 1). By using the equivalence x + 1 is an even number if and only if x is an odd
number, by pre-strenthening axiom, we conclude that:
{x is an odd number} x = x + 1 {x is an even number}
forms a Hoare triplet.
Now, let us try out a different post-condition that we aim for. Say we want
P = {x is a power of 2}
for the same statement S : x = x + 1. That is,
P [x|t] = {x+1 is a power of 2}
and hence,
{x = 2k − 1 for k ∈ N} x = x + 1 {x is a power of 2}
forms a Hoare triplet by assignment axiom.
This example demonstrates that it is possible to write multiple pre-condition and
post-condition tuples that forms Hoare triplet for a given statement (and more generally
for a given program segment).
Now we know how to show one triplet is a Hoare triplet. We can use the composition
axiom and the pre-strengthening, post-weakening etc to show Hoare triplets involving a
sequence of assignment statements. Let us do one such example:

6
Example : Swap Program Consider the following lines of pseud-code program seg-
ment which is supposedly swapping the values in the variablex x and y using a temporary
variable named temp.

temp = x
x = y
y = temp

Let us denote the program segment by S which consists of three assignment statements
S1 , S2 , and S3 . In the beginning of the program segment say x = a and y = b are true,
where a and b are integers (say). In the end of the program we expect x = b and y = a.
In other words, we expect to prove that:

{(x = a) ∧ (y = b)} S {(x = b) ∧ (y = a)}

is a Hoare triplet. To prove this, let us work backwards from the final post-condition
that we want - namely, {x = b ∧ y = a}. By applying assignment axiom, we have that:

{(x = b) ∧ (temp = a)} y = temp {(x = b) ∧ (y = a)} (1)

is a Hoare triplet. Further, if we use the pre-condition of this triplet as the required
post-condition of the statement x = y, we get, again by assignment axiom that:

{(y = b) ∧ (temp = a)} x = y {(x = b) ∧ (temp = a)} (2)

is a Hoare triplet. And if we use the pre-condition of this triplet as the required post-
condition of the statement temp = x, we get, again by assignment axiom that:

{(y = b) ∧ (x = a)} temp = x {(y = b) ∧ (temp = a)} (3)

is a Hoare triplet.
Now, from facts (1), (2) and (3), by using composition axiom, we can infer that :

{(x = a) ∧ (y = b)} temp = x; x = y; y = temp {(x = b) ∧ (y = a)}

is a Hoare triplet and it proves that the program segment correctly computes the swap
function.

Exercise 4.1 Prove that the following program segment also achieves the swap function
when the x and y hold integer values.

x = x + y;
y = x - y;
x = x - y;

Write down the complete proof using Hoare logic. Check if you are using the fact that the
values are integers.

7
5 The Next Step : If-the-Else Construct
Suppose our program contains an if-then-else construct. The general syntax is : the
program segment S can be thought of as :

if (B)
then
S1
else
S2
endif

where B is a Boolean condition (which can be thought of as a predicate having the program
variables and it returns True or False for a given substitution) and S1 and S2 are program
segments.
To show correctness, we go by the semantics of the if-then-else construct. To show
{P }S{Q} is a Hoare triplet, we need to establish that if the condition {P } is true before
the execution of the program segment S, then after the execution of the program segment
predicate Q will be true. S. Now the execution of the program depends on B. There
are two possibilities, either (P ∧ B) is true or (P ∧ ¬B) is true. In the former case, the
program segment would have executed S1 and hence to prove that {P }S{Q} is a Hoare
triplet, in this case, it suffices to prove that {P ∧ B}S{Q} is a Hoare triplet. In the latter
case, the program segment would have executed S2 and hence it suffices to prove that
{P ∧ ¬B}S2{Q} is a Hoare triplet.
Summing up, to establish that {P }S{Q} is a Hoare triplet, it is sufficient to prove
that {P ∧ B} S1 {Q} and {P ∧ ¬B} S2 {Q} Hoare triplets. Writing this idea formally
as an axiom:

Definition 5.1 (if-then-else axiom)

{P ∧ B} S1 {Q} ∧ {P ∧ ¬B} S2 {Q})


{P } if (B) then S1 else S2 {R}

Example: We take the following example where the program is supposed to be com-
puting the absolute value of a given integer x.

if (x > 0) then x = -x; else x = x; endif

Denote S to be the above program segment, S1 to be the statement x = −x, S2 to


be the statement x = x, and B to be the condition ”x > 0”. Let a be an arbitrary
integer. The pre-condition that we would like to write for the program S is Pa : {x = a}
and the post-condition Qa : {x = |a|}. And we would like to prove that ∀a ∈ Z,
{Pa } S {Qa } is a Hoare triplet. To apply this universal generalization, we need to avoid
using any particular property about a other than the fact that a ∈ Z while proving that
{Pa } S {Qa }. In the following discussion, we will drop the subscripts in Pa and Qa .
By the if-then-else axiom, it suffices to argue that:

8
• {(x = a) ∧ (x > 0)} x = −x{x = |a|} forms a Hoare triplet. To prove this, by
assignment axiom we know that.

{−x = |a|} x = −x {x = |a|}

is a Hoare triplet (To apply the axiom, recall the notation, consider t as −x, and
P in the assignment axiom is {x = |a|}. And, P [x|t] will be {−x = |a|}.)
Now note that ((x = a) ∧ (x > 0)) → (−x = |a|) by the definition of absolute value.
Hence, by pre-strengthening axiom:

{(x = a) ∧ (x > 0)} x = −x {x = |a|}

is Hoare triplet.

• {(x = a) ∧ (x > 0)} x = x {x = |a|}. Since the pre-condition implies the post-
condition, and the statement does not change the variable, it forms a Hoare triplet.
Hence by if-then-else axiom,{Pa } S {Qa } forms a Hoare triplet. Hence by univer-
sal generalization, ∀a ∈ Z, {Pa } S {Qa }, concluding that the program segment indeed
computes the absolute value if a is an integer.

6 The Fun Step : while Construct


We now handle the while construct. Again, we start with the syntax. The program
segment S looks like :

while (B)
S1
endwhile

Now this requires fresh thinking. Suppose we would like to establish that if a pre-
condition P holds before the start of the loop, then a post-condition Q holds after the
exit from the loop. A natural thought is to insist that the predicate Q holds true through
out the run of the loop. However, this is too strong and counter intuitive. We might
have designed the loop such that at the time when the loop exists the predicate Q is true.
Hence the idea to insist that Q holds in every iteration of the loop is too strong to be
even useful.
Here is a solution to the puzzle. The idea is to identify a predicate R such that:
• R is true in the beginning of the first iteration of the loop. (We could show this by
simply proving that P → R).

• If the loops body is executed (that is S1 is executed, which we called an iteration)


then R would remain to be true at the end of the iteration. And when is the body
of the loop executed? When B is true. Thus this is equivalent to establishing that

{R ∧ B} S1 {R}

is a Hoare triplet.

9
• When the loop exits, Q must be true. We could establish this by proving mathe-
matically that (R ∧ ¬B) → Q.

Together, the above establishes that {P } S {Q} is a Hoare triplet. Note that the design
of the predicate R is the challenge in the above idea. R should be weak enough that it is
implied by P , and it should be strong enough to imply (along with ¬B) the truth of Q.
Since the predicate R remains true during the execution of the loop, we call it the loop
invariant for this loop.

Example: We consider the following example - which initializes all the elements of an
array A to 0. Assume that the array has n elements and are indexed from 0 to n − 1.

i=n;
while (i <> 0)
do
i = i-1;
A[i] = 0;
endwhile

Let us write down the P and Q. Let n be a natural number,

P : {A is an integer array with n elements}

Q : {∀i : 0 ≤ i ≤ n − 1, A[i] = 0}
The challenge, as described above is to find R for which we can prove the above three
properties. The design of R comes from the idea with which the program segment was
written (Yes, the onus of proving a program to be correct is with the designer !). The task
of correctness proof is indistinguishable from the program design stage itself. However,
we are only formalizing the idea that was generated at the time of the design.
In the above setting, the idea that is used is the following. At each iteration of the loop
we ensure the increasingly longer suffix part of the array are intialized to 0. Formalizing
this thought, here is a loop invariant.

R : ∀j such that i ≤ j ≤ n − 1, A[j] = 0


We will now quickly prove the three required properties of R.

P → R. Note that there is an assignment statement i = n before the while loop. Notice
that the following is a Hoare triplet by assignment axiom:

{∀j such that n ≤ j ≤ n−1, A[j] = 0} i = n {∀j such that i ≤ j ≤ n−1, A[j] = 0}

But the LHS of the triplet is a tautology, which is implied by P . The RHS is R.

{R ∧ B}S1{R} is a Hoare triplet: To prove this, note that there are two statements in
the body of the loop. We want R to be true in the end. By assignment axiom,

{R0 } A[i] = 0 {R}

10
is a Hoare triplet, where R0 is obtained by replacing occurrence of A[i] in R with 0.
To clarify this, let us write R differently.

R : A[i] = 0 ∧ A[i + 1] = 0 ∧ A[i + 2] = 0 ∧ A[n − 1] = 0

and by replacing A[i] with 0,

R0 : 0 = 0 ∧ A[i + 1] = 0 ∧ A[i + 2] = 0 ∧ A[n − 1] = 0

Thus,
R0 : ∀j such that i + 1 ≤ j ≤ n − 1, A[j] = 0
Now applying assignment axiom for the statement i = 0, we have that,

{R00 } i = i − 1 {R0 }

is also a Hoare triplet, where R00 is the predicated R0 with variable i replaced by
i − 1. That is,

R00 : i 6= 0 ∧ ∀j such that i ≤ j ≤ n − 1, A[j] = 0

which is same as R.
Thus, {R ∧ B} S {R} is a Hoare triplet by using composition axiom to the two
Hoare triplets
{R00 } i = i − 1 {R0 }
and
{R0 } A[i] = 0 {R}

(R ∧ ¬B) → Q : This just follows from the definition of R, B and Q.

Thus we have completed the three ingredients of proving that the loop is correct.

7 The Fancy Step : Correctness of Recursive Pro-


grams

11

Вам также может понравиться