Skip to content

Latest commit

 

History

History
177 lines (132 loc) · 11.9 KB

task-doc.md

File metadata and controls

177 lines (132 loc) · 11.9 KB

课程大作业任务说明文档

任务说明

这里是 2021-2022 年春季学期软件分析与验证课程大作业任务说明文档,本次课程大作业的目标是完成一个具有以下功能的程序验证工具:

  • 对于给定的带有部分正确性验证标注的输入程序源码,能够返回其部分正确性验证的结果。
  • 对于给定的带有部分正确性验证标注和秩函数的输入程序源码,能够返回其完全正确性验证的结果。
  • 完成工具报告,大致描述你的实现过程、过程中遇到的问题以及解决方式。

推荐实现的验证算法为演绎验证,其在参考教材 The Calculus of Computation: Decision Procedures with Applications to Verification 的第五章 Program Correctness: Mechanics 中有详尽的介绍。

输入和输出

以下是对本次大作业所需实现的程序验证工具的输入和输出的描述。

输入

一个符合语法规范的,带有前置条件后置条件循环不变式断言以及秩函数等验证标注的,文本格式的 CMinor 语言的代码文件。

CMinor 语言的源程序部分大致是 C 语言的子集,验证标注部分大致是 ACSL 语言的子集。

注意:秩函数是一个整型表达式或整形表达式的元组,对于带有秩函数的源码文件,我们保证其文件中所有函数及循环都被标注了秩函数,且要么所有秩函数都是一个整形表达式、要么所有秩函数的元组长度均相同。

输出

对于一个合法的输入,所实现的验证工具会在运行结束后,在其标准输出流中输出对应的验证结果。具体格式为:

  • VERIFIED:程序中所有验证标注均为有效的,即其部分正确(如果源码中没有秩函数)或者完全正确(如果源码中有秩函数);
  • UNVERIFIED:程序中并非所有验证标注都有效的,即其部分正确性(如果源码中没有秩函数)或者完全正确性(如果源码中有秩函数)不成立;
  • UNKNOWN:程序中验证标注的有效性未知。

实现流程

验证工具的实现框图如下图所示。它包含了从程序源码到验证结果的全部流程。其中,src 表示程序源代码AST 表示抽象语法树(Abstract Syntax Tree),CFG 表示控制流图(Control-flow Graph),VC 表示验证条件(Verification Condition),solver 表示 SMT 求解器,res 表示需要输出的验证结果。其中,验证条件VC的生成,我们使用 ded-verif(deductive verification)验证算法。

在以下框图中,从源代码(src)到控制流图(CFG)的部分,和本课程教授的内容关系不大。因此,在本次作业任务中,这部分的实现,已经由助教完成,并以源码的形式给出,大家可以直接使用。而从 CFG 到验证结果(res)的部分,则是本课程所教授的核心内容,需要同学们自己独立完成。

   parser          | ded-verif    solver
     |             |     |          |
src ---> AST ---> CFG -------> VCs ---> res
                   |   
      by TA        |    by 'yourself'
                   |

CMinor 语言

验证工具输入的源代码 src 使用 CMinor 语言,一种面向教学的验证语言,其形式化语法文法如 ANTLR 文法文件 CMinorParser.g4CMinorLexer.g4 中所示。

CMinor 语言支持两种 C 中的原子类型 intfloat,支持这两种原子类型的定长的一维数组的定义和读写,也支持以这两种原则类型为成员的结构体(struct),同时也支持 C 语言中最基本的顺序条件if-then-else)和循环whilefordo-while)语句,以及函数。

以下是一个使用 CMinor 语言描述的程序的例子。在函数 fun 中,变量 count 的值从 0 开始不断增大 1,直到其值不再小于 10。随后,该函数将返回 count

int fun() {
    int count = 0;
    while (count < 10)
    {
        count = count + 1;
    }
    return count;
}

由于 CMinor 语言是验证语言,所以还会额外支持用于程序验证的标注,这些标注是一种以 /*@ 或者 //@ 开头的特殊的注释。

对于 CMinor 语言中的任意一个函数,都必须使用一阶逻辑公式标注其前置条件requires)和后置条件ensures)。同时,在循环头位置,也必须标注对应位置的循环不变式。基于前置条件和后置条件的标注,形成了一个程序验证任务。即证明对于所有在初始情况下,满足前置条件的程序执行路径,在其“末端”后置条件也需要满足。

除了前置后置条件和循环不变式外,CMinor 语言还支持对断言的标注。并且对于除数、数组下标和数组长度,会自动生成运行时断言(runtime assertion):“除数不为零”、“数组下标非负”、“数组下标小于数组长度”。

在验证标注中,CMinor 支持三种数学意义上的原子类型:integerrealboolean,类型为 C 中的 intfloat 的程序变量,在验证标注中会被提升成类型为 integerreal 的数学变量,也就是说我们在验证标注中无需考虑越界和取整的问题。所有标注的逻辑公式均使用一阶逻辑描述,即其可以被量词(\forall\exists)限定。

以如下的程序为例,函数 fun 的前置条件 为 \true,后置条件 为 \result == 10,即返回值(return value)等于 10。那么,对于如下的输入,程序验证工具需要证明,函数 fun 在任何条件下(\true 表示始终满足),其返回值都满足 \result == 10。为证明该结论,需要使用循环不变式 count <= 10

/*@ requires \true;
  @ ensures \result == 10; */
int fun() {
    int count = 0;
    //@ loop invariant count <= 10;
    while (count < 10)
    {
        count = count + 1;
    }
    return count;
}

为了证明程序的完全正确性,还需要证明程序的终止性,即程序的执行路径都是有限的。为此,CMinor 语言还支持用于证明终止性的秩函数的标注。秩函数是一个整形表达式或整形表达式的元组,在函数头以 decreases 标出,在循环头以 loop variant 标出。所有有效的秩函数标注的值都是非负的。同时,对于程序中的任意一条基本路径,在路径头标注的秩函数的值,都严格大于路径尾标注的秩函数的值(对于元组而言,是字典序意义上的大于)。以此便可以证明程序的任意执行路径都是有限的。

以如下的程序为例,函数 fun 入口位置标注的秩函数表达式为 11,而在循环入口标注的秩函数表达式为 10 - count。从程序入口的 11 到循环头的 10 - count 的基本路径上,可以证明秩函数表达式的值是严格下降的。同时,从循环头的 10 - count 到其自身的基本路径上,由于经过了语句 count = count + 1,故秩函数表达式的值也是严格下降的。11 是一个自然数,由循环不变式 count <= 10 可知 10 - count 的非负性,所以全体秩函数也是非负的。因此,可以判定秩函数标注的有效性,即程序的终止性。

注:在本次作业的验证任务中,对于函数头的秩函数,我们要求其非负性可以被函数的前置条件所蕴含;对于循环头的秩函数,我们要求其非负性可以被循环不变式所蕴含。

/*@ requires \true;
  @ decreases 11;
  @ ensures \result == 10; */
int fun() {
    int count = 0;
    /*@ loop invariant count <= 10;
      @ loop variant 10 - count; */
    while (count < 10)
    {
        count = count + 1;
    }
    return count;
}

控制流图

程序分析和验证一般在程序的控制流图上进行。控制流图是程序的一种图形表示,它将程序的控制流结构,以有向图的形式表示出来。在控制流图上的每一个节点是一个基本代码块(Basic Block),简称为基本块。而连接各个基本块的有向边称为控制流边。在控制流图中,原程序中的所有控制流语句,如 if-then-elsewhile 等,都被替换为 assume 语句以及相应的控制流边。其中,assume 语句表达的含义是,在当前的执行路径中,若 assume 的条件被满足,则路径继续执行,否则,路径将阻塞,从而不再继续执行。

下图所示的是上一个例子中的 CMinor 源码的控制流图,其中总计有 7 个块。除了基本块外,_PRECOND#1 是整个控制流图的入口块,同时也用来表达函数的前置条件及秩函数,_POSTCOND#1 是整个控制流图的出口块,同时也用来表达函数的后置条件,_LOOPHEAD#1 是循环头的虚拟基本块。这个例子展示了出于验证需要,我们在常规的控制流图上,额外设计的三类虚拟块。

                   _PRECOND#1:
                       requires true
                       decreases 11
                           |
                           |
                           V
                   _BASIC#1:
                       count$1 = 0;
                           |
                           |<-------------------
                           V                   |
                   _LOOPHEAD#1:                |
               loop invariant (count$1 <= 10)  |
               loop variant (10 - count$1)     |
                   _cond$1 = (count$1 < 10)    |
                           |                   |
       --------------------|                   |
       |                   V                   |
   _BASIC#3         _BASIC#2:                  |
assume !_cond$1        assume _cond$1          |
       |          count$1 = (count$1 + 1)      |
       |                   |                   |
       |                   |                   |
       |                   |--------------------
       ------------------->|
                           |
                   _BASIC#4:
                    \result$1 = count$1
                           |
                           |
                           V
                   _POSTCOND#1:
                ensures (\result$1 == 10)

为了方便后续的验证,我们会对程序中的所有变量作 alpha conversion,即对变量重命名,保证所有变量不重名,比如上图中的 $1 即是通过 alpha conversion 所得。对于函数调用、实参、条件表达式、数组长度、数组下标和除数,我们会为其新建临时变量,比如上图中的 _cond$1。我们在数据流图这一层中间表示中不保留结构体的概念,结构体会被以类似于“元组”的形式处理,注意这也会导致函数调用时可能会有多个返回值。

在我们的控制流图中,有四类语句:

  • assign:赋值语句,包括对变量的 assign 和对数组中元素的 assign 两种
  • assert:程序断言
  • assume:守卫(guard)条件,仅会出现在块首
  • function call:函数调用,保证所有参数都是变量,并且可能有多个返回值

至此,从验证工具输入的源文件形式 CMinor 语言,到验证算法所依赖的程序控制流图,都已经有了一个大致的介绍。同时,在本次大作业所给出的初始代码中,也已经完成了对于以上内容的实现。同学们需要结合初始代码,以及课程所讲授的内容,完成本次课程大作业。

附加任务

我们鼓励感兴趣的同学在学有余力的同时,完成一些更加进阶的验证算法。比如:

参考教材

The Calculus of Computation: Decision Procedures with Applications to Verification