程序验证之Dafny--证明霍尔逻辑的半自动化利器

news2024/9/23 1:25:25

                                     一、What is Dafny?【来自官网介绍 Dafny 

1)介绍

Dafny 是一种支持验证的编程语言,配备了一个静态程序验证器。

通过将复杂的自动推理与熟悉的编程习语和工具相结合,使开发者能够编写可证明正确的代码(相对于 {P}S{Q} 这种霍尔三元组的规范而言)。

Dafny 还可以将 Dafny 代码编译到熟悉的开发环境,如 C#、Java、JavaScript、Go 和 Python;使得严格的验证成为开发过程的有机组成部分,从而减少了可能在测试中被遗漏的、代价高昂的后期错误。

2)原理

Dafny验证器根据我们提供的前置条件后置条件,以及循环不变式,自动生成验证条件(Verification Condition,VC)的逻辑公式。 这些验证条件主要包含,不变式的可达性归纳性,以及使用循环不变式证明后置条件。 这些逻辑公式都会交给同样是由微软开发的SMT求解器Z3来求解,并依据求解结果,返回验证结果。

二、使用快速入门:


2.1 一个.dfy后缀的文件:

程序主要包含以下几部分:

  1. 类型(types)

  2. 方法(methods)

  3. 函数(functions)

  4. 用户自定义的类型包括类(class)和归纳数据类型(inductive class)

  5. 类class本身也包含一组声明(declarations)、介绍字段(introducing fields)、方法(methods)和函数(functions)

注释:// 双斜杠 或者 /* xxxxx */

2.1.1基本定义

在类中,定义字段x为数据类型(types)T: var x: T

注意事项:

  • 数据类型必需手动申明的,不会被自动推断。

  • 通过在声明前加上关键 ghost 可以将该字段声明为幽灵(即用于规范而不是执行)字段。

Dafny 的9种数据类型包括:

  • bool:布尔值
  • int:无界整数
  • string: 字符串
  • class/inductive class: 用户自定义的类和归纳类、
  • set<T>:不可变的无序集合
  • seq<T>:不可变的有序集合
  • array<T>array2<T>array3<T>: 多维数组类型
  • object:所有类型的超类
  • nat:范围是int一半,非负整数。

2.1.2方法 methods

方法的声明如下:

method M(a: A, b: B, c: C) r eturns (x: X, y: Y, z: Y)  //输入输出参数
requires Pre	//前置条件
modifies Frame  //框架
ensures Post	//后置条件
decreases TerminationMetric //变体函数
{
Body //函数体`
}

其中:

  • a, b, c : 输入参数
  • x, y, z : 输出参数
  • Pre: 表示方法 前提条件 的 布尔表达式
  • Frame: 表示类对象的集合,可以被方法更新(Frame denotes a set of objects whose fields may be updated by the method)
  • Post: 是方法 后置条件 的 布尔表达式
  • TerminationMetric: 是方法的变体函数(TerminationMetric is the method’s variant function)
  • Body: 是实现方法的语句。

2.1.2.1框架Frame

框架Frame 是单个或多个对象组成的表达式的集合。(见下面例子)

框架Frame是由类内对象和类外方法内对象两部分组成。(反正就是一堆类对象的集合)

例如,如果 c 和 d 是类C的对象,那么以下每行意思是一样的。

  • modifies {c, d}

  • modifies {c} + {d}

  • modifies c, {d}

  • modifies c, d

如果方法内啥都没写,那么前置和后置条件默认为真,框架默认为空集。

2.1.2.2变体函数 variant function

变体函数是一个表达式组成的列表,表示由给定表达式组成的字典元组,后跟隐含的“top”元素。

如果省略没写的话,Dafny 将猜测该方法的变体函数,通常是以该方法的参数列表开头的字典元组。

Dafny IDE 将在工具提示中显示猜测。

2.1.2.3ghost 关键字

通过在声明之前加上关键字 ghost 可以将方法声明为 ghost方法(仅规范而不用于执行)。

2.1.2.4this 关键字

默认情况下,类中的方法都具有隐式接收器参数 this。可以通过在方法声明之前使用关键字 static 来删除此参数。

类 C 中的静态方法 M 可以由 C.M(...) 调用。

2.1.2.5构造函数/构造体 constructor

在类中,一个方法可以通过将method关键字替换为constructor,申明一个构造方法。

构造函数(构造方法)只能在分配对象时调用(参见示例)

对于包含一个或多个构造函数的类,对象创建必须与对构造函数的调用一起完成。

通常,一个方法当然得有一个名字,但是一个类可以有一个没有名字的构造函数,也就是匿名构造函数 constructor (n:int )

constructor (n: int) //constructor 匿名构造器` 
modifies this //框架内对象的构造体 this就是this.frame?`

{
  Body
}

2.1.3函数 function

函数具有以下形式:

function F(a: A, b: B, c: C): T
requires Pre  //前置条件pre
reads Frame	//框架frame
ensures Post //后置条件post
decreases TerminationMetric //变体函数
{
  Body	//函数体
}
  • abc : 输入的形参,
  • T : 返回结果的类型,
  • Pre: 表示函数前提条件的布尔表达式,
  • Frame: 函数体body需要的对象列表
  • Post: 函数的后置条件布尔表达式
  • TerminationMetric: 变体函数
  • Body: 定义函数的表达式。

前置条件允许函数是部分的(只用前置就行不用写后置),即前置条件表示函数何时定义,并且 Dafny 会验证函数的每次使用都满足前置条件。

通常不需要后置条件,因为函数在函数体内已经给出了完整的定义。

例如:

(写个后置加个保险也行,一般后置就是声明该函数的基本属性,比如Factorial这个函数所有数字都≥1)

function Factorial(n: int): int
requires 0 <= n //前置条件pre
ensures 1 <= Factorial(n) //后置条件post
{
 if n == 0 then 1 else Factorial(n-1) * n //函数体body
}

要在后置条件中引用函数的结果,请使用函数本身的名称,如示例中所示。

默认情况下,函数是ghost,不能从可执行(非ghost)代码中调用。

为了使它从ghost变成非ghost,用关键字function method替换 function.

一个返回布尔值的函数可以用关键字声明,然后省略冒号和返回类型。

如果函数或方法被声明为类class成员,则它具有隐式接收器参数 this。可以通过在声明之前加上关键字static来删除此参数。

类 C 中的静态函数 F 可以被 C.F(...) 调用。

E.g.2:[educoder实训实例]:

method test(n: int) returns (sum: int)
  requires n >= 0
  ensures sum * 2 == n * (n+1)
{
  sum := 0;
  var i:int := 0;
  while i <= n
  invariant i<=n+1
  invariant sum*2 == i*(i-1)

  {
    sum := sum + i;
    i := i + 1;
  }
}

2.1.3.1 类 class

一个类定义如下:

class C {
 // member declarations go here
}

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

2.1.3.2数据类型 datatypes

归纳数据类型(inductive datatype)是一种类型,其值是用一组固定的构造函数创建的。

数据类型 为Tree带有构造函数 Leaf 和 Node 的函数声明如下:

datatype Tree = Leaf | Node(Tree, int, Tree)//Leaf为无参构造函数 Node为有参

构造函数由竖线分隔。 无参数构造函数不需要使用括号,如 Leaf 所示。

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

var t0 := Leaf; var t1 := Node(t0, 5, t0);

表达式 t1.Node结果为 true, t0.Node结果为false。

如果两个数据类型值是使用相同的构造函数和该构造函数的相同参数创建的,则它们是相等的。因此,对于像 Leaft.Leaf 这样的无参数构造函数,Dafny会给出与 t == Leaf 相同的结果。(没看懂,不管了)

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

datatype Tree = Leaf | Node(left: Tree, data: int, right: Tree)

那么t1.data == 5t1.left == t0在上面的代码片段之后保持不变。(还是没懂)

2.1.3.3泛型 Generics

Dafny同其他语言一样都有泛型,任何类、方法、函数都可以有类型参数,在<>中申明该数据类型T

class MyMultiset<T> {
 /*...*/
}		//类泛型
datatype Tree<T> = Leaf | Node(Tree<T>, T, Tree<T>) //自定义数据泛型
method Find<T>(key: T, collection: Tree<T>) { //方法泛型`
 /*...*/
}

function IfThenElse<T>(b: bool, x: T, y: T): T {	//函数泛型
 /*...*/
}

2.1.3.4声明 Statement

以下是 Dafny 中最常见语句:

var LocalVariables := ExprList;
Lvalues := ExprList;
assert BoolExpr;
print ExprList;

if BoolExpr0 {
  Stmts0
} else if BoolExpr1 {
  Stmts1
} else {
  Stmts2
}

  while BoolExpr
  invariant Inv
  modifies Frame
  decreases Rank
{
  Stmts
}
 match Expr {
  case Empty => Stmts0
  case Node(l, d, r) => Stmts1
}
 break;
 return;
函数/方法的返回值赋值给变量

(就是将函数/方法返回的值或对象 赋给 一个局部变量而已) var LocalVariables := ExprList; var 语句引入了局部变量。 Lvalues := ExprList;

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

  1. new T

  2. new T.Init(ExprList)

  3. new T(ExprList)

  4. new T[SizeExpr]

  5. new T[SizeExpr0, SizeExpr1]

第一种形式分配一个类型为 T 的对象。

第二种形式另外在新分配的对象上调用初始化方法或构造函数。

第三种形式是当调用匿名构造函数时的语法。

其他形式分别了T是一维和二维数组对象的匿名构造方法

assert 声明

assert 语句判断后面的表达式结果是否为真(由验证器验证)。

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

if 选择语句

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

while 循环语句
  • while 语句是通常的循环,其中
  • invariant 声明给出了一个循环变量
  • modifies语句限制了循环的框架
  • reduction语句从循环中引入了一个变体函数。

默认情况下,循环不变式为真,修改框与封闭上下文中的相同(通常是封闭方法的修改子句),并从循环保护中猜测变体函数。(真没看懂)

while BoolExpr //布尔表达式-循环条件`
  invariant Inv
  modifies Frame
  decreases Rank

{
  Statements
}
break语句

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

2.1.4 表达式 Expressions

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

2.1.4.1基本运算符

除了短路布尔运算符 && (and) 和 || (或),Dafny 有一个短路蕴涵运算符 ==> 和一个 if-and-only-if 运算符 <==>

正如它们的宽度所暗示的那样,<==> 具有比 ==>低的绑定力,而后者又比 && 和 ||具有更低的绑定力。

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

0 <= i < j <= a.Length == N

含义相同: 

0 <= i && i < j && j <= a.Length && a.Length == 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/Modulo_operation。)

集合运算

集合上的操作包括+(并)、*(交)和-(集合差)、集合比较运算符<(真子集)、<=(子集)、它们的对偶> 和>=,以及!! (脱节)。 S 中的表达式 x 表示 x 是集合 S 的成员,而 x !in S 是一个方便的写法 !(x in S)

要从某些元素创建一个集合,请将它们括在花括号中。例如,{x,y} 是由 x 和 y 组成的集合(如果 x == y,则为单例集),{x} 是包含 x 的单例集,{} 是空集。

序列运算

对序列的操作包括 +(连接)和比较运算符 <(适当的前缀)和 <=(前缀)。成员资格可以像集合一样检查:x in S 和 x !in S。序列 S 的长度表示为 |S|,并且此类序列的元素具有从 0 到小于 |S| 的索引。表达式 S[j] 表示序列 S 的索引 j 处的元素。表达式 S[m..n],其中 0 <= m <= n <= |S|,返回一个序列,其元素是S 从索引 m 开始(即,从 S[m]、S[m+1]、……直到但不包括 S[n])。表达式 S[m..]; (通常称为“drop m”)与 S[m..|S|] 相同;也就是说,它返回除 S 的前 m 个元素之外的所有元素的序列。表达式 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判断语句

if-then-else 表达式的形式为:if BoolExpr then Expr0 else Expr1

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

三、环境配置及实例

vscode内配置dafny:

打开[.dfy]文件:

插件默认在每次执行文件保存操作后,尝试对Dafny代码进行验证。未验证通过的位置会有红色波浪线标出,鼠标在红色波浪线处悬停会显示错误详情。

无法验证显示:

成功显示:

项目首页 - dafny - GitCode

达夫尼博客 |来自 Dafny 维护者和嘉宾的新闻和教育材料。

简单上手 | Dafny (aaron-clou.github.io)https://aaron-clou.github.io/dafnycommunity/pages/39fb20/#hello-dafny

程序验证(5)- 程序验证案例 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/312501103

dafny-lang/dafny: Dafny is a verification-aware programming language (github.com)https://github.com/dafny-lang/dafny

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1679662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构(C):树的概念和二叉树初见

目录 &#x1f37a;0.前言 1.树概念及结构 2.认识一棵树 3.树的表示 3.1树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 4.二叉树 4.1特殊的二叉树 4.2二叉树的性质 &#x1f48e;5.结束语 &#x1f37a;0.前言 言C之言&#xff0c;聊C之识&…

OpenAI 震撼发布:GPT-4o免费,实时语音视频交互开启新纪元

OpenAI 震撼发布&#xff1a;GPT-4o免费&#xff0c;实时语音视频交互开启新纪元 在仅仅问世17个月后&#xff0c;OpenAI 研制出了仿佛科幻片中登场的超级人工智能——GPT-4o&#xff0c;而且所有人都可以完全免费使用&#xff0c;让这个科技界的巨浪让人震撼无比&#xff01;…

JSP技术

前言&#xff1a;虽然现在Vue盛行&#xff0c;但是对于初学者和一些项目我们还是采用jsp技术来编写前端代码&#xff0c;一些老的项目也需要jsp去维护。就像老师说的法国的银行系统还是采用COBOL这种古老语言。本篇文章主要介绍jsp技术。 目录 一、概述 &#xff08;1&#…

Rust构造JSON和解析JSON

目录 一、Rust构造JSON和解析JSON 二、知识点 serde_json JSON 一、Rust构造JSON和解析JSON 添加依赖项 cargo add serde-json 代码&#xff1a; use serde_json::{Result, Value};fn main() -> Result<()>{//构造json结构 cpu_loadlet data r#"{"…

docker安装minio附带图片

1.拉镜像 docker pull minio/minio 2.创建挂载点目录 mkdir -p /usr/local/minio/config mkdir -p /usr/local/minio/data 3.创建minio容器 docker run \ -p 19000:9000 \ -p 9090:9090 \ --nethost \ --name minio \ -d --restartalways \ -e "MINIO_ACCESS_KEYmini…

【前端】CSS基础(4)

文章目录 前言1、CSS常用属性1.1 文本属性1.1.1 文本对齐1.1.2 文本装饰1.1.3 文本缩进1.1.5 行高 前言 这篇博客仅仅是对CSS的基本结构进行了一些说明&#xff0c;关于CSS的更多讲解以及HTML、Javascript部分的讲解可以关注一下下面的专栏&#xff0c;会持续更新的。 链接&…

第 397 场 LeetCode 周赛题解

A 两个字符串的排列差 模拟&#xff1a;遍历 s s s 记录各字符出现的位置&#xff0c;然后遍历 t t t 计算排列差 class Solution {public:int findPermutationDifference(string s, string t) {int n s.size();vector<int> loc(26);for (int i 0; i < n; i)loc[s…

一种基于电场连续性的高压MOSFET紧凑模型,用于精确表征电容特性

来源&#xff1a;A Compact Model of High-Voltage MOSFET Based on Electric Field Continuity for Accurate Characterization of Capacitance&#xff08;TED 24年&#xff09; 摘要 本文提出了一种新的高压MOSFET&#xff08;HV MOS&#xff09;紧凑模型&#xff0c;以消…

每天Get一个小技巧:用DolphinScheduler实现隔几天调度

转载自tuoluzhe8521 这篇小短文将教会你如何使用Apache DolphinScheduler实现隔几天调度&#xff0c;有此需求的小伙伴学起来&#xff01; 1 场景分析 DolphinScheduler定时器模块-定时调度时每3秒|每3分钟|每3天这种定时&#xff0c;不能够跨分钟&#xff0c;跨小时&#x…

在做题中学习(59):除自身以为数组的乘积

238. 除自身以外数组的乘积 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;前缀积和后缀积 思路&#xff1a;answer中的每一个元素都是除自己以外所有元素的和。那就处理一个前缀积数组和后缀积数组。 而前缀积(f[i])是&#xff1a;[0,i-1]所有元素的乘积 后缀…

6. 网络编程-网络io与select、poll,epoll

https://0voice.com/uiwebsite/html/courses/v13.7.html 首先看看这个学习计划 网络、网络编程、网络原理基础组件&#xff0c;20个。中间件 Redis ,MySQL&#xff0c;Kafka&#xff0c;RPC&#xff0c;Nginx开源框架&#xff08;解决方案&#xff09;业务开发(工程师开发&am…

【C语言】5.C语言函数(2)

文章目录 7.嵌套调⽤和链式访问7.1 嵌套调⽤7.2 链式访问 8.函数的声明和定义8.1 单个⽂件8.2 多个⽂件8.3 static 和 extern8.3.1 static 修饰局部变量8.3.2 static 修饰全局变量8.3.3 static 修饰函数 7.嵌套调⽤和链式访问 7.1 嵌套调⽤ 嵌套调用就是函数之间的互相调用。…

震撼发布!GPT-4o 上线!

5 月 14日凌晨一点&#xff0c;OpenAI 发布了 GPT-4o&#xff01; 新模型的功能简单概括就是&#xff1a;更快、更智能、更像人类。 秉承着持续更新的态度&#xff0c;Hulu AI 快速接入 GPT-4o 啦&#xff01; 继 5 月份上线 Suno 之后&#xff0c;这次是 Hulu AI 的又一重大…

Android 应用开发-实现将公共存储空间内的文件复制到应用的私用存储空间中

一、前言 几个月前&#xff0c;我用Android Studio给公司销售部门的同事开发了一款手机app&#xff0c;让同事们用自己的手机就能进行商品的扫码盘点操作&#xff0c;帮他们提高了工作效率&#xff0c;他们用了一段时间&#xff0c;反映还不错。不过前几天&#xff0c;销售部门…

Mysql 事务隔离级别

前言 在数据库管理系统中&#xff0c;事务&#xff08;Transaction&#xff09;是保证数据一致性和完整性的重要机制。在并发环境下&#xff0c;多个事务同时操作相同的数据可能会引发各种问题&#xff0c;如脏读、不可重复读、幻读等。为了解决这些问题&#xff0c;MySQL提供…

腾讯开源混元DiT文生图模型,消费级单卡可推理

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接…

script标签以及defer和async属性

1. <script>标签 将JavaScript代码嵌入到HTML中主要方式是使用<script>元素。 使用<script>的方式有两种&#xff1a; &#xff08;1&#xff09;直接在网页中嵌入JavaScript代码&#xff1a; <script>function sayHi() {console.log("Hi"…

基于springboot+vue+Mysql的大学生社团活动平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

PCIE协议-2-事务层规范-Transaction Ordering

2.4.1 事务排序规则 表2-40定义了PCI Express事务的排序要求。此表中定义的规则适用于PCI Express上的所有事务类型&#xff0c;包括内存、I/O、配置和消息事务。在单个流量类别&#xff08;Traffic Class&#xff0c;TC&#xff09;内&#xff0c;这些排序规则适用。不同TC标…

VUE之旅—day2

文章目录 Vue生命周期和生命周期的四个阶段created应用—新闻列表渲染mounted应用—进入页面搜索框就获得焦点账单统计&#xff08;Echarts可视化图表渲染&#xff09; Vue生命周期和生命周期的四个阶段 思考&#xff1a; 什么时候可以发送初始化渲染请求&#xff1f;&#xff…