This document is also available in PDF format.
Krakatoa is a tool for certification of Java programs, annotated using the Java Modeling Language [5] (JML for short), using the Why [1] tool for generating proof obligations.
This version 2.40 of Krakatoa is a major rewriting of the version 0.x family. Major changes have occurred including changes in the syntax of annotations.
Chapter 2 is a tutorial to introduce the user step by step to the use of Krakatoa.
Chapter 3 is a reference manual where all options of the tool are described, and also the modeling of Java objects and Java memory heap, that you may encounter if you discharge proofs interactively, e.g. using Coq.
In the appendix you will find various additional informations, including the requirements, a summary of known limitations, and how to get help.
The use of the Why2 VC generator is now obsolete, and it is recommended to switch to the Why3 system for specification and VC generation. Why3 must be installed independently of Why2, please instructions given at http://why3.lri.fr.
The version of Why3 that is compatible with this version 2.30 of Krakatoa is the version 0.71. Please see http://krakatoa.lri.fr/ for more details on compatibility between Why2/Krakatoa and Why3.
In this manual, it is assumed that the Why3 VC generator and IDE is in use. The old behavior using the Why2 VC generator and GUI remains possible, using the gwhy command just as in version 2.29.
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://toccata.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://toccata.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://toccata.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://toccata.lri.fr/gallery/krakatoa.en.html.
See also http://toccata.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://toccata.lri.fr/gallery/krakatoa.en.html.
See also http://toccata.lri.fr/gallery/krakatoa.en.html.
The following example illustrates code instrumentation using ghost variables. See also http://toccata.lri.fr/gallery/krakatoa.en.html.
This chapter is the reference for the specification language. It is unfortunately largely incomplete, but for missing parts and explanations we refer to the ACSL Reference Manual [2], which is very close.
We present the language of expressions one can use in annotations. These are called logic expressions.
This language is essentially the standard multi-sorted first-order logic. It is a two-valued logic language, made of propositions with standard first-order connectives, built upon a language of atoms called terms.
As far as possible, the syntax of Java expressions is reused:
conjunction is denoted as &&
, disjunction as ||
and
negation as !
. Additional connectives are ==>
for
implication, <==>
for equivalence. Universal quantification is
denoted by \forall τ x1,…,τ xn; e and existential
quantification by \exists τ x1,…,τ xn; e.
Terms are built on
In essence, terms correspond to pure Java expressions, with additional constructs that we will introduce progressively, except that method and constructor calls are not allowed.
Figure 3.1 presents the grammar for the basic construction of logic expressions.
Figure 3.1: Grammar of logic expressions
Basic additional constructs are as follows:
==>
e1) &&
(!
c ==>
e2)&&
t2 relop2 t3 &&
⋯ .
Nevertheless, it is required that the relopi operators must be in
the same “direction”, i.e. they must all belong either to
{<
, <=
, ==
} or to
{>
,>=
,==
}. For example, expressions
x < y > z
and x != y != z
are forbidden.The precedence of Java operators is conservatively extended with additional operators, as shown Figure 3.2. In this table, operators are sorted from highest to lowest priority. Operators of same priority are presented on the same line.
class associativity operators selection left [
⋯]
.
unary right !
~
+
-
(cast)multiplicative left *
/
%
additive left +
-
shift left <<
>>
>>>
comparison left <
<=
>
>=
comparison left ==
!=
bitwise and left &
bitwise xor left ^
bitwise or left |
connective and left &&
connective xor left ^^
connective or left ||
connective implies right ==>
connective equiv left <==>
ternary connective right ⋯ ?
⋯:
⋯binding left \forall \exists
Figure 3.2: Operator precedence
The semantics of logic expressions is based on mathematical first-order logic (http://en.wikipedia.org/wiki/First_order_logic), thus it is a 2-valued logic with only total functions. Consequently, expressions are never “undefined”.
This design choice has to be emphasized because it is not straightforward, and specification writer should be aware of that. The issues are shared with JML. A comprehensive list of issues has been compiled by Patrice Chalin [3, 4].
The choice of having only total functions allows to write for example
the term 1/0
, or p.f
when p is null, or t[n]
where n is outside the array bounds. In
particular, the predicates
|
are true, since they are instances of the general axiom ∀ x,
x==
x of first-order logic.
So, it is up to the writer of specification to take care of writing consistent assertions.
The language of logic expressions is typed (as for multi-sorted first-order logic). Types are either Java types or logic types defined as follows:
integer
for unbounded,
mathematical integers; real
for real numbers.
There are implicit coercions for numeric types:
char
, byte
, short
, int
and
long
are all subtypes of type integer
,
integer
is itself a subtype of type real
,
float
and double
are subtypes of type real
.
Notes:
The following integer arithmetic operations apply to mathematical integers: addition, subtraction, multiplication, unary minus, division and modulo. The value of a Java variable of an integer type is promoted to a mathematical integer. As a consequence, there is no such thing as “overflow” in logic expressions.
For division and modulo, the results are not specified if divisor is zero, otherwise if q and r are the quotient and the remainder of n divided by d then:
5/3
is 1 and 5%3
is 2
(-5)/3
is -1 and (-5)%3
is -2
5/(-3)
is -1 and 5%(-3)
is 2
(-5)/(-3)
is 1 and (-5)%(-3)
is -2
Hexadecimal and octal constants always denote non-negative integers.
In logic expressions, casting operations from mathematical integers towards a Java integer type t (among char, byte, short, int and long) is allowed, and is interpreted as follows: the result is the unique value of the corresponding type that is congruent to the mathematical result modulo the cardinal of this type.
If one wants to express, in the logic, the result of the Java computations of an expression, one should add all necessary casts. For example, the logic expression which denotes the result of Java computation of x*y+z is (int)((int)(x*y)+z).
Remark: implicit casts from integers to Java integer type are forbidden.
Quantification can be either on mathematical integer
or bounded
types short
, char
, etc. In the latter case,
quantification corresponds to integer quantification over the
corresponding interval.
\forall byte b; b <= 1000
\forall integer b; -128 <= b <= 127 ==> b <= 1000
Like arithmetic operations, bitwise operations apply to any mathematical integers: any mathematical integer as a unique infinite 2-complement binary representation with infinitely many 0 (for non-negative numbers) or 1 (for negative numbers) on the left. Then bitwise operations apply to these representation.
7 & 12
= ⋯ 00111 &
⋯ 001100 =
⋯ 00100 = 4
-8 | 5
= ⋯ 11000 |
⋯ 00101 = ⋯
11101 = -3
~5
= ~
⋯ 00101 = ⋯ 111010 = -6
-5 << 2
= ⋯ 11011 <<
2 = ⋯
11101100 = -20
5 >> 2
= ⋯ 00101 >>
2 = ⋯ 0001 = 1
-5 >> 2
= ⋯ 11011 >>
2 = ⋯ 1110 =
-2
Floating-point constants and operations are interpreted as mathematical real numbers. A Java variable of type float or double is implicitly promoted to a real. Integers are promoted to reals if necessary.
Comparisons operators are interpreted as real operators too.
A simple contract is an annotation that can be given to constructors and methods (not depending on whether they are given a body or are abstract). It is a contract between the caller and the callee, made of a precondition and a postcondition:
Additionally, a contract can be completed with a frame clause which describes side-effects, and acts as a postcondition.
Note that post-conditions of simple contracts only concern normal exit: exiting with exceptions can be specified using additional behavior clauses described in Section 3.3
contract ::= (requires-clause ∣ assigns-clause ∣ ensures-clause ) * requires-clause ::= requires proposition ; assigns-clause ::= assigns locations ; locations ::= location (, location) * ∣ \nothing ensures-clause ::= ensures proposition ;
Figure 3.3: Grammar of simple contracts
The syntax of simple contracts syntax is given Figure 3.3. Let’s consider a simple contract of the following generic form:
the semantics of such a contract can be rephrased as follows:
&&
P2 holds.
&&
E2 must hold in the corresponding poststate.
Thus, the contract above is equivalent to the following simplified one:
&&
P2; &&
E2; The multiplicity of clauses are proposed mainly to improve readibility. Also, if no clause requires is given, it defaults to requiring ‘true’, and similarly for ensures clause. Giving no assigns clause means that side-effects are not specified: it potentially modifies everything.
A simple contract can be augmented with behaviors.
A normal behavior clause as the form
The semantics of such a behavior is as follows. The callee guarantees that if it returns normally, then in the post-state:
An exceptional behavior clause as the form
The semantics of such a behavior is as follows. The callee guarantees that if it exits with exception Exc, then in the post-state:
Notice that in E, \result is bound to the exception object thrown.
A class invariant is a property attached to a class. This property is supposed to hold on any object of that class. More precisely, it holds at method entrance and exit, and at the exit of constructor.
New logic data types (i.e. immutable) can be introduced abstractly by giving
loop_variant
.
Compiling from sources requires Objective Caml compiler, version 3.09 or higher.
External theorem provers must be installed. See the page http://why.lri.fr/provers.html
For using the Coq interactive prover, you need Coq version 8.0 or higher.
Get a copy of sources at the web page http://why.lri.fr/.
Decompress the archive in a directory of your choice.
Run commands
./configure make make install
Please look at the web page http://why.lri.fr/ for binaries for popular architectures. Krakatoa is distributed as part of the Why debian package, available on standard repositories of debian-based distributions.
The webpage for Krakatoa is at URL http://krakatoa.lri.fr/ and the webpage for the Why platform in general is at http://why.lri.fr.
For general questions regarding the use of the tool, please use the Why mailing list. You need to subscribe to the list before sending a message to it. To subscribe, follow the instructions given on page http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/why-discuss
For bug reports, please use the bug tracking system at https://gforge.inria.fr/tracker/?atid=4012&group_id=999&func=browse. For security reasons, you need to register before submitting a new bug. Please create an account there, where you can put "ProVal" for the required field "INRIA Research Project you work with or work in".
In case if the mailing list above is not appropriate, you can contact the authors directly at the following address: mailto:Claude dot Marche at inria dot fr.
|
|
This document was translated from LATEX by HEVEA.