Why WP can't deduce "else" close? - frama-c

I'm trying to write the specification for a function which takes 2 pointers to int and put the smaller value to the first pointer and the other to the second.
Here is the code and the specification:
/*#
requires \valid(a) && \valid(b);
assigns *a, *b;
ensures *a > *b ==> *a == \old(*b) && *b == \old(*a);
ensures *a <= *b ==> *a == \old(*a) && *b == \old(*b);
*/
void order(int * a, int *b) {
if (*a > *b) {
int tmp = *a;
*a = *b;
*b = *a;
}
}
And here is the output of frama-c -wp so.c:
[kernel] Parsing so.c (with preprocessing)
[wp] Warning: Missing RTE guards
[wp] 4 goals scheduled
[wp] [Alt-Ergo] Goal typed_order_post_2 : Unknown (Qed:4ms) (366ms)
[wp] Proved goals: 3 / 4
Qed: 2 (0.40ms-2ms-5ms)
Alt-Ergo: 1 (30ms) (16) (unknown: 1)
WP can't prove the second post-condition, can you explain me why?

It turns out the solution to my question is in Alain Blanchard's tutorial, $3.3.1.1. (here)
In the post-conditions, I'm missing the \old primitives around *a and *b.
So the conditions should read:
ensures \old(*a) < \old(*b) ==> *a == \old(*b) && *b == \old(*a) ;
ensures \old(*a) >= \old(*b) ==> *a == \old(*a) && *b == \old(*b) ;
Without the \old, the left parts of the implications are about the values after the function execution.

Related

Failed to verifying the occurrence of a value in an array with logic function

I tried to use Frama-c to prove the occurrence of true value in an array that is the same as a integer which used to record the number of true values. But the prove failed if I want to change some values false to true.
Is there any method to solve this problem?
(Frama-c 25.0 / Alt-Ergo 2.4.2 / CVC4 1.8 / Z3 4.8.6)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define SIZE 150
bool table[SIZE] = {true};
int true_counter;
/*#
logic integer count_true(integer idx) =
idx<=0 ? 0 :
(table[idx-1]==true ? count_true(idx-1) + 1 : count_true(idx-1));
*/
/*#
requires table[0] == false;
requires true_counter == count_true(SIZE);
assigns table[0], true_counter;
ensures table[0] == true;
ensures true_counter == \old(true_counter) + 1;
*/
void ATEST(void) {
int countBF = 0;
int countAF = 0;
/*#
loop invariant 0 <= idx <= SIZE;
loop invariant 0 <= countBF == count_true(idx) <= idx;
loop assigns idx, countBF;
loop variant SIZE - idx;
*/
for(int idx=0; idx<SIZE; idx++) {
if(table[idx] == true) countBF += 1;
}
//# assert true_counter == countBF;
table[0] = true;
true_counter += 1;
/*#
loop invariant 0 <= idx <= SIZE;
loop invariant 0 <= countAF == count_true(idx) <= idx;
loop assigns idx, countAF;
loop variant SIZE - idx;
*/
for(int idx=0; idx<SIZE; idx++) {
if(table[idx] == true) countAF += 1;
}
//# assert true_counter == countAF;
//# assert countAF == countBF + 1;
}
[kernel] Parsing test.c (with preprocessing)
[rte:annot] annotating function ATEST
[wp] 33 goals scheduled
[wp] [Failed] Goal typed_ATEST_assert_2
Z3 4.8.6: Unknown (Qed:8ms) (cached)
CVC4 1.8: Timeout (Qed:8ms) (10s) (cached)
Alt-Ergo 2.4.2: Timeout (Qed:8ms) (10s) (cached)
[wp] [Cache] found:17
[wp] Proved goals: 32 / 33
Qed: 26 (0.84ms-7ms-24ms)
Alt-Ergo 2.4.2: 6 (7ms-13ms-27ms) (629) (interrupted: 1) (cached: 7)
CVC4 1.8: 4 (20ms-28ms-30ms) (11375) (interrupted: 1) (cached: 5)
Z3 4.8.6: 4 (10ms-20ms) (46252) (unknown: 1) (cached: 5)
The problem is that such a reasoning is not that direct. Here is what happen: when we write cell 0, WP creates a new memory whose properties are directly related to the old memory. The loop invariant of the first loop gives to the solver some information about the original memory, the loop invariant of the second loop gives information about the new memory. But for establishing the link between the two we need to learn something like "since only on value has changed, the remaining part has the same number of occurrences". Which is not possible with most SMT solvers.
For this particular example, we can be brutal and directly push the information in the invariant of the second loop:
/*#
loop invariant 0 <= idx <= SIZE;
loop invariant 0 <= countAF == count_true(idx) <= idx;
// added:
loop invariant idx >= 1 ==> countAF == 1 + count_true{Pre}(idx) <= idx;
loop assigns idx, countAF;
loop variant SIZE - idx;
*/
for(int idx=0; idx<SIZE; idx++) {
if(table[idx] == true) countAF += 1;
}
Which allows to create the link between the number of occurrences in the pre-state with the current number of occurrences. A more elegant solution is to dedicate a bit of ghost code so that it applies to any location:
/*#
requires 0 <= loc < SIZE ;
requires table[loc] == false;
requires true_counter == count_true(SIZE);
assigns table[loc], true_counter;
ensures table[loc] == true;
ensures true_counter == \old(true_counter) + 1;
*/
void ATEST(int loc) {
int countBF = 0;
int countAF = 0;
/*#
loop invariant 0 <= idx <= SIZE;
loop invariant 0 <= countBF == count_true(idx) <= idx;
loop assigns idx, countBF;
loop variant SIZE - idx;
*/
for(int idx=0; idx<SIZE; idx++) {
if(table[idx] == true) countBF += 1;
}
//# assert true_counter == countBF;
table[loc] = true;
true_counter += 1;
/*# ghost
/# loop invariant 0 <= i <= loc ;
loop invariant count_true(i) == count_true{Pre}(i);
loop assigns i;
loop variant loc - i ;
#/
for(int i = 0 ; i < loc ; i++);
/# loop invariant loc < i <= SIZE ;
loop invariant count_true(i) == 1 + count_true{Pre}(i);
loop assigns i;
loop variant SIZE - i ;
#/
for(int i = loc+1 ; i < SIZE ; i++);
*/
/*#
loop invariant 0 <= idx <= SIZE;
loop invariant 0 <= countAF == count_true(idx) <= idx;
loop assigns idx, countAF;
loop variant SIZE - idx;
*/
for(int idx=0; idx<SIZE; idx++) {
if(table[idx] == true) countAF += 1;
}
}
On can also express lemmas to handle the problem. For example the following lemmas can provide enough information to solvers so that they can finish the proof:
/*# lemma same_count{L1,L2}:
\forall integer size; 0<= size < SIZE ==>
(\forall integer i; 0 <= i < size ==> \at(table[i], L1) == \at(table[i],L2)) ==>
count_true{L1}(size) == count_true{L2}(size);
*/
/*# lemma same_but_one{L1,L2}:
\forall integer size; 0 <= size < SIZE ==>
\forall integer i_diff; 0 <= i_diff < size ==>
(\forall integer i; 0 <= i < size && i != i_diff ==> \at(table[i],L1) == \at(table[i],L2))
&& \at(table[i_diff],L1) == false && \at(table[i_diff],L2) == true
==>
count_true{L1}(size) + 1 == count_true{L2}(size);
*/
The first lemma states that an unchanged memory region keeps the same number of occurrences as before. The second states that when in a state a cell has true while in another it is false and all remaining cells are the same, the number of occurrences changes by one. These two lemmas can be verified via the induction tactic (see for example https://stackoverflow.com/a/73796135/4628125).
These two lemmas can be used directly by the solvers to get a proof for any location.

Failed to establish an invariant requiring the knowledge of the content of an array

I try to use Frama-C to prove the invariant below. With the precondition that the array clean has at least one "true" in the first half, the loop should terminate before the last iteration and hence i will never be increased to BLOCK_NUM (=1000). However, Frama-C seems to ignore the content of clean and failed to establish the loop invariant. Does anyone know how to fix this proof?
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#define BLOCK_NUM 1000
bool clean[BLOCK_NUM] ; // clean bit for physical block; phy block ID -> bool
/*#
requires \exists integer j; 0 <= j < BLOCK_NUM/2 ==> clean[j] == true;
*/
void test(){
int i = 0;
/*#
loop assigns i;
loop invariant 0 <= i < BLOCK_NUM;
*/
while( i < BLOCK_NUM && clean[i] == false ){
i += 1;
}
}
I also tried a different version adding more information to the invariant
void test(){
int i = 0;
/*#
loop assigns i;
loop invariant 0 <= i < BLOCK_NUM &&
\exists integer j; i <= j < BLOCK_NUM/2 ==> clean[j] == true;
*/
while( i < BLOCK_NUM && clean[i] == false ){
i += 1;
}
//# assert 0 <= i < BLOCK_NUM;
}
This translates to the following proof condition.
Goal Invariant (preserved):
Assume {
Type: is_sint32(i#L4) /\ is_sint32(1 + i#L4).
[...]
Stmt { L4: }
(* Invariant *)
Have: (0 <= i#L4) /\ (i#L4 <= 999) /\
(((i#L4 <= i_3) -> ((i_3 <= 499) -> (clean#L1[i_3] = 1)))).
(* Then *)
Have: clean#L1[i#L4] = 0.
}
Prove: ((-1) <= i#L4) /\ (i#L4 <= 998) /\
(exists i_4 : Z. ((i#L4 < i_4) -> ((i_4 <= 499) -> (clean#L1[i_4] = 1)))).
--------------------------------------------------------------------------------
Prover Alt-Ergo 2.4.1: Timeout (Qed:101ms) (10s) (cached).
But still all solvers (including Z3 and CVC4) fail to show it.
There are two issues in your specification. First, your precondition is incorrect. What you want to say is that there exists a j such that j is between appropriate bounds and clean[j] == true, i.e.
requires \exists integer j; 0 <= j < BLOCK_NUM/2 && clean[j] == true;
More generally, and this can indeed be quite confusing at first, while a universal quantification generally goes along the lines of \forall x; P(x) ==> Q(x) (For any x such that P(x) holds, then Q(x) holds as well), existential quantification is usually something like \exists x, P(x) && Q(x): if you use implication instead of conjunction in that case, then any x that does not satisfy P will be a witness for the existential formula.
The second point is that your loop invariant is too weak: you don't say anything about the values you've examined so far: with just the bounds on i, it is impossible to deduce that we will eventually check the value of the cell containing true. By adding as an invariant that we have examined all the cells before i (and found false, since otherwise we wouldn't be in the loop anymore), all the proofs can be completed automatically:
loop invariant all_false: \forall integer j; 0 <= j < i ==> clean[j] == false;
Note that I've also added a name all_false to the new loop invariant. As your number of clauses grows, it is useful to give them explicit names, that will be used in Frama-C's output, so that you can quickly spot which proof obligation failed.
For what it's worth, here is the complete file (I removed the unnecessary includes)
#include <stdbool.h>
#define BLOCK_NUM 1000
bool clean[BLOCK_NUM] ; // clean bit for physical block; phy block ID -> bool
/*#
requires \exists integer j; 0 <= j < BLOCK_NUM/2 && clean[j] == true;
*/
void test(){
int i = 0;
/*#
loop assigns i;
loop invariant all_false: \forall integer j; 0 <= j < i ==> clean[j] == false;
loop invariant 0 <= i < BLOCK_NUM;
*/
while( i < BLOCK_NUM && clean[i] == false ){
i += 1;
}
}

Assertion on pointer to array

I have defined the following function which is well proved by frama-c:
//ensures array <= \result < array+length && *\result == element;
/*#
requires 0 < length;
requires \valid_read(array + (0 .. length-1));
assigns \nothing;
behavior in:
assumes \exists int off ; 0 <= off < length && array[off] == element;
ensures *\result == element;
behavior notin:
assumes \forall int off ; 0 <= off < length ==> array[off] != element;
ensures \result == 0;
disjoint behaviors;
complete behaviors;
*/
int* search(int* array, int length, int element){
int *tmp;
/*#
loop invariant 0 <= i <= length;
loop invariant \forall int j; 0 <= j < i ==> array[j] != element;
loop assigns i;
loop variant length-i;
*/
for(int i = 0; i < length; i++)
{
if(array[i] == element)
{
tmp = &array[i];
//# assert *tmp==element;
}
else
{
tmp = 0;
}
}
return tmp;
}
and I use it in the following main entry:
int main(){
int arr[5]={1,2,3,4,5};
int *p_arr;
p_arr = search(arr,5,4);
//# assert *p_arr==30;
return 0
}
I am wondering why frama-c give the assertion "//# assert *p_arr==30;" as true, I do not understand.
Thanks
Using the command line only, I saw some problems in your code:
tmp is missing in the loop assigns;
you need to:
either add a break in the then branch of the seach function
(then you would return the pointer on the first element that match)
or initialize tmp = 0 at the beginning of the function and remove the else branch in the loop (then you would return a pointer on the last occurrence).
I didn't try the GUI, but it seems strange that you say that your example is:
well proved by frama-c
I suggest that you use the command line to begin with.
Ok now I correct my code as follow:
//ensures array <= \result < array+length && *\result == element;
/*#
requires 0 < length;
requires \valid_read(array + (0 .. length-1));
assigns \nothing;
behavior in:
assumes \exists int off ; 0 <= off < length && array[off] == element;
ensures *\result == element;
behavior notin:
assumes \forall int off ; 0 <= off < length ==> array[off] != element;
ensures \result == 0;
disjoint behaviors;
complete behaviors;
*/
int* search(int* array, int length, int element){
/*#
loop invariant 0 <= i <= length;
loop invariant \forall int j; 0 <= j < i ==> array[j] != element;
loop assigns i;
loop variant length-i;
*/
for(int i = 0; i < length ; i++)
{
if(array[i] == element)
{
return &array[i];
}
}
return 0;
}
and add the following assertion:
int main()
{
int arr[5] = {1,2,3,4,5};
int *ptr;
ptr = search(arr,5,3);
//# assert *ptr==3;
}
then run: frama-c -wp -rte myfile.c and got the result:
[wp] Proved goals: 65 / 65
Qed: 35 (1.00ms-6ms-24ms)
Alt-Ergo: 30 (16ms-30ms-94ms) (132)
If I set another assertion :
int main()
{
int arr[5] = {1,2,3,4,5};
int *ptr;
ptr = search(arr,5,3);
//# assert *ptr==5;
}
Then I get the output:
[wp] [Alt-Ergo] Goal typed_main_assert_2 : Timeout (Qed:4ms) (10s)
[wp] Proved goals: 64 / 65
Qed: 35 (1.00ms-4ms-10ms)
Alt-Ergo: 29 (16ms-28ms-109ms) (132) (interrupted: 1)
So the assertion is "unknown" as we expected and if we run frama-c-gui the bullet is orange.
So that's working fine , take care about wrong axiomatic stuff !
Thank you Anne for your help.

Frama-c verification (From decimal to binary function)

i'm trying to verify this in frama-c. I don't understand why the post-condition can't be verified. Can someone help me , please?
I initially made ​​an axiomatic with a base case and an inductive case. The problem is that the post-condition is not verified. All the loop invariant are true.
#include <stdio.h>
/*# axiomatic ToBinary {
logic integer tobinary{L}(integer n)
reads n;
axiom binary1{L} :
\forall integer n;
(n == 0) ==> tobinary(n) == 0;
axiom binary2{L} :
\forall integer n;
(n > 0 ) ==> tobinary(n) == n%2 + 10*tobinary(n/2);
}
*/
/*#
requires n >= 0;
assigns \nothing;
ensures \result == tobinary(n);
*/
int decimal_binary(int n) /* Function to convert decimal to binary.*/
{
int rem, i=1, binary=0;
/*#
loop assigns n,rem,i,binary;
loop invariant 1 <= i <= i*10 ;
loop invariant 0 <= binary;
*/
while (n!=0)
{
rem=n%2;
n/=2;
binary+=rem*i;
i*=10;
}
return binary;
}

Frama-C/WP tutorial example "mismatch algorithm"

I am trying the WP tutorial examples with Frama-C (Neon-20140301) and Alt-Ergo. I got stuck with the "mismatch algorithm" example while other similar examples such as "equal" and "find" have no problems. The code is listed below. The post conditions P0, P2 are not being discharged. Can anyone tell me what is wrong here?
#include<stdbool.h>
/*#
# predicate IsEqual {A,B} (float* a, integer n, float* b) =
# \forall integer i; 0 <= i < n ==> \at(a[i], A) == \at(b[i], B);
*/a
/*# requires n >= 0;
# requires \valid(a+(0..n-1));
# requires \valid(b+(0..n-1));
#
# assigns \nothing;
#
# behavior all_equal:
# assumes IsEqual{Here,Here}(a, n, b);
# ensures P0: \result == n;
#
# behavior some_not_equal:
# assumes !IsEqual{Here,Here}(a, n, b);
# ensures P1: 0 <= \result < n;
# ensures P2: a[\result] != b[\result];
# ensures P3: IsEqual{Here,Here}(a, \result, b);
#
# complete behaviors;
# disjoint behaviors;
*/
bool mismatch (float* a, int n, float* b)
{
/*# loop invariant 0 <= i <= n;
# loop invariant IsEqual{Here,Here}(a, i, b);
# loop assigns i;
# loop variant n-i;
*/
for (int i=0; i<n; i++) {
if (a[i] != b[i])
return i;
}
return n;
}
Your function is supposed to return a bool, i.e. either 0 or 1. Any integral value x can be converted to _Bool: the boolean is 0 if x==0 and 1 otherwise (see C99 and C11 section 6.3.1.2). If you look at the code as normalized by Frama-C, this is exactly what is being done for return i and return n. There is thus no reason that \result should be equal to n or an index at which both arrays differ. If you make your function returning an int instead, everything is discharged by Alt-ergo.

Resources