Dafny与程序验证

Dafny与程序验证

B大以前写过一个怎样写出没有 bug 的程序:程序证明的简单例子 ,是用Idris写的,我也来写一个,不过方法不太一样。

Dafny是MSR开发的程序设计语言,它混合了OOP和FP编程范式,并且自带程序验证功能,可以通过霍尔逻辑证明程序正确性。

它的语法非常简单,看起来就像C++,Scala和Haskell的混合。

  • 变量(scala风格,只是没有val)
var x:Tree;
var y:nat := 0;
  • ADT(Haskell风格)
datatype Tree = Empty | Node(left:Tree,value:real,right:Tree)
datatype List<T> = Nil | Cons(head: T, tail: List<T>)
Cons(5,Nil).Cons? && Cons(5, Nil).head == 5
  • Methed/Lemma(命令式风格的函数)

expr表示表达式,col表示集合

modifies用来标记函数的副作用,requires是前置命题,ensures是后置命题,decreses用来证明递归函数可终止。

method/lemma Abs(x: int) returns (y: int)
    modifies <col>
    requires <expr>
    ensures <expr>
    decreases <expr>
{
    if x < 0 { return -x;}
    else {return x;}
}
  • Function/Predicate(函数式风格的函数)

reads用来标记函数捕获的外部变量

function abs(x: int):int
    requires <expr>
    ensures <expr>
    reads <col>
    decreases <expr>
{
    if x < 0 then -x else x
}
  • Assert用来下断言,只不过这个断言不是运行期的,而是编译期的,支持forall和exist量词以及基本的逻辑运算符。把Assert改为Assume,可以用来假定某些命题成立。
assert forall x :: P(x) ==> Q(x)
assert forall(i | 0 <= i< n - m) { b[i] := a[m + i];}
  • 模式匹配(Scala风格,match的位置略有差异)
match t
	case Empty => 
	case Node(l:Empty,v,r) => 
  • 循环

可用invariant标记循环不变量

var i:=0
while i<n
	invariant i<=n
	decreases n-i
{}
  • 支持C++类的写法,支持Lambda表达式,支持Trait,支持yield惰性流,有复杂的模块系统,支持泛型类和高阶类型(都是用<>表达)

以上就是Dafny常用的语法,还有一些不常用的语法可以参考DafnyRef

一起来看一个稍微复杂一点的例子吧!写一个二叉搜索树的插入函数

定义二叉树

datatype Tree = Empty | Node(left:Tree,value:real,right:Tree)

在类里定义变量

class BST
{
    var tree:Tree;

定义in_tree和is_ordered函数,分别表示某个元素在树内和树符合二叉搜索树的条件

predicate method is_intree(t:Tree,x:real)
{
    match t
        case Empty => false
        case Node(l,v,r) => x==v || is_intree(l,x) || is_intree(r,x)
}
predicate is_ordered(t:Tree)
{
    match t
        case Empty => true
        case Node(l,v,r) => is_ordered(l) && is_ordered(r) &&
            (forall x::is_intree(l,x) ==> x<v) &&
            (forall y::is_intree(r,y) ==> y>=v)
}

定义函数insert_into_left和insert_into_right

protected function method insert_into_left(t:Tree,x:real):Tree
    requires t!=Empty
    requires is_ordered(t)
    ensures is_ordered(insert_into_left(t,x))
{
    match t.left
        case Empty => Node(Empty,x,Empty)
        case Node(l,v,r) =>
            if x>=v then
                insert_into_right(t.left,x)
            else
                insert_into_left(t.left,x)
}

protected function method insert_into_right(t:Tree,x:real):Tree
    requires t!=Empty
    requires is_ordered(t)
    ensures is_ordered(insert_into_right(t,x))
{
    match t.right
        case Empty => Node(Empty,x,Empty)
        case Node(l,v,r) =>
            if x>=v then
                insert_into_right(t.right,x)
            else
                insert_into_left(t.right,x)
}

以上全是pure function,最后,我们回到oop的世界,定义dirty的类成员函数

method insert(x:real)
    requires is_ordered(tree)
    modifies this
    ensures is_ordered(tree)
{
    match tree
        case Empty => {tree:=Node(Empty,x,Empty);}
        case Node(l,v,r) => 
        {
            if x>=v
            {
                tree:=insert_into_right(tree,x);
            }
            else
            {
                tree:=insert_into_left(tree,x);
            }
        }
}

编译的时候,编译器会帮你验证你写的那些requires,ensures,assert是否满足,如果不满足,它会告诉你哪些命题无法证明。经过验证之后,你可以选择将Dafny代码编译为C#或编译成dll被其他.Net程序调用。

以上只是一个简单介绍,具体代码在bst.dfy

我的slides Verification in Dafny

关于Dafny的更多精彩内容, 可以关注Dafny的项目主页,里面有一些学习资源 MSR:Dafny

12 条评论
推荐阅读