I am a new Frama-C User and I am trying to prove certain properties for a large project. I was seeing a particular proof fail, and tried reducing the problem to a minimum reproducible example, and the following is the code:
#include <stdlib.h>
/*@ requires (x != 0) && (y + 1 >= 1024);
*/
int f1(unsigned int x, unsigned int y) {
y = y + 1;
//@ assert (x != 0) && (y >= 1024);
return 0;
}
I use the following command to verify:
frama-c -wp -wp-prover=alt-ergo,z3,cvc4 a.c -kernel-warn-key annot-error=active -then -no-unicode -report
The above code fails to verify with frama-c with the following message:
Assertion x != 0 && y >= 1024: Unproven
Could you please explain why does this verification fail?
I tried simplifying my original proof manually and came up with the above example. I expect the verification to go through. I believe I may be misunderstanding how frama-c treats "y+1" for C data types, as opposed to purely mathematical datatypes like Int.
Edit:
The following code verifies successfully (I just changed the variable types from unsigned to signed):
#include <stdlib.h>
/*@ requires (x != 0) && (y + 1 >= 1024);
*/
int f1(int x, int y) {
y = y + 1;
//@ assert (x != 0) && (y >= 1024);
return 0;
}
Edit 2: Versions: Frama-C: 27.1 (Cobalt) Alt-ergo: 2.4.3 CVC4: 1.6 Z3: 4.12.2
Edit3:
I tried in the frama-c version 28.1 as well and it still fails.
The main issue is that y + 1
in ACSL does not mean the same thing as y+1
in C. More precisely, as mentioned in the ACSL manual, arithmetic operations are evaluated in ACSL over mathematical, unbounded, integers. Hence the fact that y+1>=1024
in the requires
clause is not sufficient to conclude that y>=1024
after the y=y+1
in the assignment: if we pass UINT_MAX
to f
, we end up with y == 0
. Adding another requires
, e.g. y+1 <= UINT_MAX + 1
, will solve the issue.
Regarding the variant with int
, things are a little bit different. In fact, while an unsigned overflow is perfectly defined in C (we simply wrap up the value), signed overflow is not: we have an undefined behavior. Thus, by default Frama-C will consider that a signed overflow is a runtime-error. For Wp, this means that it will assume that no such overflow can occur, and checking that is delegated to the generation of annotation with the Rte plug-in by using -wp-rte
: with this option, you get a non-proved assertion, namely the one generated by Rte.
Similarly, if you add option -warn-unsigned-overflow
to the unsigned
variant, everything seems proved at first, but adding -wp-rte
reveals an unproved, Rte-generated, assertion, while add option -no-warn-signed-overflow
to the signed
variant gives you the same behavior as the unsigned
.
To summarize, if we use the following C file t.c
and launch frama-c
with various set of options, we obtain the following results:
#include<limits.h>
/*@ requires (x != 0) && (y + 1 >= 1024);
#ifdef ACSL_NO_OVERFLOW
requires no_overflow: y + 1 <= UINT_MAX;
#endif
*/
int f1(unsigned int x, unsigned int y) {
y = y + 1;
//@ assert (x != 0) && (y >= 1024);
return 0;
}
/*@ requires (x != 0) && (y + 1 >= 1024);
#ifdef ACSL_NO_OVERFLOW
requires no_overflow: y + 1 <= INT_MAX;
#endif
*/
int f2(int x, int y) {
y = y + 1;
//@ assert (x != 0) && (y >= 1024);
return 0;
}
frama-c command | Proved Goals |
---|---|
frama-c -wp t.c | 5/6 |
frama-c -wp t.c -wp-rte | 5/7 |
frama-c -wp t.c -wp-rte -cpp-extra-args="-DACSL_NO_OVERFLOW" | 7/7 |
frama-c -wp t.c -warn-unsigned-overflow | 6/6 |
frama-c -wp t.c -warn-unsigned-overflow -wp-rte | 6/8 |
frama-c -wp t.c -no-warn-signed-overflow | 4/6 |
frama-c -wp t.c -no-warn-signed-overflow -wp-rte | 4/6 |
frama-c -wp t.c -warn-unsigned-overflow -wp-rte -cpp-extra-args="-DACSL_NO_OVERFLOW" | 8/8 |
frama-c -wp t.c -no-warn-signed-overflow -wp-rte -cpp-extra-args="-DACSL_NO_OVERFLOW" | 6/6 |