/*#
# requires \valid(p);
# assigns \nothing;
*/
void foo(int *p)
{
int *pb;
pb = p;
*pb = 1;
return;
}
void main(){
int a = 0;
foo(&a);
return;
}
As I understand the assigns clause for a function contract only works with the function's input variables. So I make the assigns clause to nothing but then get yellow status with -wp.
frama-c -wp test1.c
[kernel] Parsing test1.c (with preprocessing)
[wp] Warning: Missing RTE guards
[wp] 3 goals scheduled
[wp] [Alt-Ergo] Goal typed_foo_assign_part2 : Unknown (Qed:4ms) (51ms)
[wp] Proved goals: 2 / 3
Qed: 2
Alt-Ergo: 0 (unknown: 1)
How to prove foo() if assigns \nothing is not working?
I'm unsure of what you mean by "assigns [...] only works with the function's input variable". An assigns clause is supposed to give a superset of all memory locations that are allocated in both the pre- and post- states of the contract (there are specific clauses for handling dynamic (de)allocation) and that might be modified during the execution of the function (or the statement for a statement contract).
In particular, your function foo can't have an assigns \nothing; clause since it modifies the value of the location pointed to by p: you must have something like assigns *p;, which is easily proved by frama-c -wp.
Related
I'm working with frama-c to make a dependency analysis of our code base and we have run into a situation that I have summarized in the following example.
We have a our entry point "runnable" for the analysis:
typedef unsigned char uint8;
typedef unsigned short uint16;
void ReadFunction(uint16 *const data);
extern uint16 test_return;
void runnable(void){
uint16 test_array[8];
ReadFunction(test_array);
test_return = test_array[4];
}
In our example we are using a local variable test_array[8] to read a value given by our ReadFunction and then taking only one value from that array (test_array[4]) and assigning it to the external value test_return.
Inside of ReadFunction the code is as follows:
typedef unsigned short uint16;
typedef uint16 custom_arr_type[8];
extern custom_arr_type test_interface;
extern void mem_copy(void * dst, const void * src, uint16 length);
void ReadFunction(uint16 *const data)
{
mem_copy(data, test_interface, sizeof(custom_arr_type));
}
where mem_copy is:
void mem_copy(void * dst,
const void * src,
uint16 length)
{
uint8 * d = (uint8 *)dst;
const uint8 * s = (const uint8 *)src;
uint16 i;
for (i = 0; i < (length); i++)
{
d[i] = s[i];
}
}
When analyzing the entrypoint runnable with the following command: frama-c -eva -deps -inout -lib-entry -no-annot -eva-initialized-locals -eva-auto-loop-unroll 10 -c11 example.i read_example.i lib_example.i -main runnable we produce the following output:
[kernel] Parsing example.i (no preprocessing)
[kernel] Parsing read_example.i (no preprocessing)
[kernel] Parsing lib_example.i (no preprocessing)
[eva] Analyzing an incomplete application starting at runnable
[eva] Computing initial state
[eva] Initial state computed
[eva:initial-state] Values of globals at initialization
test_return ∈ [--..--]
test_interface[0..7] ∈ [--..--]
[eva] lib_example.i:21: starting to merge loop iterations
[eva] done for function runnable
[eva] ====== VALUES COMPUTED ======
[eva:final-states] Values at end of function mem_copy:
test_array[0..7] ∈ [--..--]
d ∈ {{ (uint8 *)&test_array }}
s ∈ {{ (uint8 const *)&test_interface }}
i ∈ [16..32767]
[eva:final-states] Values at end of function ReadFunction:
test_array[0..7] ∈ [--..--]
[eva:final-states] Values at end of function runnable:
test_return ∈ [--..--]
test_array[0..7] ∈ [--..--]
[eva:summary] ====== ANALYSIS SUMMARY ======
----------------------------------------------------------------------------
3 functions analyzed (out of 3): 100% coverage.
In these functions, 15 statements reached (out of 15): 100% coverage.
----------------------------------------------------------------------------
No errors or warnings raised during the analysis.
----------------------------------------------------------------------------
0 alarms generated by the analysis.
----------------------------------------------------------------------------
No logical properties have been reached by the analysis.
----------------------------------------------------------------------------
[from] Computing for function mem_copy
[from] Done for function mem_copy
[from] Computing for function ReadFunction
[from] Done for function ReadFunction
[from] Computing for function runnable
[from] Done for function runnable
[from] ====== DEPENDENCIES COMPUTED ======
These dependencies hold at termination for the executions that terminate:
[from] Function mem_copy:
test_array[0..7] FROM test_interface[0..7]; dst; src; length (and SELF)
[from] Function ReadFunction:
test_array[0..7] FROM test_interface[0..7]; data (and SELF)
[from] Function runnable:
test_return FROM test_interface[0..7]
[from] ====== END OF DEPENDENCIES ======
[inout] InOut (internal) for function mem_copy:
Operational inputs:
test_interface[0..7]; dst; src; length
Operational inputs on termination:
test_interface[0..7]; dst; src; length
Sure outputs:
d; s; i
[inout] InOut (internal) for function ReadFunction:
Operational inputs:
test_interface[0..7]; data
Operational inputs on termination:
test_interface[0..7]; data
Sure outputs:
\nothing
[inout] InOut (internal) for function runnable:
Operational inputs:
test_array[4]; test_interface[0..7]
Operational inputs on termination:
test_array[4]; test_interface[0..7]
Sure outputs:
test_return
Now, the expected result here four our main function would be to have a dependency from test_return to test_interface[4] since it is my understanding that local variables like test_array will never appear in the dependency analysis. However, instead of having a dependency from one value of our array to our return value, we get a dependency from the whole array (test_interface[0..7]) to the return value.
I have tried simplifying even more this example, to rule out any possibility of the custom data types or the function calls to be causing this behavior:
int test_return;
int test_extern_array[8];
void runnable(void){
int test_array[8];
for (int i = 0; i < 8; i++){
test_array[i] = test_extern_array[i];
}
test_return = test_array[4];
}
Which produces the following output:
[kernel] Parsing example.i (no preprocessing)
[eva] Analyzing an incomplete application starting at runnable
[eva] Computing initial state
[eva] Initial state computed
[eva:initial-state] Values of globals at initialization
test_return ∈ [--..--]
test_extern_array[0..7] ∈ [--..--]
[eva:loop-unroll] example.i:8: Automatic loop unrolling.
[eva] done for function runnable
[eva] ====== VALUES COMPUTED ======
[eva:final-states] Values at end of function runnable:
test_return ∈ [--..--]
test_array[0..7] ∈ [--..--]
[eva:summary] ====== ANALYSIS SUMMARY ======
----------------------------------------------------------------------------
1 function analyzed (out of 1): 100% coverage.
In this function, 10 statements reached (out of 10): 100% coverage.
----------------------------------------------------------------------------
No errors or warnings raised during the analysis.
----------------------------------------------------------------------------
0 alarms generated by the analysis.
----------------------------------------------------------------------------
No logical properties have been reached by the analysis.
----------------------------------------------------------------------------
[from] Computing for function runnable
[from] Done for function runnable
[from] ====== DEPENDENCIES COMPUTED ======
These dependencies hold at termination for the executions that terminate:
[from] Function runnable:
test_return FROM test_extern_array[0..7]
[from] ====== END OF DEPENDENCIES ======
[inout] InOut (internal) for function runnable:
Operational inputs:
test_extern_array[0..7]; test_array[4]
Operational inputs on termination:
test_extern_array[0..7]; test_array[4]
Sure outputs:
test_return; i
And still in this case we obtain a dependency to our return value from the whole array instead of the specific position that has been used.
I have tried reading through all the frama-c manual and the eva plugin manual to find any cases similar to mine or any analysis option that could help get a more accurate dependency. I tried using the -eva-precision option but the results are the same.
Is frama-c correct here and I'm misinterpreting the results?
Is there any way to get a more precise dependency for this case?
Yes (and no), and not currently.
Frama-C/Eva is correct, as in, sound (it does not "forget" to consider any possible value), but when the From plug-in is used, its result is less precise than what Eva computes itself. In particular, options such as -eva-auto-loop-unroll allow splitting different analysis contexts inside Eva (thus keeping maximum precision), but when the analysis terminates, results are consolidated by program point, per call stack, but not per loop iteration. That is, the different iterations inside a loop end up merged before From can get the information it needs from Eva, which explains why it shows the imprecise result. So, currently, it is not possible to get a more precise result.
Note that the analysis computed by From could nowadays be written as an Eva abstract domain, which would allow it to be more precise, since it would run "at the same time as" Eva itself. But that will require considerable time and effort.
I believe I am missing something obvious, but I have tried a lot and I haven't managed to find the source of the problem.
I am following the acsl guide from Frama-C.
There is this introductory example of how to verify the correctness of finding the maximum value in an array:
/*# requires n > 0;
requires \valid(p+ (0 .. n-1));
ensures \forall int i; 0 <= i <= n-1 ==> \result >= p[i];
ensures \exists int e; 0 <= e <= n-1 && \result == p[e];
*/
int max_seq(int* p, int n) {
int res = *p;
for(int i = 0; i < n; i++) {
if (res < *p) { res = *p; }
p++;
}
return res;
}
However, running frama-c -wp -wp-prover alt-ergo samenum.c -then -report I get:
[wp] Warning: Missing RTE guards
[wp] samenum.c:8: Warning: Missing assigns clause (assigns 'everything' instead)
[wp] 2 goals scheduled
[wp] [Alt-Ergo] Goal typed_max_seq_ensures_2 : Timeout (Qed:1ms) (10s)
[wp] [Alt-Ergo] Goal typed_max_seq_ensures : Timeout (Qed:2ms) (10s)
[wp] Proved goals: 0 / 2
Alt-Ergo: 0 (interrupted: 2)
[report] Computing properties status...
--------------------------------------------------------------------------------
--- Properties of Function 'max_seq'
--------------------------------------------------------------------------------
[ - ] Post-condition (file samenum.c, line 3)
tried with Wp.typed.
[ - ] Post-condition (file samenum.c, line 4)
tried with Wp.typed.
[ - ] Default behavior
tried with Frama-C kernel.
It seems that alt-ergo times out before proving the property. For what is worth, I tried with higher time-out but it still doesn't work.
I am using:
frama-c: 19.1
why3: 1.2.0
alt-ergo: 2.3.2
I am running this on Ubuntu 18.04 and before running the command I run: why3 config --detect to make sure why3 knows about alt-ergo.
Any ideas? Can anyone verify that this is example is not working?
Any help would be greatly appreciated!
This mini-tutorial was written quite a long time ago and is not really up to date. A new version of the website should appear in the upcoming months. Basically, this contract, as well as the invariant for the loop pointed by #iguerNL were meant to be verified using the Jessie plugin, and not the WP plugin of Frama-C. Among the differences between these two plugins, Jessie did not need assigns clauses for loops and functions, while WP needs them.
Thus, a complete annotated max_seq function could be this one:
/*# requires n > 0;
requires \valid(p+ (0..n−1));
assigns \nothing;
ensures \forall int i; 0 <= i <= n−1 ==> \result >= p[i];
ensures \exists int e; 0 <= e <= n−1 && \result == p[e];
*/
int max_seq(int* p, int n) {
int res = *p;
//# ghost int e = 0;
/*# loop invariant \forall integer j; 0 <= j < i ==> res >= \at(p[j],Pre);
loop invariant \valid(\at(p,Pre)+e) && \at(p,Pre)[e] == res;
loop invariant 0<=i<=n;
loop invariant p==\at(p,Pre)+i;
loop invariant 0<=e<n;
loop assigns i, res, p, e;
loop variant n-i;
*/
for(int i = 0; i < n; i++) {
if (res < *p) {
res = *p;
//# ghost e = i;
}
p++;
}
return res;
}
where we specify that the functions does not assign anything in memory, and that the loop assigns the different local variables i, res, p and e (thus leaving n unchanged).
Note that more recent tutorials are available to learn about the use of Frama-C with the WP plugin. The future version of the Frama-C website mentions:
A comprehensive tutorial on ACSL and WP
Another tutorial, with specifications inspired notably from C++ containers
You probably forgot to add invariants for the "for" loop. See Section "10.2 Loop Invariants" in the manual you gave (https://frama-c.com/acsl_tutorial_index.html)
The closest answer I found maybe related to -absolute-valid-range for the Eva plugin but is that it? Do I have to come up with read/write ACSL predicates to do dummy read/write?
Sample code:
#include <stdint.h>
#define BASE_ADDR 0x0e000000
#define BASE_LIMIT 0x0e001000
#define TEST_REG 0x10
/*# requires BASE_ADDR <= addr < BASE_LIMIT;
# assigns \nothing;
*/
static inline uint32_t mmio_read32(volatile uintptr_t addr)
{
volatile uint32_t *ptr = (volatile uint32_t *)addr;
return *ptr;
}
/*#
# requires 0 <= offset <= 0x1000;
# assigns \nothing;
*/
static inline uint32_t read32(uintptr_t offset)
{
return mmio_read32((uintptr_t)BASE_ADDR + offset);
}
void main(){
uint32_t test;
test = read32(TEST_REG);
return;
}
Frama-c command and output:
[frama -absolute-valid-range 0x0e000000-0x0e001000 -wp mmio2.c
[kernel] Parsing mmio2.c (with preprocessing)
[wp] Warning: Missing RTE guards
[wp] 6 goals scheduled
[wp] [Alt-Ergo] Goal typed_read32_call_mmio_read32_pre : Unknown (Qed:4ms) (51ms)
[wp] Proved goals: 5 / 6
Qed: 5
Alt-Ergo: 0 (unknown: 1)][1]
How to discharge goal "typed_read32_call_mmio_read32_pre" or is this expected?
The fact that the proof fails is related to two independent issues, but none of them is related to using absolute addresses.
First, as the argument of mmio_read32 is marked as volatile, WP consider that its value can be anything. In particular, none of the hypotheses that are made on offset are known to hold when evaluating addr. You can see that in the GUI by looking at the generated goal (go in the WP Goals tab at the bottom and double click in the Script colon and the line of the failed proof attempt):
Goal Instance of 'Pre-condition'
(call 'mmio_read32'):
Assume {
Type: is_uint32(offset_0).
(* Pre-condition *)
Have: (0 <= offset_0) /\ (offset_0 <= 4095).
}
Prove: (234881024 <= w) /\ (w_1 <= 234885119).
w and w_1 correspond to two read accesses to the volatile content of addr. I'm not sure if you really intended the addr parameter to be volatile (as opposed to a non-volatile pointer to a volatile location), as this would require a pretty weird execution environment.
Assuming that the volatile qualifier should not present in the declaration of addr, a remaining issue is that the constraint on offset in read32 is too weak: it should read offset < 0x1000 with a strict inequality (or the precondition of mmio_read32 be made non-strict, but again this would be quite uncommon).
Regarding the initial question about physical adressing and volatile, in ACSL (see section 2.12.1 of the manual), you have a specific volatile clause that allows you to specify (C, usually ghost) functions to represent read and write accesses to volatile locations. Unfortunately, support for these clauses is currently only accessible through a non-publicly distributed plug-in.
I'm afraid that if you want to use WP on code with physical adressing, you need indeed to use an encoding (e.g. with a ghost array of appropriate size), and/or model volatile accesses with appropriate functions.
Consider the following C code:
#include <assert.h>
//# requires p < q;
void f(char *p, char *q)
{
assert(p <= q-1);
}
//# requires a < b;
void g(int a, int b)
{
assert(a <= b-1);
}
Using alt-ergo, frama-c successfully proves that the assertion in g() holds but fail to prove the same with f(). Why?
Formally, pointers and integers are two very different things. In particular, C semantics states that pointer comparison is well defined only for pointers that points in the same allocated block (or one offset past the end of said allocated block) . This is reflected in the model used by the WP plugin of Frama-C in the definition of addr_le and friends (see $(frama-c -print-share-path)/wp/why3/Memory.why), where the pointers are checked to have the same address before the comparison is done on their offset.
For the below C function, I'm getting syntax errors from Alt-Ergo for the latest version of Frama-c.
frama-c -wp -wp-rte -lib-entry RoundNearestFive.c -wp-out temp -wp-model="nat, real"
I'm not sure what is wrong in this generated line:
...
let r_0 = dat_0 / 5.0e0 : real in /* syntax error here */
...
C function under analysis
typedef unsigned short int uint16;
/*#
# requires 0<=dat<= 300;
*/
uint16 RoundNearestFive(float dat)
{
uint16 result= 0;
float fra = 0;
result = (uint16)(dat/5);
fra = dat - (float)result*5; // fractional part of the input
if (fra < 2.5)
result = (uint16) (dat-fra);
else
result = (uint16) (dat+(5-fra));
return result;
}
I tried Alt-Ergo (version 0.95.2 and trunk) on the formula below and I got no syntax error. Are you using an old version of Alt-Ergo ? Or maybe the syntax error is elsewhere.
--
logic dat_0 : real
goal g:
let r_0 = dat_0 / 5.0e0 : real in (* syntax error here *)
false
The WP user manual explicitly states that versions of Alt-Ergo prior to 0.95 are not supported (see page 21).