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;
}
Related
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.
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;
}
}
I am working on the following proof and the invariant result_val is proved with an induction strategy on i using begin as the base case.
The sup case is trying to prove true which holds trivially using Frama-C 24.0. But when I switch to 25.0, it tries to prove a seemingly more complicated condition, which looks closer to a correct inductive inference because it did the weakest precondition computation explicitly.
However, all SMT solvers I tried cannot prove the condition generated by Frama-C 25.0.
I am a bit worried about the correctness of version 24.0's result because using true as the inductive proof goal seems to be unlikely. Can anyone hint to me at what happened? Is that a bug in 24.0 or just some difference in the implementation?
#include <stdbool.h>
#define SIZE 1000
bool data[SIZE] ;
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : (data[begin]==true) ? count(begin+1, end)+1 : count(begin+1, end);
*/
/*#
requires SIZE > begin >= 0;
requires SIZE >= end >= 0;
requires begin <= end;
assigns \nothing;
ensures \result == count(begin, end);
*/
unsigned int occurrences_of(int begin, int end)
{
unsigned int result = 0;
/*#
loop invariant i_bound: begin <= i <= end;
loop invariant result_bound: 0 <= result <= i-begin;
loop invariant result_val: result == count(begin, i);
loop assigns i, result;
loop variant end-i;
*/
for (unsigned int i = begin; i < end; ++i){
result += (data[i] == true) ? 1 : 0;
}
return result;
}
Below is the result from Frama-c 24.0
Proof:
Goal Invariant 'result_val' (preserved) (Induction: proved)
+ Goal Induction (Base) (proved)
+ Goal Induction (Induction (sup)) (proved)
+ Goal Induction (Induction (inf)) (proved)
Qed.
--------------------------------------------------------------------------------
Goal Induction (Induction (sup)):
Prove: true.
Below is the result from Frama-c 25.0
--------------------------------------------------------------------------------
Proof:
Goal Invariant 'result_val' (preserved) (Induction: pending)
+ Goal Induction (Base) (proved)
+ Goal Induction (Induction (sup)) (pending)
+ Goal Induction (Induction (inf)) (proved)
End.
--------------------------------------------------------------------------------
Goal Induction (Induction (sup)):
Let x_0 = to_uint32(end#L1).
Let x_1 = to_uint32(tmp#L12).
Let x_2 = data#L1[i#L6].
Let x_3 = result#L6.
Let x_4 = result#L13.
Let x_5 = to_uint32(1 + i#L6).
Assume {
Have: begin#L1 < i#L6.
Have: i#L6 <= end#L1.
Have: i#L6 < x_0.
Have: 0 <= x_3.
Have: x_5 <= end#L1.
Have: begin#L1 <= x_5.
Have: (begin#L1 + x_3) <= i#L6.
Have: (begin#L1 + x_4) <= x_5.
Have: is_uint32(i#L6).
Have: is_bool(x_2).
Have: is_uint32(x_3).
Have: if (x_2 = 1) then (tmp#L12 = 1) else (tmp#L12 = 0).
Have: forall i_0 : Z. let x_6 = L_count(data#L1, begin#L1, i_0) in
let x_7 = to_uint32(1 + i_0) in let x_8 = to_uint32(x_1 + x_6) in
let x_9 = data#L1[i_0] in ((i_0 <= end#L1) -> ((begin#L1 <= i_0) ->
((i_0 < i#L6) -> ((i_0 < x_0) -> ((0 <= x_6) -> ((x_7 <= end#L1) ->
((begin#L1 <= x_7) -> (((begin#L1 + x_6) <= i_0) ->
(((begin#L1 + x_8) <= x_7) -> (is_uint32(i_0) -> (is_bool(x_9) ->
(is_uint32(x_6) ->
((if (x_9 = 1) then (tmp#L12 = 1) else (tmp#L12 = 0)) ->
(L_count(data#L1, begin#L1, x_7) = x_8)))))))))))))).
[...]
Stmt { L6: }
Stmt { tmp = tmp_0; }
Stmt { L12: result = x_4; }
Stmt { L13: }
}
Prove: L_count(data#L1, begin#L1, x_5) = x_4.
Goal id: typed_occurrences_of_loop_invariant_result_val_preserved
Short id: occurrences_of_loop_invariant_result_val_preserved
--------------------------------------------------------------------------------
Prover Alt-Ergo 2.4.2: Timeout (Qed:52ms) (10s).
A bug on the typing of the induction tactic was indeed fixed between Frama-C 24 and 25 (https://git.frama-c.com/pub/frama-c/-/commit/6058453cce2715f7dcf9027767559f95fb3b1679). And the symptom was indeed that the tactic could generate ill-typed formulas with true instead of a term.
Proving this example in not that easy. For two main reasons:
the function and the definition work in the opposite directions,
the definition does not have an optimal expression for reasoning.
However, one can write a lemma function to solve the problem:
#include <stdbool.h>
#define SIZE 1000
bool data[SIZE] ;
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : ((data[begin]==true) ? count(begin+1, end)+1 : count(begin+1, end));
*/
/*# ghost
/# requires begin < end ;
assigns \nothing ;
ensures count(begin, end) == ((data[end-1]==true) ? count(begin, end-1)+1 : count(begin, end-1));
#/
void lemma(bool* d, int begin, int end){
/# loop invariant begin <= i < end ;
loop invariant count(i, end) == ((data[end-1]==true) ? count(i, end-1)+1 : count(i, end-1));
loop assigns i ;
loop variant i - begin ;
#/
for(int i = end-1 ; i > begin ; i--);
}
*/
/*#
requires SIZE > begin >= 0;
requires SIZE >= end >= 0;
requires begin <= end;
assigns \nothing;
ensures \result == count(begin, end);
*/
unsigned int occurrences_of(int begin, int end)
{
unsigned int result = 0;
/*#
loop invariant i_bound: begin <= i <= end;
loop invariant result_bound: 0 <= result <= i-begin;
loop invariant result_val: result == count(begin, i);
loop assigns i, result;
loop variant end-i;
*/
for (unsigned int i = begin; i < end; ++i){
result += (data[i] == true) ? 1 : 0;
//# ghost lemma(data, begin, i+1);
}
return result;
}
I'd suggest to use the following definition:
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : ((data[end-1]==true) ? 1 : 0) + count(begin, end-1);
*/
It works in the same direction as the function and avoids the duplication of the term count(begin, end-1) which makes reasoning easier.
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.
I'm new to Frama-c and I'd like to understand what is the problem with this simple example :
/*# requires \valid(array+(0..length-1))
# ensures \forall integer k; 0 <= k < length ==> array[k] == 0;
# assigns array[0..length-1];
*/
void fill(int array[], int length){
/*# loop invariant 0 <= i <= length;
# loop invariant \forall integer k; 0 <= k < i ==> array[k] == 0;
# loop assigns i, array[0..i-1];
# loop variant length - i;
*/
for(int i = 0; i < length; i++){
array[i] = 0;
}
}
In this example, Frama-c (WP + Value) won't prove the assign clause in the function contract and I can't understand why. Any help will be appreciated =)
This can be proven (with alt-ergo 0.95.1) if you weaken your loop assigns.
# loop assigns i, array[0..length-1];
The precondition i >= 0 is also needed, because it is not implied by \valid(array+(0..length-1). array+(0..length-1) is a valid set of locations with length <= 0, although an empty one.
The fact that your original loop assigns does not imply your precondition is a limitation of the current version of the WP plugin.