The Jessie plug-in allows to perform deductive verification of C
programs inside Frama-C. The C file possibly annotated in ACSL is
first checked for syntax errors by Frama-C core, before it is
translated to various intermediate languages inside the Why Platform
embedded in Frama-C, and finally verification conditions (VCs) are
generated and a prover is called on these, as sketched in
Figure 1.1.

By default, the Jessie plug-in launches in its GUI mode. To invoke this
mode on a file ex.c, just type

> frama-c -jessie ex.c

The GUI of the Why Plaform (a.k.a. GWhy) is called. It displays each VC in a row, with available provers arranged as columns.

To invoke Jessie in batch mode, use option -jessie-atp with the prover name as argument, e.g.

> frama-c -jessie -jessie-atp simplify ex.c

runs prover Simplify on generated VCs. Valid identifiers for
provers are documented in Why. The list includes alt-ergo,
cvc3, yices, z3. See also the prover tricks page
http://why.lri.fr/provers.html.

Finally, you can use the generic name goals to just ask for
generation of VCs without running any prover.

Simplify is an automatic theorem prover distributed separately from Why
and Jessie. Among available automatic provers, it is one that
produces good results for software verification. Another automatic
prover that produces good results is Alt-Ergo.

The result of calling prover Simplify is succinctly reported as a
sequence of symbols ., *, ?, # and
! which denote respectively that the corresponding VC is valid,
invalid or unknown, or else that a timeout or a failure occured. By
default, timeout is set to 10 s for each VC. This result is summarized
as a tuple (v,i,u,t,f) reporting the total number of each outcome.

Here, the summary (0/0/0/0/0) means that there were no VCs to prove
(calling Jessie in GUI mode similarly displays no VC to
prove). Indeed, function max is safe and we did not ask for the
verification of any functional property.

This means that Simplify could prove one of the two VCs. To see
what each VC represents, let us now invoke Jessie in GUI
mode. Each VC represents verification of the postcondition in a
different context. The first VC represents the context where
i < j:

The second VC represents the context where i >= j. Context can
be seen in the upper right panel of GWhy, expressed in the
intermediate language of Why. At this time, GWhy does not assist
the user for linking the constructs found in the context to
original C expressions or statements, although this can be done
by hand for very short functions.

Running Simplify inside GWhy shows that the second VC is not proved by
Simplify. However, it is proved by prover Alt-Ergo.

The Jessie plug-in can also be run in batch mode with prover Alt-Ergo
instead of Simplify, with option -jessie-atp alt-ergo. This
results in the following output:

In the simple max example, VCs for the postcondition are grouped
in the default behavior for function max. In general, VCs for a
function are grouped in more than one group:

Safety: VCs in this group guard against safety violations
such as null-pointer dereferencing, buffer overflow, integer overflow, etc.

Default behavior: VCs in this group concern the
verification of a function’s default behavior, which includes
verification of its postcondition, frame condition, loop invariants
and intermediate assertions.

User-defined behavior: VCs in this group concern the
verification of a function’s user-defined behavior, which includes
verification of its postcondition, frame condition, loop invariants
and intermediate assertions for this specific behavior.

Here is a more complex variant of function max which takes
pointer parameters and returns 0 on success and -1 on failure.

Notice that the annotations refer to the null pointer using ACSL
syntax \null. It would be possible to use also the C macro
NULL, but in that case we would have to ask Frama-C
preprocessor phase to process the annotations too, since it does not
by default. This is done by option -pp-annot of
Frama-C. However, this alternative is not recommended since it is
depended of the proprecessor in use (see http://bts.frama-c.com/dokuwiki/doku.php?id=mantis:frama-c:start#faq_tips_and_tricks)

VCs that are proved in one group can be available to prove VCs in other
groups. No circularity paradox is possible here, since the proof of a
VC can only rely on other VCs higher in the control-flow graph of the
function. We made the following choices:

To prove a VC in Safety, one can rely on VCs in
Default behavior. Typically, one can rely on preconditions
or loop invariants to prove safety.

To prove a VC in Default behavior, one can rely on VCs
in Safety. Typically, one can rely on ranges of values
implied by safety to prove loop invariants and postconditions.

To prove a VC in a Normal behavior, one can rely on VCs
in both Safety and Default behavior.

A preliminary to the verification of functional properties
using the Jessie plug-in is to
verify the safety of functions. Safety has several components: memory
safety, integer safety, termination. Memory safety deals with validity
of memory accesses to allocated memory. Integer safety deals with
absence of integer overflows and validity of operations on integers,
such as the absence of division by zero. Termination amounts to check
whether loops are always terminating, as well as recursive or
mutually recursive functions.

Our running example will be the famous binary_search function,
which searches for a long in an ordered array of longs. On
success, it returns the index at which the element appears in the
array. On failure, it returns -1.

To concentrate first on memory safety only, we declare two pragmas as
above. The first pragma dictates that integers in C programs behave as
infinite-precision mathematical integers, without overflows. The
second pragma instructs the plug-in to ignore termination issues.

Let’s call Frama-C with the Jessie plug-in on this program:

> frama-c -jessie binary-search.c

As seen on Figure 2.1, we get 3 VCs, an obvious one that
states the divisor 2 is not null, and two more that state the
array access t[m] should be within bounds. This is due to the
memory model used, that decomposes any access check into two: one that
states the access is above the minimal bound allowed, and one that
states the access is below the maximal bound allowed.

Figure 2.1: Memory safety with no annotations

The obvious VC is trivially proved by all provers, while the two VCs
for memory safety cannot be proved. Indeed, it is false that, in any
context, function binary_search is memory safe. To ensure
memory safety, binary_search must be called in a context
where n is positive and array t is valid
between indices 0 and n-1 included. Since function
binary_search accesses array t inside a loop,
providing a precondition is not
enough to make the generated VC provable.
One must also provide a loop invariant. A loop invariant
is a property that remains true at each iteration of the loop.
It is often necessary for the user to provide these properties
to help Jessie reason about loops. Assuming that the right property
has been provided, Jessie is then left with the easier task of
generating and verifying VCs that ensure that the property indeed
holds at the beginning of each iteration.

In this example, it is necessary to provide an invariant
that states the guarantees provided on the array
index, despite its changing value. It
states that the value of index l stays within the bounds of the
array t.

Seven VCs are now generated: 2 to guarantee the loop invariant is
initially established (because the conjunction is split), 2 to guarantee
the same loop invariant is preserved throughout the loop, and the 3 VCs
seen previously. Not all generated VCs are proved automatically with
these annotations. Of the 3 VCs seen previously, the maximal bound
check is still not proved. And the preservation of the loop invariant
that deals with an upper bound on u is not proved either. It
comes from the non-linear expression assigned to min the loop,
that is difficult to take into account automatically.

We solve this problem by adding an assertion to help automatic
provers, providing some form of hint in the proof. This can be done by
inserting assertions in the code, or by adding a globallemma, that
should be proved using available axioms, and used as an axiom in
proving the VC for safety. This works for our example.

The results are shown on Figure 2.2, where all VCs, are
proved by some prover. This guarantees the memory safety of function
binary_search. The lemma itself is proved by Alt-Ergo, which
has a little knowledge of the division operator. Given the lemma,
other VCs are fully proved by Simplify and Z3, and partly by Yices and
CVC3.

Let us now consider machine integers instead of idealized mathematical
integers. This is obtained by removing the pragma
JessieIntegerModel. Without this pragma, integer types are
now intepreted as bounded machine integers. However, the default is a
defensive interpretation, which forbids the arithmetic
operations to overflow.1

Figure 2.3: Memory safety + integer overflow safety

The result can be seen in Figure 2.3. There are ten more VCs
to check that integer operations return a result within bounds, only one of
which is not proved. With this exception, the results are nearly the same
as with exact integers (proving the lemma takes more time, due
to the additional encoding for bounded integers).

The only unproved VC expresses that l+u does not overflow.
Nothing prevents this from happening with our current
precondition for function binary_search [7]. There are two
possibilities here. The easiest is to strengthen the precondition
by requiring that n is no more than half the maximal signed
integer INT_MAX. The best way is to change the source of
binary_search to prevent overflows even in presence of large
integers. It consists in changing the buggy line

int m = (l + u) / 2;

into

int m = l + (u - l) / 2;

This is our choice here. As shown in Figure 2.4, all VCs
are now proved automatically.

The last kind of safety property we want is termination. To check it,
we first remove the pragma JessieTerminationPolicy. If we run
the VC generation again, we get an additional VC that requires to prove
the property 0 > 0. This VC is false, so our first step should be
to help Jessie generate a more provable VC.
The VC 0 > 0 is generated because we did not provide any
loop variant for the while loop. A loop variant is a
quantity which must decrease strictly at each loop iteration, while
provably remaining non-negative for as long as the loop runs.
In this example, a proper variant is u−l.
So our annotated program now looks as follows:

Termination of recursive functions can be dealt with similarly by
adding a decreases clause to the function’s contract.
It is also possible to prove termination by using variants over
any datatype d equipped with a well-founded relation.
See the ACSL documentation for details.

Now that the safety of function binary_search has been established, one
can attempt the verification of functional properties, like the
input-output behavior of function binary_search. At the
simplest, one can add a postcondition that binary_search should
respect upon returning to its caller. Here, we add bounds on the value
returned by binary_search. To prove this postcondition,
strengthening the loop invariant is necessary.

One can be more precise and separate the postcondition according to
different behaviors. The assumes clause of a behavior gives
precisely the context in which a behavior applies. Here, we state that
function binary_search has two modes: a success mode and a
failure mode. This directly relies on array t to be sorted,
thus we add this as a general requirement. The success mode states
that whenever the calling context is such that value v is in
the range of t searched, then the value returned is a valid
index. The failure mode states that whenever the calling context is
such that value v is not in the range of t searched,
then function binary_search returns -1. Again, it is
necessary to strengthen the loop invariant to prove the VC generated.

The following example introduces use of algebraic specification. The
goal is the verify a simple sorting algorithm (by extraction of the
minimum).

The first step is to introduce logical predicates to define the
meanings for an array to be sorted in increasing order, to be a
permutation of another. This is done as follows, in a separate file
say sorting.h

Each VC is proved by at least one prover. Figure 3.3
displays the results in GWhy, with emphasis on the VC for preservation
of the loop invariant for permutation behavior, the most
difficult one, only proved by Alt-Ergo.

To alleviate the annotation burden, it is possible to ask the Jessie
plug-in to infer some of them, through a combination of abstract
interpretation and weakest preconditions. This requires that APRON
library for abstract interpretation is installed and Frama-C
configuration recognized it. Then, one can call

> framac -jessie -jessie-atp=simplify -jessie-infer-annot inv max.c

to perform abstract interpretation on program max.c, which
computes necessary loop invariants and postconditions (meaning an
overapproximation of the real ones).

which attempts to compute a sufficient precondition to guard against
safety violations and prove functional properties. In case it computes
false as sufficient precondition, which occurs e.g. each time
the property is beyond the capabilities of our method, it simply
ignores it. Still, our method can compute a stronger precondition than
necessary. E.g., on function max, it computes precondition
\valid(r) && \valid(i) && \valid(j), while a more precise
precondition would allow r to be null. Still, the generated
precondition is indeed sufficient to prove the safety of function
max:

To improve on the precision of the generated precondition, various
methods have been implemented:

Quantifier elimination - This method computes an
invariant I at the program point where check C should
hold, forms the quantified formula \forall x,y... ; I ==> C
over local variables x,y..., and eliminates quantifiers from
this formula, resulting in a sufficient precondition.
This is the method called with option -jessie-infer-annot pre.

Weakest preconditions with quantifier elimination - This
method improves on direct quantifier elimination by propagating
formula I ==> C backward in the control-flow graph of the
function before quantifying over local variables and eliminating
quantifiers.
This is the method called with option -jessie-infer-annot wpre.

Modified weakest preconditions with quantifier
elimination - This method strengthens the formula obtained by
weakest preconditions with quantifier elimination, by only
considering tests and assignments which deal with variables in the
formula being propagated. Thus, it may result in a stronger
precondition (i.e. a precondition less precise) but at a smaller
computational cost. In particular, it may be applicable to programs
where weakest preconditions with quantifier elimination is too costly.
This is the method called with option -jessie-infer-annot spre.

By default, the Jessie plug-in assumes different pointers point into
different memory regions. E.g., the following postcondition
can be proved on function max, because parameters r,
i and j are assumed to point into different regions.

Now, function max should only be called in a context where
parameters r, i and j indeed point into
different regions, like the following:

int main(int a, int b) {
int c;
max(&c,&a,&b); return c;
}

In this context, all VCs are proved.

In fact, regions that are only read, like the regions pointed to by
i and j, need not be disjoint. Since nothing is written
in these regions, it is still correct to prove their contract in a
context where they are assumed disjoint, whereas they may not be
disjoint in reality. It is the case in the following context:

int main(int a, int b) {
int c;
max(&c,&a,&a); return c;
}

In this context too, all VCs are proved.

Finally, let’s consider the following case of a context in which a
region that is read and a function that is written are not disjoint:

int main(int a, int b) {
int c;
max(&a,&a,&b); return c;
}

The proof that regions are indeed disjoint boils down to proving that
set of pointers {&a} and {&a} are disjoint (because
function max only writes and reads *r and *i),
which is obviously false.

Unions without pointer fields are translated to bitvectors, so
that access in these unions are translated to low-level
accesses. Thus, the following code can be analyzed, but we do not yet
provide a way to prove the resulting assertions, by asserting that any
subset of bits from the bitvector representation of 0 is 0:

Unions with pointer fields (either direct fields or sub-fields of
structure fields) are translated differently, because we treat
pointers differently than other types, to allow an automatic analysis
of separation of memory blocks. Thus, we treat unions with pointer
fields as discriminated unions, so that writing in a field erases all
information on other fields. This allows to verify the following
program:

The plug-in will then automatically call the Jessie tool of the Why
platform to analyze the generated file f.jc above. By default, VCs are
generated and displayed in the GWhy interface. The
-jessie-atp=<p> option allows to run VCs in batch, using the
given theorem prover <p>.

direct equality on structures is not supported. Equality of
each field should be used instead (e.g. by introducing an adequate
predicate). Similarly, direct equality of arrays is not supported,
and equality of each cells should be used instead.

array and structure field functional modifiers are not supported

higher-order constructs \lambda, \sum,
\prod, etc. are not supported

Logic specifications

model variables and fields

global invariants and type invariants

volatile declarations

\initialized and \specified predicates

Contract clauses

terminates clause

abrupt termination clauses

general code invariants (only loop invariants are supported)

Ghost code

it is not checked whether ghost code does not interfere with
program code.

defensive: float types are modeled by real numbers
with appropriate bounds and roundings, and for each floating-point
arithmetic operations, it is mandatory to show that no overflow
occur. This model follows the IEEE-754 standard, under its
strict form, as explained in [1, 2].

full: models the full IEEE-754 standard, including
infinite values and NaNs. This model is the full model
discussed in [1, 2].

multirounding: models floating-point arithmetics so
as to support combinations of compilerd and architectures that do
not strictly follows IEEE-754 standard (e.g. double roundings,
80-bits extended formats, compilation usinf fused-multiply-add
instructions). This is based on paper [3, 4].

Floating point rounding mode

# pragma JessieFloatRoundingMode(value)

Possible values: nearesteven, down, up,
tozero, nearestaway.

Default value: nearesteven.

Separation policy

# pragma SeparationPolicy(value)

Possible values: none, regions

Invariant policy

# pragma InvariantPolicy(value)

Possible values: none, arguments, ownership

Termination policy

# pragma JessieTerminationPolicy(value)

Possible values: always, never, user

Default: always

always means that every loop and every recursive
function should be proved terminating. If they are not annotated
by variants, then an unprovable VC (0<0) is generated.

user means that VCs for termination are generated for
each case where a loop or function variant is given. Otherwise no
VC is generated.

never means no VC for termination are ever generated,
even for annotated loop or recursive function.

Here is a set of common error messages and possible fix or workaround.

unsupported "cannot handle this lvalue"

this message may appear in the following situations:

use an array as a parameter of a logic function. You should use
a pointer instead.

unsupported "this kind of memory access is not currently supported"

this message may appear in the following situations:

equality on structures in the logic. The workaround is to
check equality field by field. Tip: define field-by-field equality as a
logic predicate.

unsupported "Jessie plugin does not support struct or union as
parameter to logic functions."

This
is already quite explicit: jessie does not support structures or
unions as a parameter of logic functions or predicates. You can
circumvent this limitation by using an indirection via a pointer.

unsupported "cannot take address of a function"

The jessie
plugin does not support functions as parameters to other
functions. There is no simple workaround. One thing you can try is
to remove the function parameter and use a fixed abstract function
(i.e. with a contract but no body), and then prove that all the
functions that might be passed as parameters respect this contract.

unsupported "Type builtin_va_list not allowed"

Jessie does not
handle varyadic functions. The same trick as above could be
attempted.

unsupported "Casting from type <..> to type <..> not allowed"

Jessie does not support this cast, typically between pointer and
integer. There is no simple workaround. One way of proving such kind
of code is to replace the casts by an abstract function, whose
post-condition explicitly explains how the conversion is made.

failure: cannot interpreted this lvalue

This may happen if

using a structure in an assigns clause. You need to expand ans
say which field are assigned.