verus-lang / verus

Verified Rust for low-level systems code

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Incompleteness in loop `ensures` when there's an opaque constant in the loop condition

matthias-brun opened this issue · comments

[Edited from the original report to replace with a smaller repro by @utaal]

use vstd::prelude::*;

verus!{

fn main() { }

#[verifier::opaque]
const X: usize = 1;

fn foo() {
    let mut i: usize = 0;
    reveal(X);
    while i < X
        ensures i >= 1
    {
        assume(false);
        break;
        assume(false);
    }
}
}
ξ verus-release /st/verus/test.rs --multiple-errors 5
warning: unreachable statement
  --> /st/verus/test.rs:18:9
   |
17 |         break;
   |         ----- any code following this expression is unreachable
18 |         assume(false);
   |         ^^^^^^^^^^^^^^ unreachable statement
   |
   = note: `#[warn(unreachable_code)]` on by default

error: loop invariant not satisfied
  --> /st/verus/test.rs:13:11
   |
13 |     while i < X
   |           ^^^^^ at this loop exit
14 |         ensures i >= 1
   |                 ------ failed this invariant

error: aborting due to previous error; 1 warning emitted

verification results:: 3 verified, 1 errors

It took me a while to understand what's going on here, in part because the error is a little confusing.

First of all, it says "at this exit", but points to the condition of the loop. What does this mean? This is actually checking the case where the while-condition is not satisfied on entry, and the body of the loop executes 0 times. Verus needs to check that the 'ensures' clause will hold in this case. It generates a VC like !(i < X) ==> i >= 1.

However this VC is generated as part of the loop inner body AIR query. Thus it doesn't get any proof context from outside the loop. But, even though it's part of the inner body AIR query, it's treated as a separate case from the main body, so it also doesn't get any proof context from the inside of the loop. The only way to add proof context is to put it in the condition itself: while ({ reveal(X); i < X }), but this is ugly and nobody would want to write this.

In my opinion, this check should probably be part of the outer query, along with the check that the invariant holds one entry. Also, the error message should be improved.