How to prove an iterative loop with computations in frama-c wp?

548 views Asked by At

I have my test code (to study the WP loop invariants) which adds two long integers with each digit's representation in an array cell:

int main(int argc, const char * argv[]) {

char a[32], b[32];//size can be very big
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
scanf("%s %s", a, b);
unsigned int size1 = strlen(a);
unsigned int size2 = strlen(b);
//code to reverse a string. currently proved 
reverse(a, size1);
reverse(b, size2);
for (unsigned int i = 0; i < size1; i++) a[i]-='0'; //move from chars to integers
for (unsigned int j = 0; j < size2; j++) b[j]-='0';
unsigned int maxsize = size1;
if (size2 > maxsize) maxsize = size2;
int over = 0;
//actual computation code

/*@
loop invariant maxsize == \max(size1, size2);
loop invariant bound: 0 <= k <= maxsize;
loop invariant ov: \forall integer i; 0 < i < k ==> \at(a[i], Here) == (\at(a[i], Pre) + b[i]+ Over((char *)a, (char *)b, i)) % 10;
loop assigns k, a[0..maxsize-1],over;
loop variant maxsize - k;
*/
for (unsigned int k = 0; k < maxsize; k++)
{
    char sum=a[k] + b[k] + over; 
    //over=overflow is for 9+9 = 18, result=8, over=1
    a[k] = sum % 10;
    over = sum / 10;
}
if (over != 0) a[maxsize++] = over;
//...
return 0;
}

I want to specify only the last loop invariant. I have already tried some pieces of ACSL code with \at(a[i], LoopCurrent). I received Unknown status or Timeout. Finally, I finished with an axiomatic recursive solution without any success.

I have no ideas now what else should I try to verify this. Help?

UPDATE: Created a function for actual calculations. Improved axioms. Still got Timeout status. Ran with Frama-C latest release from opam with default settings (timeout=10, prover Alt-Ergo).

/*@
@predicate
Unchanged{K,L}(char* a, integer first, integer last) = \forall integer i; first <= i < last ==> \at(a[i],K) == \at(a[i],L);

axiomatic ReminderAxiomatic
{
logic integer Reminder {l} (integer x, integer y);
axiom ReminderEmpty: \forall integer x, integer y; x < y ==> Reminder(x, y) == x;
axiom ReminderNext: \forall integer x, integer y; x>=y ==> Reminder(x, y) == Reminder(x-y, y)+0; 
}

axiomatic DivAxiomatic
{
logic integer Div {l} (integer x, integer y);
axiom DivEmpty: \forall integer x, integer y; x < y ==> Div(x, y) == 0 ;
axiom DivNext: \forall integer x, integer y; x >= y ==> Div(x, y) == Div(x - y, y) + 1 ;
}

axiomatic OverAxiomatic
{
logic integer Over {L}(char *a, char * b, integer step) reads a[0..step-1], b[0..step-1];
axiom OverEmpty: \forall char *a, char * b, integer step; step <= 0 ==> Over(a, b, step) == 0;
axiom OverNext: \forall char *a, char * b, integer step; step > 0 && a[step-1]<10 && a[step-1]>=0 && b[step-1]<10 && b[step-1]>=0
==> Over(a, b, step) ==  Div((int) a[step-1]+(int)b[step-1]+(int)Over(a,b,step-1), 10);
}
*/
/*@
requires 0 <= maxsize < 10;
requires \valid (a+(0..maxsize-1));
requires \valid (b+(0..maxsize-1));
requires \valid (res+(0..maxsize));
requires \forall integer i; 0 <= i < maxsize ==> 0 <= (int)a[i] < 10;
requires \forall integer i; 0 <= i < maxsize ==> 0 <= (int)b[i] < 10;
*/
void summ(char *a, char *b, char *res, unsigned int maxsize) {

char over = 0;

/*@
loop invariant bound: 0 <= k <=maxsize;
loop invariant step: \forall integer i; 0 <= i < k ==> res[i] == (char) Reminder((int)a[i]+ (int)b[i]+ (int)Over((char *)a, (char *)b, i) , 10);
loop invariant unch: Unchanged{Here,LoopEntry}((char *)res, k, maxsize);
loop assigns k, over, res[0..maxsize-1];
loop variant maxsize-k;
*/
for (unsigned int k = 0; k < maxsize; k++)
{
 char sum = a[k] + b[k] + over;
 res[k] = sum % 10;
 over = sum / 10;
}

//
if (over != 0) res[maxsize++] = over;

}
1

There are 1 answers

5
Virgile On

First of all, it would be much better if your question contained a MCVE, which in the present case include the C function you're currently working on (instead of only its body), and the ACSL annotations you've written, at their exact place in the code. The command line used to launch Frama-C wouldn't hurt either, as well as the list of annotations for which you have trouble.

Apart from that, here are a few things that might be related to your problem (again, without an exact description, it's difficult to tell for sure).

  • the loop assigns is wrong: you're assigning over in the loop, but it is not mentioned there.
  • I'm unsure whether \max is well supported by WP.
  • modulo and integer division are two operations automated provers tend to struggle with. You might need a few additional assertions and/or axioms to help them.
  • your loop invariant does not say anything about the values of a[i] for k <= i< maxsize. Since loop assigns indicates that all cells of a might have been modified, you have to add an invariant telling that the ones with higher indices have not been touched so far.
  • I'm not completely sure about your usage of \at(a[i],Pre): Pre indicates the beginning of the current function. Thus, if scanf and reverse are in the same function as the loop (again, a MCVE would have clarified this), this is not true. You might want to speak about \at(a[i],Loop_entry) to refer to the value of the cell in the state in which the loop is entered for the first time.

UPDATE

I'm afraid WP will not be able to completely prove your annotations, but I managed to obtain a version in which only a single lemma, that is basically an expanded version of the reads clause for the Over function is left unproved (and I believe that there is no way to prove it under WP's actual representation of C memory). Note that it implies playing with the TIP feature of WP. Code and scripts are provided below, but I'll start by commenting on your original version:

  • there is no need for Div and Reminder. If anything, they will confuse the prover. You can stick with xxx/10 and xxx%10. In fact, my remark on division and modulo was a bit too cautious. In the present case, everything seems to be fine at this level.
  • Similarly, Unchanged could be inlined, but it shouldn't hurt to keep it that way.
  • I have used unsigned char instead of char, as it avoids some spurious conversions due to integer promotions, and removed any cast in the logic. You normally don't have to introduce such casts in ACSL annotations, except perhaps to indicate that some computation stays within an appropriate range (as in \let x = y + z; x == (int) x; ). Such casts are translated into function calls in the proof obligation, and again it can confuse the provers.
  • an invariant was missing to indicate that over does indeed contain the potential carry from the previous step (again, a good hint that you're missing an invariant is that a location mentioned in loop assigns does not appear in any loop invariant).
  • Finally, there is a small subtlety: if you don't indicate somehow that over can only be 0 or 1, there's nothing that prevent the addition to overflow the unsigned char (making it impossible to prove that a C computation with unsigned char and its ACSL counterpart with unbounded integers give the same result). Due to the recursive nature of the Over function, this can be established by a so-called lemma function in which the mathematical induction is carried out by a simple for loop with appropriate invariants
  • In addition, I've added explicitely as invariant that a and b are left untouched. This is normally implied by the loop assigns, but having these explicit hypotheses makes it easier during the scripts.

To sum up, here is the final code:

/*@
axiomatic Carry {
 logic integer Over {L}(unsigned char* a, unsigned char* b, integer step)
 reads a[0 .. step - 1], b[0 .. step - 1];

 axiom null_step: \forall unsigned char* a, *b, integer step;
                  step <= 0 ==> Over(a,b,step) == 0;
 axiom prev_step: \forall unsigned char* a, *b, integer step;
                  step > 0 ==>
                  Over(a,b,step) ==
                  (a[step-1] + b[step - 1] + Over(a,b,step-1)) / 10;

lemma OverFootPrint{L1,L2}:
\forall unsigned char* a, unsigned char*b, integer step;
(\forall integer i; 0<=i<step ==> \at(a[i],L1) == \at(a[i],L2)) &&
(\forall integer i; 0<=i<step ==> \at(b[i],L1) == \at(b[i],L2)) ==>
Over{L1}(a,b,step) == Over{L2}(a,b,step);
}
*/

/*@
  requires \valid(a+(0 .. step-1));
  requires \valid(b+(0 .. step - 1));
  requires \forall integer i; 0<=i<step ==> 0<=a[i]<10 && 0<=b[i]<10;
  assigns \nothing;
  ensures 0<= Over(a,b,step) <= 1;
*/
void lemma_function(unsigned char* a, unsigned char* b, unsigned int step) {
  /*@
      loop invariant 0<=i<=step;
      loop invariant \forall integer k; 0<=k<=i ==> 0 <= Over(a, b, k) <= 1;
      loop assigns i;
  */
  for (int i = 0; i < step; i++);
}

/*@
requires 0 <= maxsize < 10;
requires \valid (a+(0..maxsize-1));
requires \valid (b+(0..maxsize-1));
requires \valid (res+(0..maxsize));
requires \separated (a+(0..maxsize-1),res+(0..maxsize));
requires \separated (b+(0..maxsize-1),res+(0..maxsize));
requires \forall integer i; 0 <= i < maxsize ==> 0 <= a[i] < 10;
requires \forall integer i; 0 <= i < maxsize ==> 0 <= b[i] < 10;
*/
void summ(unsigned char* a, unsigned char*b, unsigned char* res,
          unsigned int maxsize) {

unsigned char over = 0;

/*@
loop invariant bound: 0 <= k <=maxsize;
loop invariant step: \forall integer i; 0 <= i < k ==> res[i] == (a[i]+ b[i]+ Over(a,b,i)) % 10;
loop invariant over: over == Over(a,b,k);
loop invariant a_unchanged: \forall integer i; 0 <= i < maxsize ==>
\at(a[i],LoopEntry) == a[i];
loop invariant b_unchanged: \forall integer i; 0 <= i < maxsize ==>
\at(b[i],LoopEntry) == b[i];
loop invariant unch: \forall integer i; k<=i<=maxsize ==> \at(res[i],LoopEntry) == res[i];
loop assigns k, over, res[0..maxsize-1];
loop variant maxsize-k;
*/
for (unsigned int k = 0; k < maxsize; k++)
{
 unsigned char sum = a[k] + b[k] + over;
 res[k] = sum % 10;
 over = sum / 10;
 lemma_function(a,b,k);
}

//
if (over != 0) res[maxsize++] = over;

}

The two TIP scripts are available here. To use them, put them in a directory scripts/typed next to the code (say file.c) and use this command line:

frama-c[-gui] -wp -wp-prover alt-ergo,script -wp-session scripts file.c

You can change the name of the scripts directory if you wish (it must match the argument of -wp-session), but typed and the filenames of the scripts must be as given since WP will use them to detect which proof obligation they are supposed to prove. In GUI mode, you can have a look at the script, but it is probably difficult to understand and explaining each step will not fit in a SO's answer.