Dafny:功能正确性的语言和程序验证程序

成立日期:2008年12月23日

达芬·洛戈(dafny-logo_sm)Dafny是一种具有内置规范结构的编程语言。Dafny静态程序验证器可用于验证程序的功能正确性。

Dafny编程语言旨在支持程序的静态验证。它是命令式的、顺序的,支持泛型类、动态分配和归纳数据类型,并构建在规范构造中。规范包括前置和后置条件、帧规范(读写集)和终止度量。

为了进一步支持规范,该语言还提供了可更新的重影变量、递归函数以及集和序列等类型。规范和虚结构仅在验证期间使用;编译器在可执行代码中省略了它们。

Dafny验证器作为编译器的一部分运行。因此,程序员与它的交互方式与与静态类型检查器的交互方式大致相同—当工具产生错误时,程序员通过更改程序的类型声明、规范和语句来进行响应。

最简单的尝试方式Dafny在你的网络浏览器里,网址是rise4fun(在新选项卡中打开)。一旦你变得严肃一点,你可能更愿意下载(在新选项卡中打开)在你的机器上运行它。尽管Dafny可以从命令行(在Windows或其他平台上)运行,但运行它的首选方式是在Microsoft Visual Studio 2010中,在程序员编辑程序时,Dafny验证器在后台运行。

Dafny验证器由布吉(在新选项卡中打开)和Z3。

Dafny编译器从经过验证的程序中为生成代码(.dll或.exe)。NET平台。然而,与其他设备接口的设施。NET代码是最少的。

这个源代码(在新选项卡中打开)Dafny有空。

  • 了解更多信息

    要成为Dafny的用户,请遵循Dafny教程(在新选项卡中打开)在线。

    你也可以在以下几集中看到达夫尼的表演验证角(在新选项卡中打开)

    要了解更多有关Dafny功能的信息,Dafny快速参考可能适合您。

    下面介绍Dafny的显著特征以及用Dafny编写的Schorr-Waite算法。如果你有科学或技术方面的倾向,可以阅读并引用以下内容:

    K.鲁斯坦·莱诺(K.Rustan M.Leino)。 Dafny:功能正确性的自动程序验证程序.英寸LPAR-16型LNCS第6355卷,第348-370页。施普林格出版社,2010年。[PDF格式(在新选项卡中打开)][会议演示文稿中的幻灯片(在新选项卡中打开)]

    为了深入研究Dafny背后的技术,Marktoberdorf 2008暑期学校的以下课堂讲稿描述了Dafny在Boogie 2中的编码:

    K.鲁斯坦·莱诺(K.Rustan M.Leino)。 面向对象软件的规范和验证.英寸软件安全工程方法和工具,《北约和平与安全科学系列D:信息和通信安全》第22卷,第231-266页。IOS出版社,2009年[PDF格式(在新选项卡中打开)][演讲中的幻灯片(在新选项卡中打开)]

  • 愚蠢的快速参考

    本页展示了Dafny中许多最常见的语言功能。为了让你更快地开始,这里的描述被简化了——本页不是语言参考。例如,这些页面不会进入模块、迭代器或精化,除非您用Dafny编写更大或更高级的程序,否则不需要这些。

    程序

    在顶层,Dafny程序(存储为扩展名为.dfy的文件)是一组声明。声明介绍类型方法,功能,其中介绍顺序无关。这些用户定义的类型包括归纳数据类型。类本身还包含一组声明,引入字段、方法和函数。如果程序包含一个名为Main的无参数方法,则编译后的程序将从那里开始执行,但不需要使用Main方法进行验证。

    注释以//开头并转到行尾,或者以/*开头并以*/结尾并可以嵌套。

    领域

    在类中,某种类型T的字段x声明为:

    变量x:T

    与局部变量和绑定变量不同,类型是必需的,不会被推断。通过在声明之前使用关键字ghost,可以将字段声明为ghost字段。Dafny的类型包括bool表示布尔值,int表示数学(即无界)整数,string表示字符串,用户定义的类和归纳数据类型,set对于T值的有限数学(不可变)集(其中T是类型),以及seq用于T值的数学(不可变)序列。此外,还有一个或多个维度的数组类型(类似于预定义的“类”类型),即写入数组,阵列2,阵列3, …. 类型对象是所有类类型的超类型,也就是说,对象表示任何引用,包括null。另一个有用的类型是nat,它表示int的子范围,即非负整数。

    方法

    方法声明(在顶层或类内部)的形式如下:

    方法M(a:a,b:b,c:c)返回(x:x,y:y,z:y)需要PremodifiesFrameassuresPostdirectedisRank{Body}

    其中a、b、c是方法的内参数,x、y、z是方法的外参数,Pre是表示方法的前提条件的布尔表达式,Frame表示其字段可以由方法更新的一组对象,Post是表示方法后条件的布尔表达,Rank是方法的变量函数,Body是实现该方法的语句。框架可以是表达式的列表,每个表达式都是一组对象或单个对象,后者代表由该对象组成的单个集。方法的框架是这些集合的并集,加上方法体分配的对象集。例如,如果c和d是c类的参数,那么

    修改{c,d}

    修改{c}+{d}

    修改c,{d}

    修改c,d

    所有的意思都是一样的。

    如果省略,前置和后置条件默认为true,框架默认为空集。变量函数是表达式列表,表示由给定表达式隐式后跟“top”元素组成的无限字典元组。如果省略,Dafny将猜测该方法的一个变量函数,即以该方法的内参数列表开头的字典元组。

    通过在声明之前使用关键字ghost,可以将方法声明为ghost。默认情况下,类中的方法具有隐式接收器参数this。可以通过在方法声明之前使用关键字static删除此参数。类C中的静态方法M可以由C.M(…)调用。

    在类中,可以通过将关键字方法替换为关键字构造函数来将方法声明为构造函数方法。构造函数只能在分配对象时调用(参见下面的对象创建示例),对于包含一个或多个构造函数的类,对象创建必须与对构造函数的调用一起完成。当然,通常情况下,一个方法有一个名称,但一个类可以有一个没有名称的构造函数——匿名构造函数–就像这样:

    构造函数(n:int)修改这个{Body}

    通常,ghost方法被用作引理。通过使用引理而不是ghost方法声明方法,可以在程序文本中更清楚地说明这一点。

    下面是一个示例方法,它采用3个整数作为内参数,并按排序顺序在3个外参数中返回这些整数:

    Sort方法(a:intb:int,c:int)返回(x:int,y:int,z:int)确保x<=y<=z&&multiset{a,b,c}==multisset{x,y,z}{x,y,z:=a,b;c;如果z<y{y,z:=z,y;}如果y<x{x,y:=y,x;}

    功能

    函数声明(在顶层或类内部)的形式如下:

    函数F(a:a,b:b,c:c):查询预读取帧确保后降低排名{正文}

    其中a、b、c是方法的参数,T是函数结果的类型,Pre是表示函数前提的布尔表达式,Frame表示函数体可能依赖其字段的一组对象,Post是表示函数后条件的布尔表达式;Rank是函数的变量函数,Body是定义函数的表达式。前提条件允许函数是部分的,也就是说,前提条件说明了函数的定义时间(Dafny将验证函数的每次使用都符合前提条件)。通常不需要后置条件,因为函数体给出了完整的定义。然而,后置条件可以是一个方便的地方来声明可能需要归纳证明才能建立的函数的属性。例如:

    函数阶乘(n:int):函数间0≤n≥1≤阶乘(n){如果n==0,则1否则阶乘(n-1)*n}

    表示阶乘的结果总是正数,Dafny从函数体中归纳验证了这一点。要在后置条件中引用函数的结果,请使用函数本身,如示例所示。

    默认情况下,函数是ghost,不能从非ghost代码调用。要使其非重影,请使用两个关键字函数方法替换关键字函数。返回布尔值的函数可以用关键字谓词声明(然后省略冒号和返回类型)。

    默认情况下,类中的函数具有隐式接收器参数this。可以通过在函数声明之前使用关键字static删除此参数。类C中的静态函数F可以由F.M(…)调用。这可以为在单独的类中声明多个helper函数提供一种方便的方法。

    课程

    类的定义如下:

    类C{//此处显示成员声明}

    其中类的成员(字段、方法和函数)是在大括号内定义的(如上所述)。

    数据类型

    归纳数据类型是一种类型,其值是使用一组固定的构造函数创建的。带有构造函数Leaf和Node的数据类型Tree声明如下:

    数据类型Tree=叶|节点(Tree,int,Tree)

    施工人员之间用竖条隔开。无参数构造函数不需要使用括号,如这里所示的Leaf。

    对于每个构造函数Ct,数据类型隐式声明一个布尔成员Ct?,对于使用Ct构造的那些值,返回true。例如,在代码段之后:

    var t0:=叶;var t1:=节点(t0,5,t0);

    表达式t1.Node?计算为true和t0。节点?计算结果为false。如果使用相同的构造函数和该构造函数的相同参数创建了两个数据类型值,则这两个值相等。因此,对于像Leaf这样的无参数构造函数,t.Leaf?给出了与t==Leaf相同的结果。

    构造函数可以有选择地为其任何参数声明析构函数,这是通过为参数引入名称来实现的。例如,如果Tree声明为:

    数据类型树=叶|节点(左:树,数据:int,右:树)

    然后t1.data==5和t1.left==t0保持在上面的代码片段之后。

    泛型

    Dafny支持类型参数。也就是说,任何类、归纳数据类型、方法和函数都可以有类型参数。这些在声明内容名称后的尖括号中声明。例如:

    类MyMultiset{/*…*/}数据类型树=叶|节点(树,T,树)方法Find(键:T,集合:树){/*…*/}函数IfThenElse(b:bool,x:T,y:T):T{/*…*/}

    声明

    以下是Dafny中最常见的语句示例。

    var局部变量:=ExprList;L值:=ExprList;断言BoolExpr;打印ExprList;

    如果BoolExpr0{Stmts0},则为else如果BoolExpr1{Stmts1},否则为{Stmts2}

    BoolExprinvariant Inv修改帧时降低秩{Stmts}

    match Expr{case Empty=>Stmts0case节点(l,d,r)=>Stmts1}

    断裂;回报;

    var语句引入了局部变量(不允许隐藏在同一组最紧密的花括号内声明的其他变量)。对于任何类型的T,每个变量都可以有选择地后跟:T,这将显式地为前面的变量提供类型T(而不是被推断)。具有初始值的ExprList是可选的。要将变量声明为ghost变量,请在声明之前使用关键字ghost。

    赋值语句将ExprList中的每个右侧赋值给Lvalues中相应的左侧。这些赋值是并行执行的(更确切地说,所有必要的读取都发生在写入之前),因此左侧必须表示不同的L值。每个右侧可以是以下形式之一的表达式或对象创建:

    new Tnew T.Init(ExprList)new T(Expr列表)new T[SizeExpr]new T[大小Expr0,大小Expr1]

    第一种形式分配类型为T的对象。第二种形式还对新分配的对象调用初始化方法或构造函数。第三种形式显示了当调用的构造函数是匿名的时,语法与第二种形式的区别。其他形式显示了数组分配的示例,特别是T值的一维数组和二维数组。

    赋值的整个右侧也可以是方法调用,在这种情况下,左侧是实际的输出参数(如果没有输出参数,则省略:=)。

    assert语句声明给定表达式的计算结果为true(由验证器检查)。

    print语句将给定打印表达式的值输出到标准输出。字符串中的字符可以转义;例如,print语句感兴趣的是,n表示字符串中的换行符。

    if语句是常见的语句。该示例显示了使用else-if将备选方案串在一起。else分支通常是可选的。

    while语句是常见的循环,其中不变量声明提供循环不变量,modifies子句限制循环的修改框架,decreases子句为循环引入变量函数。默认情况下,循环不变量为true,修改框架与封闭上下文中的相同(通常是封闭方法的modifies子句),变量函数是从循环保护中猜测出来的。

    match语句计算源Expr(一个其类型为归纳数据类型的表达式),然后执行与使用哪个构造函数创建源数据类型值相对应的情况,将构造函数参数绑定到给定的名称。如果不需要它们来标记match语句的结尾,那么可以省略围绕大小写的花括号。

    break语句可用于退出循环,return语句可用于结束方法。

    表达式

    Dafny中的表达式与类Java语言中的表达式非常相似。这里有一些值得注意的差异。

    除了短路布尔运算符&&(and)和||(or)之外,Dafny还有一个短路隐含运算符==>和一个if-and-only-if运算符<==>。从宽度来看,<==>的结合力低于==>,而==>又低于&&和||。

    Dafny比较表达式可以是链式的,这意味着“同一方向”的比较可以串在一起。例如,

    0<=i<j<=a.长度==N

    含义与:

    0<=i&&i<j&&j<=a.长度&&a.长度==N

    注意,布尔等式可以使用==和<==>来表示。这两者之间有两个区别。首先,==比<==>具有更高的约束力。第二,==是连锁而<==>是相联的也就是说,a==b==c与a==b&&b==c相同,而a<===>b<===>c与a<===>(b<===>c)相同,后者也与(a<===>b)<===>c相同。

    对整数的运算是常见的,除了/(整数除法)和%(整数模)遵循欧几里德定义,这意味着%总是产生非负数。(因此,当/或%的第一个参数为负数时,结果与在C、Java或C#中得到的结果不同,请参见http://en.wikipedia.org/wiki/Moduleo_operation网站(在新选项卡中打开).)

    Dafny表达式包括通用量词和存在量词,其形式如下:

    对于所有x::Expr

    与exists类似,其中x是绑定变量(可以用显式类型声明,如x:T),Expr是布尔表达式。

    集合上的运算包括+(并集)、*(交集)和–(集差),以及集合比较运算符<(适当子集)、<=(子集)、它们的对偶>和>=、和!!(脱节)。S中的表达式x表示x是集合S的一个成员,并且x!S是一种方便的书写方式!(x英寸S)。要从某些元素组成一个集合,请将它们括在花括号中。例如,{x,y}是由x和y组成的集(如果x==y,则为单例集),{x}是包含x的单例集,而{}是空集。

    序列上的操作包括+(串联)和比较运算符<(适当的前缀)和<=(前缀)。可以像检查集合那样检查成员身份:S中的x和x!在S中,序列S的长度用|S|表示,这样的序列的元素具有从0到小于|S|的索引。表达式S[j]表示序列S的索引j处的元素。表达式S[m.n]返回一个序列,其中0<=m<=n<=|S|的元素是从索引m开始的S的n-m元素(即从S[m]、S[m+1]……到但不包括S[n])。表达式S[m..](通常称为“drop m”)与S[m.|S|]相同,也就是说,它返回的序列的元素除了S的前m个元素外,其余都是S的元素。表达式S[..n](通常称作“take n”)与S[0..n]相同,即它返回由S的前n个元素组成的序列。如果j是序列S的有效索引,那么表达式S[j:=x]是类似于S的序列,只是它在索引j处有x。最后,要从一些元素构成序列,请将它们括在方括号中。例如,[x,y]是由两个元素x和y组成的序列,其中[x,y][0]==x和[x,y][1]==y,[x]是唯一元素为x的单元素序列,[]是空序列。

    if-then-else表达式的形式为:

    如果是BoolExpr,则是Expr0 else Else1

    其中Expr0和Expr1是相同类型的任何表达式。与if语句不同,if-then-else表达式使用then关键字,并且必须包含显式else分支。

    match表达式类似于match语句,其形式如下:

    match Expr{case Empty=>Expr0case节点(l,d,r)=>Expr 1}

    大括号可以用来标记匹配表达式的结尾,但最常见的情况是不需要这样做,然后可以省略大括号。

  • Dafny是VSTTE 2012计划验证竞赛(在新选项卡中打开)。它也用于2011年成本验证竞赛(在新选项卡中打开),作为FoVeOOS会议的一部分。它被用于VSComp 2010(在新选项卡中打开)2010年VSTTE的比赛报告(在新选项卡中打开)发布时间:

    Vladimir Klebanov、Peter Müller、Natarajan Shankar、Gary T.Leavens、Valentin Wüstholz、Eyad Alkassar、Rob Arthan、Derek Bronish、Rod Chapman、Ernie Cohen、Mark Hillebrand、Bart Jacobs、K.Rustan M.Leino、Rosemary Monahan、Frank Piessens、Nadia Polikarpova、Tom Ridge、Jan Smans、Stephan Tobies、Thomas Tuerk、Mattias Ulbrich和Benjamin Weiss。 第一届验证软件竞赛:经验报告.英寸FM 2011:形式方法——第17届形式方法国际研讨会LNCS第6664卷,第154-168页。施普林格出版社,2011年[PDF格式(在新选项卡中打开)]

    (获得2011年FM最佳论文奖)。您可以在Dafny来源(在新选项卡中打开)

    Dafny还被用于解决各种验证基准挑战。在验证软件:工具、技术和实验(VSTTE 2008)会议上,Weide等人提出了一些验证基准,以促进不同规范语言和验证器之间的比较。本文介绍了这些基准的Dafny程序:

    K.鲁斯坦·M·莱诺和罗斯玛丽·莫纳汉。 Dafny迎接验证基准挑战.英寸VSTTE 2010,LNCS第6217卷,第112-126页。施普林格出版社,2010年[PDF格式(在新选项卡中打开)]

    Dafny还用于解决一些VACID-0验证基准:

    K.Rustan M.Leino和MichałMoskal。 VACID-0:数据结构不变量的充分正确性验证,第0版2010年VSTTE工具和实验研讨会。[PDF格式(在新选项卡中打开)]

  • 达夫尼被用于教学。以下是正在或一直在以某种身份使用Dafny进行讲座和课堂教学的大学的部分列表:

    • 加州理工学院,Rajeev Joshi(CS 116型(在新选项卡中打开))
    • 罗蒙诺索夫莫斯科国立大学
    • 堪萨斯州立大学托本·阿姆托夫特分校
    • 伦敦帝国理工学院(Imperial College London)、索菲亚·德罗索波卢(Sophia Drossopoulou)和威尔·索尼克斯(Will Sonnex)
    • NUI Maynooth、Rosemary Monahan
    • 苏黎世ETH,Peter Müller
    • 华盛顿大学伊桑·杰克逊分校;埃米娜·托拉克
    • 爱荷华州大学塞萨尔·蒂内利分校
    • 塞尔达尔·塔申奥大学
    • 斯瓦拉特·乔杜里莱斯大学(压缩机507(在新选项卡中打开))
    • 新南威尔士州,卡罗尔·摩根
    • 多伦多大学,Azadeh Farzan
    • 普林斯顿,安德鲁·阿佩尔
    • CMU,乔纳森·奥尔德里奇
    • 莫阿·约翰逊查尔默斯技术大学
    • 埃因霍温技术大学Kees Huizing
    • 俄亥俄州立大学,Bruce Weide
    • 穆拉利·西塔拉曼克莱姆森大学
    • 路易斯·凯雷斯新里斯本FCT大学
    • 巴斯克大学帕基·卢西奥分校
    • 南安普顿大学迈克尔·巴特勒分校
    • 特温特大学,玛丽克·赫斯曼
    • 耶鲁大学,Ruzica Piskac
    • 纽约州立大学石溪分校,刘安妮
    • 爱丁堡大学,伊恩·斯塔克(APL14(APL14)(在新选项卡中打开))
    • 吕贝克应用科学大学,安德烈亚斯·谢弗
    • UniBw München、Birgit Elbl和Lothar Schmitz
    • IIT马德拉斯,加内桑·拉马林根
    • 克拉克森大学,克里斯托弗·林奇
    • 特拉维夫大学,穆利·萨吉夫
    • 犹他大学兹沃尼米尔·拉卡马利分校
    • 西奥多·诺维尔纪念大学
    • 加利福尼亚大学圣克鲁斯分校
    • 都柏林三一学院,Vasileios Koutavas
    • Farhad Mehta,高铁Hochschule für Technik Rapperswil

    以及辅导和暑期学校:

    • 马克托伯多夫暑期学校,2008年和2011年
    • 2011年激光暑期学校
    • 2008年俄勒冈州尤金市编程语言逻辑和定理证明暑期学校
    • 教程,KTH,斯德哥尔摩,2012
    • 受邀教程,VSTTE 2012
    • 教程,HILT 2012
    • 2013年ICSE教程

    如果你正在或曾经使用Dafny教学,鲁斯坦·莱诺(在新选项卡中打开)我很想知道你的经历。如果你想在这些名单上提及,请让他知道。

Chris Hawblitzel的肖像

克里斯·霍布利泽尔

高级研究员

杰·洛奇的肖像

杰伊·洛希

高级首席研究员

米查尔·莫斯卡尔肖像

米查尔·莫斯卡尔

首席研究软件开发工程师

Nikhil Swamy肖像

尼基尔·斯瓦米

高级首席研究员