验证移动数组区域的Dafny方法

时间:2016-04-06 19:05:38

标签: induction formal-verification dafny

我正在使用Dafny制作一个删除方法:

  • char array line

  • 数组的长度l

  • 职位at

  • 要删除的字符数p

首先删除从at到at + p的行中的字符,然后您必须将at + p右侧的所有字符移至at

例如,如果您有[e][s][p][e][r][m][a]at = 3p = 3,则最终结果应为[e][s][p][a]

我试图证明一个有意义的后置条件,如:

ensures forall j :: (at<=j<l) ==> line[j] == old(line[j+p]);

确保at + p右侧的所有字符都在新位置。

但是Dafny输出了两个错误:

  

指数超出范围7 53

     

后置条件可能不会在此返回路径上保留。 19 2

method delete(line:array<char>, l:int, at:int, p:int)
  requires line!=null;
  requires 0 <= l <= line.Length && p >= 0 && at >= 0;
  requires 0 <= at+p <= l;
  modifies line;
  ensures forall j :: (at<=j<l) ==> line[j] == old(line[j+p]) ; 
{
  var tempAt:int := at;
  var tempAt2:int := at;
  var tempPos:int := at+p;
  while(tempAt < at + p)
    invariant at<=tempAt<=at + p;
  { 
    line[tempAt] := ' ';
    tempAt := tempAt + 1;
  }

  while(tempPos < line.Length && tempAt2 < at + p)
    invariant at + p<=tempPos<=line.Length;
    invariant at<=tempAt2<=at+p;
  {
    line[tempAt2] := line[tempPos];
    tempAt2 := tempAt2 + 1; 
    line[tempPos] := ' ';
    tempPos := tempPos + 1;
  }
}

Here is the program on rise4fun

1 个答案:

答案 0 :(得分:1)

我认为没有必要使用量词来表达这种后置条件。通常通过将数组切割成序列来更好地表达它们。

当你试图验证一个循环时,你需要提供一个循环不变量,它强大到足以暗示后置条件与循环条件的否定结合。

选择循环不变量的一个好策略是使用postcondition方法,但用循环索引替换数组长度。

你的循环不变量也需要足够强大才能使归纳发挥作用。在这种情况下,您不仅需要说明循环如何修改线条,还要说明每次迭代中线条的哪些部分保持不变。

Solution on rise4fun.

// line contains string of length l
// delete p chars starting from position at
method delete(line:array<char>, l:nat, at:nat, p:nat)
  requires line!=null
  requires l <= line.Length
  requires at+p <= l
  modifies line
  ensures line[..at] == old(line[..at])
  ensures line[at..l-p] == old(line[at+p..l])
{
  var i:nat := 0;
  while(i < l-(at+p))
    invariant i <= l-(at+p)
    invariant at+p+i >= at+i 
    invariant line[..at] == old(line[..at])
    invariant line[at..at+i] == old(line[at+p..at+p+i])
    invariant line[at+i..l] == old(line[at+i..l]) // future is untouched
  { 
    line[at+i] := line[at+p+i];
    i := i+1;
  }
}

用空格覆盖

如果要用空格覆盖旧字符串的尾部,可以执行以下操作:

method delete(line:array<char>, l:nat, at:nat, p:nat)
  requires line!=null
  requires l <= line.Length
  requires at+p <= l
  modifies line
  ensures line[..at] == old(line[..at])
  ensures line[at..l-p] == old(line[at+p..l])
  ensures forall i :: l-p <= i < l ==> line[i] == ' '  
{
  var i:nat := 0;
  while(i < l-(at+p))
    invariant i <= l-(at+p)
    invariant at+p+i >= at+i 
    invariant line[..at] == old(line[..at])
    invariant line[at..at+i] == old(line[at+p..at+p+i])
    invariant line[at+i..l] == old(line[at+i..l]) // future is untouched
  { 
    line[at+i] := line[at+p+i];
    i := i+1;
  }

  var j:nat := l-p;
  while(j < l)
    invariant l-p <= j <= l
    invariant line[..at] == old(line[..at])
    invariant line[at..l-p] == old(line[at+p..l])
    invariant forall i :: l-p <= i < j ==> line[i] == ' '
  {
    line[j] := ' ';
    j := j+1;
  }
}

Extended solution on rise4fun.