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

张卓若 等: 面向   Rust 语言的形式化验证方法研究综述                                                 3621


                 对值. 在创建一个新的可变借用&mut a 时, 我们对其最终状态               a o 进行了预言, 这就像是对程序未来状态的一次预
                 见. 在创建借用时, 我们并不立即确定           a o 的值. 我们用当前状态    a 和预言的借用结束后值        a o 来表示可变引用     ma,
                 形成一对值⟨a, a o ⟩. 当这个可变引用⟨a, a o ⟩ 的生命周期结束, 即所有权被释放时, 我们确立了             a o =a 的关系, 意味着最
                 终状态   a o 被设定为那个特定时刻的当前状态          a——这一过程称为预言决议          (prophecy resolution). 随后, 这一决议
                 可以被有效地“传递”回原始的所有者, 从而明确告知借用结束后对象的确切状态. RustHorn                         的这一核心概念, 为
                 Rust 程序提供了一种用一阶逻辑形式来描述的方法, 使得程序验证更加高效和直观. 例如, 图                          13  的左边  Rust 代
                 码  (和图  12  同样的函数) 会被  RustHorn  转成右边的一阶逻辑公式表示, 并验证程序满足第              6  行断言. 在  max_mut
                 函数中, 第  2  行被  drop  的引用  (如第  1  个分支的  mb) 其值就被决定了   (如相应的   mb.b o =mb.b). 而另一个被返回的
                 引用, 会在第   5  行被  mc 继续修改.

                             1   #[ensures(*result >= old(*ma) && *result >= old(*mb))]
                             2   #[ensures(*result == old(*ma) || *result == old(*mb))]
                             3   #[after_expiry(if old(*ma >= *mb) { (*ma == before_expiry(*result) && *mb == old(*mb))}
                             4    else {(*ma == old(*ma) && *mb == before_expiry(*result))})]
                             5   fn take_max<'a>(ma: &'a mut i32, mb: &'a mut i32) -> &'a mut i32 {
                             6    if *ma >= *mb { ma } else { mb }
                             7   }
                             8   #[requires(a < i32::MAX && b < i32::MAX)]
                             9   #[ensures(result == true)]
                             10  fn inc_max(mut a: i32, mut b: i32) -> bool {
                             11    { let mc = take_max(&mut a, &mut b); // borrow a and b
                             12    *mc += 1; // end of borrow}
                             13    a != b
                             14  }
                                                  图 12 Prusti 程序验证例子


                  1  fn take_max<'a>(ma: &'a mut i32, mb: &'a mut i32) ->
                  2   &'a mut i32 {if *ma >= *mb { ma } else { mb } }
                  3  fn inc_max(mut a: Box<i32>, mut b: Box<i32>) {
                  4     {let mc = take_max(&mut a, &mut b);
                  5     *mc += 1;
                  6     assert!( *a != *b); }
                  7    }

                                                图 13 RustHorn  程序验证例子

                    RustHorn  实现了一个原型验证器, 针对       Rust 子集进行了实现, 并通过初步实验确认了该方法的有效性, 但该工
                 具在表达能力和性能上具有较大的局限性. RustHorn            作者通过非机械化的证明, 证明了其逻辑编码对于原始                 Rust 程
                 序内存的语义的可靠性. 随后, RustHornBelt 利用      RustBelt 框架, 机械化地证明了    RustHorn  风格逻辑编码的可靠性.
                    后续, Denis 等人  [35] 基于  RustHornBelt 提出的理论实现了  Creusot 工具. 类似于  RustHorn, Creusot 将  Rust 程
                 序转化为一阶逻辑       (FOL) 公式, 使用  RustHorn  风格的预言  (prophecy-based) 处理可变引用. 此外, Creusot 引入了
                 一个用于描述功能正确性等规范的注释语言                Pearlite, 并通过良好的规范设计提供了对         trait 验证的支持. Creusot
                 以带有规范注释的       Rust 程序作为输入, 转化为后端引擎         Why3  的中间表示并生成验证条件, 使用          SMT  求解器进
                 行验证.
   193   194   195   196   197   198   199   200   201   202   203