1. 引言
随着科技的发展,软件系统的应用日益广泛,其正确性也越来越重要,尤其是安全攸关 [1],商业攸关以及任务攸关等系统。由于C语言具有编译效率高、可移植性好等特点,大多数软件系统较底层的开发是由它实现的。验证C程序的正确性可采用形式化方法,该方法是通过使用数学方法,对软件或硬件系统进行描述、开发和验证的一种技术。然而,大型C程序的验证是一个十分巨大且困难的工作,并且该问题已被证明是一个不可判定性问题,即不可以完全自动化验证。因此,交互式的证明C程序的正确性是一个很好的选择。
自20世纪60年代以来,Hoare提出的Hoare逻辑是一直被广泛应用的程序验证逻辑系统 [2],用于对命令式程序进行推理验证,是程序逻辑研究领域的一个重大进展。但是Hoare逻辑对于带有指针的程序的验证仍然十分繁琐。
2002年,Reynolds和O’Hearn将Hoare逻辑拓展为可推理验证操作动态分配内存程序的分离逻辑 [3] [4],进一步发展了程序逻辑理论。近二十年以来,分离逻辑已被拓展至多种版本,其使用范围更加广泛,在程序验证中占据着越来越重要的地位。分离逻辑具有更强的表达力和验证能力,例如并发程序的验证等。然而表达力越强的程序逻辑,其验证过程越复杂。分离逻辑已被证明是一个不可判定的程序逻辑,因此使用分离逻辑验证C程序的过程无法达到完全自动化。然而大型的C程序证明十分复杂,手动证明不但需要大量的人力物力,可信度也不是很高,因此人机交互式的证明C程序是不可避免的。
在过去的几十年中,有关交互式定理证明器实现与应用分离逻辑的进展有很多,例如 [5] [6] [7] [8] [9]。Coq [10] 是一个被广泛应用的人机交互式的定理证明器,其基本理论是归纳结构的演算。一些由 [11] 提供的Coq实用策略也是用于基于分离逻辑的C程序验证,这些高度自动化的策略可以仅使用单行证明脚本完成对特定C程序的验证,例如,该系统只需使用1行仅有4个单词的证明脚本来证明经典的就地反转链表算法,而McCreight的Coq策略 [12] 需要68行400个单词,Charge的策略 [13] 需要25行105个单词。本文将分离逻辑中的分离蕴含运算符添加到该系统的分离逻辑断言语法中,并提供一些相关的自动证明策略。
2. 程序逻辑和证明助手
2.1. Hoare逻辑
Hoare逻辑是使用严格的数理逻辑推理来验证命令式程序正确性的逻辑系统,它使用逻辑断言来描述程序的状态。
Hoare使用霍尔三元组来描述程序的行为,是Hoare逻辑的中心特征,其形式如下:
其中断言P是前置条件,断言Q是后置条件,c是程序。前置条件和后置条件是分别描述执行程序c前后的程序状态的断言。一个有效的霍尔三元组表示:如果程序c执行前,内存状态满足断言P,那么程序执行后,内存状态满足断言Q。考虑以下霍尔三元组:
它显然是有效的,由于程序
执行前x的值为3,执行后可得出x的值为6,因此
为真。然而上述霍尔三元组并不够精准,如果更换其后置条件为
,则霍尔三元组为
,因为
蕴含
,所以该霍尔三元组也是有效的。显然
相比于
更加精准,但是相对于
还是要弱一些,像
这样的后置条件通常被称之为最强后置条件。一般的,对于给定的霍尔三元组{P} c {Q},对于所有的Q',如果有{P} c {Q'},且Q ⇒ Q',则称Q为对应的P和c的最强后置条件。与最强后置条件类似,如果对于所有的P',有t {P'} c {Q},且P' ⇒ P,那么称P为最弱前置条件。在霍尔逻辑中,下面的规则可以根据对应的语句与后置条件生成最弱前置条件:
其中Q[a/x]表示使用a替换Q中x的每次自由出现。此外,推论规则(如下)可以加强前置条件或者减弱后置条件:
自1960年起,Hoare逻辑被广泛的应用于命令式程序语言的验证中,然而,在验证带有指针的程序时,其证明过程仍然十分复杂,极具挑战性。2002年,分离逻辑作为霍尔逻辑的拓展,简化了此类证明过程。
2.2. 分离逻辑
分离逻辑是Hoare霍尔逻辑的拓展,它引入了两个重要的运算符分别是分离合取(∗)和分离蕴涵(−∗),它们的形式语义如下:
其中s表示栈,h表示堆,
表示堆
和
不相交,
表示堆
和
的并集。[P * Q] s h断言整个堆h分为两个不相交的部分
和
,对于子堆
,断言P为真,对于子堆
,断言Q为真。
也就是说,如果堆的扩展h'满足断言
,并且断言
对h'是正确的,则对于扩展后的堆
满足断言
。
分离逻辑通过表达显示分离的逻辑连接词以及相应的规则消除了共享的可能,具有较强的表达能力。分离逻辑用于程序验证,简化了程序的验证工作,例如带有数组和指针的程序,并支持局部推理,其分离思想和对应的框架规则使得局部推理可以很好地应用到程序验证中。较比Hoare逻辑,分离逻辑具有更强的表达能力与验证能力,因此继Hoare逻辑之后,分离逻辑成为程序验证的一种重要方法。
2.3. Coq证明助手
交互式定理证明工具Coq,为交互式形式化验证提供了一个丰富的环境。Coq的规范语言是Galina,它支持开发数学定理以及证明程序规范,其命令语言为vernacular。
通常,Coq是一个开发数学证明的环境,其应用包括法伊特–汤普森(Feit-Thompson)定理的证明,实现了CompCert编译器 [14] 以及VST [11] [15] 等。它虽然是交互式的定理证明器,不能达到完全自动化,但是它拥有着强大的判定过程以及自动化证明策略库,还有一种编写自动证明策略的语言,称为Ltac [16]。本文旨在使用Coq证明助手验证C程序的正确性,其中的证明策略使用Ltac语言实现。
3. 分离逻辑断言
分离逻辑引入分离合取和分离蕴涵等运算符扩展断言语法,从而可以对堆进行更简洁而灵活的描述。本节将添加分离蕴含到原有系统中,扩展系统的断言语法 [17]。
下面阐述如何逐步将分离蕴含添加到原有系统中,首先,扩充原有断言语法如下:
在Coq中,添加构造子Awand,这意味着将分离蕴含加入到Asrt的归纳定义中:
随后定义
意味着P −∗ Q代表 (Awand P Q)。下一步,在Coq中描述s |= P −∗ Q的含义如下:
其中 (sat s P) 表示s| = P,即内存s满足断言P。显然,M,M1和M2代表堆,并且参照章节2.2中[P −∗ Q] s h的定义,(MemMod.join M1 M2 M)表示
并且
。
当然,仍有一些关于添加分离蕴含到系统中的细节,我们在这里不关注。
4. 策略
从本节开始,我们将在Coq中证明工作中使用的部分推理规则的可靠性并介绍证明带有分离蕴含的分离逻辑断言的策略。
4.1. 推理规则
以下是两个重要的推理规则,分别称为柯灵规则和柯灵逆反:
柯灵规则及其逆反规则表述了分离合取和分离蕴涵之间的关系,并且由此可以推导出一系列推理规则。我们从几个基本示例开始阐述如何在Coq中证明它们是正确的。考虑下面的推断规则,它可由柯灵规则推出:
证明过程如下:
Theorem是一个命令,它表示正确性证明的开始,其证明过程从以下状态开始:
proof是指启动证明脚本,而intro P将P转变为自由变量。这里,我们可以应用柯灵规则如下:
astar_elim是一个证明定理,其内容是:对所有P,emp ∗ P⇒P成立。应用astar_elim后,则不再有子目标,这意味着证明已完成。
最后,Qed检查证明是否完成。
注意到上面证明的规则可以被用来消去一些分离蕴含,那么以这样一个简单的设想为例,实现一个简单的策略,以展示如何使用交互式的机器证明代替手动证明。该策略称为sclearwand:
首先匹配目标的模式,如果目标是类似于第2行的模式,则在第3行中,调用find_awand以查找A中分离蕴涵的位置。在第3行中,sep_lift将其移至A的左侧,随后证明目标将在第5~11行中解决,其中astar_l_aemp_intro和astar_mono分别是如下规则:
sclearwand可以消除(P −∗ P),之后“emp”将在证明目标中取代它,例如:
现在,考虑下面的推理规则并在Coq中证明其整确定性
mytac是原有系统中用于断言的自动证明策略,而上面的规则可以使用mytac证明,说明分离蕴含已被很好的添加到系统中。
仍然有很多关于分离蕴含的推理规则如下,在这里省略对他们的可靠性证明:
4.2. 资源检测
在 [5] 提供的实用策略可以仅使用一行证明脚本证明整个程序,即“repeat hoare forward; sep pure”,其中repeat表示重复执行某项策略,这里指重复执行“hoare forward; sep pure”,sep pure是证明关于纯断言的策略,而hoare forward可以证明霍尔三元组⊢{P} c {Q},是验证程序的的关键。
hoare forward的第一步是资源检测(check_resource),它检测前置条件是否具有语句执行所需的信息。例如:
其中
表示变量x的值为v。显然,如果将x的值赋给y我们首先需要x的值。这里,resource_check在前置条件中没有找到有关x的信息,它将返回一条用户友好的消息,用户可以根据将相应的信息添加前置条件中,如下:
这就像开始验证前的预处理一样。然而,尽管如上一节所述分离蕴含已经被添加到系统中,并且前置条件中具有足够的信息,resource_check也无法检测到有关分离蕴含的信息,例如:
前置条件中虽然存在
,但无法被识别。因此,我们需要调整resource_check的组件使得分离蕴含可以被识别并可以从中检测到需要的信息。
考虑resource_check的组件find_match_retV,它可以识别“∗”,但无法识别“−∗”,如下:
将其更改如下:
将resource_check中的find_match_retV替换为find_match_retV'之后,上述 ,因此hoare forward可以正常开始运行。
5. 一个简单的例子
本节将给出一个简单的示例来表明,我们可以证明程序状态由含有分离蕴含的断言描述的程序规范。
sclearwand in是可以证明含有分离蕴含的断言的策略,例如,sclearwand in策略可以证明下面的证明目标:
我们以一个简单的例子来说明它在程序验证中的作用,如下:
这里,运行“repeat hoare forward; sep pure”后得到一个子目标:
运行“sclearwand in H”,该子目标被解决,证明完成。
6. 总结
我们通过添加分离蕴含操作符到已有系统的分离逻辑断言语法中,拓展了该验证系统,并提供了一些相关的自动证明策略。但是由于分离逻辑断言的复杂多样性,我们现有的策略不足以解决包含分离蕴涵的断言的所有证明。将来,我们将扩展自动证明策略以提高程序验证的效率。
NOTES
*通讯作者。