prusti-dev icon indicating copy to clipboard operation
prusti-dev copied to clipboard

Failing (trivial) assertion in presence of a quantifier over a slice

Open Pointerbender opened this issue 3 years ago • 1 comments

The following Rust program:

use prusti_contracts::*;

pub fn main() {}

#[requires(forall(|i: usize|
    (0 <= i && i < slice.len()) ==> (slice[i] == i),
    triggers=[(slice[i],)]
))]
pub fn test(slice: &mut [usize]) {
    if slice.len() == 3 {
        debug_assert!(slice[0] == 0);
        debug_assert!(slice[1] == 1);
        debug_assert!(slice[2] == 2);
    }
}

Fails verification with the error:

error: [Prusti: verification error] the asserted expression might not hold
  --> src/lib.rs:30:9
   |
30 |         debug_assert!(slice[2] == 2);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the macro `$crate::assert` (in Nightly builds, run with -Z macro-backtrace for more info)

The weird part is that the first 2 assertions are verified okay, but then Prusti misses out on the third assertion (which should hold).

Pointerbender avatar Apr 17 '22 11:04 Pointerbender

A small addendum, it seems this is because the assertions do not (always?) seem to trigger the quantifier.

The quantifier being (at the Viper level):

  inhale (let _LET_0 == (_1.val_ref) in (
    forall _0_quant_0: Int ::
    { read$Snap$Slice$usize$__$TY$__(snap$__$TY$__Snap$Slice$usize$(_1.val_ref), _0_quant_0) }
    0 <= _0_quant_0 && _0_quant_0 <= 18446744073709551615
      ==> !(0 <= _0_quant_0) || (
        !(_0_quant_0 < len$Snap$Slice$usize$__$TY$__(snap$__$TY$__Snap$Slice$usize$(_LET_0)))
        || _0_quant_0 < len$Snap$Slice$usize$__$TY$__(snap$__$TY$__Snap$Slice$usize$(_LET_0))
        && read$Snap$Slice$usize$__$TY$__(snap$__$TY$__Snap$Slice$usize$(_LET_0), _0_quant_0) == _0_quant_0
      )
  ))

while the array accesses in the Viper method are expressed as:

inhale lookup_pure__$TY$__Slice$usize$usize$(_1.val_ref, _11) == __t26
inhale lookup_pure__$TY$__Slice$usize$usize$(_1.val_ref, _21) == __t30
inhale lookup_pure__$TY$__Slice$usize$usize$(_1.val_ref, _31) == __t34

In order to verify the original example, it needs to properly trigger the quantifier at least once, so that this Viper function is called at least once, after which Silicon knows to connect lookup_pure with read$Snap:

function snap$__$TY$__Snap$Slice$usize$(self: Ref): Snap$Slice$usize
  requires acc(Slice$usize(self), read$())
  ensures [(forall i: Int :: { read$Snap$Slice$usize$__$TY$__(result, i) } { lookup_pure__$TY$__Slice$usize$usize$(self, i) } 0 <= i && i < Slice$len__$TY$__usize$(self) ==> read$Snap$Slice$usize$__$TY$__(result, i) == lookup_pure__$TY$__Slice$usize$usize$(self, i)), true]
  ensures [Slice$len__$TY$__usize$(self) == len$Snap$Slice$usize$__$TY$__(result), true]
{
  cons$Snap$Slice$usize$__$TY$__(seq_collect$Slice$usize$__$TY$__(self, 0))
}

An example of a program that does verify:

use prusti_contracts::*;

fn main() {}

#[requires(forall(|i: usize|
    (0 <= i && i < slice.len()) ==> (slice[i] == i),
    triggers=[(slice[i],)]
))]
#[requires(slice[0] == slice[0])] // dummy statement
pub fn test(slice: &mut [usize]) {
    if slice.len() == 3 {
        debug_assert!(slice[0] == 0);
        debug_assert!(slice[1] == 1);
        debug_assert!(slice[2] == 2);
    }
}

Although it is still interesting why it can verify the first 2 assertions in the original example but not the 3rd, I would expect all three assertions to fail due to the quantifier not triggering :D Also, for the working example there is no pre-condition that describes the length of slice. Should #[requires(slice[0] == slice[0])] result in a possible out-of-bounds error in this case, because the slice could be an empty slice?

Existing Github issues related to this one are #796 and #877.

Pointerbender avatar Apr 17 '22 18:04 Pointerbender