我目前正在发现frama-c的功能,特别是WP&价值分析工具。我的最终目标是能够在涉及多个层的较大代码上使用frama-c:
到目前为止,我一直试图应用自下而上的方法,即开始指定不包含任何函数调用的函数,并通过-lib-entry和-main内核选项隔离它们来分析它们的行为。通过这样做,我确保如果假设前置条件为真,则验证整个函数契约。一旦我尝试指定调用这些函数的上层,事情变得复杂。首先,我经常必须指定被调用函数的行为,这并不总是容易的,因为这些函数可能会处理当前函数范围之外的变量/函数。
让我举个简单的例子:
让我们说在 file1.h 中我们定义了一个数据结构" my_struct"包含字段编号和字段奇偶校验。
在 file1.c 中,我有两个功能:
在 file2.c 中,我有一个fonction" outside_caller"只调用correct_parity()。我的目标是能够以我指定correct_parity的方式指定outside_caller。贝娄是相应的源代码:
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;
}
这里的主要问题是,为了指定outside_caller(),我需要访问超出file2.c范围的_sVar。这意味着要处理在file1.h中声明并在correct_parity函数中更新的ghost变量(g_sVar)。为了使调用者(correct_parity)能够使用被调用者合约,必须在被调用者的合同中使用重影变量g_sVar。
以下是WP分析的结果:
(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]注释函数check_parity
[wp]预定14个进球[wp]探明目标: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]注释函数correct_parity [wp]预定18个进球 [wp]证明目标: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]注释函数outside_caller
[wp]预定14个进球 [wp] [Alt-Ergo]目标typed_outside_caller_assign_exit:未知(Qed:4ms)(515ms)
[wp] [Alt-Ergo]目标typed_outside_caller_call_correct_parity_pre_correct_req_is_par___:未知(636毫秒)
[wp] [Alt-Ergo]目标typed_outside_caller_assert:超时
[wp] [Alt-Ergo]目标typed_outside_caller_assign_normal_part1:超时
[wp] [Alt-Ergo]目标typed_outside_caller_call_correct_parity_pre_correct_req_is_num___:未知(205ms)
[wp]证明目标:9/14
Qed:9(4ms)
Alt-Ergo:0(中断:2)(未知:3)
==> WP : GUI Output
在此配置中,使用g_sVar ghost变量指定了被调用者,但require和assings子句除外有两个原因:
但通过这样做,我以某种方式使调用者的规范无效,因为你可以看到WP的输出。
为什么看起来我调用的函数越多,证明函数行为变得越复杂?有没有一种正确的方法来处理多个函数调用和静态变量?
提前多多谢谢你!
PS:我在运行Ubuntu 14.04,64位机器的VM上使用Magnesium-20151002版本。我知道开始使用WhyML和Why3可以帮助我很多但是到目前为止我还没有能够在this教程的每一步之后在Windows和Ubuntu上安装Why3 ide
答案 0 :(得分:0)
首先,请注意-main
和-lib-entry
对WP有用(您提到您也对EVA /价值分析感兴趣,但您的问题是针对WP)。
你对静态变量的问题是一个已知问题,最简单的处理方法就是在标题中声明一个ghost变量。但是,你必须用ghost变量而不是static
来表达你的合同。
否则,呼叫者将无法使用这些合同,因为他们对_sVar
一无所知。根据经验,最好将合同放在标题中:这样,您只能使用在翻译单元外可见的标识符。
关于函数调用,重点是您尝试用WP证明的函数调用的任何函数都必须带有至少包含assigns
子句的契约(可能还有更多)精确的规范,取决于被调用者的效果与您想要在调用者身上证明的属性相关的程度)。这里要记住的重要一点是,从WP的角度来看,在通话之后,只有通过ensures
在被叫方合同中明确规定的内容才是真实的,加上任何事实都是如此。不在assigns
子句中的位置保持不变。