简体   繁体   中英

How to prove the functionality of a C stringCompare function with Frama-C?

I would like to express, with Frama-c and the WP plug-in, that the stringCompare function written below acts as "it is supposed to" - namely: That given identical input strings, the function returns 0 and a result different from 0 if the strings are not identical. I have annotated the related functions as seen below and would like to be able to prove the unproven goals generated by WP, how can that be done?

The output I get from trying to run the plug-in with the annotations as is, can be seen below the code

#include <string.h>
#include <stdio.h>

/*@  
    requires validPointers: \valid_read(s1) && \valid_read(s2) ;
    requires validLengthS1: 100 >= strlen(s1) >= 0;
    requires validLengthS2: 100 >= strlen(s2) >= 0;
    assigns \nothing ;
    allocates \nothing ;  
    frees \nothing ;
    behavior allEqual:
        assumes \forall integer k; 0 <= k < n ==> s1[k] == s2[k];
        ensures \result == 0;
    behavior SomeDifferent:
        assumes \exists integer k; 0 <= k < n ==> s1[k] != s2[k];
        ensures \result != 0;
    
    disjoint behaviors;
    complete behaviors;
    */ 
int stringCompare(const char* s1, const char* s2, int n) {
    if (s1 == s2) 
        return 0;
    int i = 0;

    /*@ assert \valid_read(s1) ; */
    /*@ assert \valid_read(s2) ;*/
    /*@ loop invariant 0 <= i; 
        loop assigns i , s1, s2; */    
    while (*s1 == *(s2++))
    {
        /*@ assert i <= 2147483647 ; */
        ++i;
        if (*(s1++) == '\0')
            return 0;
    }
   
    return *(unsigned char*)s1 - *(unsigned char*)(--s2);
}

/*@ assigns \nothing ;
    ensures rightResult: \result == strlen(\old(str));
    ensures rightEndCharacter: str[\result] == '\0' ;  */
int stringLength(const char* str) {
    int result = 0;

    /*@ loop assigns result ;
        loop invariant 0 <= result ; */
    while (str[result++] != '\0');

    return --result;

}

/*@ assigns \nothing ;
    ensures \result == 0 ;*/
int main(void) {

   const char* hello = "hello";
   const char* helli = "helli";

   /*@ assert \valid_read(hello) && \valid_read(helli) ; */
   stringCompare(hello, helli, 5);
   return 0;
} 

WP is being run with the command: 'frama-c -wp -wp-model "Typed+var+int+real" -wp-timeout 20 strcmp.c'

The output generated from the WP-plugin:

[wp] Warning: Missing RTE guards
[wp] strcmp.c:48: Warning:
  Cast with incompatible pointers types (source: sint8*) (target: uint8*)
[wp] strcmp.c:48: Warning:
  Cast with incompatible pointers types (source: sint8*) (target: uint8*)
[wp] 49 goals scheduled
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_requires_validLengthS1 : Timeout (Qed:2ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_requires_validLengthS2 : Timeout (Qed:2ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_2_requires_validLengthS1 : Timeout (Qed:4ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_2_requires_validLengthS2 : Timeout (Qed:3ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_3_requires_validLengthS1 : Timeout (Qed:8ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_3_requires_validLengthS2 : Timeout (Qed:8ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_4_requires_validLengthS1 : Timeout (Qed:11ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_main_call_stringCompare_4_requires_validLengthS2 : Timeout (Qed:12ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_stringCompare_disjoint_SomeDifferent_allEqual : Timeout (Qed:3ms) (20s)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_stringCompare_allEqual_ensures : Timeout (Qed:15ms) (20s) (Stronger, 2 warnings)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_stringCompare_SomeDifferent_ensures : Timeout (Qed:14ms) (20s) (Stronger, 2 warnings)
[wp] [Alt-Ergo 2.2.0] Goal typed_real_stringLength_ensures_rightResult : Timeout (Qed:5ms) (20s)
[wp] Proved goals:   37 / 49
  Qed:              30  (1ms-3ms-11ms)
  Alt-Ergo 2.2.0:    7  (8ms-43ms-126ms) (464) (interrupted: 12)

There are a quite a few points here. First, I'd say that playing with memory models might not be the first thing to try when confronted with a verification issue (especially, since you're not using floating point arithmetic, the +real component is completely useless). I'd thus propose to remove -wp-model from the equation altogether, the default choice is usually good enough. On the other hand, adding -wp-rte to also check for potential runtime errors might be interesting.

When you specify \valid_read(s1) , you're saying that you can access s1[0] , but nothing else. If you want to speak about the validity of several memory cells, you can use \valid_read(s1 + (0.. n)) , or, in the case of null-terminated C strings, \valid_string(s1) .

Your assumes clauses in both behaviors of stringCompare are incorrect: we're only searching for a difference until strlen(s1) (included), not until n (which by the way is pretty useless and could probably be removed: you want to specify that strlen(s{1,2}) are bounded, but SIZE_MAX in stdint.h should do the trick). Moreover, the reverse of \forall i, P(i) ==> Q(i) is \exists i, P(i) && !Q(i) (ie do not use ==> after \exists ).

It would be better to use size_t for C variables that are meant to be used as offsets. Otherwise weird things might happen for very large strings.

You lack some invariants. Basically, in stringCompare , you must capture the fact that:

  • i stays between 0 and strlen(s1) (resp. strlen(s2) )
  • the current value of s1 is in fact \at(s1,Pre)+i (ditto for s2 )
  • so far, all elements of s1 and s2 are equal...
  • ...and non-null

Since the default architecture targeted by Frama-C uses char as signed, the conversion to unsigned char in the return statement will confuse WP. This is admittedly a weakness of WP itself.

For stringLength , it is also necessary to require something like valid_string(str) . However, this time you must constrain strlen(str) to be strictly less than SIZE_MAX (assuming you change your return type and the declaration of result to be size_t ), since result must go up to strlen(str)+1 without overflowing.

Again, the loop invariants must be strengthened: result is bounded by strlen(str) , and we must indicate that so far all characters are non-0.

Finally, in your main function, another weakness of WP prevents to check that the pre-conditions of stringCompare are fulfilled. If hello and helli are defined explicitely as char arrays, then everything will be fine.

tl;dr: the code below can be entirely proved with frama-c -wp -wp-rte file.c (Frama-C 22.0 Titanium and Alt-Ergo 2.2.0)

#include <stdint.h>
#include <string.h>
#include <stdio.h>

/*@  
    requires validPointers: valid_read_string(s1) && valid_read_string(s2);
    requires validLengthS1: n >= strlen(s1) >= 0;
    requires validLengthS2: n >= strlen(s2) >= 0;
    assigns \nothing ;
    allocates \nothing ;  
    frees \nothing ;
    behavior allEqual:
        assumes \forall integer k; 0 <= k <= strlen(s1) ==> s1[k] == s2[k];
        ensures \result == 0;
    behavior SomeDifferent:
        assumes \exists integer k; 0 <= k <= strlen(s1) && s1[k] != s2[k];
        ensures \result != 0;
    
    disjoint behaviors;
    complete behaviors;
    */ 
int stringCompare(const char* s1, const char* s2, size_t n) {
    if (s1 == s2) 
        return 0;
    size_t i = 0;

    /*@ assert \valid_read(s1) ; */
    /*@ assert \valid_read(s2) ;*/
    /*@ loop invariant index: 0 <= i <= strlen(\at(s1,Pre));
        loop invariant index_1: 0<= i <= strlen(\at(s2,Pre));
        loop invariant s1_pos: s1 == \at(s1,Pre)+i;
        loop invariant s2_pos: s2 == \at(s2,Pre)+i;
        loop invariant equal: \forall integer j; 0<= j < i ==> \at(s1,Pre)[j] == \at(s2,Pre)[j];
        loop invariant not_eos: \forall integer j; 0<= j < i ==> \at(s1,Pre)[j] != 0;
        loop assigns i , s1, s2; */    
    while (*s1 == *(s2++))
    {
        /*@ assert i <= n ; */
        ++i;
        if (*(s1++) == '\0')
            return 0;
    }
   
    return *(s1) - *(--s2);
}

/*@
  requires valid_string(str);
  requires strlen(str) < SIZE_MAX;
  assigns \nothing ;
  ensures rightResult: \result == strlen(\old(str));
  ensures rightEndCharacter: str[\result] == '\0' ;  */
size_t stringLength(const char* str) {
    size_t result = 0;

    /*@ loop assigns result ;
        loop invariant 0 <= result <= strlen(str);
        loop invariant \forall integer i; 0<= i < result ==> str[i]!=0;
    */
    while (str[result++] != '\0');

    return --result;

}

/*@ assigns \nothing ;
    ensures \result == 0 ;*/
int main(void) {

   const char hello[] = { 'h', 'e', 'l', 'l', 'o', '\0'};
   const char helli[] =  { 'h', 'e', 'l', 'l', 'i', '\0'};

   /*@ assert \valid_read(&hello[0]) && \valid_read(&helli[0]) ; */
   stringCompare(hello, helli, 5);
   return 0;
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM