1

我有我的测试代码(用于研究 WP 循环不变量),它在数组单元格中添加了两个长整数,每个数字的表示形式:

int main(int argc, const char * argv[]) {

char a[32], b[32];//size can be very big
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
scanf("%s %s", a, b);
unsigned int size1 = strlen(a);
unsigned int size2 = strlen(b);
//code to reverse a string. currently proved 
reverse(a, size1);
reverse(b, size2);
for (unsigned int i = 0; i < size1; i++) a[i]-='0'; //move from chars to integers
for (unsigned int j = 0; j < size2; j++) b[j]-='0';
unsigned int maxsize = size1;
if (size2 > maxsize) maxsize = size2;
int over = 0;
//actual computation code

/*@
loop invariant maxsize == \max(size1, size2);
loop invariant bound: 0 <= k <= maxsize;
loop invariant ov: \forall integer i; 0 < i < k ==> \at(a[i], Here) == (\at(a[i], Pre) + b[i]+ Over((char *)a, (char *)b, i)) % 10;
loop assigns k, a[0..maxsize-1],over;
loop variant maxsize - k;
*/
for (unsigned int k = 0; k < maxsize; k++)
{
    char sum=a[k] + b[k] + over; 
    //over=overflow is for 9+9 = 18, result=8, over=1
    a[k] = sum % 10;
    over = sum / 10;
}
if (over != 0) a[maxsize++] = over;
//...
return 0;
}

我只想指定最后一个循环不变量。我已经用 \at(a[i], LoopCurrent) 尝试了一些 ACSL 代码。我收到未知状态或超时。最后,我完成了一个公理递归解决方案,但没有任何成功。

我现在不知道我还应该尝试什么来验证这一点。帮助?

更新:为实际计算创建了一个函数。改进的公理。仍然有超时状态。使用来自 opam 的 Frama-C 最新版本以默认设置运行(超时 = 10,证明者 Alt-Ergo)。

/*@
@predicate
Unchanged{K,L}(char* a, integer first, integer last) = \forall integer i; first <= i < last ==> \at(a[i],K) == \at(a[i],L);

axiomatic ReminderAxiomatic
{
logic integer Reminder {l} (integer x, integer y);
axiom ReminderEmpty: \forall integer x, integer y; x < y ==> Reminder(x, y) == x;
axiom ReminderNext: \forall integer x, integer y; x>=y ==> Reminder(x, y) == Reminder(x-y, y)+0; 
}

axiomatic DivAxiomatic
{
logic integer Div {l} (integer x, integer y);
axiom DivEmpty: \forall integer x, integer y; x < y ==> Div(x, y) == 0 ;
axiom DivNext: \forall integer x, integer y; x >= y ==> Div(x, y) == Div(x - y, y) + 1 ;
}

axiomatic OverAxiomatic
{
logic integer Over {L}(char *a, char * b, integer step) reads a[0..step-1], b[0..step-1];
axiom OverEmpty: \forall char *a, char * b, integer step; step <= 0 ==> Over(a, b, step) == 0;
axiom OverNext: \forall char *a, char * b, integer step; step > 0 && a[step-1]<10 && a[step-1]>=0 && b[step-1]<10 && b[step-1]>=0
==> Over(a, b, step) ==  Div((int) a[step-1]+(int)b[step-1]+(int)Over(a,b,step-1), 10);
}
*/
/*@
requires 0 <= maxsize < 10;
requires \valid (a+(0..maxsize-1));
requires \valid (b+(0..maxsize-1));
requires \valid (res+(0..maxsize));
requires \forall integer i; 0 <= i < maxsize ==> 0 <= (int)a[i] < 10;
requires \forall integer i; 0 <= i < maxsize ==> 0 <= (int)b[i] < 10;
*/
void summ(char *a, char *b, char *res, unsigned int maxsize) {

char over = 0;

/*@
loop invariant bound: 0 <= k <=maxsize;
loop invariant step: \forall integer i; 0 <= i < k ==> res[i] == (char) Reminder((int)a[i]+ (int)b[i]+ (int)Over((char *)a, (char *)b, i) , 10);
loop invariant unch: Unchanged{Here,LoopEntry}((char *)res, k, maxsize);
loop assigns k, over, res[0..maxsize-1];
loop variant maxsize-k;
*/
for (unsigned int k = 0; k < maxsize; k++)
{
 char sum = a[k] + b[k] + over;
 res[k] = sum % 10;
 over = sum / 10;
}

//
if (over != 0) res[maxsize++] = over;

}
4

1 回答 1

7

首先,如果您的问题包含一个MCVE会更好,在本例中包括您当前正在处理的 C 函数(而不仅仅是它的主体),以及您编写的 ACSL 注释,在他们的代码中的确切位置。用于启动 Frama-C 的命令行以及您遇到问题的注释列表也不会受到伤害。

除此之外,这里有一些可能与您的问题有关的事情(同样,如果没有确切的描述,很难确定)。

  • loop assigns是错误的:你over在循环中分配,但那里没有提到。
  • 我不确定\maxWP是否很好地支持。
  • 模数和整数除法是自动化证明者往往难以解决的两个操作。您可能需要一些额外的断言和/或公理来帮助他们。
  • loop invariant没有说明 for 的a[i]k <= i< maxsize。由于loop assigns表示a可能已修改所有单元格,因此您必须添加一个不变量,告诉您到目前为止尚未触及具有较高索引的单元格。
  • 我不完全确定您对\at(a[i],Pre):的用法Pre表示当前函数的开始。因此,如果scanfreverse与循环在同一个函数中(同样,MCVE 会澄清这一点),这是不正确的。您可能要谈到\at(a[i],Loop_entry)在第一次进入循环的状态下引用单元格的值。

更新

恐怕 WP 将无法完全证明您的注释,但我设法获得了一个版本,其中只有lemma一个没有办法在 WP 对 C 内存的实际表示下证明它)。请注意,这意味着使用 WP 的 TIP 功能。下面提供了代码和脚本,但我将首先评论您的原始版本:readsOver

  • 不需要Divand Reminder。如果有的话,他们会混淆证明者。你可以坚持使用xxx/10and xxx%10。事实上,我对除法和模数的评论有点过于谨慎了。在目前的情况下,在这个级别上一切似乎都很好。
  • 同样,Unchanged可以内联,但保持这种方式应该不会有什么坏处。
  • 我使用unsigned char了代替char,因为它避免了由于整数提升而导致的一些虚假转换,并删除了逻辑中的任何强制转换。您通常不必在 ACSL 注释中引入此类强制转换,除非可能表明某些计算保持在适当的范围内(如\let x = y + z; x == (int) x;)。这种强制转换被转换为证明义务中的函数调用,并且再次混淆证明者。
  • 缺少不变量以表明over确实包含上一步的潜在进位(再次,您缺少不变量的一个很好的提示是 中提到的位置loop assigns没有出现在 any 中loop invariant)。
  • 最后,有一个小细节:如果你不以某种方式指出over只能是0or 1,那么没有什么可以防止加法溢出unsigned char(使得无法证明unsigned char使用无界整数的 C 计算及其对应的 ACSL 给出结果相同)。由于Over函数的递归性质,这可以通过所谓的引理函数来建立,其中数学归纳是通过具有适当不变量的简单 for 循环执行的
  • 此外,我明确地添加了不变的内容,a并且b保持不变。这通常是由 暗示的loop assigns,但是拥有这些明确的假设会使脚本更容易。

总结一下,最后的代码如下:

/*@
axiomatic Carry {
 logic integer Over {L}(unsigned char* a, unsigned char* b, integer step)
 reads a[0 .. step - 1], b[0 .. step - 1];

 axiom null_step: \forall unsigned char* a, *b, integer step;
                  step <= 0 ==> Over(a,b,step) == 0;
 axiom prev_step: \forall unsigned char* a, *b, integer step;
                  step > 0 ==>
                  Over(a,b,step) ==
                  (a[step-1] + b[step - 1] + Over(a,b,step-1)) / 10;

lemma OverFootPrint{L1,L2}:
\forall unsigned char* a, unsigned char*b, integer step;
(\forall integer i; 0<=i<step ==> \at(a[i],L1) == \at(a[i],L2)) &&
(\forall integer i; 0<=i<step ==> \at(b[i],L1) == \at(b[i],L2)) ==>
Over{L1}(a,b,step) == Over{L2}(a,b,step);
}
*/

/*@
  requires \valid(a+(0 .. step-1));
  requires \valid(b+(0 .. step - 1));
  requires \forall integer i; 0<=i<step ==> 0<=a[i]<10 && 0<=b[i]<10;
  assigns \nothing;
  ensures 0<= Over(a,b,step) <= 1;
*/
void lemma_function(unsigned char* a, unsigned char* b, unsigned int step) {
  /*@
      loop invariant 0<=i<=step;
      loop invariant \forall integer k; 0<=k<=i ==> 0 <= Over(a, b, k) <= 1;
      loop assigns i;
  */
  for (int i = 0; i < step; i++);
}

/*@
requires 0 <= maxsize < 10;
requires \valid (a+(0..maxsize-1));
requires \valid (b+(0..maxsize-1));
requires \valid (res+(0..maxsize));
requires \separated (a+(0..maxsize-1),res+(0..maxsize));
requires \separated (b+(0..maxsize-1),res+(0..maxsize));
requires \forall integer i; 0 <= i < maxsize ==> 0 <= a[i] < 10;
requires \forall integer i; 0 <= i < maxsize ==> 0 <= b[i] < 10;
*/
void summ(unsigned char* a, unsigned char*b, unsigned char* res,
          unsigned int maxsize) {

unsigned char over = 0;

/*@
loop invariant bound: 0 <= k <=maxsize;
loop invariant step: \forall integer i; 0 <= i < k ==> res[i] == (a[i]+ b[i]+ Over(a,b,i)) % 10;
loop invariant over: over == Over(a,b,k);
loop invariant a_unchanged: \forall integer i; 0 <= i < maxsize ==>
\at(a[i],LoopEntry) == a[i];
loop invariant b_unchanged: \forall integer i; 0 <= i < maxsize ==>
\at(b[i],LoopEntry) == b[i];
loop invariant unch: \forall integer i; k<=i<=maxsize ==> \at(res[i],LoopEntry) == res[i];
loop assigns k, over, res[0..maxsize-1];
loop variant maxsize-k;
*/
for (unsigned int k = 0; k < maxsize; k++)
{
 unsigned char sum = a[k] + b[k] + over;
 res[k] = sum % 10;
 over = sum / 10;
 lemma_function(a,b,k);
}

//
if (over != 0) res[maxsize++] = over;

}

此处提供了两个 TIP 脚本。要使用它们,请将它们放在scripts/typed代码旁边的目录中(例如file.c)并使用以下命令行:

frama-c[-gui] -wp -wp-prover alt-ergo,script -wp-session scripts file.c

您可以根据需要更改scripts目录的名称(它必须与 的参数匹配-wp-session),但是typed脚本的文件名必须与给定的相同,因为 WP 将使用它们来检测它们应该证明的证明义务。在 GUI 模式下,您可以查看脚本,但可能很难理解,并且解释每个步骤都不适合 SO 的答案。

于 2017-08-28T12:10:26.997 回答