This chapter provides the basic techniques for specifying a Java source code and prove it correct with Krakatoa. We also recommend to look at the collection of verified programs at URL http://proval.lri.fr/gallery/krakatoa.en.html to get more hints on how to specify and prove programs with Krakatoa.
Before going to the first lesson, we recommend to start by creating a new
directory tutorial
, and code each example in that directory.
We start by a very simple static method, that computes the maximum of
two integers. In the tutorial
sub-directory, write the
following Java program
into a new file Lesson1.java
.
The comment starting with /*@
just before the method
max is a contract specifying a intended behavior for
max.
The ensures clause introduce a
postcondition, which is a formula supposed to hold at end of
execution of that method, for any value of its arguments. In that
formula,
result denotes the returned value for that method,
hence that formula means three things: (i) the result is greater than or
equal to x, (ii) the result is also greater than or
equal to y, and (iii) the result is the least of all integers both
greater than x and y.
Our aim is to verify that the body of method max is a correct implementation, in the sense that it satisfies the contract given. Indeed, we intentionally made a mistake: the second return should return y instead of x.
Running the verification process can be done by executing the
following command, in the directory tutorial
:
krakatoa Lesson1.java
The command will read the given file, and generate a set of logic formulas call verification conditions (abbreviated as VC), which express the validity of the program. The generated formulas are then displayed into the graphical interface of the Why3 platform, as shown in Figure 2.1
![]()
Figure 2.1: Verification conditions displayed in Why3 IDE
Please refer to Why3’s user manual for details on the use of this interface. We only remind here the main features.
The column on the left is a tool bar. The large window in the middle is the tree view of all generated VCs. There are 4 VCs generated from this program, given as sub-rows of the row called “WP Jessie program”. Generally speaking, there is one VCs for each behavior of each method or constructor. Safety is one of these behaviors, which includes checking absence of null dereferencing, absence of out-of-bounds array acces, absence of division by zero, and other kind of possible runtime errors like that. In this tree view, the first VC has been selected was selected, by clicking on the corresponding row. The corresponding specification frim the source code is shown on the bottom right part: it is here the post-condition we gave to method max. The top right part of the window displays the logical formula associated to the VC.
The left toolbar contains a series of buttons for each prover that was detected when you run “why3config –detect”. A given prover can be run on a given goal, or a set of goals by selecting the wanted part of the tree. In our example, we can select the row “WP Jessie program” and click e.g on Alt-Ergo. Clicking further on other provers like Z3 or CVC3, will run these on the VCs that are not yet proved (see Why3 manual for details). The result is displayed on Figure 2.2.
![]()
Figure 2.2: Provers run on verification conditions
The VC for the post-condition of max is not proved. To investigate further, and since it is a conjunction of several propositions, it is a good idea to split it in parts. This is done by selecting the VC and clicking on the “split” button. This results into two subgoals for that VC. Again, we can click on provers Alt-Ergo, Z3 and CVC3 to check these subgoals. The second remains unproved. It can be splitted in parts again, which this time results into 3 subgoals, and provers can be run again on them. The result is displayed on Figure 2.3.
![]()
Figure 2.3: Splitting verification condition in parts
The subgoal that remains unproved was selected, and the corresponging
spec is shown on the bottom left: it is the part \result >= y
which is not valid. Indeed, this comes from our intentional mistake;
the result s not necessarily greater or equal to y in the second
branch. It is time to fix the source code. You can quit the graphical
interface, and replace with your favorite editor the last x
by y. If you rerun the krakatoa command as above,
you will see that the state of your proof session was recorded and
thus see all the splitting you made earlier. You notice also that most
of the goals are marked as “obsolete” meaning that the results
recorded in your proof sessions are not accurate any more. It is the
right time to use the “replay” button of the interface to rerun
prover on all obsolete proofs. Then click on the remaining VC and on a
prover like Alt-ergo, to see that this time the proof is OK. Indeed,
the splittings that you made to reach this state are not necessary
anymore; select the first split and the “remove” button, then
restart provers on the first VC, it should be proved. This means that
the method implementation now satisfies its specification.
Methods get a bit harder to prove in presence of loops. Below is a contract of a method for computing the square root of an integer (rounded towards zero).
The new requires introduce a precondition. This is formula that is supposed to hold at the beginning of the method call, here we ask for the parameter x to be non-negative, otherwise computing its square root would not be possible. The precondition is not something guaranteed by the method itself: it has to be checked by the caller of the method.
The ensures clause given states now that (i) the result is non-negative, (ii) the square of the result is less than or equal to x, and (iii) the successor of the result has a square which is greater than x. This ensures that the result is indeed the square root rounded towards zero.
To implement such a method, we propose an algorithm based on the following remark: we know that
| 2i+1 = k2 |
so the square root of n is the smallest integer k such that
| 2i+1 |
is greater than n.
Now, add the following to your class Lesson1.
If you generate the VCs now, using
krakatoa Lesson1.java
you will see the VC for the post-condition, as shown on Figure 2.4 is unproved. (Please ignore the VC “Safety” for the moment.)
![]()
Figure 2.4: Verification conditions for sqrt
Indeed, as usual in Floyd-Hoare logic, to prove a program with a loop it is required to add a loop invariant to have enough information. Finding the right invariant is usually hard, and usually follows from the principle of the algorithm. Here, count will increase one by one, whereas sum will always be the sum of odd integers between 1 and 2*count+1, that is (count+1)*(count+1). So we suggest the following
If you rerun the krakatoa tool, you should now see that CVC3 and Z3 are able to prove the normal behevior of the method.
![]()
Figure 2.5: Safety VCs for sqrt
Let’s now look at the “Safety” section of VCs. This section contains VCs for checking that no runtime error may occur during execution. These includes:
To understand why safety is not proved, let’s first split the goal and try provers on sub-goals. On this example, arithmetic overflow is the concern. On Figure 2.5, the focus is put on the VC corresponding to expression sum+2*count in the source code (bottom right windows). Looking at the top right window, the goal to prove, which is
(- 2147483648) <= (integer_of_int32 sum + integer_of_int32 result3) <= 2147483647
The constant 2147483648 is 231. This goal amounts to prove that the mathematical result of sum + 2*count fits into the int type. Indeed it is not provable, because it is not always true: if the value of x is too large, arithmetic overflow may occur.
It is possible to find a bound for x, to be added in the precondition, together with appropriate bounds for sum and count to be added as loop invariants, to prove the absence of arithmetic overflow. This is left as an exercise. In some situation, one may want to simply ignore possible arithmetic overflow. This is done by added the following pragma at the beginning of the file:
You may try this now, and checks that the “arithmetic overflow” VCs disappear.
There is another VC that remains to prove, named “loop variant decreases”. Indded, since the sqrt method body contains a while loop, it is possible that the method execution does not terminate for some of the input. To prove termination, you can add another clause to the loop annotation: a loop_variant clause. An integer expression must follow, which is supposed to decreases at each loop iteration, and remaining non-negative. On the sqrt example, that x-sum is decreasing, so you can add:
You can check again that this is proved automatically.
In this lesson, we now consider arrays. You should now create a new
file Arrays.java
. Here is the specification of a method for computing
the maximum element of an array of ints, given as argument:
The precondition is necessary to ensure that t
is a non-empty
array, so that the maximum exists. An alternative would have been to
raise an exception in that case, but exceptions will be considered
later. We also add the pragma for avoiding arithmetic overflow
checking for simplicity, although in that case there will be no
overflow problem.
We propose the following code for that method:
The loop invariant ensures that r
is always the index of the maximum
element of t
between 0
and i-1
. We put also as
invariant the facts that r
and i
remain in the bounds of
t
.
The following example is the same is the previous one. We just introduce a logic predicate to avoid the writing of similar formulas. See also http://proval.lri.fr/gallery/krakatoa.en.html.
The following method shifts for one cell to the right the contents of
an array. It illustrates the \old
and the \at
constructs.
See also http://proval.lri.fr/gallery/krakatoa.en.html.
It is time to consider true Java objects. Let’s consider the following class that implements a very simple electronic purse. See also http://proval.lri.fr/gallery/krakatoa.en.html.
See also http://proval.lri.fr/gallery/krakatoa.en.html.
These tests cannot be proved, the credit and withdraw methods must be given assigns clauses as follows:
See also http://proval.lri.fr/gallery/krakatoa.en.html.
See also http://proval.lri.fr/gallery/krakatoa.en.html.
The following example illustrates code instrumentation using ghost variables. See also http://proval.lri.fr/gallery/krakatoa.en.html.