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)
(i.e. 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)
)s1
is in fact \at(s1,Pre)+i
(ditto for s2
)s1
and s2
are equal...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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With