Frama-c: Function calls and static variables - frama-c

I'm currently discovering frama-c's features, and especially WP & Value's analysis tools. My final goal is to be able to use frama-c on larger codes that involves several layers with:
a lot of function calls
use of complex data structures
static and/or global variables
So far I've been trying to apply a bottom-up method i.e starting specifying functions that does not includes any function calls and analyze their behaviors by isolating them thanks to -lib-entry and -main kernel options. By doing that I make sure that if pre conditions are assumed to be true, then the whole function contract is verified. As soon as I tried to specify upper layers that invokes those functions, things gets complicated. First I often have to specify the behavior of the called functions which is not always easy because those functions may deal with variables/functions out of the scope of the current function.
Let me give you an easy example:
Let's say that in file1.h we define a data structure "my_struct" that contains a field number and a field parity.
In file1.c I have two functions:
A first function "check_parity" that just tests if the parity field of the static variable _sVar is correct.
A second function "correct_parity" that calls the first function, and corrects the parity if the field was not correct.
In file2.c, I have a function "outside_caller" that just calls correct_parity(). My objective is to be able to specify outside_caller the same way I'm specifying correct_parity. Below is the corresponding source code:
file1.h
/* parity = 0 => even ; 1 => odd */
typedef unsigned char TYP_U08;
typedef unsigned short TYP_U16;
typedef unsigned int TYP_U32;
typedef unsigned long TYP_U64;
typedef struct {
unsigned char parity;
unsigned int number;
} my_stuct;
typedef enum
{
S_ERROR = -1
,S_OK = 0
,S_WARNING = 1
} TYPE_STATUS;
/*# ghost my_stuct* g_sVar; */
/*# predicate fc_pre_is_parity_ok{Labl}(my_stuct* i_sVar) =
(
\at(i_sVar->parity, Labl) == ((TYP_U08) (\at(i_sVar->number,Labl) % 2u))
);
# predicate fc_pre_valid_parity{Labl}(my_stuct* i_sVar) =
(
(\at(i_sVar->parity,Labl) == 0) ||
(\at(i_sVar->parity, Labl) == 1)
);
# predicate fc_pre_is_parity_readable(my_stuct* i_sVar) =
(
\valid_read(&i_sVar->parity)
);
# predicate fc_pre_is_parity_writeable(my_stuct* i_sVar) =
(
\valid(&i_sVar->parity)
);
# predicate fc_pre_is_number_readable(my_stuct* i_sVar) =
(
\valid_read(&i_sVar->number)
);
# predicate fc_pre_is_number_writeable(my_stuct* i_sVar) =
(
\valid(&i_sVar->number)
);
*/
TYPE_STATUS check_parity(void);
TYPE_STATUS correct_parity(void);
file1.c
static my_stuct* _sVar;
/*# requires check_req_parity_readable:
fc_pre_is_parity_readable(_sVar);
# requires check_req_number_readable:
fc_pre_is_number_readable(_sVar);
# assigns check_assigns:
g_sVar;
# ensures check_ensures_error:
!fc_pre_valid_parity{Post}(g_sVar) ==> \result == S_ERROR;
# ensures check_ensures_ok:
(
fc_pre_valid_parity{Post}(g_sVar) &&
fc_pre_is_parity_ok{Post}(g_sVar)
) ==> \result == S_OK;
# ensures check_ensures_warning:
(
fc_pre_valid_parity{Post}(g_sVar) &&
!fc_pre_is_parity_ok{Post}(g_sVar)
) ==> \result == S_WARNING;
# ensures check_ensures_ghost_consistency:
\at(g_sVar, Post) == _sVar;
*/
TYPE_STATUS check_parity(void)
{
//# ghost g_sVar = _sVar;
TYPE_STATUS status = S_OK;
if(!(_sVar->parity == 0 || _sVar->parity == 1)) {
status = S_ERROR;
} else if ( _sVar->parity == (TYP_U08)(_sVar->number % 2u) ){
status = S_OK;
} else {
status = S_WARNING;
}
return status;
}
/*# requires correct_req_is_parity_writeable:
fc_pre_is_parity_writeable(_sVar);
# requires correct_req_is_number_readable:
fc_pre_is_number_readable(_sVar);
# assigns correct_assigns:
_sVar->parity,
g_sVar,
g_sVar->parity;
# ensures correct_ensures_error:
!fc_pre_valid_parity{Pre}(g_sVar) ==> \result == S_ERROR;
# ensures correct_ensures_ok:
(
fc_pre_valid_parity{Pre}(g_sVar) &&
fc_pre_is_parity_ok{Pre}(g_sVar)
) ==> \result == S_OK;
# ensures correct_ensures_warning:
(
fc_pre_valid_parity{Pre}(g_sVar) &&
!fc_pre_is_parity_ok{Pre}(g_sVar)
) ==> \result == S_WARNING;
# ensures correct_ensures_consistency:
fc_pre_is_parity_ok{Post}(g_sVar);
# ensures correct_ensures_validity :
fc_pre_valid_parity{Post}(g_sVar);
# ensures correct_ensures_ghost_consistency:
\at(g_sVar, Post) == _sVar;
*/
TYPE_STATUS correct_parity(void)
{
//# ghost g_sVar = _sVar;
TYPE_STATUS parity_status = check_parity();
if(parity_status == S_ERROR || parity_status == S_WARNING) {
_sVar->parity = (TYP_U08)(_sVar->number % 2u);
/*# assert (\at(g_sVar->parity,Here) == 0) ||
(\at(g_sVar->parity, Here) == 1);
*/
//# assert \at(g_sVar->parity, Here) == (TYP_U08)(\at(g_sVar->number,Here) % 2u);
}
return parity_status;
}
file2.c
/*# requires out_req_parity_writable:
fc_pre_is_parity_writeable(g_sVar);
# requires out_req_number_writeable:
fc_pre_is_number_readable(g_sVar);
# assigns out_assigns:
g_sVar,
g_sVar->parity;
# ensures out_ensures_error:
!fc_pre_valid_parity{Pre}(g_sVar) ==> \result == S_ERROR;
# ensures out_ensures_ok:
(
fc_pre_valid_parity{Pre}(g_sVar) &&
fc_pre_is_parity_ok{Pre}(g_sVar)
) ==> \result == S_OK;
# ensures out_ensures_warning:
(
fc_pre_valid_parity{Pre}(g_sVar) &&
!fc_pre_is_parity_ok{Pre}(g_sVar)
) ==> \result == S_WARNING;
# ensures out_ensures_consistency:
fc_pre_is_parity_ok{Post}(g_sVar);
# ensures out_ensures_validity:
fc_pre_valid_parity{Post}(g_sVar);
*/
TYPE_STATUS outside_caller(void)
{
TYPE_STATUS status = correct_parity();
//# assert fc_pre_is_parity_ok{Here}(g_sVar) ==> status == S_OK;
/*# assert !fc_pre_is_parity_ok{Here}(g_sVar) &&
fc_pre_valid_parity{Here}(g_sVar) ==> status == S_WARNING; */
//# assert !fc_pre_valid_parity{Here}(g_sVar) ==> status == S_ERROR;
return status;
}
Here the main issue is that in order to specify outside_caller(), I need to access _sVar which is out of scope in file2.c. That implies to deal with a ghost variable (g_sVar) that is declared in file1.h and updated in correct_parity function. In order to make the caller (correct_parity) able to use the callee's contracts, the ghost variable g_sVar must be used inside the contracts of the callees.
Here are the results of WP analysis:
(1) check_parity()
frama-c -wp src/main.c src/test.c -cpp-command 'gcc -C -E -Isrc/'
-main 'check_parity' -lib-entry -wp-timeout 1 -wp-fct check_parity -wp-rte -wp-fct check_parity -then -report
[rte] annotating function check_parity
[wp] 14 goals scheduled [wp] Proved goals: 14 / 14
Qed: 9 (4ms)
Alt-Ergo: 5 (8ms-12ms-20ms) (30)
(2) correct_parity()
frama-c -wp src/main.c src/test.c -cpp-command 'gcc -C -E -Isrc/' -main 'correct_parity' -lib-entry -wp-timeout 1 -wp-fct correct_parity -wp-rte -wp-fct correct_parity -then -report
[rte] annotating function correct_parity
[wp] 18 goals scheduled
[wp] Proved goals: 18 / 18
Qed: 12 (4ms)
Alt-Ergo: 6 (4ms-37ms-120ms) (108)
(3) outside_caller()
frama-c -wp src/main.c src/test.c -cpp-command 'gcc -C -E -Isrc/' -main 'outside_caller' -lib-entry -wp-timeout 1 -wp-fct outside_caller -wp-rte -wp-fct outside_caller -then -report
[rte] annotating function outside_caller
[wp] 14 goals scheduled
[wp] [Alt-Ergo] Goal typed_outside_caller_assign_exit : Unknown (Qed:4ms) (515ms)
[wp] [Alt-Ergo] Goal typed_outside_caller_call_correct_parity_pre_correct_req_is_par___ : Unknown (636ms)
[wp] [Alt-Ergo] Goal typed_outside_caller_assert : Timeout
[wp] [Alt-Ergo] Goal typed_outside_caller_assign_normal_part1 : Timeout
[wp] [Alt-Ergo] Goal typed_outside_caller_call_correct_parity_pre_correct_req_is_num___ : Unknown (205ms)
[wp] Proved goals: 9 / 14
Qed: 9 (4ms)
Alt-Ergo: 0 (interrupted: 2) (unknown: 3)
==> WP : GUI Output
In this configuration, the callees are specified with g_sVar ghost variable, except for requires and assings clauses for 2 reasons:
I need to check _sVar R/W accesses with \valid & \valid_read since its a pointer
When I tried to specify assigns clauses of the callees with g_sVar, I was not able to verify the corresponding clause.
But by doing so, I somehow made the specification of the caller invalid, as you can see on WP's output.
Why does it seems the more functions calls I have, the more it becomes complicated to prove the behavior of the functions? Is there a proper way to deal with multiple function calls and static variables?
Thank you a lot in advance!
PS: I'm working with Magnesium-20151002 version, on a VM running with Ubuntu 14.04, 64-bit machine. I know that getting started with WhyML and Why3 could help me a lot but so far I haven't been able to install Why3 ide neither on windows nor on Ubuntu following each step of this tutorial.

First of all, please note that -main and -lib-entry aren't that useful for WP (you mentioned that you are also interested in EVA/Value Analysis, but your question is directed towards WP).
Your issue with static variables is a known one, and the easiest way to deal with it is indeed to declare a ghost variable in the header. But then you must express your contracts in terms of the ghost variable and not the static one.
Otherwise, the callers will not be able to make use of these contracts, since they do not know anything about _sVar. As a rule of thumb, it is better to put the contract in the header: this way, you're bound to only use identifiers that are visible outside of the translation unit.
Regarding function calls, the main point is that any function that is called by the function you're trying to prove with WP must come with a contract that at least contain an assigns clause (and possibly more precise specifications, depending on how much the effects of the callee are relevant for the property that you want to prove on the caller). The important thing to remember here is that, from WP's point of view, after the call, only what is explicitly stated in the callee's contract through ensures is true, plus the fact that any location not in the assigns clause has been left unchanged.

Related

How does null move up the call stack in recursive functions, if a return value of null is not specified?

Hi I'm currently learning about recursive Inorder Binary Tree Traversal using C#. There's one main aspect I cannot understand, in particular with this code below.
public void InOrder(BinaryTreeNode node)
{
if (node != null)
{
InOrder(node.Left);
Console.WriteLine(node.Value);
InOrder(node.Right);
}
}
If I had a Binary tree that looked like this...
9
/ \
4 20
/ \ / \
1 6 15 170
I know that eventually by recursively calling Inorder(node.left) I will get to the left leaf of the binary tree i.e. the very end of the tree, where node.left will equal null as there are no more nodes.
The tree would look like this...
9
/ \
4 20
/ \ / \
1 6 15 170
/
null
Because node.left = null, the first recursive function
InOrder(node.left)
will terminate, and
Console.Writeline(node.left)
will execute
Printing a value of 1
Eventually these null values move up the call stack after each node is analysed, and all nodes are printed, the tree starts to look like this, as null value moves up the tree..
9
/ \
4 20
/ \ / \
null 6 15 170
/ \ / \
null null null
Eventually all the nodes in the tree are equal to null, and all nodes are printed in order to an output of ...
1, 4, 6, 9, 15, 20, 170
What I don't understand is how this null value is moving up the tree, and changing all the nodes that have been analysed to null when there is no return value. Normally there would be a base case like...
if (node == null)
{
return null;
}
For this, I understand that null is being returned so will persist/return up the call stack. But for fist block of code above, there is no return statement.
I also find it just as confusing when there is only a return statement without a return value like...
if (node == null)
{
return;
}
Again there is no return of null specified, so how does this null value move up the tree as each node is evaluated?
There isn't a problem with any of this code, it works as expected, and prints all the nodes of the Binary Tree InOrder. This is more about understanding Recursion, and why the first block of code still works even though a return null value is not specified.
Thanks in Advance for the help.
there is no return of null specified, so how does this null value move up the tree as each node is evaluated?
The function will still return, even if there is no value to return. It's done executing, so control is passed back to the caller.
if (node != null) <- skipped entirely when the node is null
{
InOrder(node.Left);
Console.WriteLine(node.Value);
InOrder(node.Right);
}
For the tree you gave, this is what happens at the node with value=1:
It's not null, so we go into the if block.
We evaluate InOrder(node.Left) which is just InOrder(null):
It's null, so the if block is skipped.
We return to the caller, InOrder(node with value=1)
Console.WriteLine(node.Value) prints 1.
etc...
Although you can't 'see' the base case in the code, it's still there :) just implicitly.

ACSL Logic Struct Declarations Not Working as in Reference Manual

I would like to have a way to describe logic/spec level structs that include abstract lists. Example 2.2.7 on page 27 of the ACSL Reference Manual suggests that there is a
way to do this and it is as follows:
//# type point = struct { real x; real y; };
//# type triangle = point[3];
//# logic point origin = { .x = 0.0 , .y = 0.0 };
/*# logic triangle t_iso = { [0] = origin,
# [1] = { .y = 2.0 , .x = 0.0 }
# [2] = { .x = 2.0 , .y = 0.0 }};
#*/
/*# logic point centroid(triangle t) = {
# .x = mean3(t[0].x,t[1].x,t[2].x);
# .y = mean3(t[0].y,t[1].y,t[2].y);
# };
#*/
//# type polygon = point[];
/*# logic perimeter(polygon p) =
# \sum(0,\length(p)-1,\lambda integer i;d(p[i],p[(i+1) % \length(p)])) ;
#*/
If I copy/paste this exact code into a text editor and try to run this code with the wp plugin with:
frama-c -wp -wp-rte -wp-prover alt-ergo shapes.c
I get an error:
[kernel:annot-error] shapes.c:1: Warning: unexpected token '{'
If I give up on trying to write logic/spec level declarations of struct types, but would still like to write logic/spec level expressions that instantiate structs defined in C as follows:
struct somestruct {
int x;
int y;
};
/*#
logic struct somestruct foo = { .x = 3, .y = 4 };
*/
I still get an error:
[kernel:annot-error] aggregate_err.c:7: Warning:
unsupported aggregated field construct. Ignoring global annotation
and not having a way to write particular values of structs as expressions in specifications leads to some fairly ugly specifications, so I am hoping that I am doing something wrong.
If I dig into the source of frama-C 20.0 to try to find the part of the parser-generator code for /*# type declarations, it looks like the syntax in Ex 2.2.7 is not really implemented. It looks like the syntax for type level declarations is line 799 of
frama-c-20.0-Calcium/src/kernel_internals/parsing/logic_parser.mly
(called type_spec)
And the parse rule for type level declarations of structs is:
| STRUCT exit_rt_type identifier_or_typename { LTstruct $3 }
which looks like it would support
//# type foo = struct c_struct;
but not something like what Ex 2.2.7 has as in:
//# type point = struct { real x; real y; };
Is there something else I should be doing to have better support for structs in ACSL/Frama-C? Thanks!
Not all ACSL constructions are supported by the current Frama-C implementation. With each Frama-C release comes an ACSL implementation manual, which describes the constructions that are not yet implemented. For Frama-C 20.0 Calcium, this can be found here. In this document, unsupported constructions appear in red in the relevant BNF rule. Note however that other parts of the manual are left untouched. Notably, the fact that an example is included in the implementation manual does not imply that it is expected to be successfully parsed by the current Frama-C version. In your case, these are the rules of figure 2.17 on page 57, which show that indeed records are not implemented.
As you have already discovered by yourselves, it is indeed possible to define a C struct (possibly ghost) and an ACSL type out of it. Of course, since the struct lives in the C world, its fields must have C types (ACSL types in ghost declarations is unsupported as well).
Similarly, you can simulate the absence of direct record definition by an update (the \with construction) of all the fields of an arbitrary record, as in the following example:
//# ghost struct c_s { float x; float y; };
//# type point = struct c_s;
//# axiomatic Arbitrary_point { logic point empty; }
//# logic point my_point = {{ empty \with .x = (float)1. } \with .y = (float)2.};

printing string and calling recursive function

I am currently learning sml but I have one question that I can not find an answer for. I have googled but still have not found anything.
This is my code:
fun diamond(n) =
if(n=1) then (
print("*")
) else (
print("*")
diamond(n-1)
)
diamond(5);
That does not work. I want the code to show as many * as number n is and I want to do that with recursion, but I don't understand how to do that.
I get an error when I try to run that code. This is the error:
Standard ML of New Jersey v110.78 [built: Thu Aug 20 19:23:18 2015]
[opening a4_p2.sml] a4_p2.sml:8.5-9.17 Error: operator is not a
function [tycon mismatch] operator: unit in expression:
(print "*") diamond /usr/local/bin/sml: Fatal error -- Uncaught exception Error with 0 raised at
../compiler/TopLevel/interact/evalloop.sml:66.19-66.27
Thank you
You can do side effects in ML by using ';'
It will evaluate whatever is before the ';' and discard its result.
fun diamond(n) =
if(n=1)
then (print "*"; 1)
else (print "*"; diamond(n-1));
diamond(5);
The reason for the error is because ML is a strongly typed language that although you don't need to specify types explicitly, it will infer them based on environmental factors at compile time. For this reason, every evaluation of functions, statements like if else need to evaluate to an unambiguous singular type.
If you were allowed to do the following:
if(n=1)
then 1
else print "*";
then the compiler will get a different typing for the then and else branch respectively.
For the then branch the type would be int -> int whereas the type for the else branch would be int -> unit
Such a dichotomy is not allowed under a strongly typed language.
As you need to evaluate to a singular type, you will understand that ML does not support the execution of a block of instructions as we commonly see in other paradigms which transposed to ML naively would render something like this:
....
if(n=1)
then (print "1"
print "2"
)
else (print "3"
diamond(n-1)
)
...
because what type would the then branch evaluate to? int -> unit? Then what about the other print statement? A statement has to return a singular result(even it be a compound) so that would not make sense. What about int -> unit * unit? No problem with that except that syntactically speaking, you failed to communicate a tuple to the compiler.
For this reason, the following WOULD work:
fun diamond(n) =
if(n=1)
then (print "a", 1) /* A tuple of the type unit * int */
else diamond(n-1);
diamond(5);
As in this case you have a function of type int -> unit * int.
So in order to satisfy the requirement of the paradigm of strongly typed functional programming where we strive for building mechanisms that evaluate to one result-type, we thus need to communicate to the compiler that certain statements are to be executed as instructions and are not to be incorporated under the typing of the function under consideration.
For this reason, you use ';' to communicate to the compiler to simply evaluate that statement and discard its result from being incorporated under the type evaluation of the function.
As far as your actual objective is concerned, following is a better way of writing the function, diamond as type int -> string:
fun diamond(n) =
if(n=1)
then "*"
else "*" ^ diamond(n-1);
print( diamond(5) );
The above way is more for debugging purposes.

LTL Formula with Aorai

I am trying to find an example about the LTL operator _ F_ which means fatally with Aorai but i can't figure out exactly what this operator aims and there are no examples in the repository "tests" of Aorai
For example, i wrote this formula
CALL(main) && _X_ (CALL(a) && _X_(RETURN(a) && _F_ (RETURN(b) && _X_ (RETURN(main)) ) ))
which says that in my program main, i have to call the function a() and after this i don't understand what happens with the operator fatally but it seems that it takes and accepts whatever we call after the function a() with no warning or error from Aorai. If anybody could help me or could give a right example about it.
For example, i have this program below which i would like to test with this formula above
void a()
{}
void b()
{}
int main()
{ a();
a();
b();
b();
a();
return 0;}
I type frama-c -aorai-ltl test.ltl test.c
Normally, there should be an error or warning from Aorai. No?
Your question is more about temporal logic than Frama-C/Aorai itself, but the meaning of this formula is that main must call a, then do whatever it wants, before calling b and returning just after that.
NB: note that Aorai only traces call and return events, so that e.g. "just after" here means that main cannot not call any function after its last call to b, but can still perform some actions, such as x++;.
Update
I've run your complete example on Frama-C. Indeed a post-condition is missing in the contract for main generated by Aorai, namely that the state of the generated automaton at the end of main (T0_S4) is supposed to be accepting, which is not the case here. This is a bug. If you write explicitely an equivalent automaton in the ya language, as
%init: S0;
%accept: Sf;
S0: { CALL(main) } -> S1;
S1: { [ a() ] } -> S2;
S2: { RETURN(b) } -> S3
| other -> S2;
S3: { RETURN(main) } -> Sf;
Sf: -> Sf;
Then the generated contract for main contains a requires \false;, which indeed indicates that the function is not conforming to the automaton, and Aoraï warns about that.
Please note however that in the general case, Aoraï will not emit any warning. It generates contracts that, if fulfilled, imply that the whole program is conforming to the automaton. The proof of the contract must be done by another plugin (e.g. WP or Value Analysis)

ensures proved even though code is defective?

In the following, how are the postconditions for behavior neg_limit being proven true when the relevant C code is commented-out?
One of the Safety->check arithmetic overflow isn't provable, as expected, but it seems like neg_limit should also be unprovable.
Context: I'm using Frama-C-Boron, Jessie and, via gWhy, Alt-Ergo in order to learn how to write specifications and prove that functions meet them. Any cluebatting, RTFMing, etc., about specification strategies, tools, etc. is also appreciated. So far, I am reading both the ACSL 1.7 implementation manual (which is more recent that -Boron's) and the Jessie tutorial & ref. manual.
Thanks!
/*# behavior non_neg:
assumes v >= 0;
ensures \result == v;
behavior neg_in_range:
assumes INT32_MIN < v < 0;
ensures \result == -v;
behavior neg_limit:
assumes v == INT32_MIN;
ensures \result == INT32_MAX;
disjoint behaviors;
complete behaviors;
*/
int32_t my_abs32(int32_t v)
{
if (v >= 0)
return v;
//if (v == INT32_MIN)
// return INT32_MAX;
return -v;
}
Here is the gWhy goal for the first postcondition:
goal my_abs32_ensures_neg_limit_po_1:
forall v_2:int32.
(integer_of_int32(v_2) = ((-2147483647) - 1)) ->
(integer_of_int32(v_2) >= 0) ->
forall __retres:int32.
(__retres = v_2) ->
forall return:int32.
(return = __retres) ->
("JC_13": (integer_of_int32(return) = 2147483647))
and for the second:
goal my_abs32_ensures_neg_limit_po_2:
forall v_2:int32.
(integer_of_int32(v_2) = ((-2147483647) - 1)) ->
(integer_of_int32(v_2) < 0) ->
forall result:int32.
(integer_of_int32(result) = (-integer_of_int32(v_2))) ->
forall __retres:int32.
(__retres = result) ->
forall return:int32.
(return = __retres) ->
("JC_13": (integer_of_int32(return) = 2147483647))
Regarding documentation, you might want to have a look at Fraunhofer FOKUS' ACSL By Example: http://www.fokus.fraunhofer.de/de/quest/_download_quest/_projekte/acsl_by_example.pdf
Concerning your question, I've repeated your result (BTW, you're missing an #include <stdint.h>" in your code) with Frama-C Fluorine, and Jessie+Alt-ergo still manages to prove the post-condition. But remember that the post-condition is proved under the hypothesis that no runtime error occurs, which is not the case of your code, as the failed safety PO shows.
Namely, the second post-condition contains the hypothesis (integer_of_int32(result) = (-integer_of_int32(v_2))) which can be rewritten as (integer_of_int32(result) = 2147483648). This is in contradiction with an axiom in Jessie's prelude, that says that
forall v:int32. integer_of_int32(v)<=2147483647.
I guess that this outlines once again that you cannot claim to have verified an ACSL annotation as long as some proof obligations remain unchecked, even if they do not stem directly from this annotation.

Resources