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

List tutorial fails to verify

Open pinzaghi opened this issue 3 years ago • 6 comments

I am trying to verify (using the prusti-rustc binary) the basic stack proposed in the tutorial:

#![feature(box_patterns)]
use prusti_contracts::*;

use std::mem;

pub enum TrustedOption {
    Some(i32),
    None,
}

pub struct List {
    head: Link,
}

enum Link {
    Empty,
    More(Box<Node>),
}

struct Node {
    elem: i32,
    next: Link,
}

impl List {

    #[pure]
    pub fn len(&self) -> usize {
        self.head.len()
    }

    #[ensures(result.len() == 0)]
    pub fn new() -> Self {
        List {
            head: Link::Empty,
        }
    }

}

impl Link {

    #[pure]
    #[ensures(!self.is_empty() ==> result > 0)]
    #[ensures(result >= 0)]
    fn len(&self) -> usize {
        match self {
            Link::Empty => 0,
            Link::More(box node) => 1 + node.next.len(),
        }
    }

    #[pure]
    fn is_empty(&self) -> bool {
        match self {
            Link::Empty => true,
            Link::More(box node) => false,
        }
    }
}

fn main(){}

which produces this verification error in the method List.new():

error: [Prusti: verification error] postcondition might not hold. #[ensures(result.len() == 0)]

I can solve it adding the following postcondition to the Link.len() method:

#[ensures(self.is_empty() ==> result == 0)]

But this postcondition can be derived by the current one !self.is_empty() ==> result > 0 && result >= 0.

My questions are:

  • Does the tutorial fails to verify on purpose?
  • Is it ok to add this new postcondition or should it be deduced by Prusti?

pinzaghi avatar Mar 16 '22 14:03 pinzaghi

Thanks for reporting that. Which version of Prusti are you using? Using the latest version of the master branch this program verifies for me, without the redundant ... result == 0 postcondition.

Does the tutorial fails to verify on purpose?

No, it seems an (unintended) incompleteness that has been fixed in the meantime. We should add the examples of that tutorial to the test suite to avoid more regressions.

Is it ok to add this new postcondition or should it be deduced by Prusti?

It should be deduced, but it's also ok to add that postcondition because it's anyway going to be checked when verifying the implementation of len.

fpoli avatar Mar 16 '22 15:03 fpoli

I tried the last nightly release without luck. What I am doing is unzipping the binaries for Ubuntu, giving execution permissions to all binaries and executing:

./prusti-rustc --edition=2021 list.rs

The errors continue. Should I build directly from source?

pinzaghi avatar Mar 16 '22 16:03 pinzaghi

No need to compile from source; the last nightly release should be enough. I actually found that I had check_overflows=false in my Prusti.toml; my bad. With overflow checks enabled I can reproduce the issue.

(You also get that 1 + node.next.len() might overflow, I expect.)

fpoli avatar Mar 16 '22 16:03 fpoli

Exactly, I have two errors.

If I consider the whole example, I have even more errors (5 to be exact). Thanks for confirming this.

#![feature(box_patterns)]
use prusti_contracts::*;

use std::mem;

pub enum TrustedOption {
    Some(i32),
    None,
}

impl TrustedOption {

    #[pure]
    pub fn is_none(&self) -> bool {
        match self {
            TrustedOption::Some(_) => false,
            TrustedOption::None => true,
        }
    }

    #[pure]
    pub fn is_some(&self) -> bool {
        !self.is_none()
    }

    #[pure]
    #[requires(self.is_some())]
    pub fn peek(&self) -> i32 {
        match self {
            TrustedOption::Some(val) => *val,
            TrustedOption::None => unreachable!(),
        }
    }

}

pub struct List {
    head: Link,
}

enum Link {
    Empty,
    More(Box<Node>),
}

struct Node {
    elem: i32,
    next: Link,
}

#[trusted]
#[requires(src.is_empty())]
#[ensures(dest.is_empty())]
#[ensures(old(dest.len()) == result.len())]
#[ensures(forall(|i: usize| (0 <= i && i < result.len()) ==> 
                old(dest.lookup(i)) == result.lookup(i)))] 
fn replace(dest: &mut Link, src: Link) -> Link {
    mem::replace(dest, src)
}


impl List {

    #[pure]
    pub fn len(&self) -> usize {
        self.head.len()
    }

    #[pure]
    #[requires(0 <= index && index < self.len())]
    pub fn lookup(&self, index: usize) -> i32 {
        self.head.lookup(index)
    }

    #[ensures(result.len() == 0)]
    pub fn new() -> Self {
        List {
            head: Link::Empty
        }
    }

    #[ensures(self.len() == old(self.len()) + 1)] 
    #[ensures(self.lookup(0) == elem)]
    #[ensures(forall(|i: usize| (1 <= i && i < self.len()) ==>
        old(self.lookup(i-1)) == self.lookup(i)))]
    pub fn push(&mut self, elem: i32) {
        let new_node = Box::new(Node {
            elem: elem,
            next: replace(&mut self.head, Link::Empty),
        });

        self.head = Link::More(new_node);
    }

    #[ensures(old(self.len()) == 0 ==> result.is_none())]
    #[ensures(old(self.len()) > 0 ==> result.is_some())]
    #[ensures(old(self.len()) == 0 ==> self.len() == 0)]
    #[ensures(old(self.len()) > 0 ==> self.len() == old(self.len()-1))]
    #[ensures(old(self.len()) > 0 ==> result.peek() == old(self.lookup(0)))]
    #[ensures(old(self.len()) > 0 ==>
    forall(|i: usize| (0 <= i && i < self.len()) ==>
        old(self.lookup(i+1)) == self.lookup(i)))]
    pub fn pop(&mut self) -> TrustedOption {
        match replace(&mut self.head, Link::Empty) {
            Link::Empty => {
                TrustedOption::None
            }
            Link::More(node) => {
                self.head = node.next;
                TrustedOption::Some(node.elem)
            }
        }
    }
}

impl Link {

    #[pure]
    #[ensures(!self.is_empty() ==> result > 0)]
    #[ensures(self.is_empty() ==> result == 0)]
    #[ensures(result >= 0)]
    fn len(&self) -> usize {
        match self {
            Link::Empty => 0,
            Link::More(box node) => 1 + node.next.len(),
        }
    }

    #[pure]
    fn is_empty(&self) -> bool {
        match self {
            Link::Empty => true,
            Link::More(box node) => false,
        }
    }

    #[pure]
    #[requires(0 <= index && index < self.len())]
    pub fn lookup(&self, index: usize) -> i32 {
        match self {
            Link::Empty => unreachable!(),
            Link::More(box node) => {
                if index == 0 {
                    node.elem
                } else {
                    node.next.lookup(index - 1)
                }
            }
        }
    }

}

fn main(){}

pinzaghi avatar Mar 16 '22 16:03 pinzaghi

@fpoli should it be harmful to use the check_overflows=false flag? I am not interested in checking overflows for now. I see that the postconditions are still checked and the code verifies.

pinzaghi avatar Mar 17 '22 15:03 pinzaghi

@fpoli should it be harmful to use the check_overflows=false flag? I am not interested in checking overflows for now. I see that the postconditions are still checked and the code verifies.

That flag is well tested and it even used to be the default, so there should be no issues with that.

fpoli avatar Mar 18 '22 08:03 fpoli