Page 201 - 《软件学报》2025年第8期
P. 201

3624                                                       软件学报  2025  年第  36  卷第  8  期


                 的程序验证. 不同的是, Aeneas 设计了       Rust 以所有权为中心的函数式操作语义, 并使用基于借用                (loan) 的方式对
                 程序进行了借用检查, 而没有相信          Rust 编译器的检查结果. 具体来说, Aeneas 提出了语法类似            MIR  的低级借用演
                 算  (low-level borrow calculus, LLBC), 并赋予其纯粹的函数式的操作语义. 该语义是基于值的, 因此没有内存、地
                 址或指针算术的概念, 减轻了证明过程中复杂的内存的推理. 进一步地, Aeneas 将                     LLBC  转为纯  lambda 演算, 允
                 许用户通过指定的定理证明器验证程序. Aeneas 目前提供了对                  F*和  Coq  的支持, 后续计划扩展到      Lean, HOL4
                 等. 为处理函数返回借用这一复杂情况, Aeneas 使用类似于             Creusot 中预言的方法, 通过反向函数      (backward function)
                 来重构借用结束后返回给原始对象的值. 如, 图             12  的例子会被   Aeneas 自动转为图    16  所示  F*函数式表示. 文章通
                 过实验结果表明, Aeneas 可以验证一个低级的、可调整大小的哈希表的功能性正确性.

                                                                         let inc_max_fwd : result unit =
                    1   let take_max_fwd (t : Type) (x : t) (y : t) : result t =
                                                                           val <-- if x>=y then true else false;
                    2      if x>=y then Return x else Return y
                                                                           i <-- take_max_fwd i32 1 2;
                    3
                                                                           z <-- i32_add i 1;
                    4   let take_max_back (t : Type) (b : bool) (x : t) (y : t) (ret : t) :
                                                                           massert (z = 3); (* monadic assert *)
                    5   result (t & t) =
                    6      if b then Return (ret, y) else Return (x, ret)  (x0, y0) <-- take_max_back i32 val 1 2 z;
                    7   }                                                  massert (x0 = 1);
                    8                                                      massert (y0 = 3);
                    9                                                      Return ()
                                                 图 16 Aeneas 程序验证例子

                 4.4   模型检测方法
                    模型检测方法将验证对象抽象成一个形式化的状态机模型, 并使用状态空间搜索的方法遍历该模型, 以此检
                 查模型是否满足特定规约. 模型检测方法容易实现验证过程的机器完全自动化, 且当系统性质未被满足时可给出
                 反例轨迹, 帮助用户定位错误位置. 但该技术只能对有限状态进行验证, 且存在状态空间爆炸问题. 因此, 该方法对
                 于复杂性较高、规模较大程序的验证存在局限性. 为了缓解该问题, 有界模型检测                          (bounded model checking) 通过
                 限制程序执行情况的搜索空间, 如限制循环或递归的深度, 在牺牲一定准确性的情况下提升模型检测的效率. 此
                 外, 由于并发程序交替执行时, 每一步都可以由两个程序中的任意一个进行, 使得其可能的执行顺序远不止一种,
                 导致模型检测方法在处理并发问题时效率低下. 为了缓解该问题, 无状态模型检测                          (stateless model checking) 不直
                 接追踪程序状态的变化, 而是通过特殊的调度程序遍历所有并发执行调度情况, 从而对并发程序所有可能的执行
                 进行搜索. 图   17  描述了利用模型检测方法进行         Rust 代码验证的典型步骤.

                                                                                                 程序满足/
                                                                                                 不满足规约
                          用户写验证用例     Rust 模型 编译                已有模型
                  Rust 程序                          中间表示                    状态机模型      SMT 求解器
                                       检查器                      检查器
                                                                                                 程序不满足
                                                                                                 规约的反例

                                                 图 17 模型检测方法验证流程

                    Toman  等人结合穷举测试用例生成和有界模型检测技术提出                  Crust  [38] , 验证  unsafe Rust 代码的内存安全, 并
                 判断  Rust 指针别名的使用是否违反不变量. Crust 以某库模块为输入, 使用基于测试序列生成的方式自动构造调
                 用了目标模块中关键函数的一系列驱动函数. Crust 将这些驱动函数和                    Rust 库代码共同编译为      C  中间表示, 使用
                 针对  C  语言的有界模型检测器       CBMC [53] 来验证每个被检测函数的内存安全属性. 为了测试              Crust 的有效性, 该研
                 究选择了向量     (vector), 切片  (slice) 和环形缓冲区  (ring buffer) 这  3  个内部调用了  unsafe 代码块的  Rust 标准库模
                 块, 在其中手动引入两个内存错误, 实验结果表明              Crust 能够发现引入的两个内存错误.
   196   197   198   199   200   201   202   203   204   205   206