Learning how to prove Frama-C pre-condition goals - static-analysis

I have the following sample code:
typedef struct {
BYTE fs_type; /* FAT sub-type (0:Not mounted) */
BYTE drv; /* Physical drive number */
} FATFS_temp;
FATFS_temp *FatFs_temp[1]; /* Pointer to the file system objects (logical drives) */
/*#
# requires (vol <= 0) && (fs != \null) ==> \valid((fs)) ; // problematic one
# behavior mount:
# //assumes \valid(fs) && vol <= 0;
# assumes fs != \null && vol <= 0;
# ensures (vol <= 0) ==> (FatFs_temp[vol] == \old(fs));
# ensures fs->fs_type == 0;
# behavior unmount:
# assumes fs == \null && vol <= 0;
# ensures (vol <= 0) ==> (FatFs_temp[vol] == \null);
# behavior error:
# assumes vol > 0;
# ensures \result == 88;
# complete behaviors mount, unmount, error;
# disjoint behaviors mount, unmount, error;
*/
int f_mount_temp (
BYTE vol, /* Logical drive number to be mounted/unmounted */
FATFS_temp *fs /* Pointer to new file system object (NULL for unmount)*/
)
{
FATFS_temp *rfs;
if (vol >= 1) /* Check if the drive number is valid */
return 88;
rfs = FatFs_temp[vol]; /* Get current fs object */
if (rfs) {
rfs->fs_type = 0; /* Clear old fs object */
}
if (fs) {
fs->fs_type = 0; /* Clear new fs object */
}
FatFs_temp[vol] = fs; /* Register new fs object */
return 22;
}
But Frama-C / Why3 couldn't prove one of the 'requires' as commented in the code.
the .Why file states the following:
goal WP "expl:Pre-condition (file src/ff_temp.c, line 12) in 'f_mount_temp'":
forall vol_0 : int.
forall malloc_0 : map int int.
forall fatFs_temp_0 : map int addr.
forall fs_0 : addr.
(fs_0 <> null) ->
(vol_0 <= 0) ->
((linked malloc_0)) ->
((is_uint8 vol_0)) ->
(forall k_0 : int. (0 <= k_0) -> (k_0 <= 0) -> (null = fatFs_temp_0[k_0])) ->
((valid_rw malloc_0 fs_0 2))
end
for the sake of learning, my questions are:
1) what is wrong with that pre-condition?
2) based on the outputs in the .Why file, what should my approach be to find out whats wrong?
3) can someone point me to resources to learn how to go about debugging my function contracts?
EDIT:
i ran Frama-c with the following flags: "-wp -wp-rte -wp-fct f_mount_temp"
i did not call this f_mount_temp from elsewhere. i ran Frama-c to check on this f_mount_temp() directly.
now its clearer to me, its likely the additional assertions that caused the pre-condition to fail. the processed function contracts are the following, with the comments indicating the status of each assertion:
/*# requires vol ≤ 0 ∧ fs ≢ \null ⇒ \valid(fs); // unknown
behavior mount: // unknown
assumes fs ≢ \null ∧ vol ≤ 0;
ensures \old(vol) ≤ 0 ⇒ FatFs_temp[\old(vol)] ≡ \old(fs);
ensures \old(fs)->fs_type ≡ 0;
behavior unmount: //unknown
assumes fs ≡ \null ∧ vol ≤ 0;
ensures \old(vol) ≤ 0 ⇒ FatFs_temp[\old(vol)] ≡ \null;
behavior error: //unknown
assumes vol > 0;
ensures \result ≡ 88;
complete behaviors mount, unmount, error; // green
disjoint behaviors mount, unmount, error; // green
*/
the inline assertions added by the -wp-rfe flags are:
int f_mount_temp(BYTE vol, FATFS_temp *fs) {
int __retres;
FATFS_temp *rfs;
if ((int)vol >= 1) {
__retres = 88;
goto return_label;
}
/*# assert rte: index_bound: vol < 1; */ // ok
rfs = FatFs_temp[vol];
if (rfs) {
/*# assert rte: mem_access: \valid(&rfs->fs_type); */ //unknown
rfs->fs_type = (unsigned char)0;
}
if (fs) {
/*# assert rte: mem_access: \valid(&fs->fs_type); */ // unknown
fs->fs_type = (unsigned char)0;
}
/*# assert rte: index_bound: vol < 1; */ // unknown
FatFs_temp[vol] = fs;
__retres = 22;
return_label: return __retres;
}

1) what is wrong with that pre-condition?
You are using && and ==> as if their relative precedences were well-known. This is wrong from a human point of view, because as ==> does not appear in many languages other than ACSL, only ACSL specialists can know what a formula that depends on its precedence means.
Apart from that, there can never be anything wrong with a pre-condition in a code snippet that does not involve a call to the function. The pre-condition it not a property that is proved with respect to the implementation of the function but with respect to the context in which the function is used. You could have made a mistake and written the logical equivalent of \false and the pre-condition would still be fine for your snippet (it would only mean that all calls to the function are invalid and must themselves be proved to be unreachable).
For your question to make sense, it would have to either:
involve the proof (or lack of proof) of a post-condition of f_mount_temp and provide this function's implementation, or
involve the proof (or lack of proof) of a pre-condition of f_mount_temp and the code of the function in which f_mount_temp is called, including that function's pre-conditions, so that it is possible to tell whether this calling function respects f_mount_temp's pre-condition. In this latter case it is not necessary to provide f_mount_temp's code or post-condition, unless it is called several times in the caller. Also the code of the other functions called from the caller need not be provided, but their contracts should be.
What you have done here, providing f's code and asking why f's pre-condition is not proved, is not coherent.
2) based on the outputs in the .Why file, what should my approach be to find out whats wrong?
This is not a bad place to ask, and I think that you could receive help if you ask again with the right bits of information.
3) can someone point me to resources to learn how to go about debugging my function contracts?
I am not aware of many of these, but this site could become a resource explaining the most common debugging tricks if you ask again…

Related

Function call in a "if" clause to ACSL

Consider the following code
int f(int a, int b){
return 0;
}
/*#
ensures (f(2,3)== 0) ==> \result == 2;
*/
int g() {
if (f(2,3) == 0)
return 2;
return 0;
}
The response of frama-c to the following code is the following error
[kernel:annot-error] fing.c:5: Warning:
unbound function f. Ignoring logic specification of function g
[kernel] User Error: warning annot-error treated as fatal error.
[kernel] User Error: stopping on file "fing.c" that has errors. Add '-kernel-msg-key pp'
for preprocessing command.
[kernel] Frama-C aborted: invalid user input.
Is there a way to write a specification where there's a call of a function inside an "if" clause ?
In ACSL, you cannot use a C function in the specification. If you want to do something like this, the only possibility is to define a logic function (ideally provably) equivalent to the C function and to use this logic function.
/*# logic integer l_f(integer a, integer b) = 0 ; */
//# ensures \result == l_f(a, b);
int f(int a, int b){
return 0;
}
/*#
ensures (l_f(2,3)== 0) ==> \result == 2;
*/
int g() {
if (f(2,3) == 0)
return 2;
return 0;
}

Formal proof of a recursive Quicksort using frama-c

As homework, I've decided to try verify an implementation of quicksort (taken and adapted from here) using frama-c with wp and rte plugins. Note that at first leftmost is 0 and rightmost is equal to size-1. Here my proof.
/*#
requires \valid(a);
requires \valid(b);
ensures *a == \old(*b);
ensures *b == \old(*a);
assigns *a,*b;
*/
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
/*#
requires \valid(t +(leftmost..rightmost));
requires 0 <= leftmost;
requires 0 <= rightmost;
decreases (rightmost - leftmost);
assigns *(t+(leftmost..rightmost));
*/
void quickSort(int * t, int leftmost, int rightmost)
{
// Base case: No need to sort arrays of length <= 1
if (leftmost >= rightmost)
{
return;
} // Index indicating the "split" between elements smaller than pivot and
// elements greater than pivot
int pivot = t[rightmost];
int counter = leftmost;
/*#
loop assigns i, counter, *(t+(leftmost..rightmost));
loop invariant 0 <= leftmost <= i <= rightmost + 1 <= INT_MAX ;
loop invariant 0 <= leftmost <= counter <= rightmost;
loop invariant \forall int i; leftmost <= i < counter ==> t[i] <= pivot;
loop variant rightmost - i;
*/
for (int i = leftmost; i <= rightmost; i++)
{
if (t[i] <= pivot)
{
/*#assert \valid(&t[counter]);*/
/*#assert \valid(&t[i]);*/
swap(&t[counter], &t[i]);
counter++;
}
}
// NOTE: counter is currently at one plus the pivot's index
// (Hence, the counter-2 when recursively sorting the left side of pivot)
quickSort(t, leftmost, counter-2); // Recursively sort the left side of pivot
quickSort(t, counter, rightmost); // Recursively sort the right side of pivot
}
As side note, I know that wp doesn't support recursion hence the ignored decreases statement when running Frama-c -wp -wp-rte.
here is the result in the gui:
As you can see my loop invariants are not verified even though it makes senses to me.
Frama-c able to verify under hypotheses the second recursive call when it's not supporting recursion. To my understanding the call quickSort(t, leftmost, counter-2) isn't verified since can violate the precondition requires 0 <= rightmost. I'am not too sure about Frama-c behaviour in that case though and how to tackle it.
I would like some input about what is going on. I think that the invariant not being verified as nothing to do with recursion as even by removing the recursion calls, they aren't verified. And finally could you explain to me what is the Frama-c behaviour in the case of the recursive calls? Are they treated as any other function call or is there a behaviour that I'am unaware of?
Thanks
First, unlike Eva, WP has no real problem with recursive functions, apart from proving termination, which is completely orthogonal to prove that the post-condition holds each time the function returns (meaning that we don't have to prove anything for the non-terminating cases): in the literature, this is referred to as partial correctness vs. total correctness when you can also prove that the function always terminates. The decreases clause only serves to prove termination, so that the fact that it is unsupported is only an issue if you want total correctness. For partial correctness, everything is fine.
Namely for partial correctness, a recursive call is treated like any other call: you take the contract of the callee, prove that the pre-condition holds at this point, and try to prove the post-condition of the caller assuming that the post-condition of the callee holds after the call. Recursive calls are in fact easier for the developer: since the caller and the callee are the same, you get to write only one contract 😛.
Now regarding the proof obligations that fail: when the 'established' part of a loop invariant fails, it is often a good idea to start investigating that. This is usually a simpler proof obligation than the preservation: for the established part, you want to prove that the annotation holds when you encounter the loop the first time (i.e. this is the base case), while for the preservation, you have to prove that if you assume the invariant true at the beginning of an arbitrary loop step, it stays true at the end of said step (i.e. this is the inductive case). In particular, you can not deduce from your pre-conditions that right_most+1 <= INT_MAX. Namely, if you have rightmost == INT_MAX, you will encounter issues, especially as the final i++ will overflow. In order to avoid such arithmetic subtleties, it is probably simpler to use size_t for leftmost and to consider rightmost to be one past the greatest offset to consider. However, if you requires that both leftmost and rightmost to be strictly less than INT_MAX, then you will be able to proceed.
However, that is not all. First, your invariant for bounding counter is too weak. You want that counter<=i, not merely that counter<=rightmost. Finally, it is necessary to guard the recursive calls to avoid violating the pre-conditions for leftmost or rightmost in case the pivot was ill-chosen and your original indices were close to the limit (i.e. counter ends up being 0 or 1 because the pivot was too small or INT_MAX because it was too big. In any case, this can only happen if the corresponding side would be empty).
In the end, the following code gets completely proved by WP (Frama-C 20.0 Calcium, using -wp -wp-rte):
#include <limits.h>
/*#
requires \valid(a);
requires \valid(b);
ensures *a == \old(*b);
ensures *b == \old(*a);
assigns *a,*b;
*/
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
/*#
requires \valid(t +(leftmost..rightmost));
requires 0 <= leftmost < INT_MAX;
requires 0 <= rightmost < INT_MAX;
decreases (rightmost - leftmost);
assigns *(t+(leftmost..rightmost));
*/
void quickSort(int * t, int leftmost, int rightmost)
{
// Base case: No need to sort arrays of length <= 1
if (leftmost >= rightmost)
{
return;
} // Index indicating the "split" between elements smaller than pivot and
// elements greater than pivot
int pivot = t[rightmost];
int counter = leftmost;
/*#
loop assigns i, counter, *(t+(leftmost..rightmost));
loop invariant 0 <= leftmost <= i <= rightmost + 1;
loop invariant 0 <= leftmost <= counter <= i;
loop invariant \forall int i; leftmost <= i < counter ==> t[i] <= pivot;
loop variant rightmost - i;
*/
for (int i = leftmost; i <= rightmost; i++)
{
if (t[i] <= pivot)
{
/*#assert \valid(&t[counter]);*/
/*#assert \valid(&t[i]);*/
swap(&t[counter], &t[i]);
counter++;
}
}
// NOTE: counter is currently at one plus the pivot's index
// (Hence, the counter-2 when recursively sorting the left side of pivot)
if (counter >= 2)
quickSort(t, leftmost, counter-2); // Recursively sort the left side of pivot
if (counter < INT_MAX)
quickSort(t, counter, rightmost); // Recursively sort the right side of pivot
}

/*undefined sequence*/ in sliced code from Frama-C

I am trying to slice code using Frama-C.
The source code is
static uint8_T ALARM_checkOverInfusionFlowRate(void)
{
uint8_T ov;
ov = 0U;
if (ALARM_Functional_B.In_Therapy) {
if (ALARM_Functional_B.Flow_Rate > ALARM_Functional_B.Flow_Rate_High) {
ov = 1U;
} else if (ALARM_Functional_B.Flow_Rate >
ALARM_Functional_B.Commanded_Flow_Rate * div_s32
(ALARM_Functional_B.Tolerance_Max, 100) +
ALARM_Functional_B.Commanded_Flow_Rate) {
ov = 1U;
} else {
if (ALARM_Functional_B.Flow_Rate > ALARM_Functional_B.Commanded_Flow_Rate * div_s32(ALARM_Functional_B.Tolerance_Min, 100) + ALARM_Functional_B.Commanded_Flow_Rate) {
ov = 2U;
}
}
}
return ov;
}
When I sliced the code usig Frama-C, I get the following. I don't know what this “undefined sequence” means.
static uint8_T ALARM_checkOverInfusionFlowRate(void)
{
uint8_T ov;
ov = 0U;
if (ALARM_Functional_B.In_Therapy)
if ((int)ALARM_Functional_B.Flow_Rate > (int)ALARM_Functional_B.Flow_Rate_High)
ov = 1U;
else {
int32_T tmp_0;
{
/*undefined sequence*/
tmp_0 = div_s32((int)ALARM_Functional_B.Tolerance_Max,100);
}
if ((int)ALARM_Functional_B.Flow_Rate > (int)ALARM_Functional_B.Commanded_Flow_Rate * tmp_0 + (int)ALARM_Functional_B.Commanded_Flow_Rate)
ov = 1U;
else {
int32_T tmp;
{
/*undefined sequence*/
tmp = div_s32((int)ALARM_Functional_B.Tolerance_Min,100);
}
if ((int)ALARM_Functional_B.Flow_Rate > (int)ALARM_Functional_B.Commanded_Flow_Rate * tmp + (int)ALARM_Functional_B.Commanded_Flow_Rate)
ov = 2U;
}
}
return ov;
}
Appreciate any help in explaining why this happens.
/* undefined sequence */ in a block simply means that the block has been generated during the code normalization at parsing time but that with respect to C semantics there is no sequence point between the statements composing it. For instance x++ + x++ will be normalized as
{
/*undefined sequence*/
tmp = x;
x ++;
tmp_0 = x;
x ++;
;
}
Internally, each statement in such a sequence is decorated with lists of locations that are accessed for writing or reading (use -kernel-debug 1 with -print to see them in the output). Option -unspecified-access used together with -val will check that such accesses are correct, i.e. that there is at most one statement inside the sequence that write to a given location and if this is the case, that there is no read access to it (except for building the value it is assigned to). In addition, this option does not take care of side-effects occurring in a function call inside the sequence. There is a special plug-in for that, but it has not been released yet.
Finally note that since Frama-C Neon, the comment reads only /*sequence*/, which seems to be less daunting for the user. Indeed, the original code may be correct or may show undefined behavior, but syntactic analysis is too weak to decide in the general case. For instance, (*p)++ + (*q)++ is correct as long as p and q do not overlap. This is why the normalization phase only points out the sequences and leaves it up to more powerful analysis plug-ins to check whether there might be an issue.

Which Frama-C version is best suited to develop a slicing plugin?

I want to explore Frama-C to apply Assertion-based Slicing (using ACSL notation).
I have found that there are several different versions of Frama-C with some different features.
My question is which version is best suited to develop a a slicing plugin to Frama-C and to manipulate the AST created by Frama-C.
There already is a slicing plug-in in Frama-C (in all versions).
This plug-in uses the results of the value analysis plug-in, which assumes the properties written inside ACSL assertions (after having attempted to verify them).
So, depending on what you call “assertion-based slicing” (and be aware that the article that comes up first in Google is behind a paywall), what you propose to do may already exists as a Frama-C plug-in (and one that works pretty well as of the last two or three Frama-C versions).
To answer your question anyway, the best version to use is the latest one, which is Fluorine 20130601 as of this writing.
Example of existing slicing features in Frama-C:
$ cat t.c
int f(unsigned int x)
{
int y;
/*# assert x == 0 ; */
if (x)
y = 9;
else
y = 10;
return y;
}
$ frama-c -sparecode t.c -main f
...
t.c:4:[value] Assertion got status unknown.
...
/* Generated by Frama-C */
int f(unsigned int x)
{
int y;
/*# assert x ≡ 0; */
;
y = 10;
return (y);
}
Is the above what you have in mind when you speak of “assertion-based slicing”?
Note: Frama-C's option -sparecode is a slicing option for the criterion “preserve all results of the program”. It still removes any statement that is without consequences, such as y=3; in y=3; y=4;, and being based on Frama-C's value analysis, it removes anything that is considered unreachable or without consequences because of the value analysis' results.
Another example to illustrate:
$ cat t.c
int f(unsigned int x)
{
int y;
int a, b;
int *p[2] = {&a, &b};
/*# assert x == 0 ; */
a = 100;
b = 200;
if (x)
y = 9;
else
y = 10;
return y + *(p[x]);
}
$ frama-c -sparecode t.c -main f
...
t.c:6:[value] Assertion got status unknown.
...
/* Generated by Frama-C */
int f(unsigned int x)
{
int __retres;
int y;
int a;
int *p[2];
p[0] = & a;
/*# assert x ≡ 0; */
;
a = 100;
y = 10;
__retres = y + *(p[x]);
return (__retres);
}

Understanding Frama-C slicer results

I'd like to know if it's possible to do some kind of forward conditioned slicing with Frama-C and I'm playing with some examples to understand how one could achieve this.
I've got this simple example which seems to result in an imprecise slice and I can't understand why. Here is the function I'd like to slice :
int f(int a){
int x;
if(a == 0)
x = 0;
else if(a != 0)
x = 1;
return x;
}
If I use this specification :
/*# requires a == 0;
# ensures \old(a) == a;
# ensures \result == 0;
*/
then Frama-C returns the following slice (which is precise), using "f -slice-return" criterion and f as entry point :
/*# ensures \result ≡ 0; */
int f(void){
int x;
x = 0;
return x;
}
But when using this specification :
/*# requires a != 0;
# ensures \old(a) == a;
# ensures \result == 1;
*/
then all instructions (& annotations) remain (when I was waiting for this slice to be returned :
/*# ensures \result ≡ 1; */
int f(void){
int x;
x = 1;
return x;
}
)
In the last case, is the slice imprecise? In this case, what could be the cause?
Regards,
Romain
Edit : I wrote "else if(a != 0) ..." but the problem remains with "else ..."
In Frama-C, the slicing plug-in relies on the result of a preliminary static analysis plug-in called the value analysis.
This value analysis can represent the values for variable a when a == 0 (the set of values is in this case { 0 }) but has a hard time to represent the values for a when it is known that a != 0. In the latter case, if a is not already known to be positive or negative, the value analysis plug-in needs to approximate the set of values for a. If a was known to be positive, for instance if it was an unsigned int, then the nonzero values could be represented as an interval, but the value analysis plug-in cannot represent “all values of type int except 0”.
If you are willing to change the pre-condition, you can write it in a form that is more easily understood by the value analysis plug-in (together with value analysis option -slevel):
$ cat t.c
/*# requires a < 0 || a > 0 ;
# ensures \old(a) == a;
# ensures \result == 0;
*/
int f(int a){
int x;
if(a == 0)
x = 0;
else if(a != 0)
x = 1;
return x;
}
$ frama-c -slevel 10 t.c -main f -slice-return f -then-on 'Slicing export' -print
…
/* Generated by Frama-C */
/*# ensures \result ≡ 0; */
int f(void)
{
int x;
x = 1;
return x;
}
This has no relevance whatsoever with your main question, but your ensures a == \old(a) clause is not doing what you expect. If you pretty-print your source code with option -print, you will see it has been silently transformed into ensures \old(a) == \old(a).
The ACSL language does not permit referring about the value of formal variables in the post-state, mostly because this is meaningless from the point of view of the caller. (The stack frame of the callee is popped after the call terminates.)

Resources