From a48c0da69d36372c7168c5c2b73fbcf54a93b8ac Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Mon, 23 Jan 2023 18:00:00 +0100 Subject: [PATCH 01/31] Rework user guide --- docs/user-guide/src/SUMMARY.md | 1 + docs/user-guide/src/basic.md | 14 +- docs/user-guide/src/tour/getting-started.md | 41 +- docs/user-guide/src/tour/new.md | 334 +++----- docs/user-guide/src/tour/pop.md | 3 +- docs/user-guide/src/tour/push.md | 759 ++++++------------ docs/user-guide/src/tour/setup.md | 40 + docs/user-guide/src/tour/summary.md | 1 + .../tour/tour-src/04-chapter-2-2-full-code.rs | 47 ++ .../src/tour/tour-src/04-chapter-2-2.rs | 6 + .../src/tour/tour-src/push_final.rs | 91 +++ .../src/tour/tour-src/push_property_1.rs | 67 ++ .../src/tour/tour-src/push_property_2.rs | 89 ++ 13 files changed, 740 insertions(+), 753 deletions(-) create mode 100644 docs/user-guide/src/tour/setup.md create mode 100644 docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs create mode 100644 docs/user-guide/src/tour/tour-src/push_final.rs create mode 100644 docs/user-guide/src/tour/tour-src/push_property_1.rs create mode 100644 docs/user-guide/src/tour/tour-src/push_property_2.rs diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index 1c60f9b4678..24ae40bc03c 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -4,6 +4,7 @@ - [Installation](install.md) - [Basic Usage](basic.md) - [Guided Tour](tour/summary.md) + - [Setup](tour/setup.md) - [Getting Started](tour/getting-started.md) - [New](tour/new.md) - [Push](tour/push.md) diff --git a/docs/user-guide/src/basic.md b/docs/user-guide/src/basic.md index 28974739ea9..e2bfda0518d 100644 --- a/docs/user-guide/src/basic.md +++ b/docs/user-guide/src/basic.md @@ -15,15 +15,15 @@ See the [following chapter](verify/summary.md) for a list of verification featur To run Prusti on a file using the command-line setup: -```bash -$ prusti-rustc --edition=2018 path/to/file.rs +```sh +prusti-rustc --edition=2018 path/to/file.rs ``` ## Introductory example Let us verify that the function `max` below, which takes two integers and returns the greater one, is implemented correctly. -```rust +```rust,noplaypen fn max(a: i32, b: i32) -> i32 { if a > b { a @@ -42,7 +42,7 @@ This tells us that To also verify that `max` indeed always returns the maximum of its two inputs, we have to add a corresponding specification, which states that the return value of `max` is at least as large as both `a` and `b` and, additionally, coincides with `a` or `b`: -```rust +```rust,noplaypen use prusti_contracts::*; #[ensures(result >= a && result >= b)] @@ -68,7 +68,7 @@ Notice that Prusti assumes by default that integer types are bounded; it thus pe Next, we add a second function `max3` which returns the maximum of three instead of two integers; we reuse the already verified function `max` in the new function's specification to show that this function is implemented correctly. -```rust +```rust,noplaypen use prusti_contracts::*; #[pure] @@ -103,7 +103,7 @@ If we omit this annotation, Prusti will complain that the postcondition of funct So far, we only considered programs that meet their specification and that, consequently, Prusti successfully verified. To conclude this example, assume we accidentally return `c` instead of `b` if `b > c` holds: -```rust +```rust,noplaypen #[ensures(result == max(a, max(b, c)))] fn max3(a: i32, b: i32, c: i32) -> i32 { if a > b && a > c { @@ -122,7 +122,7 @@ In this case, Prusti will highlight the line with the error and report that the For debugging purposes, it is often useful to add `assert!(...)` macros to our code to locate the issue. For example, in the code below, we added an assertion that fails because `b > c` and thus the maximum of `b` and `c` is `b` instead of `c`. -```rust +```rust,noplaypen use prusti_contracts::*; #[pure] diff --git a/docs/user-guide/src/tour/getting-started.md b/docs/user-guide/src/tour/getting-started.md index ed43de74caf..cebe27249d8 100644 --- a/docs/user-guide/src/tour/getting-started.md +++ b/docs/user-guide/src/tour/getting-started.md @@ -30,7 +30,7 @@ Node storing the payload—an integer—and the link to the next node: // Prusti: VERIFIES ``` -This design avoid making both `Link` and `Node` public. +As explained in the chapter [2.1: Basic Data Layout](https://rust-unofficial.github.io/too-many-lists/first-layout.html), this design avoids making both `Link` and `Node` public. Moreover, it benefits from the Rust compiler's [null-pointer optimization](https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#discriminant-elision-on-option-like-enums) and makes sure that all list elements are uniformly allocated on the heap. @@ -40,7 +40,8 @@ Prusti automatically checks that no statement or macro that causes an explicit runtime error, such as [`panic`](https://doc.rust-lang.org/std/macro.panic.html), [`unreachable`](https://doc.rust-lang.org/std/macro.unreachable.html), -[`unimplemented`](https://doc.rust-lang.org/std/macro.unimplemented.html), or +[`unimplemented`](https://doc.rust-lang.org/std/macro.unimplemented.html), +[`todo`](https://doc.rust-lang.org/std/macro.todo.html), or possibly a failing [assertion](https://doc.rust-lang.org/std/macro.assert.html), is reachable. @@ -48,21 +49,7 @@ For example, the following test function creates a node with no successor and pa if the node's payload is greater than 23: ```rust,noplaypen -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -{{#include tour-src/03-chapter-2-1.rs:15:24}} +{{#rustdoc_include tour-src/03-chapter-2-1.rs:15:24}} // Prusti: VERIFIES ``` Prusti successfully verifies the above function @@ -72,23 +59,7 @@ whenever execution reaches the `if` statement. This is not the case for the following function in which the test node is initialized with an arbitrary integer: ```rust,noplaypen -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -#fn main() {} -# -{{#include tour-src/03-fail.rs:26:35}} +{{#rustdoc_include tour-src/03-fail.rs:26:35}} // Prusti: FAILS ``` @@ -96,7 +67,7 @@ Prusti reports errors in the same fashion as the Rust compiler (although with th `Prusti: verification error`). For example, the error produced for the above function is: -``` +```markdown error: [Prusti: verification error] panic!(..) statement might be reachable --> 03-fail.rs:33:9 | diff --git a/docs/user-guide/src/tour/new.md b/docs/user-guide/src/tour/new.md index bec7f956bd6..70c10b82c69 100644 --- a/docs/user-guide/src/tour/new.md +++ b/docs/user-guide/src/tour/new.md @@ -13,21 +13,7 @@ We first provide a static function to create empty lists: ```rust,noplaypen -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -{{#include tour-src/04-chapter-2-2.rs:15:22}} +{{#rustdoc_include tour-src/04-chapter-2-2.rs:15:22}} // Prusti: VERIFIES ``` @@ -42,38 +28,7 @@ For simplicity, we will not actually compute the length of a `Link` yet. Rather, we will just always return 0. ```rust,noplaypen -#![feature(box_patterns)] // convenience notation for boxes -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -impl List { - pub fn len(&self) -> usize { - self.head.len() - } -# -# pub fn new() -> Self { -# List { -# head: Link::Empty, -# } -# } -} - -impl Link { - fn len(&self) -> usize { - 0 - } -} +{{#rustdoc_include tour-src/04-chapter-2-2.rs:15:28}} // Prusti: VERIFIES ``` @@ -84,39 +39,38 @@ That is, we attach the [postcondition](../verify/prepost.md) `result.len() == 0` to the function `new()`: ```rust,noplaypen -#//uncomment: #![feature(box_patterns)] -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -#impl List { -# pub fn len(&self) -> usize { -# self.head.len() -# } -# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# struct Node { +# elem: i32, +# next: Link, +# } +# +# impl List { +# pub fn len(&self) -> usize { +# self.head.len() +# } +# #[ensures(result.len() == 0)] pub fn new() -> Self { List { head: Link::Empty } } -#} -# -#impl Link { -# fn len(&self) -> usize { -# 0 -# } -#} +# } +# +# impl Link { +# fn len(&self) -> usize { +# 0 +# } +# } ``` Unfortunately, Prusti—or rather: the Rust compiler—will complain about @@ -132,33 +86,32 @@ error: cannot find attribute `ensures` in this scope Prusti's specifications consist of Rust [macros and attributes](https://doc.rust-lang.org/reference/procedural-macros.html) -that are defined in a separate crate called `prusti_contracts`. +that are defined in a separate crate called `prusti_contracts`. To see how to add this crate to your project, check out [Setup](setup.md). Before we can use these specifications, we need to make the path to these macros and attributes visible: ```rust,noplaypen -#//uncomment: #![feature(box_patterns)] use prusti_contracts::*; -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# struct Node { +# elem: i32, +# next: Link, +# } # impl List { -# pub fn len(&self) -> usize { -# self.head.len() -# } -# +# pub fn len(&self) -> usize { +# self.head.len() +# } +# #[ensures(result.len() == 0)] pub fn new() -> Self { List { @@ -166,18 +119,18 @@ impl List { } } } -# -#impl Link { -# fn len(&self) -> usize { -# 0 -# } -#} +# +# impl Link { +# fn len(&self) -> usize { +# 0 +# } +# } ``` Declaring that we use the `prusti_contracts` crate removes the compiler error but leads to a new error. This time it is an error raised by Prusti: -``` +```markdown error: [Prusti: invalid specification] use of impure function "List::len" in pure code is not allowed --> list.rs:34:15 | @@ -197,48 +150,47 @@ After adding the `#[pure]` attribute to our `List::len()` method, it is allowed appear in Prusti specifications: ```rust,noplaypen -#//uncomment: #![feature(box_patterns)] -#use prusti_contracts::*; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# +# use prusti_contracts::*; +# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# 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 { -# fn len(&self) -> usize { -# 0 -# } -#} +# +# #[ensures(result.len() == 0)] +# pub fn new() -> Self { +# List { +# head: Link::Empty, +# } +# } +} +# +# impl Link { +# fn len(&self) -> usize { +# 0 +# } +# } ``` However, Prusti still won't verify! It produces the same error but now it refers to the *body* of `len()`: -``` +```markdown error: [Prusti: invalid specification] use of impure function "Link::len" in pure code is not allowed --> list.rs:30:9 | @@ -255,50 +207,49 @@ namely `Link::len()`, within the body of the pure function `List::len()`. To fix this issue, it suffices to mark `Link::len()` as pure as well. ```rust,noplaypen -#//uncomment: #![feature(box_patterns)] -#use prusti_contracts::*; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#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, -# } -# } -#} -# +# use prusti_contracts::*; +# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# 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] fn len(&self) -> usize { 0 } } -# -# fn main() {} // in case Prusti is used via command line -# +# +# fn main() {} // in case Prusti is used via command line +# // Prusti: VERIFIES ``` -``` +```markdown $ prusti-rustc list.rs // ... Successful verification of 4 items @@ -306,8 +257,13 @@ Successful verification of 4 items Prusti now manages to verify that `new()` always returns a list for which the method `len()` returns 0. (notice -this is hardly surprising since `len()` ultimately always returns 0 but we will change -this soon.) +this is hardly surprising since `len()` ultimately always returns 0.) + +We will now properly implement `len()`, and while we're at it, `is_empty()` for `Link`. Both of them are pure functions, so we will add the `#[pure]` annotation. Both functions don't have any postconditions that are required to call them, so we don't have to write a `requires` annotation. We also don't need to add any additional postconditions, since pure functions will be inlined wherever they are used during verification. + +```rust,noplaypen +{{#rustdoc_include tour-src/04-chapter-2-2-full-code.rs:32:45}} +``` ## Full code listing @@ -316,45 +272,7 @@ It should successfully verify with Prusti and we will further extend it througho the next four chapters. ```rust,noplaypen -#![feature(box_patterns)] -use prusti_contracts::*; - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -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] - fn len(&self) -> usize { - 0 - } -} - -fn main() {} // in case Prusti is used via command line +{{#rustdoc_include tour-src/04-chapter-2-2-full-code.rs}} // Prusti: VERIFIES ``` diff --git a/docs/user-guide/src/tour/pop.md b/docs/user-guide/src/tour/pop.md index 16ef6a15f0f..3ef09764d5f 100644 --- a/docs/user-guide/src/tour/pop.md +++ b/docs/user-guide/src/tour/pop.md @@ -1,6 +1,7 @@ # Pop -**TODO** +> **Recommended reading:** +> [2.5: Pop](https://rust-unofficial.github.io/too-many-lists/first-pop.html), ```rust,noplaypen {{#include tour-src/10-chapter-2-5.rs:1:}} diff --git a/docs/user-guide/src/tour/push.md b/docs/user-guide/src/tour/push.md index 42658de2dcd..a98b8442f63 100644 --- a/docs/user-guide/src/tour/push.md +++ b/docs/user-guide/src/tour/push.md @@ -10,7 +10,7 @@ Our next goal is to implement and verify a method that pushes an integer onto a In contrast to methods like `len`, `push` modifies the list; it thus takes `&mut self` as its first argument: -```rust,noplaypen +```rust,noplaypen,ignore impl List { pub fn push(&mut self, elem: i32) { // TODO @@ -32,51 +32,52 @@ pure method `len` introduced in the [previous chapter](new.md): ```rust,noplaypen # #![feature(box_patterns)] -#use prusti_contracts::*; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} +# //extern crate prusti_contracts; +# use prusti_contracts::*; +# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# 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, -# } -# -# } -# +# #[pure] +# pub fn len(&self) -> usize { +# self.head.len() +# } +# +# #[ensures(result.len() == 0)] +# pub fn new() -> Self { +# List { +# head: Link::Empty, +# } +# +# } +# #[ensures(self.len() == old(self.len()) + 1)] pub fn push(&mut self, elem: i32) { // TODO } } # -#impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -#} +# impl Link { +# #[pure] +# fn len(&self) -> usize { +# match self { +# Link::Empty => 0, +# Link::More(box node) => 1 + node.next.len(), +# } +# } +# } ``` The above postcondition depends on *two* states, namely the state before and after @@ -85,7 +86,7 @@ We use an [old expression](../syntax.md#old-expressions) to refer to the state before execution of `self.push(elem)`. Since we have not implemented `push` yet, Prusti will, unsurprisingly, complain: -``` +```markdown [Prusti: verification error] postcondition might not hold. ``` @@ -97,37 +98,38 @@ we create a new instance of our struct for list nodes that stores We may thus be tempted to write the following: -```rust,noplaypen +```rust,noplaypen,ignore # #![feature(box_patterns)] -#use prusti_contracts::*; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} +# extern crate prusti_contracts; +# use prusti_contracts::*; +# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# 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, -# } -# } -# +# #[pure] +# pub fn len(&self) -> usize { +# self.head.len() +# } +# +# #[ensures(result.len() == 0)] +# pub fn new() -> Self { +# List { +# head: Link::Empty, +# } +# } +# #[ensures(self.len() == old(self.len()) + 1)] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { @@ -138,21 +140,21 @@ impl List { self.head = Link::More(new_node); } } -# -#impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -#} +# +# impl Link { +# #[pure] +# fn len(&self) -> usize { +# match self { +# Link::Empty => 0, +# Link::More(box node) => 1 + node.next.len(), +# } +# } +# } ``` Unfortunately, the Rust compiler will complain about this attempt: -``` +```markdown [E0507] cannot move out of `self.head` which is behind a mutable reference. ``` @@ -178,37 +180,38 @@ Using this function, the Rust compiler accepts the following implementation: ```rust,noplaypen # #![feature(box_patterns)] -#use prusti_contracts::*; -# +# //extern crate prusti_contracts; +# use prusti_contracts::*; +# use std::mem; -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# 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, -# } -# } -# +# #[pure] +# pub fn len(&self) -> usize { +# self.head.len() +# } +# +# #[ensures(result.len() == 0)] +# pub fn new() -> Self { +# List { +# head: Link::Empty, +# } +# } +# #[ensures(self.len() == old(self.len()) + 1)] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { @@ -219,22 +222,22 @@ impl List { self.head = Link::More(new_node); } } -# -#impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -#} +# +# impl Link { +# #[pure] +# fn len(&self) -> usize { +# match self { +# Link::Empty => 0, +# Link::More(box node) => 1 + node.next.len(), +# } +# } +# } ``` In fact, the above implementation of `push` is correct. However, attempting to verify it with Prusti still yields a verification error: -``` +```markdown [Prusti: verification error] postcondition might not hold. ``` @@ -261,160 +264,76 @@ list. We can remedy this issue by strengthening the specification of `std::mem::replace`. In this tutorial, we will assume that the standard library is correct, that is, we do not attempt to verify specifications for functions in external crates, -like `std::mem::replace`. -To this end, we introduce a [trusted](../verify/trusted.md) wrapper function `replace` -that calls `std::mem::replace` and is equipped with the desired specification. -The attribute `#[trusted]` tells Prusti to assume the provided specification is correct -without attempting to verify it against a function's body. +like `std::mem::replace`. To this end, we have to add the specification to the function. +This can be done with another piece of Prusti syntax: + +```rust,noplaypen,ignore +#[extern_spec] +mod std { + mod mem { + # //extern crate prusti_contracts; + use prusti_contracts::*; + + #[ensures(snap(dest) === src)] + #[ensures(result === old(snap(dest)))] + fn replace(dest: &mut T, src: T) -> T; + } +} +``` + +New syntax: +```rust,noplaypen,ignore +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; +``` -After introducing a trusted wrapper that ensures that `replace` does not change the -length of the original list, the following implementation verifies: +Lets break this code down step by step. +- First we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. +- Next we need to figure out where the function is located. In this case it is `std::mem`, which we then write down with `mod std { mod mem { ... } }` +- After a quick search for *\"rust std mem replace\"* we can find the [documentation for std::mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html). Here we can get the function signature: `pub fn replace(dest: &mut T, src: T) -> T`. We then write down the signature in the inner module, followed by a `;`. +- To get the Prusti syntax in scope, we add `use prusti_contracts::*` to the inner module. +- Since there is no preconditions to `replace`, we don't have to write one down. The default precondition is `#[requires(true)]`. +- For writing the postcondition, we use four pieces of syntax added by Prusti: + - `===` is called **snapshot equality** or **logical equality**. Is essentially checks if the left and right sides are structurally equal. More details can be seen [here](../syntax.md#snapshot-equality). `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. + - The `snap()` function takes a snapshot of a reference. This function should only be used in specifications, since it ignores the borrow checker. + - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state from before the function was called. + - The identifier `result` is used to refer to the return parameter of the function. +- The postcondition consists of two parts, which can either be written in one condition with `&&`, or in multiple annotations like in the example above. + - The first condition `snap(dest) === src` means: *After the function returns, the location referenced by `dest` is structurally equal to the parameter `src`* + - The second part of the postcondition is `result === old(snap(dest))`. This means: *The `result` returned by the function is structurally equal to the the element that was referenced by `dest` **before** the function was called.* +- An important thing to note here is the Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. + +Depending on when you are reading this, the Rust standard library might be (partially) annotated, so this external specification may not be needed anymore. + +Trusted functions can be used for verifying projects containing external code without Prusti annotations, or projects using Rust features not yet supported by Prusti. +An example is printing a string: ```rust,noplaypen -# #![feature(box_patterns)] -#use prusti_contracts::*; -# -#use std::mem; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# #[trusted] -#[ensures(old(dest.len()) == result.len())] -fn replace(dest: &mut Link, src: Link) -> Link { - mem::replace(dest, src) -} - -impl List { -# #[pure] -# pub fn len(&self) -> usize { -# self.head.len() -# } -# -# #[ensures(result.len() == 0)] -# pub fn new() -> Self { -# List { -# head: Link::Empty, -# } -# } -# - #[ensures(self.len() == old(self.len()) + 1)] - 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); - } +fn print(s: &str) { + println!("{s}"); } -# -#impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -#} -// Prusti: VERIFIES ``` - -Trusted functions are useful for dealing with external code, like `std::mem::replace`, -or constructs that are not yet supported by Prusti. -**However, a single incorrect specification of a trusted function can invalidate the - correctness of Prusti as a whole!** +Prusti will ***not*** check trusted functions for correctness. **A single incorrect specification of a trusted function can invalidate the correctness of Prusti as a whole!** Hence, trusted functions, like unsafe Rust code, need to be treated carefully and require external justification. -It is good practice to provide strong preconditions for trusted functions -to reduce the chance that they are accidentially called with unintended arguments. -In our case, we could additionally require that `replace` always takes an empty -list as its second argument. -We formalize this precondition by introducing another pure function `is_empty`: - -```rust,noplaypen -# #![feature(box_patterns)] -#use prusti_contracts::*; -# -#use std::mem; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# +For example, the following function will not cause the verification to fail: +```rust,noplaypen,norun #[trusted] -#[requires(src.is_empty())] -#[ensures(dest.is_empty())] -#[ensures(old(dest.len()) == result.len())] -fn replace(dest: &mut Link, src: Link) -> Link { - mem::replace(dest, src) +fn incorrect_function() -> i32 { + panic!() } +``` -#impl List { -# #[pure] -# pub fn len(&self) -> usize { -# self.head.len() -# } -# -# #[ensures(result.len() == 0)] -# pub fn new() -> Self { -# List { -# head: Link::Empty, -# } -# } -# -# #[ensures(self.len() == old(self.len()) + 1)] -# 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); -# } -#} -# -impl Link { -# #[pure] -# 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, - } - } -} -// Prusti: VERIFIES +After adding the external specification for `std::mem::replace`, we can finally verify the `push` function: + +```rust,noplaypen +{{#rustdoc_include tour-src/push_property_1.rs:18:39}} + +// Prusti: Verifies ``` This completes our implementation of `push` but we still need to verify @@ -432,58 +351,58 @@ Our second desired property then corresponds to the postcondition `self.lookup(0) == elem`. ```rust,noplaypen -# #![feature(box_patterns)] -#use prusti_contracts::*; -# -#use std::mem; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -# #[trusted] -# #[ensures(old(dest.len()) == result.len())] -#fn replace(dest: &mut Link, src: Link) -> Link { -# mem::replace(dest, src) -#} -# +# //extern crate prusti_contracts; +# use prusti_contracts::*; +# +# use std::mem; +# +# pub struct List { +# head: Link, +# } +# +# enum Link { +# Empty, +# More(Box), +# } +# +# struct Node { +# elem: i32, +# next: Link, +# } +# +# #[trusted] +# #[ensures(old(dest.len()) == result.len())] +# fn replace(dest: &mut Link, src: Link) -> Link { +# mem::replace(dest, src) +# } +# impl List { #[pure] pub fn lookup(&self, index: usize) -> i32 { self.head.lookup(index) } -# -# #[pure] -# pub fn len(&self) -> usize { -# self.head.len() -# } -# -# #[ensures(result.len() == 0)] -# pub fn new() -> Self { -# List { -# head: Link::Empty, -# } -# } +# +# #[pure] +# pub fn len(&self) -> usize { +# self.head.len() +# } +# +# #[ensures(result.len() == 0)] +# pub fn new() -> Self { +# List { +# head: Link::Empty, +# } +# } #[ensures(self.len() == old(self.len()) + 1)] #[ensures(self.lookup(0) == elem)] 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); +# let new_node = Box::new(Node { +# elem: elem, +# next: replace(&mut self.head, Link::Empty), +# }); +# self.head = Link::More(new_node); } } @@ -491,7 +410,7 @@ impl Link { #[pure] pub fn lookup(&self, index: usize) -> i32 { match self { - Link::More(box node) => { + Link::More(node) => { if index == 0 { node.elem } else { @@ -501,15 +420,15 @@ impl Link { Link::Empty => unreachable!(), } } -# -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -} +# +# #[pure] +# fn len(&self) -> usize { +# match self { +# Link::Empty => 0, +# Link::More(node) => 1 + node.next.len(), +# } +# } +} ``` Consider the `match` statement in the last function. @@ -518,97 +437,30 @@ Since there is no sensible implementation of `lookup` if the underlying list is we used the macro `unreachable!()`, which will crash the program with a panic. Since nothing prevents us from calling `lookup` on an empty list, Prusti complains: -``` +```markdown unreachable!(..) statement in pure function might be reachable ``` We can specify that `lookup` should only be called on non-empty lists by adding the -precondition `0 <= index && index < self.len()` to *both* `lookup` functions; this is +precondition `index < self.len()` to *both* `lookup` functions; this is sufficient to verify our second property for `push`: - ```rust,noplaypen -# #![feature(box_patterns)] -#use prusti_contracts::*; -# -#use std::mem; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box), -#} -# -#struct Node { -# elem: i32, -# next: Link, -#} -# -# #[trusted] -# #[ensures(old(dest.len()) == result.len())] -#fn replace(dest: &mut Link, src: Link) -> Link { -# mem::replace(dest, src) -#} -# -impl List { - #[pure] - #[requires(0 <= index && index < self.len())] - pub fn lookup(&self, index: usize) -> i32 { - self.head.lookup(index) - } -# -# #[pure] -# pub fn len(&self) -> usize { -# self.head.len() -# } -# -# #[ensures(result.len() == 0)] -# pub fn new() -> Self { -# List { -# head: Link::Empty -# } -# } -# -# #[ensures(self.len() == old(self.len()) + 1)] -# 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); -# } -} +{{#rustdoc_include tour-src/push_property_2.rs:30:33}} + // ... +``` -impl Link { - #[pure] - #[requires(0 <= index && index < self.len())] - pub fn lookup(&self, index: usize) -> i32 { - // ... -# match self { -# Link::More(box node) => { -# if index == 0 { -# node.elem -# } else { -# node.next.lookup(index - 1) -# } -# }, -# Link::Empty => unreachable!(), -# } - } -# -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -} -// Prusti: VERIFIES +```rust,noplaypen +{{#rustdoc_include tour-src/push_property_2.rs:61:64}} + // ... ``` +We don't need to add the condition `0 <= index` to the precondition, since `index` has the type `usize`, so `index` is always non-negative. (If you don't want Prusti to add this condition automatically, you can add the line `encode_unsigned_num_constraint = false` to your `Prusti.toml` file). + +After these changes, Prusti can successfully verify the code, so the first two properties of `push` are correct. + + + + ## Third property The third and final property we will verify for `push` is that the original list @@ -620,116 +472,19 @@ To formalize the above property, we can reuse our pure function `lookup`, [quantifiers](../syntax.md#quantifiers), and [old expressions](../syntax.md#old-expressions), that is, we add the postcondition: -```rust,noplaypen -#[ensures(forall(|i: usize| (1 <= i && i < self.len()) ==> - old(self.lookup(i - 1)) == self.lookup(i)))] -pub fn push(&mut self, elem: i32) { - // ... -} +```rust,noplaypen,ignore +{{#rustdoc_include tour-src/push_final.rs:39:41}} ``` -After adding the above postcondition, Prusti will complain that the postcondition -might not hold; the reason is similar to an issue we encountered when verifying -the first property: the specification of `replace` is too weak. -Verification succeeds after adding the same postcondition to `replace`. -We conclude this section with the full code for verifying `push`: +Lets break this expression down like before: +- `forall(..)` denotes a quantifier, and it takes a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html). +- The closure (denoted by the two vertical bars: `||`), takes the parameter `i: usize` and returns a `bool`. You can think of the forall expression as follows: *Any parameter passed to the closure makes it return `true`*. + - Closures in a `forall` expression can take any number of parameters, separated by a comma: `|i: usize, j: usize|`. +- In this case we use the implication operator `==>`. It takes a left and right argument of type `bool` and is true, if the left side is false, or both sides are true. `a ==> b` is equivalent to `!a || b` and `!(a && !b)`. + - The left side of the implication is `(1 <= i && i < self.len())`, which is the range where the right side must hold. + - The right side is the condition for everything being shifted back by one element: `old(self.lookup(i - 1)) == self.lookup(i)))`. +## Full code listing ```rust,noplaypen -# #![feature(box_patterns)] -#use prusti_contracts::*; -# -#use std::mem; -# -#pub struct List { -# head: Link, -#} -# -#enum Link { -# Empty, -# More(Box) -#} -# -#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] -# #[requires(0 <= index && index < self.len())] -# pub fn lookup(&self, index: usize) -> i32 { -# self.head.lookup(index) -# } -# -# #[pure] -# pub fn len(&self) -> usize { -# self.head.len() -# } -# -# #[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); - } -} -# -#impl Link { -# #[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) -# } -# }, -# } -# } -# -# #[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, -# } -# } -#} -// Prusti: VERIFIES -``` +{{#include tour-src/push_final.rs}} +``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md new file mode 100644 index 00000000000..9b7d9ad5238 --- /dev/null +++ b/docs/user-guide/src/tour/setup.md @@ -0,0 +1,40 @@ +# Setup + +To get started, you can use an existing Rust project or create a new one with Cargo: + +```sh +cargo new prusti_tutorial +``` +Then you can change the current working directory to the project's directory: +```sh +cd ./prusti_tutorial/ +``` + + +To use the additional syntax used for verification with Prusti, you need to add the [prusti_contracts](https://docs.rs/prusti-contracts/latest/prusti_contracts/) crate to your project. If you have at least Cargo version 1.62.0, you can use this command to add the dependency: +```sh +cargo add prusti-contracts +``` +For older versions of Rust, you can manually add the dependency in your Cargo.toml file: +```toml +[dependencies] +prusti-contracts = "0.1.2" +``` + + +To use prusti-contracts in a Rust file, just add the following line: +```rust,ignore +use prusti_contracts::*; +``` + + +To simplify the tutorial, overflow checks by Prusti will be disabled. To do that, create a file called `Prusti.toml` in your projects root directory (where `Cargo.toml` is located). +In this file, you can set [configuration flags](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html) used by Prusti. To disable overflow checking, add the following line: +```toml +check_overflows = false +``` + +TODO: +- nightly compiler needed(?) +- mention how to run Prusti on the created project +- mention that strings are not supported (remove print from generated main fn) \ No newline at end of file diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index ec2fd51efd9..43fea01c69e 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -38,6 +38,7 @@ devices. As a quick reference, the main steps of this tour and the involved Prusti features are as follows: +0. [Setup](setup.md) How to add Prusti to a project 1. [Getting Started](getting-started.md): Simple runtime errors caught by Prusti 2. [New](new.md): Postconditions, pure functions 3. [Push](push.md): Preconditions, trusted functions, old expressions, quantifiers diff --git a/docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs b/docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs new file mode 100644 index 00000000000..e4c991d24ce --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs @@ -0,0 +1,47 @@ +#![feature(box_patterns)] +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +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] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} + +fn main() {} // in case Prusti is used via command line diff --git a/docs/user-guide/src/tour/tour-src/04-chapter-2-2.rs b/docs/user-guide/src/tour/tour-src/04-chapter-2-2.rs index 5d4a9271b74..6284aedadeb 100644 --- a/docs/user-guide/src/tour/tour-src/04-chapter-2-2.rs +++ b/docs/user-guide/src/tour/tour-src/04-chapter-2-2.rs @@ -20,3 +20,9 @@ impl List { } } } + +impl Link { + fn len(&self) -> usize { + 0 + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/push_final.rs b/docs/user-guide/src/tour/tour-src/push_final.rs new file mode 100644 index 00000000000..903da39fcf3 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/push_final.rs @@ -0,0 +1,91 @@ +// extern crate prusti_contracts; +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec] +mod std { + mod mem { + //extern crate prusti_contracts; + use prusti_contracts::*; + + #[ensures(snap(dest) === src)] + #[ensures(result === old(snap(dest)))] + fn replace(dest: &mut T, src: T) -> T; + } +} + +impl List { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.head.lookup(index) + } + + #[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: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { + head: Link::Empty, + } + } +} + +impl Link { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + match self { + Link::More(node) => { + if index == 0 { + node.elem + } else { + node.next.lookup(index - 1) + } + }, + Link::Empty => unreachable!(), + } + } + + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/push_property_1.rs b/docs/user-guide/src/tour/tour-src/push_property_1.rs new file mode 100644 index 00000000000..58deabcd130 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/push_property_1.rs @@ -0,0 +1,67 @@ +// extern crate prusti_contracts; +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec] +mod std { + mod mem { + //extern crate prusti_contracts; + use prusti_contracts::*; + + #[ensures(snap(dest) === src)] + #[ensures(result === old(snap(dest)))] + fn replace(dest: &mut T, src: T) -> T; + } +} + +impl List { + #[ensures(self.len() == old(self.len()) + 1)] + pub fn push(&mut self, elem: i32) { + let new_node = Box::new(Node { + elem: elem, + next: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { + head: Link::Empty, + } + } +} + +impl Link { + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/push_property_2.rs b/docs/user-guide/src/tour/tour-src/push_property_2.rs new file mode 100644 index 00000000000..71c4bf0b472 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/push_property_2.rs @@ -0,0 +1,89 @@ +// extern crate prusti_contracts; +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec] +mod std { + mod mem { + //extern crate prusti_contracts; + use prusti_contracts::*; + + #[ensures(snap(dest) === src)] + #[ensures(result === old(snap(dest)))] + fn replace(dest: &mut T, src: T) -> T; + } +} + +impl List { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.head.lookup(index) + } + + #[ensures(self.lookup(0) == elem)] + #[ensures(self.len() == old(self.len()) + 1)] + pub fn push(&mut self, elem: i32) { + let new_node = Box::new(Node { + elem: elem, + next: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { + head: Link::Empty, + } + } +} + +impl Link { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + match self { + Link::More(node) => { + if index == 0 { + node.elem + } else { + node.next.lookup(index - 1) + } + }, + Link::Empty => unreachable!(), + } + } + + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} \ No newline at end of file From b04a39d0ab7204d014816dea14ff594c660b57e7 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Thu, 2 Feb 2023 11:39:00 +0100 Subject: [PATCH 02/31] Change book authors --- .gitignore | 3 +++ docs/dev-guide/book.toml | 2 +- docs/user-guide/book.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 38d1386d7df..55f21d7cb51 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ log/ nll-facts/ benchmark-output/ oprofile_data/ + +docs/user-guide/book/ +docs/dev-guide/book/ diff --git a/docs/dev-guide/book.toml b/docs/dev-guide/book.toml index d282c4cfccc..a9274329985 100644 --- a/docs/dev-guide/book.toml +++ b/docs/dev-guide/book.toml @@ -1,5 +1,5 @@ [book] -authors = ["Aurel Bílý"] +authors = ["Prusti Devs "] language = "en" multilingual = false src = "src" diff --git a/docs/user-guide/book.toml b/docs/user-guide/book.toml index 59d05af7c24..858668f40c8 100644 --- a/docs/user-guide/book.toml +++ b/docs/user-guide/book.toml @@ -1,5 +1,5 @@ [book] -authors = ["Aurel Bílý"] +authors = ["Prusti Devs "] language = "en" multilingual = false src = "src" From be056877836859c1677b4f9c953e8be2cf9b9f89 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Thu, 2 Feb 2023 11:41:07 +0100 Subject: [PATCH 03/31] Create inner crate for user guide --- Cargo.toml | 3 + docs/user-guide/src/tour/tour-src/Cargo.toml | 11 ++ docs/user-guide/src/tour/tour-src/Prusti.toml | 2 + .../tour/tour-src/{ => src}/01-chapter-2-1.rs | 0 .../tour/tour-src/{ => src}/02-chapter-2-1.rs | 0 .../tour/tour-src/{ => src}/03-chapter-2-1.rs | 0 .../src/tour/tour-src/{ => src}/03-fail.rs | 0 .../tour/tour-src/{ => src}/04-chapter-2-2.rs | 0 .../tour/tour-src/{ => src}/05-chapter-2-3.rs | 0 .../tour/tour-src/{ => src}/06-chapter-2-4.rs | 0 .../tour/tour-src/{ => src}/07-chapter-2-4.rs | 0 .../tour/tour-src/{ => src}/08-chapter-2-4.rs | 0 .../tour/tour-src/{ => src}/09-chapter-2-4.rs | 0 .../tour/tour-src/{ => src}/10-chapter-2-5.rs | 0 .../tour/tour-src/{ => src}/11-chapter-2-5.rs | 0 .../tour/tour-src/{ => src}/12-chapter-2-6.rs | 0 .../{ => src}/13-too-many-lists-final.rs | 0 .../src/tour/tour-src/src/getting_started.rs | 2 + .../tour-src/src/getting_started/failing.rs | 35 ++++ .../tour-src/src/getting_started/working.rs | 24 +++ docs/user-guide/src/tour/tour-src/src/lib.rs | 6 + docs/user-guide/src/tour/tour-src/src/new.rs | 6 + .../new/full_code.rs} | 9 +- .../src/tour/tour-src/src/new/impl_new.rs | 21 ++ .../tour/tour-src/src/new/spec_failing_1.rs | 38 ++++ .../tour/tour-src/src/new/spec_failing_2.rs | 38 ++++ .../tour/tour-src/src/new/spec_failing_3.rs | 35 ++++ .../src/tour/tour-src/src/new/spec_fixed.rs | 36 ++++ docs/user-guide/src/tour/tour-src/src/pop.rs | 2 + .../src/tour/tour-src/src/pop/final_code.rs | 184 ++++++++++++++++++ .../src/tour/tour-src/src/pop/initial_code.rs | 112 +++++++++++ docs/user-guide/src/tour/tour-src/src/push.rs | 5 + .../src/tour/tour-src/src/push/final_code.rs | 92 +++++++++ .../push/initial_code.rs} | 24 +-- .../src/tour/tour-src/src/push/property_1.rs | 65 +++++++ .../push/property_2_missing_bounds.rs} | 39 ++-- .../push/property_2_with_bounds.rs} | 42 ++-- docs/user-guide/src/tour/tour-src/src/test.rs | 19 ++ 38 files changed, 785 insertions(+), 65 deletions(-) create mode 100644 docs/user-guide/src/tour/tour-src/Cargo.toml create mode 100644 docs/user-guide/src/tour/tour-src/Prusti.toml rename docs/user-guide/src/tour/tour-src/{ => src}/01-chapter-2-1.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/02-chapter-2-1.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/03-chapter-2-1.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/03-fail.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/04-chapter-2-2.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/05-chapter-2-3.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/06-chapter-2-4.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/07-chapter-2-4.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/08-chapter-2-4.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/09-chapter-2-4.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/10-chapter-2-5.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/11-chapter-2-5.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/12-chapter-2-6.rs (100%) rename docs/user-guide/src/tour/tour-src/{ => src}/13-too-many-lists-final.rs (100%) create mode 100644 docs/user-guide/src/tour/tour-src/src/getting_started.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/getting_started/working.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/lib.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/new.rs rename docs/user-guide/src/tour/tour-src/{04-chapter-2-2-full-code.rs => src/new/full_code.rs} (79%) create mode 100644 docs/user-guide/src/tour/tour-src/src/new/impl_new.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/pop.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/pop/final_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/push.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/push/final_code.rs rename docs/user-guide/src/tour/tour-src/{push_property_1.rs => src/push/initial_code.rs} (67%) create mode 100644 docs/user-guide/src/tour/tour-src/src/push/property_1.rs rename docs/user-guide/src/tour/tour-src/{push_property_2.rs => src/push/property_2_missing_bounds.rs} (70%) rename docs/user-guide/src/tour/tour-src/{push_final.rs => src/push/property_2_with_bounds.rs} (66%) create mode 100644 docs/user-guide/src/tour/tour-src/src/test.rs diff --git a/Cargo.toml b/Cargo.toml index 6175322a7f3..44ccc629e00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ members = [ "jni-gen", "jni-gen/systest", ] +exclude = [ + "docs/user-guide/src/tour/tour-src" +] [profile.dev] debug = 1 diff --git a/docs/user-guide/src/tour/tour-src/Cargo.toml b/docs/user-guide/src/tour/tour-src/Cargo.toml new file mode 100644 index 00000000000..15cf08a8580 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = ["Prusti Devs "] +name = "tour-src" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +prusti-contracts = "0.1.3" +# prusti-contracts = { path = "../../../../../prusti-contracts/prusti-contracts/" } diff --git a/docs/user-guide/src/tour/tour-src/Prusti.toml b/docs/user-guide/src/tour/tour-src/Prusti.toml new file mode 100644 index 00000000000..057163cb03a --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/Prusti.toml @@ -0,0 +1,2 @@ +check_overflows = false +encode_unsigned_num_constraint = true \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/01-chapter-2-1.rs b/docs/user-guide/src/tour/tour-src/src/01-chapter-2-1.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/01-chapter-2-1.rs rename to docs/user-guide/src/tour/tour-src/src/01-chapter-2-1.rs diff --git a/docs/user-guide/src/tour/tour-src/02-chapter-2-1.rs b/docs/user-guide/src/tour/tour-src/src/02-chapter-2-1.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/02-chapter-2-1.rs rename to docs/user-guide/src/tour/tour-src/src/02-chapter-2-1.rs diff --git a/docs/user-guide/src/tour/tour-src/03-chapter-2-1.rs b/docs/user-guide/src/tour/tour-src/src/03-chapter-2-1.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/03-chapter-2-1.rs rename to docs/user-guide/src/tour/tour-src/src/03-chapter-2-1.rs diff --git a/docs/user-guide/src/tour/tour-src/03-fail.rs b/docs/user-guide/src/tour/tour-src/src/03-fail.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/03-fail.rs rename to docs/user-guide/src/tour/tour-src/src/03-fail.rs diff --git a/docs/user-guide/src/tour/tour-src/04-chapter-2-2.rs b/docs/user-guide/src/tour/tour-src/src/04-chapter-2-2.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/04-chapter-2-2.rs rename to docs/user-guide/src/tour/tour-src/src/04-chapter-2-2.rs diff --git a/docs/user-guide/src/tour/tour-src/05-chapter-2-3.rs b/docs/user-guide/src/tour/tour-src/src/05-chapter-2-3.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/05-chapter-2-3.rs rename to docs/user-guide/src/tour/tour-src/src/05-chapter-2-3.rs diff --git a/docs/user-guide/src/tour/tour-src/06-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/06-chapter-2-4.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/06-chapter-2-4.rs rename to docs/user-guide/src/tour/tour-src/src/06-chapter-2-4.rs diff --git a/docs/user-guide/src/tour/tour-src/07-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/07-chapter-2-4.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/07-chapter-2-4.rs rename to docs/user-guide/src/tour/tour-src/src/07-chapter-2-4.rs diff --git a/docs/user-guide/src/tour/tour-src/08-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/08-chapter-2-4.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/08-chapter-2-4.rs rename to docs/user-guide/src/tour/tour-src/src/08-chapter-2-4.rs diff --git a/docs/user-guide/src/tour/tour-src/09-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/09-chapter-2-4.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/09-chapter-2-4.rs rename to docs/user-guide/src/tour/tour-src/src/09-chapter-2-4.rs diff --git a/docs/user-guide/src/tour/tour-src/10-chapter-2-5.rs b/docs/user-guide/src/tour/tour-src/src/10-chapter-2-5.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/10-chapter-2-5.rs rename to docs/user-guide/src/tour/tour-src/src/10-chapter-2-5.rs diff --git a/docs/user-guide/src/tour/tour-src/11-chapter-2-5.rs b/docs/user-guide/src/tour/tour-src/src/11-chapter-2-5.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/11-chapter-2-5.rs rename to docs/user-guide/src/tour/tour-src/src/11-chapter-2-5.rs diff --git a/docs/user-guide/src/tour/tour-src/12-chapter-2-6.rs b/docs/user-guide/src/tour/tour-src/src/12-chapter-2-6.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/12-chapter-2-6.rs rename to docs/user-guide/src/tour/tour-src/src/12-chapter-2-6.rs diff --git a/docs/user-guide/src/tour/tour-src/13-too-many-lists-final.rs b/docs/user-guide/src/tour/tour-src/src/13-too-many-lists-final.rs similarity index 100% rename from docs/user-guide/src/tour/tour-src/13-too-many-lists-final.rs rename to docs/user-guide/src/tour/tour-src/src/13-too-many-lists-final.rs diff --git a/docs/user-guide/src/tour/tour-src/src/getting_started.rs b/docs/user-guide/src/tour/tour-src/src/getting_started.rs new file mode 100644 index 00000000000..e5447ac113d --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/getting_started.rs @@ -0,0 +1,2 @@ +// mod failing; +mod working; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs b/docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs new file mode 100644 index 00000000000..4585a7f066a --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs @@ -0,0 +1,35 @@ +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +fn main() { + let test = Node { + elem: 17, + next: Link::Empty, + }; + + if test.elem > 23 { + panic!() // unreachable + } +} + +fn test(x: i32) { + let test = Node { + elem: x, // unknown value + next: Link::Empty, + }; + + if test.elem > 23 { + panic!() + } +} diff --git a/docs/user-guide/src/tour/tour-src/src/getting_started/working.rs b/docs/user-guide/src/tour/tour-src/src/getting_started/working.rs new file mode 100644 index 00000000000..e76fd5e8dd1 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/getting_started/working.rs @@ -0,0 +1,24 @@ +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +fn main() { + let test = Node { + elem: 17, + next: Link::Empty, + }; + + if test.elem > 23 { + panic!() // unreachable + } +} diff --git a/docs/user-guide/src/tour/tour-src/src/lib.rs b/docs/user-guide/src/tour/tour-src/src/lib.rs new file mode 100644 index 00000000000..974a939143c --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/lib.rs @@ -0,0 +1,6 @@ +#![allow(dead_code, unused)] + +mod getting_started; +mod new; +// mod push; // TOOD: fix extern_spec (new syntax) +mod pop; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new.rs b/docs/user-guide/src/tour/tour-src/src/new.rs new file mode 100644 index 00000000000..e22c73724c0 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/new.rs @@ -0,0 +1,6 @@ +mod impl_new; +// mod spec_failing_1; +// mod spec_failing_2; +// mod spec_failing_3; +mod spec_fixed; +mod full_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs b/docs/user-guide/src/tour/tour-src/src/new/full_code.rs similarity index 79% rename from docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs rename to docs/user-guide/src/tour/tour-src/src/new/full_code.rs index e4c991d24ce..d2c2433c188 100644 --- a/docs/user-guide/src/tour/tour-src/04-chapter-2-2-full-code.rs +++ b/docs/user-guide/src/tour/tour-src/src/new/full_code.rs @@ -1,4 +1,3 @@ -#![feature(box_patterns)] use prusti_contracts::*; pub struct List { @@ -44,4 +43,10 @@ impl Link { } } -fn main() {} // in case Prusti is used via command line +fn _test_len(link: &Link) { + let link_is_empty = link.is_empty(); + let link_len = link.len(); + assert!(link_is_empty == (link_len == 0)); +} + +fn main() {} diff --git a/docs/user-guide/src/tour/tour-src/src/new/impl_new.rs b/docs/user-guide/src/tour/tour-src/src/new/impl_new.rs new file mode 100644 index 00000000000..418acb8b70a --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/new/impl_new.rs @@ -0,0 +1,21 @@ +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: impl_new +impl List { + pub fn new() -> Self { + List { head: Link::Empty } + } +} +//// ANCHOR_END: impl_new \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs b/docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs new file mode 100644 index 00000000000..2f8a548f7e0 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs @@ -0,0 +1,38 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: first_spec_1 +impl List { + pub fn len(&self) -> usize { + self.head.len() + } +//// ANCHOR_END: first_spec_1 + +//// ANCHOR: first_spec_2 + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + } +//// ANCHOR_END: first_spec_2 +//// ANCHOR: first_spec_1 +} + +impl Link { + fn len(&self) -> usize { + 0 + } +} +//// ANCHOR_END: first_spec_1 \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs b/docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs new file mode 100644 index 00000000000..72fe80d45a0 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs @@ -0,0 +1,38 @@ +//// ANCHOR: import_prusti +use prusti_contracts::*; + +//// ANCHOR_END: import_prusti +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: import_prusti +impl List { +//// ANCHOR_END: import_prusti + pub fn len(&self) -> usize { + self.head.len() + } + +//// ANCHOR: import_prusti + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + } +} +//// ANCHOR_END: import_prusti + +impl Link { + fn len(&self) -> usize { + 0 + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs b/docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs new file mode 100644 index 00000000000..6df329e0324 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs @@ -0,0 +1,35 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: pure_annotation +impl List { + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } +//// ANCHOR_END: pure_annotation + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + } +} + +impl Link { + fn len(&self) -> usize { + 0 + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs b/docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs new file mode 100644 index 00000000000..74876b12ee2 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs @@ -0,0 +1,36 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +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 } + } +} + +//// ANCHOR: pure_annotation +impl Link { + #[pure] + fn len(&self) -> usize { + 0 + } +} +//// ANCHOR_END: pure_annotation \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/pop.rs b/docs/user-guide/src/tour/tour-src/src/pop.rs new file mode 100644 index 00000000000..b67069a7559 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/pop.rs @@ -0,0 +1,2 @@ +// mod initial_code; // should fail +mod final_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs b/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs new file mode 100644 index 00000000000..e21634630e8 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs @@ -0,0 +1,184 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; +} + + +//// ANCHOR: two_state_predicate +//// ANCHOR: predicate_use +//// ANCHOR: try_pop_empty +impl List { + //// ANCHOR_END: two_state_predicate + //// ANCHOR_END: predicate_use + //// ANCHOR_END: try_pop_empty + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[pure] + fn is_empty(&self) -> bool { + self.head.is_empty() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.head.lookup(index) + } + + #[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: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + //// ANCHOR: two_state_predicate + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| + (1 <= i && i < prev.len()) + ==> prev.lookup(i) == self.lookup(i - 1)) // Every element will be shifted forwards by one + } + } + //// ANCHOR_END: two_state_predicate + + //// TEST: WITH ONE LESS LEVEL OF REFERENCE + predicate! { + fn pop_correct(&self, prev: &Self, return_value: Option) -> bool { + self.head_removed(prev) && + return_value === Some(prev.lookup(0)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + old(self.is_empty()) ==> self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.pop_correct(&old(snap(self)), snap(&result)))] + pub fn try_pop_2(&mut self) -> Option { + match std::mem::replace(&mut self.head, Link::Empty) { + Link::Empty => None, + Link::More(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + //// TEST: WITH ONE LESS LEVEL OF REFERENCE + + //// ANCHOR: try_pop_empty + #[ensures(old(self.is_empty()) ==> + result.is_none() && + old(self.is_empty()) ==> self.is_empty() + )] + //// ANCHOR_END: try_pop_empty + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && result === Some(old(snap(self)).lookup(0)) + )] + //// ANCHOR: try_pop_empty + pub fn try_pop(&mut self) -> Option { + // ... + //// ANCHOR_END: try_pop_empty + match std::mem::replace(&mut self.head, Link::Empty) { + Link::Empty => None, + Link::More(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + //// ANCHOR: predicate_use + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> i32 { + self.try_pop().unwrap() + //// ANCHOR: try_pop_empty + } + //// ANCHOR: two_state_predicate +} +//// ANCHOR_END: two_state_predicate +//// ANCHOR_END: predicate_use +//// ANCHOR_END: try_pop_empty + +impl Link { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + match self { + Link::More(node) => { + if index == 0 { + node.elem + } else { + node.next.lookup(index - 1) + } + } + Link::Empty => unreachable!(), + } + } + + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} diff --git a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs new file mode 100644 index 00000000000..910e20d36d2 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs @@ -0,0 +1,112 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: is_empty +//// ANCHOR: initial +//// ANCHOR: pop_precondition +impl List { + //// ANCHOR_END: initial + //// ANCHOR_END: is_empty + //// ANCHOR_END: pop_precondition + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + //// ANCHOR: is_empty + #[pure] + fn is_empty(&self) -> bool { + self.head.is_empty() + } + //// ANCHOR_END: is_empty + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { + head: Link::Empty, + } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.head.lookup(index) + } + + #[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: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + //// ANCHOR: initial + pub fn try_pop(&mut self) -> Option { + match std::mem::replace(&mut self.head, Link::Empty) { + Link::Empty => None, + Link::More(node) => { + self.head = node.next; + Some(node.elem) + }, + } + } + + //// ANCHOR: pop_precondition + #[requires(!self.is_empty())] + pub fn pop(&mut self) -> i32 { + self.try_pop().unwrap() + } + //// ANCHOR: is_empty +} +//// ANCHOR_END: is_empty +//// ANCHOR_END: initial +//// ANCHOR_END: pop_precondition + +impl Link { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + match self { + Link::More(node) => { + if index == 0 { + node.elem + } else { + node.next.lookup(index - 1) + } + }, + Link::Empty => unreachable!(), + } + } + + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/push.rs b/docs/user-guide/src/tour/tour-src/src/push.rs new file mode 100644 index 00000000000..ebc97b0e627 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/push.rs @@ -0,0 +1,5 @@ +mod initial_code; +mod property_1; +mod property_2_missing_bounds; +mod property_2_with_bounds; +mod final_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs b/docs/user-guide/src/tour/tour-src/src/push/final_code.rs new file mode 100644 index 00000000000..f61091d0123 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/push/final_code.rs @@ -0,0 +1,92 @@ +//// ANCHOR: all +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +//// ANCHOR: shifted_back +impl List { + //// ANCHOR_END: shifted_back + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.head.lookup(index) + } + + #[ensures(self.len() == old(self.len()) + 1)] // 1. Property + #[ensures(self.lookup(0) == elem)] // 2. Property + //// ANCHOR: shifted_back + #[ensures(forall(|i: usize| (1 <= i && i < self.len()) ==> + old(self.lookup(i - 1)) == self.lookup(i)))] // 3. Property + pub fn push(&mut self, elem: i32) { + // ... + //// ANCHOR_END: shifted_back + let new_node = Box::new(Node { + elem: elem, + next: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + //// ANCHOR: shifted_back + } +} +//// ANCHOR_END: shifted_back + +impl Link { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + match self { + Link::More(node) => { + if index == 0 { + node.elem + } else { + node.next.lookup(index - 1) + } + } + Link::Empty => unreachable!(), + } + } + + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + //// ANCHOR: bounds + } +} +//// ANCHOR_END: bounds +//// ANCHOR_END: all diff --git a/docs/user-guide/src/tour/tour-src/push_property_1.rs b/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs similarity index 67% rename from docs/user-guide/src/tour/tour-src/push_property_1.rs rename to docs/user-guide/src/tour/tour-src/src/push/initial_code.rs index 58deabcd130..b14e21bd801 100644 --- a/docs/user-guide/src/tour/tour-src/push_property_1.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs @@ -1,4 +1,3 @@ -// extern crate prusti_contracts; use prusti_contracts::*; pub struct List { @@ -15,20 +14,8 @@ struct Node { next: Link, } -#[extern_spec] -mod std { - mod mem { - //extern crate prusti_contracts; - use prusti_contracts::*; - - #[ensures(snap(dest) === src)] - #[ensures(result === old(snap(dest)))] - fn replace(dest: &mut T, src: T) -> T; - } -} - +//// ANCHOR: initial_code impl List { - #[ensures(self.len() == old(self.len()) + 1)] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { elem: elem, @@ -37,6 +24,7 @@ impl List { self.head = Link::More(new_node); } + //// ANCHOR_END: initial_code #[pure] pub fn len(&self) -> usize { @@ -45,11 +33,11 @@ impl List { #[ensures(result.len() == 0)] pub fn new() -> Self { - List { - head: Link::Empty, - } + List { head: Link::Empty } } + //// ANCHOR: initial_code } +//// ANCHOR_END: initial_code impl Link { #[pure] @@ -64,4 +52,4 @@ impl Link { fn is_empty(&self) -> bool { matches!(self, Link::Empty) } -} \ No newline at end of file +} diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_1.rs b/docs/user-guide/src/tour/tour-src/src/push/property_1.rs new file mode 100644 index 00000000000..13ac54b1cb4 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/push/property_1.rs @@ -0,0 +1,65 @@ +//// ANCHOR: extern_spec +use prusti_contracts::*; + +//// ANCHOR_END: extern_spec +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: extern_spec +//// ANCHOR: property_1 +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; +//// ANCHOR_END: extern_spec + +impl List { + #[ensures(self.len() == old(self.len()) + 1)] // 1. Property + pub fn push(&mut self, elem: i32) { + let new_node = Box::new(Node { + elem: elem, + next: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + //// ANCHOR_END: property_1 + + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + } + //// ANCHOR: property_1 +} +//// ANCHOR_END: property_1 + +impl Link { + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} diff --git a/docs/user-guide/src/tour/tour-src/push_property_2.rs b/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs similarity index 70% rename from docs/user-guide/src/tour/tour-src/push_property_2.rs rename to docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs index 71c4bf0b472..d56aeb109d3 100644 --- a/docs/user-guide/src/tour/tour-src/push_property_2.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs @@ -1,4 +1,3 @@ -// extern crate prusti_contracts; use prusti_contracts::*; pub struct List { @@ -15,28 +14,23 @@ struct Node { next: Link, } -#[extern_spec] -mod std { - mod mem { - //extern crate prusti_contracts; - use prusti_contracts::*; - - #[ensures(snap(dest) === src)] - #[ensures(result === old(snap(dest)))] - fn replace(dest: &mut T, src: T) -> T; - } -} +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; +//// ANCHOR: lookup impl List { #[pure] - #[requires(index < self.len())] pub fn lookup(&self, index: usize) -> i32 { self.head.lookup(index) } - #[ensures(self.lookup(0) == elem)] #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(self.lookup(0) == elem)] // 2. Property pub fn push(&mut self, elem: i32) { + // ... + //// ANCHOR_END: lookup let new_node = Box::new(Node { elem: elem, next: std::mem::replace(&mut self.head, Link::Empty), @@ -52,15 +46,13 @@ impl List { #[ensures(result.len() == 0)] pub fn new() -> Self { - List { - head: Link::Empty, - } + List { head: Link::Empty } + //// ANCHOR: lookup } } impl Link { #[pure] - #[requires(index < self.len())] pub fn lookup(&self, index: usize) -> i32 { match self { Link::More(node) => { @@ -69,11 +61,12 @@ impl Link { } else { node.next.lookup(index - 1) } - }, - Link::Empty => unreachable!(), + } + Link::Empty => unreachable!(), } } - + //// ANCHOR_END: lookup + #[pure] fn len(&self) -> usize { match self { @@ -86,4 +79,6 @@ impl Link { fn is_empty(&self) -> bool { matches!(self, Link::Empty) } -} \ No newline at end of file + //// ANCHOR: lookup +} +//// ANCHOR_END: lookup diff --git a/docs/user-guide/src/tour/tour-src/push_final.rs b/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs similarity index 66% rename from docs/user-guide/src/tour/tour-src/push_final.rs rename to docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs index 903da39fcf3..19d07e0fcaa 100644 --- a/docs/user-guide/src/tour/tour-src/push_final.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs @@ -1,4 +1,3 @@ -// extern crate prusti_contracts; use prusti_contracts::*; pub struct List { @@ -15,29 +14,23 @@ struct Node { next: Link, } -#[extern_spec] -mod std { - mod mem { - //extern crate prusti_contracts; - use prusti_contracts::*; - - #[ensures(snap(dest) === src)] - #[ensures(result === old(snap(dest)))] - fn replace(dest: &mut T, src: T) -> T; - } -} +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; +//// ANCHOR: bounds impl List { #[pure] #[requires(index < self.len())] pub fn lookup(&self, index: usize) -> i32 { + // ... + //// ANCHOR_END: bounds self.head.lookup(index) } - #[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)))] + #[ensures(self.len() == old(self.len()) + 1)] // 1. Property + #[ensures(self.lookup(0) == elem)] // 2. Property pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { elem: elem, @@ -54,9 +47,8 @@ impl List { #[ensures(result.len() == 0)] pub fn new() -> Self { - List { - head: Link::Empty, - } + List { head: Link::Empty } + //// ANCHOR: bounds } } @@ -64,6 +56,8 @@ impl Link { #[pure] #[requires(index < self.len())] pub fn lookup(&self, index: usize) -> i32 { + // ... + //// ANCHOR_END: bounds match self { Link::More(node) => { if index == 0 { @@ -71,11 +65,11 @@ impl Link { } else { node.next.lookup(index - 1) } - }, - Link::Empty => unreachable!(), + } + Link::Empty => unreachable!(), } } - + #[pure] fn len(&self) -> usize { match self { @@ -87,5 +81,7 @@ impl Link { #[pure] fn is_empty(&self) -> bool { matches!(self, Link::Empty) + //// ANCHOR: bounds } -} \ No newline at end of file +} +//// ANCHOR_END: bounds diff --git a/docs/user-guide/src/tour/tour-src/src/test.rs b/docs/user-guide/src/tour/tour-src/src/test.rs new file mode 100644 index 00000000000..b8983090848 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/test.rs @@ -0,0 +1,19 @@ +// TODO: DELETE THIS FILE + +fn test() { + //// ANCHOR: code_1 + // Comment 1 + // Comment 2 + let code_1 = 6; + //// ANCHOR_END: code_1 + + // Comment 3 + // Comment 4 + let code_2 = 8 + //// ANCHOR: code_1 + + // Comment 5 + // Comment 6 + let code_3 = 10 + //// ANCHOR_END: code_1 +} \ No newline at end of file From a714dd76de17711b91d596398787698a7b972f77 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Thu, 2 Feb 2023 11:42:11 +0100 Subject: [PATCH 04/31] Continue work on user guide rework --- docs/user-guide/src/SUMMARY.md | 2 +- docs/user-guide/src/basic.md | 6 + docs/user-guide/src/syntax.md | 83 +++- docs/user-guide/src/tour/bad-stack.md | 9 - docs/user-guide/src/tour/code.md | 30 +- docs/user-guide/src/tour/generics.md | 6 +- docs/user-guide/src/tour/getting-started.md | 12 +- docs/user-guide/src/tour/new.md | 182 ++------ docs/user-guide/src/tour/options.md | 22 +- docs/user-guide/src/tour/peek.md | 9 + docs/user-guide/src/tour/pop.md | 148 ++++++- docs/user-guide/src/tour/push.md | 410 ++++-------------- docs/user-guide/src/tour/setup.md | 2 +- docs/user-guide/src/tour/summary.md | 2 +- docs/user-guide/src/tour/testing.md | 25 +- docs/user-guide/src/tour/tour-src/Prusti.toml | 2 +- .../src/tour/tour-src/src/generic.rs | 1 + .../tour/tour-src/src/generic/initial_code.rs | 1 + docs/user-guide/src/tour/tour-src/src/lib.rs | 5 +- .../src/tour/tour-src/src/option.rs | 1 + .../tour/tour-src/src/option/initial_code.rs | 239 ++++++++++ .../src/tour/tour-src/src/pop/final_code.rs | 62 +-- .../src/tour/tour-src/src/pop/initial_code.rs | 2 +- .../src/tour/tour-src/src/push/final_code.rs | 2 +- .../tour/tour-src/src/push/initial_code.rs | 2 +- .../src/tour/tour-src/src/push/property_1.rs | 2 +- .../src/push/property_2_missing_bounds.rs | 2 +- .../src/push/property_2_with_bounds.rs | 2 +- .../src/tour/tour-src/src/testing.rs | 1 + .../tour/tour-src/src/testing/initial_code.rs | 208 +++++++++ .../src/verify/assert_refute_assume.md | 43 +- 31 files changed, 937 insertions(+), 586 deletions(-) delete mode 100644 docs/user-guide/src/tour/bad-stack.md create mode 100644 docs/user-guide/src/tour/tour-src/src/generic.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/option.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/option/initial_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/testing.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index 24ae40bc03c..d4c9e44be94 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -1,5 +1,6 @@ # Summary +- [TODO: Remove this after writing The guided tour](tour/code.md) - [Introduction](intro.md) - [Installation](install.md) - [Basic Usage](basic.md) @@ -10,7 +11,6 @@ - [Push](tour/push.md) - [Pop](tour/pop.md) - [Testing](tour/testing.md) - - [A Bad Stack](tour/bad-stack.md) - [Options](tour/options.md) - [Generics](tour/generics.md) - [Peek](tour/peek.md) diff --git a/docs/user-guide/src/basic.md b/docs/user-guide/src/basic.md index e2bfda0518d..934f110e12f 100644 --- a/docs/user-guide/src/basic.md +++ b/docs/user-guide/src/basic.md @@ -19,6 +19,12 @@ To run Prusti on a file using the command-line setup: prusti-rustc --edition=2018 path/to/file.rs ``` +To run Prusti on a Rust crate: + +```sh +cargo-prusti +``` + ## Introductory example Let us verify that the function `max` below, which takes two integers and returns the greater one, is implemented correctly. diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index 26c32303feb..92eb5ae74d6 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -14,11 +14,39 @@ Prusti specifications are a superset of Rust boolean expressions. They must be d | [`exists(...)`](#quantifiers) | Existential quantifier | | [... |= ...](#specification-entailments) | Specification entailment | -## Old expressions + +## `result` Variable + +When using Prusti, `result` is used to refer to what a function returns. +`result` can only be used inside a postcondition, meaning that variables called `result` used in a function need to be renamed. + +Here is an example for returning an integer: +``` +use prusti_contracts::*; + +#[ensures(result == 5)] +fn five() -> i32 { + 5 +} +``` + +And an example for returning a tuple and accessing individual fields: +``` +use prusti_contracts::*; + +#[ensures(result.0 / 2 == result.1 && result.2 == 'a')] +fn tuple() -> (i32, i32, char) { + (10, 5, 'a') +} +``` + + +## Old Expressions Old expressions are used to refer to the value that a memory location pointed at by a mutable reference had at the beginning of the function: ```rust,noplaypen +# extern crate prusti_contracts; use prusti_contracts::*; #[ensures(*x == old(*x) + 1)] @@ -27,27 +55,34 @@ pub fn inc(x: &mut u32) { } ``` + ## Implications -Implications express a [relationship](https://en.wikipedia.org/wiki/Material_conditional) between two boolean expressions: +Implications express a [relationship](https://en.wikipedia.org/wiki/Material_conditional) between two Boolean expressions: -```rust,noplaypen +```rust,noplaypen,ignore +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[pure] #[ensures(result ==> self.len() == 0)] #[ensures(!result ==> self.len() > 0)] pub fn is_empty(&self) -> bool; ``` -There is syntax for a right-to-left implication: +`a ==> b` is equivalent to `!a || b` and `!(a && !b)`. This also extends to the short-circuiting behaviour: if `a` is not true, `b` is not evaluated. -```rust,noplaypen +```rust,noplaypen,ignore +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[pure] #[ensures(self.len() == 0 <== result)] #[ensures(self.len() > 0 <== !result)] pub fn is_empty(&self) -> bool; ``` -As well as a biconditional ("if and only if"): +There is also syntax for biconditionals ("if and only if"): ```rust,noplaypen #[pure] @@ -70,7 +105,10 @@ equalities do not necessarily coincide. For example, some types do not implement Nonetheless, snapshot equality could be used to compare values of such types, as in the following code: -```rust,noplaypen +```rust,noplaypen,ignore +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[requires(a === b)] fn foo(a: T, b: T) {} @@ -81,13 +119,39 @@ fn main() { } ``` -Snapshot *in*equality is expressed using the `!==` operator. +There is also the counterpart for `!=` for checking structural inequality: `!==`. + +# TODO + +## `snap` Function +The function `snap` can be used to take a snapshot of a reference in specifications. +Its functionality is similar to the `clone` function, but `snap` is only intended for use in specifications. It also does not require the type behind the reference to implement the `Clone` trait. + +The `snap` function enables writing specifications that would otherwise break Rusts ownership rules: +```rust,noplaypen +# use prusti_contracts::*; +# +struct NonCopyInt { + value: i32 +} + +#[ensures(x === old(x))] // Error: Cannot borrow "*x" mutably +fn do_nothing_1(x: &mut NonCopyInt) {} + +#[ensures(snap(x) === old(snap(x)))] +fn do_nothing_2(x: &mut NonCopyInt) {} +``` + +TODO: avoid snap ## Quantifiers Quantifiers are typically used for describing how a method call changes a container such as a vector: ```rust,noplaypen +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[requires(0 <= index && index < self.len())] #[ensures(self.len() == old(self.len()))] #[ensures(self.lookup(index) == value)] @@ -98,7 +162,7 @@ Quantifiers are typically used for describing how a method call changes a contai ) )] pub fn store(&mut self, index: usize, value: i32) { - ... + // ... } ``` @@ -120,6 +184,7 @@ and the syntax of existential ones: exists(|: , ...| ) ``` + ## Specification entailments Specification entailments provide the contract for a given closure or function variable. See the [specification entailments](verify/spec_ent.md) chapter for more details. diff --git a/docs/user-guide/src/tour/bad-stack.md b/docs/user-guide/src/tour/bad-stack.md deleted file mode 100644 index 710d4ef9d7f..00000000000 --- a/docs/user-guide/src/tour/bad-stack.md +++ /dev/null @@ -1,9 +0,0 @@ -# A Bad Singly-Linked Stack - Full Code - -**TODO** - -```rust,noplaypen -{{#include tour-src/13-too-many-lists-final.rs:1:}} -``` - - diff --git a/docs/user-guide/src/tour/code.md b/docs/user-guide/src/tour/code.md index c9607cfde74..74d5f5c7ae9 100644 --- a/docs/user-guide/src/tour/code.md +++ b/docs/user-guide/src/tour/code.md @@ -70,54 +70,54 @@ $ prusti-rustc --edition=2018 path/to/file.rs ``` ```rust,noplaypen -{{#include tour-src/01-chapter-2-1.rs:1:}} +{{#include tour-src/src/01-chapter-2-1.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/02-chapter-2-1.rs:1:}} +{{#include tour-src/src/02-chapter-2-1.rs:1:}} ``` -```rust,noplaypen -{{#include tour-src/03-chapter-2-1.rs:1:}} -``` + ```rust,noplaypen -{{#include tour-src/04-chapter-2-2.rs:1:}} +{{#include tour-src/src/04-chapter-2-2.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/05-chapter-2-3.rs:1:}} +{{#include tour-src/src/05-chapter-2-3.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/06-chapter-2-4.rs:1:}} +{{#include tour-src/src/06-chapter-2-4.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/07-chapter-2-4.rs:1:}} +{{#include tour-src/src/07-chapter-2-4.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/08-chapter-2-4.rs:1:}} +{{#include tour-src/src/08-chapter-2-4.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/09-chapter-2-4.rs:1:}} +{{#include tour-src/src/09-chapter-2-4.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/10-chapter-2-5.rs:1:}} +{{#include tour-src/src/10-chapter-2-5.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/11-chapter-2-5.rs:1:}} +{{#include tour-src/src/11-chapter-2-5.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/12-chapter-2-6.rs:1:}} +{{#include tour-src/src/12-chapter-2-6.rs:1:}} ``` ```rust,noplaypen -{{#include tour-src/13-too-many-lists-final.rs:1:}} +{{#include tour-src/src/13-too-many-lists-final.rs:1:}} ``` diff --git a/docs/user-guide/src/tour/generics.md b/docs/user-guide/src/tour/generics.md index efe17801134..e84fb468336 100644 --- a/docs/user-guide/src/tour/generics.md +++ b/docs/user-guide/src/tour/generics.md @@ -1,3 +1,7 @@ -# Generics +# Options + +> **Recommended reading:** +> [3.2: Generic](https://rust-unofficial.github.io/too-many-lists/second-generic.html) + **TODO** diff --git a/docs/user-guide/src/tour/getting-started.md b/docs/user-guide/src/tour/getting-started.md index cebe27249d8..ed82b8eb885 100644 --- a/docs/user-guide/src/tour/getting-started.md +++ b/docs/user-guide/src/tour/getting-started.md @@ -26,7 +26,7 @@ the head of the list, an enum `Link` representing either an empty list or a heap Node storing the payload—an integer—and the link to the next node: ```rust,noplaypen -{{#include tour-src/03-chapter-2-1.rs:1:13}} +{{#include tour-src/src/getting_started/working.rs:1:13}} // Prusti: VERIFIES ``` @@ -43,13 +43,13 @@ an explicit runtime error, such as [`unimplemented`](https://doc.rust-lang.org/std/macro.unimplemented.html), [`todo`](https://doc.rust-lang.org/std/macro.todo.html), or possibly a failing [assertion](https://doc.rust-lang.org/std/macro.assert.html), -is reachable. +is reachable. [Prusti assertions](../syntax.md#prusti-assertions) are also checked. These are like the normal `assert` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks when compiled normally. For example, the following test function creates a node with no successor and panics if the node's payload is greater than 23: ```rust,noplaypen -{{#rustdoc_include tour-src/03-chapter-2-1.rs:15:24}} +{{#rustdoc_include tour-src/src/getting_started/working.rs:15:24}} // Prusti: VERIFIES ``` Prusti successfully verifies the above function @@ -59,7 +59,7 @@ whenever execution reaches the `if` statement. This is not the case for the following function in which the test node is initialized with an arbitrary integer: ```rust,noplaypen -{{#rustdoc_include tour-src/03-fail.rs:26:35}} +{{#rustdoc_include tour-src/src/getting_started/failing.rs:26:35}} // Prusti: FAILS ``` @@ -67,9 +67,9 @@ Prusti reports errors in the same fashion as the Rust compiler (although with th `Prusti: verification error`). For example, the error produced for the above function is: -```markdown +```plain error: [Prusti: verification error] panic!(..) statement might be reachable - --> 03-fail.rs:33:9 + --> getting_started_failing.rs:33:9 | 33 | panic!() | ^^^^^^^^ diff --git a/docs/user-guide/src/tour/new.md b/docs/user-guide/src/tour/new.md index 70c10b82c69..61813fb6ae2 100644 --- a/docs/user-guide/src/tour/new.md +++ b/docs/user-guide/src/tour/new.md @@ -13,7 +13,7 @@ We first provide a static function to create empty lists: ```rust,noplaypen -{{#rustdoc_include tour-src/04-chapter-2-2.rs:15:22}} +{{#rustdoc_include tour-src/src/new/impl_new.rs:impl_new}} // Prusti: VERIFIES ``` @@ -28,7 +28,7 @@ For simplicity, we will not actually compute the length of a `Link` yet. Rather, we will just always return 0. ```rust,noplaypen -{{#rustdoc_include tour-src/04-chapter-2-2.rs:15:28}} +{{#rustdoc_include tour-src/src/new/spec_failing_1.rs:first_spec_1}} // Prusti: VERIFIES ``` @@ -36,47 +36,16 @@ Now that we have implemented a method for computing the length of a list, we can write our first specification for `new()`: the returned list should always have length zero. That is, we attach the [postcondition](../verify/prepost.md) -`result.len() == 0` to the function `new()`: +`result.len() == 0` to the function `new()`. The special variable [`result`](../syntax.md#result-variable) is used in Prusti specifications to refer to the value that is returned by a function: ```rust,noplaypen -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# struct Node { -# elem: i32, -# next: Link, -# } -# -# impl List { -# pub fn len(&self) -> usize { -# self.head.len() -# } -# - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { - head: Link::Empty - } - } -# } -# -# impl Link { -# fn len(&self) -> usize { -# 0 -# } -# } +{{#rustdoc_include tour-src/src/new/spec_failing_1.rs:first_spec_2}} ``` Unfortunately, Prusti—or rather: the Rust compiler—will complain about the postcondition: -``` +```plain error: cannot find attribute `ensures` in this scope --> list.rs:39:7 | @@ -91,40 +60,7 @@ Before we can use these specifications, we need to make the path to these macros and attributes visible: ```rust,noplaypen -use prusti_contracts::*; - -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# struct Node { -# elem: i32, -# next: Link, -# } -# -impl List { -# pub fn len(&self) -> usize { -# self.head.len() -# } -# - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { - head: Link::Empty - } - } -} -# -# impl Link { -# fn len(&self) -> usize { -# 0 -# } -# } +{{#rustdoc_include tour-src/src/new/spec_failing_2.rs:import_prusti}} ``` Declaring that we use the `prusti_contracts` crate removes the compiler error but @@ -150,41 +86,7 @@ After adding the `#[pure]` attribute to our `List::len()` method, it is allowed appear in Prusti specifications: ```rust,noplaypen -# use prusti_contracts::*; -# -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# 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 { -# fn len(&self) -> usize { -# 0 -# } -# } +{{#rustdoc_include tour-src/src/new/spec_failing_3.rs:pure_annotation}} ``` However, Prusti still won't verify! It produces the same error but now it refers @@ -207,50 +109,12 @@ namely `Link::len()`, within the body of the pure function `List::len()`. To fix this issue, it suffices to mark `Link::len()` as pure as well. ```rust,noplaypen -# use prusti_contracts::*; -# -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# 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] - fn len(&self) -> usize { - 0 - } -} -# -# fn main() {} // in case Prusti is used via command line -# +{{#rustdoc_include tour-src/src/new/spec_fixed.rs:pure_annotation}} // Prusti: VERIFIES ``` ```markdown -$ prusti-rustc list.rs +$ cargo-prusti // ... Successful verification of 4 items ``` @@ -259,12 +123,32 @@ Prusti now manages to verify that `new()` always returns a list for which the method `len()` returns 0. (notice this is hardly surprising since `len()` ultimately always returns 0.) -We will now properly implement `len()`, and while we're at it, `is_empty()` for `Link`. Both of them are pure functions, so we will add the `#[pure]` annotation. Both functions don't have any postconditions that are required to call them, so we don't have to write a `requires` annotation. We also don't need to add any additional postconditions, since pure functions will be inlined wherever they are used during verification. +## Proper implementation of `len` + +We will now properly implement `len()`, and while we're at it, `is_empty()` for `Link`. Both of them are pure functions, so we will add the `#[pure]` annotation. Both functions can be called without any restrictions, so they have the default postcondition `#[requires(true)]`, which we don't have to add manually. We also don't need to add any additional postconditions, since pure functions will be inlined wherever they are used during verification. + +```rust,noplaypen +{{#rustdoc_include tour-src/src/new/full_code.rs:31:44}} +``` +We can now check if the specification is working, by writing a function that panics if the specification is wrong: ```rust,noplaypen -{{#rustdoc_include tour-src/04-chapter-2-2-full-code.rs:32:45}} +{{#rustdoc_include tour-src/src/new/full_code.rs:46:50}} ``` +The last line asserts, that the `is_empty` function only returns `true`, if the `len` function returns `0`. +And Prusti can verify it! Now we know that this assert statement can never fail, no matter what `Link` is passed to the test function. + +### Overflow checks + +Here you can also see why we disabled overflow checking for this tutorial. If you remove the `check_overflows = false` setting in the `Prusti.toml` file, and then try to verify the crate again, you will get an error: +```plain +[Prusti: verification error] assertion might fail with "attempt to add with overflow" + Link::More(node) => 1 + node.next.len(), + ^^^^^^^^^^^^^^^^^^^ +``` +This overflow could happen, if you call `len` on a list with more than `usize::MAX` elements. To prevent this verification error, we would have to constrain the maximum size of a `List`, which is beyond this tutorial. + ## Full code listing Before we continue, we provide the full code implented in this chapter. @@ -272,7 +156,5 @@ It should successfully verify with Prusti and we will further extend it througho the next four chapters. ```rust,noplaypen -{{#rustdoc_include tour-src/04-chapter-2-2-full-code.rs}} - -// Prusti: VERIFIES +{{#rustdoc_include tour-src/src/new/full_code.rs}} ``` diff --git a/docs/user-guide/src/tour/options.md b/docs/user-guide/src/tour/options.md index 36228989d79..69a3b096c57 100644 --- a/docs/user-guide/src/tour/options.md +++ b/docs/user-guide/src/tour/options.md @@ -1,3 +1,23 @@ # Options -**TODO** +> **Recommended reading:** +> [3: An Ok Single-Linked Stack](https://rust-unofficial.github.io/too-many-lists/second.html), +> [3.1. Option](https://rust-unofficial.github.io/too-many-lists/second-option.html) + +Just like in the "Learning Rust With Entirely Too Many Linked Lists" tutorial, we can change our `enum Link` to use the `Option` type via a type alias instead of manually implementing `Empty` and `More`: + +```rust,noplaypen,ignore +type Link = Option>; +``` + +In order to use the `Option::take` and the `Option::map` function, we also have to implement the `extern_spec` for them: + +```rust,noplaypen + +``` + +These changes require some adjustments of the code and specifications: + +```rust,noplaypen + +``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/peek.md b/docs/user-guide/src/tour/peek.md index 5816a8bfb00..3f644223675 100644 --- a/docs/user-guide/src/tour/peek.md +++ b/docs/user-guide/src/tour/peek.md @@ -1,3 +1,12 @@ # Peek +> **Recommended reading:** +> [3.3: Peek](https://rust-unofficial.github.io/too-many-lists/second-peek.html) + +Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. This is currently not possible in Prusti, since structures containing references are not supported at the moment. +The return type of `try_peek` is `Option<&T>`, which is not supported. + +We can still implement `peek` though, we just cannot do it by using `try_peek` like before: + + **TODO** diff --git a/docs/user-guide/src/tour/pop.md b/docs/user-guide/src/tour/pop.md index 3ef09764d5f..e3831302bf1 100644 --- a/docs/user-guide/src/tour/pop.md +++ b/docs/user-guide/src/tour/pop.md @@ -3,12 +3,156 @@ > **Recommended reading:** > [2.5: Pop](https://rust-unofficial.github.io/too-many-lists/first-pop.html), +Let's continue with a function to remove and return one element from the top of a list. The way to write such a function is described in [2.5: Pop](https://rust-unofficial.github.io/too-many-lists/first-pop.html), we will focus on the verification in this chapter. + +We will rename the `pop` function to `try_pop`. The return type is still `Option` and `try_pop` will only return `Some(item)` if the list has elements, and `None` otherwise. We will then add a new `pop` function, which has the return type `i32`, and will panic if it is called with an empty list. However, by using the correct precondition, we can prevent the `pop` function to ever panic! Here is the code: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/pop/initial_code.rs:initial}} +``` + +For the implementation of our `pop` method, we can reuse the implementation of `try_pop`. We call `try_pop` on the list, then call `unwrap` on the result. `unwrap` will return the inner value of the `Option` if it is `Some`, and will panic otherwise. +Normally, it is bad form to use `unwrap` in production code, where you should handle potential errors, instead of just panicing. +But, since we are verifying that there will never be `None` passed to `unwrap`, we should be able to get away with it here. + +## Properties of `try_pop` + +Lets start by (informally) listing the properties we want our `try_pop` method to have. +We do not need a precondition for `try_pop`, since it can be called on any list. +This just leaves all the postconditions, which can be put into two categories: + +- The input list is empty before the call + 1. The `result` will be `None` + 2. The list will still be empty +- The input list is not empty before the call + 1. The `result` will not be `None` + 2. The `result` will contain the first value of the old list + 3. The length will get reduced by one + 4. All elements will be shifted forwards by one + +## Properties of `pop` + +For `pop`, we will add a precondition that the list is not empty. +The postconditions are similar to those of `try_pop`, but we can skip all those that only apply to empty lists: + +1. The `result` will be the first value of the old list +2. The length will get reduced by one +3. All elements will be shifted forwards by one + +## Implementing the specification + +### Adding `List::is_empty` + +Since we will need to check if a list is empty, we can implement a `#[pure]` `is_empty` function for List. It can just call the `is_empty` function on `self.head`: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/pop/initial_code.rs:is_empty}} +``` + +### Precondition of `pop` + +Let's start our specification with the precondition of `pop`. Since the `unwrap` will panic if it is passed `None`, and `try_pop` returns `None` if the list is empty, we have to ensure that `pop` is only called on non-empty lists. Therefore we add it as a precondition: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/pop/initial_code.rs:pop_precondition}} +``` + +`try_pop` does not require a precondition, since it will not panic on an empty list, but instead just return `None`. + +### `try_pop` postcondition for empty lists + +Now we will implement the two conditions that hold for `try_pop` if you pass an empty list to it. +To ensures that these are only checked for empty lists, we use the implication operator `==>` again: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/pop/final_code.rs:try_pop_empty}} +``` + +This specification ensures that for empty lists, the list will still be empty afterwards, and `try_pop` will return `None`. + +### Checking if the correct result is returned + +Now we can add the specification for checking if the correct result is returned. Like with `push`, we will use the `lookup` function for checking that the `result` is the old head of the list. For this we call `lookup(0)` on a snapshot of `self` before the function call: `old(snap(self)).lookup(0)`. + +We can check this condition for snapshot equality with `result`. This will always hold for `pop`, since the list is never empty: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/pop/final_code.rs:pop_result_correct}} +``` + +For `try_pop`, condition only holds if the list was not empty before the call. In addition, the `result` is an `Option::Some`, so we will have to include this in our postcondition: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/pop/final_code.rs:try_pop_result_correct}} +``` + + +### Using `predicate!` to reduce code duplication + +You may have noticed that the last two conditions for `pop` are the same as the last two of `try_pop`. We could just write the same conditions twice, but we can also place them in a Prusti [`predicate`](../verify/predicate.md) and then use that `predicate` in both specifications. + +A `predicate` is basically just a [`pure`](../verify/pure.md) function that returns a `bool`, but it can use all the additional syntax available in Prusti specifications. Lets look at an example: + +```rust,noplaypen +predicate! { + fn larger_than_5(x: i32) -> bool { + x > 5 + } +} + +#[requires(larger_than_5(input))] +#[ensures(larger_than_5(result))] +fn add_one(input: i32) -> i32 { + input + 1 +} +``` + +In our specific case, we want to have a predicate to compare the state of the list before the call to the state afterwards. The `old` function cannot be used inside a predicate, we have to pass the two states as separate arguments. For this we write a `predicate` takes 2 arguments, which represent the state after and before the function. Such a predicate is also called a `two-state predicate`. +Note that we take both arguments by (immutable) reference, since we don't need the predicate to take ownership over the arguments: + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +impl List { + predicate! { + fn head_removed(&self, prev: &Self) -> bool { + // ... + } + } +} +``` + +The 2 parameters are called `self` and `prev`, both with the type `&Self`. + +The goal of this predicate is to check if the head of a list was correctly removed. +For this we need check two properties: +1. The new length is the old length minus one +2. Each element is shifted forwards by one + +We combine these two properties into a single expression using `&&`: + ```rust,noplaypen -{{#include tour-src/10-chapter-2-5.rs:1:}} +{{#rustdoc_include tour-src/src/pop/final_code.rs:two_state_predicate}} ``` +Here we are able to call `.len()` and `.lookup()` on both references, because they are pure functions. + +To use this predicate, we call it on the list `self`, and then pass in a snapshot of the `self` from before the function call. Like with the condition for correctness of the `result`, we can just add this `predicate` to `pop`, but we need to restrict it to non-empty lists for `try_pop`: + ```rust,noplaypen -{{#include tour-src/11-chapter-2-5.rs:1:}} +{{#rustdoc_include tour-src/src/pop/final_code.rs:predicate_use}} ``` +## Summary + +We have now implemented the entire specification we formulated at the start of this chapter! +The list should now be able to correctly `push`, `pop`, `try_pop` elements, as described in our specifications. +But how do we know that our specifications match the behavior of the code? We will look at this in the next chapter. + +If you want the full code we have now, expand the code block below: + +```rust,noplaypen +// Expand to see full code up to this chapter +{{#rustdoc_include tour-src/src/pop/final_code.rs:none}} +``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/push.md b/docs/user-guide/src/tour/push.md index a98b8442f63..f96d305a043 100644 --- a/docs/user-guide/src/tour/push.md +++ b/docs/user-guide/src/tour/push.md @@ -18,226 +18,37 @@ impl List { } ``` +Since `push` modifies `self`, it cannot be marked as a `#[pure]` function. This means we will not be able to use `push` inside specifications for other functions later. + Before we implement `push`, let us briefly think of possible specifications. Ideally, our implementation satisfies at least the following properties: 1. Executing `push` increases the length of the underlying list by one. 2. After `push(elem)` the first element of the list stores the value `elem`. -3. After executing `push(elem)`, the elements of the original list remain unchanged. - -## First property - -The first property can easily be expressed as a postcondition that uses the -pure method `len` introduced in the [previous chapter](new.md): - -```rust,noplaypen -# #![feature(box_patterns)] -# //extern crate prusti_contracts; -# use prusti_contracts::*; -# -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# 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, -# } -# -# } -# - #[ensures(self.len() == old(self.len()) + 1)] - pub fn push(&mut self, elem: i32) { - // TODO - } -} -# -# impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -# } -``` - -The above postcondition depends on *two* states, namely the state before and after -execution of `self.push(elem)`, respectively. -We use an [old expression](../syntax.md#old-expressions) -to refer to the state before execution of `self.push(elem)`. -Since we have not implemented `push` yet, Prusti will, unsurprisingly, complain: - -```markdown -[Prusti: verification error] postcondition might not hold. -``` +3. After executing `push(elem)`, the elements of the original list remain unchanged (just moved back by 1). -## Implementing Push +## Initial code -Conceptually, implementing `list.push(i)` should be straightforward: -we create a new instance of our struct for list nodes that stores -`i` in its `elem` field and the original list in its `next` field. -We may thus be tempted to write the following: +We start out with an implementation of `push`. If you want to learn more about how this code works, you can read [2.4: Push](https://rust-unofficial.github.io/too-many-lists/first-push.html), where it is explained in detail. +Here is our initial code: ```rust,noplaypen,ignore -# #![feature(box_patterns)] -# extern crate prusti_contracts; -# use prusti_contracts::*; -# -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# 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, -# } -# } -# - #[ensures(self.len() == old(self.len()) + 1)] - pub fn push(&mut self, elem: i32) { - let new_node = Box::new(Node { - elem: elem, - next: self.head, - }); - - self.head = Link::More(new_node); - } -} -# -# impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -# } +{{#rustdoc_include tour-src/src/push/initial_code.rs:initial_code}} ``` -Unfortunately, the Rust compiler will complain about this attempt: - -```markdown -[E0507] cannot move out of `self.head` which is behind a mutable reference. -``` - -`self` is a mutable borrow, meaning once it expires ownership to the referenced data - returns to the original owner. -However, we moved parts of those, the original list, into our newly created node. -This would leave the borrow in a partially initialized state when it expires and would -not allow us to return ownership. -Hence, the compiler raises an error. -See [2.3: Ownership 101](https://rust-unofficial.github.io/too-many-lists/first-ownership.html) -and [2.4: Push](https://rust-unofficial.github.io/too-many-lists/first-push.html) -in the Rust tutorial for details. - -A working alternative exploits that `self` is a *mutable* borrow which allows -us to completely overwrite the referenced data. -Instead of moving the original list into the new node, we *swap* it -with an empty list that can be returned to the owner once the borrow expires. -The [Rust standard library](https://doc.rust-lang.org/std/mem/fn.replace.html) - provides the function `std::mem::replace` for that purpose - it moves its second -argument into the referenced first argument and returns the originally referenced value. -Using this function, the Rust compiler accepts the following implementation: +## First property +The first property can easily be expressed as a postcondition that uses the +pure method `len` introduced in the [previous chapter](new.md): ```rust,noplaypen -# #![feature(box_patterns)] -# //extern crate prusti_contracts; -# use prusti_contracts::*; -# -use std::mem; - -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# 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, -# } -# } -# - #[ensures(self.len() == old(self.len()) + 1)] - pub fn push(&mut self, elem: i32) { - let new_node = Box::new(Node { - elem: elem, - next: mem::replace(&mut self.head, Link::Empty), - }); - - self.head = Link::More(new_node); - } -} -# -# impl Link { -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(box node) => 1 + node.next.len(), -# } -# } -# } +{{#rustdoc_include tour-src/src/push/property_1.rs:property_1}} ``` -In fact, the above implementation of `push` is correct. -However, attempting to verify it with Prusti still yields a verification error: +Even though the above implementation of `push` is correct, attempting to verify it with Prusti still yields a verification error: -```markdown +```plain [Prusti: verification error] postcondition might not hold. ``` @@ -265,79 +76,83 @@ We can remedy this issue by strengthening the specification of `std::mem::replac In this tutorial, we will assume that the standard library is correct, that is, we do not attempt to verify specifications for functions in external crates, like `std::mem::replace`. To this end, we have to add the specification to the function. -This can be done with another piece of Prusti syntax: +This can be done with another piece of Prusti syntax, the [extern_spec](../verify/external.md): ```rust,noplaypen,ignore -#[extern_spec] -mod std { - mod mem { - # //extern crate prusti_contracts; - use prusti_contracts::*; - - #[ensures(snap(dest) === src)] - #[ensures(result === old(snap(dest)))] - fn replace(dest: &mut T, src: T) -> T; - } -} -``` - -New syntax: -```rust,noplaypen,ignore -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; +{{#rustdoc_include tour-src/src/push/property_1.rs:extern_spec}} ``` - Lets break this code down step by step. -- First we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. -- Next we need to figure out where the function is located. In this case it is `std::mem`, which we then write down with `mod std { mod mem { ... } }` +- First we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. This requires `prusti_contracts::*` to be imported first. +- Next we need to figure out where the function is located. In this case it is `std::mem`, which we then put as the parameter in `#[extern_spec(std::mem)]` - After a quick search for *\"rust std mem replace\"* we can find the [documentation for std::mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html). Here we can get the function signature: `pub fn replace(dest: &mut T, src: T) -> T`. We then write down the signature in the inner module, followed by a `;`. -- To get the Prusti syntax in scope, we add `use prusti_contracts::*` to the inner module. -- Since there is no preconditions to `replace`, we don't have to write one down. The default precondition is `#[requires(true)]`. -- For writing the postcondition, we use four pieces of syntax added by Prusti: +- Since there are no preconditions to `replace`, we can use the (implicit) default `#[requires(true)]`. +- For writing the postcondition, we use four pieces of Prusti syntax: - `===` is called **snapshot equality** or **logical equality**. Is essentially checks if the left and right sides are structurally equal. More details can be seen [here](../syntax.md#snapshot-equality). `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. - - The `snap()` function takes a snapshot of a reference. This function should only be used in specifications, since it ignores the borrow checker. - - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state from before the function was called. - - The identifier `result` is used to refer to the return parameter of the function. -- The postcondition consists of two parts, which can either be written in one condition with `&&`, or in multiple annotations like in the example above. + - The [`snap()`](../syntax.md#snap-function) function takes a snapshot of a reference. It has a similar functionality to the `clone()` method, but does not require the type of the reference it is called on to implement the `Clone` trait. `snap` should only be used in specifications, since it ignores the borrow checker. + - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state of `snap(dest)` from before the function was called. + - The identifier [`result`](../syntax.md#result-variable) is used to refer to the return parameter of the function. +- The postcondition consists of two parts, which can either be written in one condition with `&&`, or in multiple `#[ensures(...)]` annotations like in the example above. - The first condition `snap(dest) === src` means: *After the function returns, the location referenced by `dest` is structurally equal to the parameter `src`* - The second part of the postcondition is `result === old(snap(dest))`. This means: *The `result` returned by the function is structurally equal to the the element that was referenced by `dest` **before** the function was called.* -- An important thing to note here is the Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. -Depending on when you are reading this, the Rust standard library might be (partially) annotated, so this external specification may not be needed anymore. +Since `result` is structurally equal to `dest` from before the function call, Prusti knows that the pure function `len()` called on `result` returns the same value as it would have for `dest`. + -Trusted functions can be used for verifying projects containing external code without Prusti annotations, or projects using Rust features not yet supported by Prusti. -An example is printing a string: +An important thing to note here is the Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. + +### Future + +There is currently new functionality planed for Prusti-assistant, which should enable the user to automatically generate parts of the `extern_spec` syntax. + +There is also work being done for providing external specifications for the Rust standard library. Depending on when you are reading this, the `std::mem::replace` function might be annotated already, in that case this `extern_spec` may not be needed anymore. +You can track the progress and find some already completed specifications [in this Pull Request](https://github.com/viperproject/prusti-dev/pull/1249), + +## Trusted Functions + +As mentioned above, `extern_specs` are implicitly `#[trusted]` by Prusti. +Trusted functions can be used for verifying projects containing external code that does not have Prusti annotations, or projects using Rust features that are not yet supported by Prusti. +An example is printing a string slice (not supported yet): ```rust,noplaypen #[trusted] fn print(s: &str) { println!("{s}"); } ``` -Prusti will ***not*** check trusted functions for correctness. **A single incorrect specification of a trusted function can invalidate the correctness of Prusti as a whole!** +Prusti will ***not*** check trusted functions for correctness, so it is the programmers responsibility to check their the specification manually. **A single incorrect specification of a trusted function can invalidate the correctness of Prusti as a whole!** Hence, trusted functions, like unsafe Rust code, need to be treated carefully and require external justification. For example, the following function will not cause the verification to fail: ```rust,noplaypen,norun +# use prusti_contracts::*; +# #[trusted] fn incorrect_function() -> i32 { panic!() } ``` -After adding the external specification for `std::mem::replace`, we can finally verify the `push` function: +This one is even worse, this will enable anything to be verified: +```rust,noplaypen,norun +# use prusti_contracts::*; +# +#[trusted] +#[ensures(false)] +fn wrong() {} +``` + +### Checking the `extern_spec` + +Let's get back to our code. After adding the external specification for `std::mem::replace`, we can finally verify the `push` function: ```rust,noplaypen -{{#rustdoc_include tour-src/push_property_1.rs:18:39}} +{{#rustdoc_include tour-src/src/push/property_1.rs:property_1}} // Prusti: Verifies ``` -This completes our implementation of `push` but we still need to verify -the remaining properties of its specification. +With this, the first or our three property of `push` is verified, but we still have 2 more to prove. ## Second property @@ -351,84 +166,7 @@ Our second desired property then corresponds to the postcondition `self.lookup(0) == elem`. ```rust,noplaypen -# //extern crate prusti_contracts; -# use prusti_contracts::*; -# -# use std::mem; -# -# pub struct List { -# head: Link, -# } -# -# enum Link { -# Empty, -# More(Box), -# } -# -# struct Node { -# elem: i32, -# next: Link, -# } -# -# #[trusted] -# #[ensures(old(dest.len()) == result.len())] -# fn replace(dest: &mut Link, src: Link) -> Link { -# mem::replace(dest, src) -# } -# -impl List { - #[pure] - pub fn lookup(&self, index: usize) -> i32 { - self.head.lookup(index) - } -# -# #[pure] -# pub fn len(&self) -> usize { -# self.head.len() -# } -# -# #[ensures(result.len() == 0)] -# pub fn new() -> Self { -# List { -# head: Link::Empty, -# } -# } - - #[ensures(self.len() == old(self.len()) + 1)] - #[ensures(self.lookup(0) == elem)] - 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); - } -} - -impl Link { - #[pure] - pub fn lookup(&self, index: usize) -> i32 { - match self { - Link::More(node) => { - if index == 0 { - node.elem - } else { - node.next.lookup(index - 1) - } - }, - Link::Empty => unreachable!(), - } - } -# -# #[pure] -# fn len(&self) -> usize { -# match self { -# Link::Empty => 0, -# Link::More(node) => 1 + node.next.len(), -# } -# } -} +{{#rustdoc_include tour-src/src/push/property_2_missing_bounds.rs:lookup}} ``` Consider the `match` statement in the last function. @@ -437,24 +175,18 @@ Since there is no sensible implementation of `lookup` if the underlying list is we used the macro `unreachable!()`, which will crash the program with a panic. Since nothing prevents us from calling `lookup` on an empty list, Prusti complains: -```markdown +```plain unreachable!(..) statement in pure function might be reachable ``` -We can specify that `lookup` should only be called on non-empty lists by adding the -precondition `index < self.len()` to *both* `lookup` functions; this is -sufficient to verify our second property for `push`: -```rust,noplaypen -{{#rustdoc_include tour-src/push_property_2.rs:30:33}} - // ... -``` +We can specify that `lookup` should only be called with an `index` which is between `0` and `self.len()` (which implies that we cannot call lookup on an empty list). We do this by adding the precondition `index < self.len()` to *both* `lookup` functions. This is +sufficient to verify our second property for `push`: ```rust,noplaypen -{{#rustdoc_include tour-src/push_property_2.rs:61:64}} - // ... +{{#rustdoc_include tour-src/src/push/property_2_with_bounds.rs:bounds}} ``` -We don't need to add the condition `0 <= index` to the precondition, since `index` has the type `usize`, so `index` is always non-negative. (If you don't want Prusti to add this condition automatically, you can add the line `encode_unsigned_num_constraint = false` to your `Prusti.toml` file). +We don't need to add the condition `0 <= index` to the precondition, since `index` has the type `usize`, and unsigned integers are always non-negative. (If you don't want Prusti to add this condition automatically, you can add the line `encode_unsigned_num_constraint = false` to your `Prusti.toml` file). After these changes, Prusti can successfully verify the code, so the first two properties of `push` are correct. @@ -466,25 +198,31 @@ After these changes, Prusti can successfully verify the code, so the first two p The third and final property we will verify for `push` is that the original list content is not modified: -> 3. After executing `push(elem)`, the elements of the original list remain unchanged. +> 3. After executing `push(elem)`, the elements of the original list remain unchanged (just shifted back by one). To formalize the above property, we can reuse our pure function `lookup`, [quantifiers](../syntax.md#quantifiers), and [old expressions](../syntax.md#old-expressions), that is, we add the postcondition: ```rust,noplaypen,ignore -{{#rustdoc_include tour-src/push_final.rs:39:41}} +{{#rustdoc_include tour-src/src/push/final_code.rs:shifted_back}} ``` Lets break this expression down like before: +- We start with the `ensures` annotation, to denote a postcondition. - `forall(..)` denotes a quantifier, and it takes a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html). -- The closure (denoted by the two vertical bars: `||`), takes the parameter `i: usize` and returns a `bool`. You can think of the forall expression as follows: *Any parameter passed to the closure makes it return `true`*. +- The closure is denoted by the two vertical bars: `||`, which contain the parameters it the closure takes. Here we only have one parameter `i: usize`. The return type of the closure is `bool`. You can think of the `forall` expression as follows: *Any parameter passed to the closure makes it return `true`*. - Closures in a `forall` expression can take any number of parameters, separated by a comma: `|i: usize, j: usize|`. -- In this case we use the implication operator `==>`. It takes a left and right argument of type `bool` and is true, if the left side is false, or both sides are true. `a ==> b` is equivalent to `!a || b` and `!(a && !b)`. - - The left side of the implication is `(1 <= i && i < self.len())`, which is the range where the right side must hold. +- In this case, the closure uses the [implication operator `==>`](../syntax.md#implications). It takes a left and right argument of type `bool` and is true, if the left side is false, or both sides are true. + - The left side of the implication is `(1 <= i && i < self.len())`, which is the range where the right side must hold. If the index `i` is outside of this range, we don't care about it, so the condition will be false, making the entire implication true. - The right side is the condition for everything being shifted back by one element: `old(self.lookup(i - 1)) == self.lookup(i)))`. +This code is verified successfully by Prusti, so we know that the `lookup` function correctly implements the three postconditions! + + ## Full code listing +Here you can see the final code we have after this chapter: + ```rust,noplaypen -{{#include tour-src/push_final.rs}} +{{#rustdoc_include tour-src/src/push/final_code.rs:all}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md index 9b7d9ad5238..5febce45cdc 100644 --- a/docs/user-guide/src/tour/setup.md +++ b/docs/user-guide/src/tour/setup.md @@ -18,7 +18,7 @@ cargo add prusti-contracts For older versions of Rust, you can manually add the dependency in your Cargo.toml file: ```toml [dependencies] -prusti-contracts = "0.1.2" +prusti-contracts = "0.1.3" ``` diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index 43fea01c69e..38e1dd48e53 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -38,7 +38,7 @@ devices. As a quick reference, the main steps of this tour and the involved Prusti features are as follows: -0. [Setup](setup.md) How to add Prusti to a project +0. [Setup](setup.md) How to add Prusti to a Rust project 1. [Getting Started](getting-started.md): Simple runtime errors caught by Prusti 2. [New](new.md): Postconditions, pure functions 3. [Push](push.md): Preconditions, trusted functions, old expressions, quantifiers diff --git a/docs/user-guide/src/tour/testing.md b/docs/user-guide/src/tour/testing.md index f587e6a25be..17af4d3681f 100644 --- a/docs/user-guide/src/tour/testing.md +++ b/docs/user-guide/src/tour/testing.md @@ -1,8 +1,29 @@ # Testing -**TODO** +> **Recommended reading:** +> [2.6: Testing](https://rust-unofficial.github.io/too-many-lists/first-test.html), + +The linked chapter in the "Learning Rust With Entirely Too Many Linked Lists" tutorial explains how testing normal Rust code works. In this chapter we will check both the code and the specifications that we added in the previous chapters. + +Note: Normal tests (marked by `#[cfg(test)]`) are currently ***not*** checked by Prusti, but this may be added in the future. + +To check our specifications and code, we can write a function that relies on the expected behavior. Just like in the `Testing` chapter, we can create a new namespace for the test: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/testing/initial_code.rs:test_1}} +// Prusti: verifies +``` +We can also have tests that take arguments and also have pre- and postconditions: ```rust,noplaypen -{{#include tour-src/12-chapter-2-6.rs:1:}} +{{#rustdoc_include tour-src/src/testing/initial_code.rs:test_2}} +// Prusti: verifies ``` +This passes as well. In this test we can see that we can call `pop` at least 4 times without the possibility of a panic, since the length of `list_0` is at least 4. + +Running Prusti on the project again should still verify. If you change any parts of this test, you will get a verification error, e.g., testing for any different lengths will cause the verification to fail. + +Verification should not completely replace normal testing. +A passing verification just means that the code will not panic and that it does exactly what the specifications describe. +Just like in normal code, what the code and specification encode may not be the same as what the programmer wants, and testing can help catch such missalignments. \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/Prusti.toml b/docs/user-guide/src/tour/tour-src/Prusti.toml index 057163cb03a..e53117479e5 100644 --- a/docs/user-guide/src/tour/tour-src/Prusti.toml +++ b/docs/user-guide/src/tour/tour-src/Prusti.toml @@ -1,2 +1,2 @@ check_overflows = false -encode_unsigned_num_constraint = true \ No newline at end of file +# counterexample = true \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/generic.rs b/docs/user-guide/src/tour/tour-src/src/generic.rs new file mode 100644 index 00000000000..979f797ca0e --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/generic.rs @@ -0,0 +1 @@ +mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs new file mode 100644 index 00000000000..0ffdd02fcbc --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/lib.rs b/docs/user-guide/src/tour/tour-src/src/lib.rs index 974a939143c..ac2d5238191 100644 --- a/docs/user-guide/src/tour/tour-src/src/lib.rs +++ b/docs/user-guide/src/tour/tour-src/src/lib.rs @@ -3,4 +3,7 @@ mod getting_started; mod new; // mod push; // TOOD: fix extern_spec (new syntax) -mod pop; \ No newline at end of file +// mod pop; +// mod testing; +mod option; +// mod generic; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/option.rs b/docs/user-guide/src/tour/tour-src/src/option.rs new file mode 100644 index 00000000000..979f797ca0e --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/option.rs @@ -0,0 +1 @@ +mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs new file mode 100644 index 00000000000..f925fc1565c --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs @@ -0,0 +1,239 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +type Link = Option>; + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; + + #[ensures(self.is_none() === result.is_none())] + #[ensures(self.is_some() ==> { + if let Some(inner) = snap(&self) { + result === Some(f(inner)) + } else { + unreachable!() + } + })] + pub fn map(self, f: F) -> Option + where + F: FnOnce(T) -> U; +} + +impl List { + #[pure] + pub fn len(&self) -> usize { + Self::link_len(&self.head) + } + + + #[pure] + fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + Self::link_len(&node.next), + } + } + + // #[pure] + // pub fn len(&self) -> usize { + // let mut curr = &self.head; + // let mut i = 0; + // while let Some(node) = curr { + // body_invariant!(true); + // i += 1; + // curr = &node.next; + // } + // i + // } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + // let mut curr = &self.head; + // let mut i = index; + // while let Some(node) = curr { + // body_invariant!(true); + // if i == 0 { + // return node.elem; + // } + // i -= 1; + // curr = &node.next; + // } + // unreachable!() + + + #[pure] + #[requires(index < List::link_len(curr))] + fn lookup_rec(curr: &Link, index: usize) -> i32 { + match curr { + Some(node) => { + if index == 0 { + node.elem + } else { + lookup_rec(&node.next, index - 1) + } + } + None => unreachable!(), + } + } + lookup_rec(&self.head, index) + } + + #[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, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| + (1 <= i && i < prev.len()) + ==> prev.lookup(i) == self.lookup(i - 1)) // Every element will be shifted forwards by one + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(old(snap(self)).lookup(0)) + )] + pub fn try_pop(&mut self) -> Option { + match self.head.take() { + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + /* // [Prusti: unsupported feature] unsuported creation of unique borrows (implicitly created in closure bindings) + self.head.take().map(|node| { + self.head = node.next; + node.elem + }) + */ + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> i32 { + self.try_pop().unwrap() + } +} + +mod prusti_tests { + use super::*; + + fn _test_1(){ + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty and have 0 length + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); // now the list should not be empty and have a length of 1 + prusti_assert!(list.lookup(0) == 5); // the head of the list should be 5 + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(list.lookup(0) == 10); // head is 10 + prusti_assert!(list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0)).lookup(0)); + prusti_assert!(x1 == old(snap(list_0)).lookup(1) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0)).lookup(2)); + prusti_assert!(x3 == old(snap(list_0)).lookup(3)); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1)).lookup(0)); + prusti_assert!(y0 == x3); + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs b/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs index e21634630e8..57053d56086 100644 --- a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs @@ -1,3 +1,5 @@ +//// ANCHOR: none +//// ANCHOR_END: none use prusti_contracts::*; pub struct List { @@ -27,7 +29,7 @@ impl std::option::Option { #[requires(self.is_some())] #[ensures(old(self) === Some(result))] pub fn unwrap(self) -> T; - + #[pure] #[ensures(result == matches!(self, None))] pub const fn is_none(&self) -> bool; @@ -37,14 +39,17 @@ impl std::option::Option { pub const fn is_some(&self) -> bool; } - //// ANCHOR: two_state_predicate //// ANCHOR: predicate_use //// ANCHOR: try_pop_empty +//// ANCHOR: pop_result_correct +//// ANCHOR: try_pop_result_correct impl List { //// ANCHOR_END: two_state_predicate //// ANCHOR_END: predicate_use //// ANCHOR_END: try_pop_empty + //// ANCHOR_END: pop_result_correct + //// ANCHOR_END: try_pop_result_correct #[pure] pub fn len(&self) -> usize { self.head.len() @@ -72,7 +77,7 @@ impl List { old(self.lookup(i - 1)) == self.lookup(i)))] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { - elem: elem, + elem, next: std::mem::replace(&mut self.head, Link::Empty), }); @@ -91,45 +96,29 @@ impl List { } //// ANCHOR_END: two_state_predicate - //// TEST: WITH ONE LESS LEVEL OF REFERENCE - predicate! { - fn pop_correct(&self, prev: &Self, return_value: Option) -> bool { - self.head_removed(prev) && - return_value === Some(prev.lookup(0)) - } - } - - #[ensures(old(self.is_empty()) ==> - result.is_none() && - old(self.is_empty()) ==> self.is_empty() - )] - #[ensures(!old(self.is_empty()) ==> - self.pop_correct(&old(snap(self)), snap(&result)))] - pub fn try_pop_2(&mut self) -> Option { - match std::mem::replace(&mut self.head, Link::Empty) { - Link::Empty => None, - Link::More(node) => { - self.head = node.next; - Some(node.elem) - } - } - } - //// TEST: WITH ONE LESS LEVEL OF REFERENCE - //// ANCHOR: try_pop_empty #[ensures(old(self.is_empty()) ==> result.is_none() && - old(self.is_empty()) ==> self.is_empty() + self.is_empty() )] //// ANCHOR_END: try_pop_empty + //// ANCHOR: predicate_use + //// ANCHOR: try_pop_result_correct #[ensures(!old(self.is_empty()) ==> + //// ANCHOR_END: try_pop_result_correct self.head_removed(&old(snap(self))) - && result === Some(old(snap(self)).lookup(0)) + //// ANCHOR_END: predicate_use + && + //// ANCHOR: try_pop_result_correct + result === Some(old(snap(self)).lookup(0)) + //// ANCHOR: predicate_use )] //// ANCHOR: try_pop_empty pub fn try_pop(&mut self) -> Option { // ... //// ANCHOR_END: try_pop_empty + //// ANCHOR_END: predicate_use + //// ANCHOR_END: try_pop_result_correct match std::mem::replace(&mut self.head, Link::Empty) { Link::Empty => None, Link::More(node) => { @@ -137,21 +126,32 @@ impl List { Some(node.elem) } } + //// ANCHOR: predicate_use } + //// ANCHOR_END: predicate_use #[requires(!self.is_empty())] - //// ANCHOR: predicate_use #[ensures(self.head_removed(&old(snap(self))))] + //// ANCHOR: predicate_use + //// ANCHOR: pop_result_correct #[ensures(result === old(snap(self)).lookup(0))] pub fn pop(&mut self) -> i32 { + // ... + //// ANCHOR_END: predicate_use + //// ANCHOR_END: pop_result_correct self.try_pop().unwrap() //// ANCHOR: try_pop_empty + //// ANCHOR: pop_result_correct + //// ANCHOR: try_pop_result_correct + //// ANCHOR: predicate_use } //// ANCHOR: two_state_predicate } //// ANCHOR_END: two_state_predicate //// ANCHOR_END: predicate_use //// ANCHOR_END: try_pop_empty +//// ANCHOR_END: pop_result_correct +//// ANCHOR_END: try_pop_result_correct impl Link { #[pure] diff --git a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs index 910e20d36d2..d6239207bfe 100644 --- a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs @@ -52,7 +52,7 @@ impl List { old(self.lookup(i - 1)) == self.lookup(i)))] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { - elem: elem, + elem, next: std::mem::replace(&mut self.head, Link::Empty), }); diff --git a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs b/docs/user-guide/src/tour/tour-src/src/push/final_code.rs index f61091d0123..56c314b6ec2 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/final_code.rs @@ -38,7 +38,7 @@ impl List { // ... //// ANCHOR_END: shifted_back let new_node = Box::new(Node { - elem: elem, + elem, next: std::mem::replace(&mut self.head, Link::Empty), }); diff --git a/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs index b14e21bd801..f62a0a93550 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs @@ -18,7 +18,7 @@ struct Node { impl List { pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { - elem: elem, + elem, // we can use `elem` instead of `elem: elem,` here, since the variable has the same name as the field next: std::mem::replace(&mut self.head, Link::Empty), }); diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_1.rs b/docs/user-guide/src/tour/tour-src/src/push/property_1.rs index 13ac54b1cb4..fcb6803f9ce 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/property_1.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/property_1.rs @@ -28,7 +28,7 @@ impl List { #[ensures(self.len() == old(self.len()) + 1)] // 1. Property pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { - elem: elem, + elem, next: std::mem::replace(&mut self.head, Link::Empty), }); diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs b/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs index d56aeb109d3..1b83e3a7d4b 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs @@ -32,7 +32,7 @@ impl List { // ... //// ANCHOR_END: lookup let new_node = Box::new(Node { - elem: elem, + elem, next: std::mem::replace(&mut self.head, Link::Empty), }); diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs b/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs index 19d07e0fcaa..b567439775b 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs @@ -33,7 +33,7 @@ impl List { #[ensures(self.lookup(0) == elem)] // 2. Property pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { - elem: elem, + elem, next: std::mem::replace(&mut self.head, Link::Empty), }); diff --git a/docs/user-guide/src/tour/tour-src/src/testing.rs b/docs/user-guide/src/tour/tour-src/src/testing.rs new file mode 100644 index 00000000000..979f797ca0e --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/testing.rs @@ -0,0 +1 @@ +mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs new file mode 100644 index 00000000000..bbb962f9cd9 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs @@ -0,0 +1,208 @@ +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +enum Link { + Empty, + More(Box), +} + +struct Node { + elem: i32, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; +} + +impl List { + #[pure] + pub fn len(&self) -> usize { + self.head.len() + } + + #[pure] + fn is_empty(&self) -> bool { + self.head.is_empty() + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: Link::Empty } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.head.lookup(index) + } + + #[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, + next: std::mem::replace(&mut self.head, Link::Empty), + }); + + self.head = Link::More(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| + (1 <= i && i < prev.len()) + ==> prev.lookup(i) == self.lookup(i - 1)) // Every element will be shifted forwards by one + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(old(snap(self)).lookup(0)) + )] + pub fn try_pop(&mut self) -> Option { + match std::mem::replace(&mut self.head, Link::Empty) { + Link::Empty => None, + Link::More(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> i32 { + self.try_pop().unwrap() + } +} + +impl Link { + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + match self { + Link::More(node) => { + if index == 0 { + node.elem + } else { + node.next.lookup(index - 1) + } + } + Link::Empty => unreachable!(), + } + } + + #[pure] + fn len(&self) -> usize { + match self { + Link::Empty => 0, + Link::More(node) => 1 + node.next.len(), + } + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self, Link::Empty) + } +} + +//// ANCHOR: test_1 +//// ANCHOR: test_2 +mod prusti_tests { + use super::*; + + //// ANCHOR_END: test_2 + fn _test_1(){ + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty and have 0 length + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); // now the list should not be empty and have a length of 1 + prusti_assert!(list.lookup(0) == 5); // the head of the list should be 5 + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(list.lookup(0) == 10); // head is 10 + prusti_assert!(list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + //// ANCHOR: test_2 + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0)).lookup(0)); + prusti_assert!(x1 == old(snap(list_0)).lookup(1) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0)).lookup(2)); + prusti_assert!(x3 == old(snap(list_0)).lookup(3)); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1)).lookup(0)); + prusti_assert!(y0 == x3); + } +} +//// ANCHOR_END: test_1 +//// ANCHOR_END: test_2 \ No newline at end of file diff --git a/docs/user-guide/src/verify/assert_refute_assume.md b/docs/user-guide/src/verify/assert_refute_assume.md index 714921bc1ce..175be7fb244 100644 --- a/docs/user-guide/src/verify/assert_refute_assume.md +++ b/docs/user-guide/src/verify/assert_refute_assume.md @@ -7,14 +7,11 @@ function (via an assumption). ## Assertions -The `prusti_assert!` macro instructs Prusti to verify that a certain property -holds at a specific point within the body of a function. In contrast to the -`assert!` macro, which only accepts Rust expressions, `prusti_assert!` accepts -[specification](../syntax.md) expressions as arguments. Therefore, quantifiers -and `old` expressions are allowed within a call to `prusti_assert!`, as in -the following example: +The macros `prusti_assert!`, `prusti_assert_eq` and `prusti_assert_ne` instruct Prusti to verify that a certain property holds at a specific point within the body of a function. In contrast to the `assert!`, `assert_eq` and `assert_ne` macros, which only accept Rust expressions, the Prusti variants accept [specification](../syntax.md) expressions as arguments. Therefore, [quantifiers](../syntax.md#quantifiers), [`old`](../syntax.md#old-expressions) expressions and other Prusti specification syntax is allowed within a call to `prusti_assert!`, as in the following example: ```rust,noplaypen +# use prusti_contracts::*; +# #[requires(*x != 2)] fn go(x: &mut u32) { *x = 2; @@ -22,18 +19,38 @@ fn go(x: &mut u32) { } ``` -Note that the expression given to `prusti_assert!` must be side-effect free. -Therefore, certain calls might work within an `assert!`, but not within a -`prusti_assert!`. For example: +The two macros `prusti_assert_eq` and `prusti_assert_ne` are also slightly different than their standard counterparts, in that they use [snapshot equality](syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. -```rust,noplaypen +```rust +# use prusti_contracts::*; +# +#[requires(a === b)] +fn equal(a: u64, b: u64) { + // these 2 lines do the same: + prusti_assert!(a === b); + prusti_assert_eq!(a, b); +} + +#[requires(a !== b)] +fn different(a: u64, b: u64) { + // these 2 lines do the same: + prusti_assert!(a !== b); + prusti_assert_ne!(a, b); +} +``` + +Note that the expression given to `prusti_assert!` must be side-effect free, since they will not result in any runtime code. Therefore, using code containing [impure](../verify/pure.md) functions will work in an `assert!`, but not within a `prusti_assert!`. For example: + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +# fn test(map: std::collections::HashMap) { assert!(map.insert(5)); prusti_assert!(map.insert(5)); // error +# } ``` -`prusti_assert_eq!` and `prusti_assert_ne!` are the Prusti counterparts to -`assert_eq!` and `assert_ne!`, but the check is made for -[snapshot equality](../syntax.md#snapshot-equality), resp. snapshot inequality. +Using Prusti assertions instead of normal assertions can speed up verification, because every `assert` results in a branch in the code, while `prusti_assert` does not. ## Refutations From 308dfbc98e88f7d32285fa0d678c734ebbdece1f Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 15 Feb 2023 13:02:53 +0100 Subject: [PATCH 05/31] Continued work on User Guide --- docs/user-guide/src/SUMMARY.md | 4 +- docs/user-guide/src/tour/generics.md | 36 +- docs/user-guide/src/tour/option.md | 85 +++++ docs/user-guide/src/tour/options.md | 23 -- docs/user-guide/src/tour/peek.md | 24 +- docs/user-guide/src/tour/pledges.md | 72 +++- docs/user-guide/src/tour/pop.md | 20 +- docs/user-guide/src/tour/push.md | 4 +- docs/user-guide/src/tour/summary.md | 6 +- .../tour/tour-src/src/generic/initial_code.rs | 237 ++++++++++++- docs/user-guide/src/tour/tour-src/src/lib.rs | 6 +- .../src/loop_invariants/initial_code.rs | 294 ++++++++++++++++ .../tour/tour-src/src/option/initial_code.rs | 138 ++++---- docs/user-guide/src/tour/tour-src/src/peek.rs | 1 + .../tour/tour-src/src/peek/initial_code.rs | 232 +++++++++++++ .../src/tour/tour-src/src/peek_mut.rs | 3 + .../tour-src/src/peek_mut/assert_on_expiry.rs | 319 ++++++++++++++++++ .../tour/tour-src/src/peek_mut/final_code.rs | 274 +++++++++++++++ .../tour-src/src/peek_mut/initial_code.rs | 289 ++++++++++++++++ .../src/tour/tour-src/src/pop/final_code.rs | 8 +- .../src/tour/tour-src/src/pop/initial_code.rs | 4 +- .../src/tour/tour-src/src/push/final_code.rs | 10 +- .../tour/tour-src/src/testing/initial_code.rs | 13 +- .../src/verify/assert_refute_assume.md | 2 +- docs/user-guide/src/verify/closure.md | 2 + 25 files changed, 1965 insertions(+), 141 deletions(-) create mode 100644 docs/user-guide/src/tour/option.md delete mode 100644 docs/user-guide/src/tour/options.md create mode 100644 docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/peek.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/peek_mut.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs create mode 100644 docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index d4c9e44be94..00f4f342856 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -11,11 +11,11 @@ - [Push](tour/push.md) - [Pop](tour/pop.md) - [Testing](tour/testing.md) - - [Options](tour/options.md) + - [Option](tour/option.md) - [Generics](tour/generics.md) - [Peek](tour/peek.md) + - [Pledges (mutable peek)](tour/pledges.md) - [Final Code](tour/final.md) - - [Pledges](tour/pledges.md) - [Verification Features](verify/summary.md) - [Absence of panics](verify/panic.md) - [Overflow checks](verify/overflow.md) diff --git a/docs/user-guide/src/tour/generics.md b/docs/user-guide/src/tour/generics.md index e84fb468336..f20aa53f39e 100644 --- a/docs/user-guide/src/tour/generics.md +++ b/docs/user-guide/src/tour/generics.md @@ -1,7 +1,39 @@ -# Options +# Making it all Generic > **Recommended reading:** > [3.2: Generic](https://rust-unofficial.github.io/too-many-lists/second-generic.html) +Just like the corresponding chapter in the "Learning Rust With Entirely Too Many Linked Lists" book, we will change our list to have a generic element type `T`, not just `i32`. For this, we go through our code an add the generic parameter `T` where required. The compiler really helps for this, since it will mark where a generic parameter is needed. +If you do this process with Prusti, at some point you will encounter the following error: +```plain +[E0369] binary operation `==` cannot be applied to type `T`. +``` +This is because the generic type `T` might not have an equality function that could be called on it like `i32` does. Since we only used `==` inside of specifications, we can fix this problems by using [snapshot equality `===`](../syntax.md#snapshot-equality) instead. -**TODO** +Here you can see where some of the changes where done (expand to see full changes): + +```rust,noplaypen +{{#rustdoc_include tour-src/src/generic/initial_code.rs:generic_types}} +``` + +This code still fails to compile, this time with an error from the function `link_lookup`: +```plain +[E0507] cannot move out of `node.elem` which is behind a shared reference. +[Note] move occurs because `node.elem` has type `T`, which does not implement the `Copy` trait +``` + +To fix this, we will change `List::lookup` and `link_lookup` to return a reference to the element at index `i`, instead of the element itself. This was not needed for `i32`, since it implements the [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) trait. By returning a reference instead, the lookups will work for any type `T`, even if it is not `Copy`! + +In addition to returning a reference, we will have to adjust some of the places where `lookup` is used, mostly by dereferencing or using `snap` on the reference: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/generic/initial_code.rs:lookup_reference}} +``` + +After all these changes, Prusti is able to verify the code again, so we now have a linked list that can store any type, not just `i32`! +If you want to see the full code after all the changes, expand the following code block. + +```rust,noplaypen +// Expand to see full code up to this chapter +{{#rustdoc_include tour-src/src/generic/initial_code.rs:nothing}} +``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/option.md b/docs/user-guide/src/tour/option.md new file mode 100644 index 00000000000..296faf6ed96 --- /dev/null +++ b/docs/user-guide/src/tour/option.md @@ -0,0 +1,85 @@ +# Options + +> **Recommended reading:** +> [3: An Ok Single-Linked Stack](https://rust-unofficial.github.io/too-many-lists/second.html), +> [3.1. Option](https://rust-unofficial.github.io/too-many-lists/second-option.html) + +Just like in the "Learning Rust With Entirely Too Many Linked Lists" tutorial, we can change our `enum Link` to use the `Option` type via a type alias instead of manually implementing `Empty` and `More`: + +```rust,noplaypen,ignore +type Link = Option>; +``` + +In order to use the `Option::take` function, we also have to implement the `extern_spec` for it. As you can see, it is quite similar to the `extern_spec` for `mem::replace`, since `take` does the same as `replace(&mut self, None)`: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/option/initial_code.rs:option_take_extern_spec}} +``` + +These changes require some adjustments of the code and specifications. With the new type alias for `Link`, we cannot have an `impl` block anymore, so our `lookup` and `len` functions on `Link` are now normal, non-associated functions: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/option/initial_code.rs:rewrite_link_impl}} +``` + +Due to current [limitations of Prusti](../limitations.md), we cannot replace our `len` and `lookup` functions with loops: + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +# pub struct List { +# head: Link, +# } +# +# type Link = Option>; +# +# struct Node { +# elem: i32, +# next: Link, +# } +# +impl List { + // Prusti cannot verify these functions at the moment, + // since loops in pure functions are not yet supported: + #[pure] + pub fn len(&self) -> usize { + let mut curr = &self.head; + let mut i = 0; + while let Some(node) = curr { +# body_invariant!(true); + i += 1; + curr = &node.next; + } + i + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + let mut curr = &self.head; + let mut i = index; + while let Some(node) = curr { +# body_invariant!(true); + if i == 0 { + return node.elem; + } + i -= 1; + curr = &node.next; + } + unreachable!() + } +} +``` + +Since Prusti doesn't fully support closures yet, we also cannot do the rewrite to use the `Option::map` function: +```rust,noplaypen +{{#rustdoc_include tour-src/src/option/initial_code.rs:try_pop_rewrite}} +``` + +After all the changes done in this chapter, Prusti is still be able to verify the code, so we didn't break anything. +If you want to see the full code after all the changes, expand the following code block. + +```rust,noplaypen +// Expand to see full code up to this chapter +{{#rustdoc_include tour-src/src/option/initial_code.rs:nothing}} +``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/options.md b/docs/user-guide/src/tour/options.md deleted file mode 100644 index 69a3b096c57..00000000000 --- a/docs/user-guide/src/tour/options.md +++ /dev/null @@ -1,23 +0,0 @@ -# Options - -> **Recommended reading:** -> [3: An Ok Single-Linked Stack](https://rust-unofficial.github.io/too-many-lists/second.html), -> [3.1. Option](https://rust-unofficial.github.io/too-many-lists/second-option.html) - -Just like in the "Learning Rust With Entirely Too Many Linked Lists" tutorial, we can change our `enum Link` to use the `Option` type via a type alias instead of manually implementing `Empty` and `More`: - -```rust,noplaypen,ignore -type Link = Option>; -``` - -In order to use the `Option::take` and the `Option::map` function, we also have to implement the `extern_spec` for them: - -```rust,noplaypen - -``` - -These changes require some adjustments of the code and specifications: - -```rust,noplaypen - -``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/peek.md b/docs/user-guide/src/tour/peek.md index 3f644223675..364ac321cb9 100644 --- a/docs/user-guide/src/tour/peek.md +++ b/docs/user-guide/src/tour/peek.md @@ -3,10 +3,26 @@ > **Recommended reading:** > [3.3: Peek](https://rust-unofficial.github.io/too-many-lists/second-peek.html) -Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. This is currently not possible in Prusti, since structures containing references are not supported at the moment. -The return type of `try_peek` is `Option<&T>`, which is not supported. +Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. This is currently not possible in Prusti, since structures containing references are not supported at the moment (see chapter [Prusti Limitations](../limitations.md)). -We can still implement `peek` though, we just cannot do it by using `try_peek` like before: +We can still implement `peek`, but we just cannot do it by using `try_peek` like before in `pop`. Instead, we can reuse th already implemented and verified `lookup` function! Since `lookup` can return a reference to any element of the list, we can just call `self.lookup(0)` inside of `peek`: +```rust,noplaypen +{{#rustdoc_include tour-src/src/peek/initial_code.rs:implementation}} +``` -**TODO** +We can also write a test again, to see if our specification holds up in actual code: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/peek/initial_code.rs:test_peek}} +``` + +This verifies too, so it appears our implementation of `peek` is correct. + +The `peek` method only returns an immutable reference, but what if you want to change elements of the list? We will see how in the next chapter. + +Here you can see the full code we have now: +```rust,noplaypen +// Expand to see full code up to this chapter +{{#rustdoc_include tour-src/src/peek/initial_code.rs:nothing}} +``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/pledges.md b/docs/user-guide/src/tour/pledges.md index 3e5354fa028..b5171cab559 100644 --- a/docs/user-guide/src/tour/pledges.md +++ b/docs/user-guide/src/tour/pledges.md @@ -1,3 +1,73 @@ # Pledges -**TODO** +Now we will look at [`pledges`](../verify/pledge.md). Pledges are used for functions that return mutable reference into some datastructure. +They explain to Prusti how the original object gets affected by changes to the returned reference. +We will demonstrate it by implementing a function that gives you a mutable reference to the first element in the list: + +## Implementing `peek_mut` + +The `peek_mut` will return a mutable reference of type `T`, so the precondition of the list requires it to be non-empty. +As a first postcondition, we want to ensure that the `result` of `peek_mut` points to the first element of the list. + +In the code, we need to get a mutable reference to the type inside the `Link = Option>>`. This requires the use of the type `Option<&mut T>`, which is a structure containing a reference, so it is not yet supported by Prusti (see [limitations chapter](../limitations.md)). To still be able to verify `peek_mut`, we mark it as `trusted` for now: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/peek_mut/initial_code.rs:peek_mut_code}} +``` + +Note that `peek_mut` cannot be `#[pure]`, since it returns a mutable reference. + +## Writing a test for our specification + +Lets write a test to see if our specification works: +- Create a list with two elements: [16, 8] +- Get a mutable reference to the first element (16) +- Change the first element to 5 +- Check if the list still has the expected properties after `first` gets dropped + - Is the length 2? + - Is the first element 5? + - Is the second element 8? + +```rust,noplaypen +{{#rustdoc_include tour-src/src/peek_mut/initial_code.rs:test_peek_mut}} +``` + +But this fails, Prusti cannot verify any of our last three `prusti_assert` statements. This is where `pledges` come in. We have to tell Prusti how the `result` affects the original list. Without this, Prusti assumes that the reference can change anything about the original list, so nothing can be known about it after the reference gets dropped. + +## Writing the pledge + +The pledge gets written with an annotation like for `ensures` and `requires`, but with the keyword `after_expiry`. +Inside we have all the conditions that hold after the returned reference gets dropped: + +```rust,noplaypen +{{#rustdoc_include tour-src/src/peek_mut/final_code.rs:pledge}} +``` + +We have 3 conditions here: +1. The list will have the same length afterwards +2. Any element of the list with index `1..list.len()` will not be changed +3. The element at the head of the list is the value that was assigned to the returned reference. This is denoted with the `before_expiry` function + +With these 3 conditions, our test verifies successfully! + +## Assert on expiry + +Like `after_expiry`, there is also `assert_on_expiry`. It is used to check for conditions that have to be true when the returned reference expires, in order to uphold some type invariant. + +As an example, we could use this to make sure that our list of `i32` can only contain elements between 0 and 16. +Given that this invariant held before the reference was given out, it will hold again if the changed element is still in the correct range: + +```rust,noplaypen,ignore +{{#rustdoc_include tour-src/src/peek_mut/assert_on_expiry.rs:assert_on_expiry}} +``` +The syntax here is `#[assert_on_expiry(condition, invariant)]`. +This means that the `invariant` holds, given that `condition` is true when the reference expires. + +Note that for some condition `A`, `after_expiry(A)` is equal to `assert_one_expiry(true, A)`. + +## Full Code + +```rust,noplaypen +// Expand to see full code up to this chapter +{{#rustdoc_include tour-src/src/peek_mut/final_code.rs:nothing}} +``` diff --git a/docs/user-guide/src/tour/pop.md b/docs/user-guide/src/tour/pop.md index e3831302bf1..f142cb82ecb 100644 --- a/docs/user-guide/src/tour/pop.md +++ b/docs/user-guide/src/tour/pop.md @@ -62,7 +62,7 @@ Let's start our specification with the precondition of `pop`. Since the `unwrap` ### `try_pop` postcondition for empty lists Now we will implement the two conditions that hold for `try_pop` if you pass an empty list to it. -To ensures that these are only checked for empty lists, we use the implication operator `==>` again: +To ensures that these are only checked for empty lists, we use the implication operator `==>`: ```rust,noplaypen {{#rustdoc_include tour-src/src/pop/final_code.rs:try_pop_empty}} @@ -80,7 +80,7 @@ We can check this condition for snapshot equality with `result`. This will alway {{#rustdoc_include tour-src/src/pop/final_code.rs:pop_result_correct}} ``` -For `try_pop`, condition only holds if the list was not empty before the call. In addition, the `result` is an `Option::Some`, so we will have to include this in our postcondition: +For `try_pop`, the condition only holds if the list was *not* empty before the call. In addition, the `result` is an `Option::Some`, so we will have to include this in our postcondition: ```rust,noplaypen {{#rustdoc_include tour-src/src/pop/final_code.rs:try_pop_result_correct}} @@ -89,25 +89,26 @@ For `try_pop`, condition only holds if the list was not empty before the call. I ### Using `predicate!` to reduce code duplication -You may have noticed that the last two conditions for `pop` are the same as the last two of `try_pop`. We could just write the same conditions twice, but we can also place them in a Prusti [`predicate`](../verify/predicate.md) and then use that `predicate` in both specifications. +You may have noticed that the last two conditions for `pop` are the same as the last two of `try_pop`. We could just write the same conditions twice, but we can also place them in a Prusti [`predicate`](../verify/predicate.md), and then use that `predicate` in both specifications. A `predicate` is basically just a [`pure`](../verify/pure.md) function that returns a `bool`, but it can use all the additional syntax available in Prusti specifications. Lets look at an example: ```rust,noplaypen +# use prusti_contracts::*; +# predicate! { - fn larger_than_5(x: i32) -> bool { + fn larger_than_five(x: i32) -> bool { x > 5 } } -#[requires(larger_than_5(input))] -#[ensures(larger_than_5(result))] -fn add_one(input: i32) -> i32 { - input + 1 +#[ensures(larger_than_five(result))] +fn ten() -> i32 { + 10 } ``` -In our specific case, we want to have a predicate to compare the state of the list before the call to the state afterwards. The `old` function cannot be used inside a predicate, we have to pass the two states as separate arguments. For this we write a `predicate` takes 2 arguments, which represent the state after and before the function. Such a predicate is also called a `two-state predicate`. +In our specific case, we want to have a predicate to compare the state of the list before the call to the state afterwards. The `old` function cannot be used inside a predicate, so we have to pass the two states as separate arguments. For this we write a `predicate` with 2 parameters, which represent the state before and after the function. Such a predicate is also called a `two-state predicate`. Note that we take both arguments by (immutable) reference, since we don't need the predicate to take ownership over the arguments: ```rust,noplaypen,ignore @@ -115,6 +116,7 @@ Note that we take both arguments by (immutable) reference, since we don't need t # impl List { predicate! { + // two-state predicate to check if the head of a list was correctly removed fn head_removed(&self, prev: &Self) -> bool { // ... } diff --git a/docs/user-guide/src/tour/push.md b/docs/user-guide/src/tour/push.md index f96d305a043..19a695214c3 100644 --- a/docs/user-guide/src/tour/push.md +++ b/docs/user-guide/src/tour/push.md @@ -221,8 +221,8 @@ This code is verified successfully by Prusti, so we know that the `lookup` funct ## Full code listing -Here you can see the final code we have after this chapter: ```rust,noplaypen -{{#rustdoc_include tour-src/src/push/final_code.rs:all}} +// Expand to see full code up to this chapter +{{#rustdoc_include tour-src/src/push/final_code.rs:nothing}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index 38e1dd48e53..0d41a85eeb5 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -29,7 +29,7 @@ Throughout this tour, we will cover the following Prusti concepts: - Writing function-modular specifications - Using *pure* functions in specifications - Writing loop invariants -- Using *trusted wrappers* to deal with library code +- Using *extern_spec* to deal with library code - Basic troubleshooting @@ -38,13 +38,15 @@ devices. As a quick reference, the main steps of this tour and the involved Prusti features are as follows: +# TODO: Update <====== + 0. [Setup](setup.md) How to add Prusti to a Rust project 1. [Getting Started](getting-started.md): Simple runtime errors caught by Prusti 2. [New](new.md): Postconditions, pure functions 3. [Push](push.md): Preconditions, trusted functions, old expressions, quantifiers 4. [Pop](pop.md): Exercise with similar concepts as for push 5. [Testing](testing.md): More runtime errors caught by Prusti -6. [A Bad Stack](bad-stack.md): Wrap-up of the second chapter of +6. --> [A Bad Stack](bad-stack.md): Wrap-up of the second chapter of [Learn Rust with Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). 7. [Options](options.md): Verification with option types 7. [Generics](generics.md): Prusti and generics diff --git a/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs index 0ffdd02fcbc..479810e6b16 100644 --- a/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs @@ -1 +1,236 @@ -// TODO \ No newline at end of file +//// ANCHOR: nothing +//// ANCHOR_END: nothing +use prusti_contracts::*; + +//// ANCHOR: generic_types +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +//// ANCHOR_END: generic_types +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; +} + +//// ANCHOR: generic_types +//// ANCHOR: lookup_reference +impl List { + // ... + + //// ANCHOR_END: generic_types + //// ANCHOR_END: lookup_reference + #[pure] + pub fn len(&self) -> usize { + link_len(&self.head) + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + //// ANCHOR: lookup_reference + pub fn lookup(&self, index: usize) -> &T { // Return type is changed from `T` to `&T` + link_lookup(&self.head, index) + } + //// ANCHOR_END: lookup_reference + + #[ensures(self.len() == old(self.len()) + 1)] + //// ANCHOR: lookup_reference + #[ensures(snap(self.lookup(0)) === elem)] // Here we add a `snap` + //// ANCHOR_END: lookup_reference + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) === self.lookup(i + 1)))] + //// ANCHOR: lookup_reference + pub fn push(&mut self, elem: T) { + // ... + //// ANCHOR_END: lookup_reference + let new_node = Box::new(Node { + elem, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| // Every element will be shifted forwards by one + (1 <= i && i < prev.len()) + ==> prev.lookup(i) === self.lookup(i - 1)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(snap(old(snap(self)).lookup(0))) + )] + //// ANCHOR: generic_types + pub fn try_pop(&mut self) -> Option { + // ... + //// ANCHOR_END: generic_types + match self.head.take() { // Replace mem::swap with the buildin Option::take + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + //// ANCHOR: generic_types + } + + //// ANCHOR_END: generic_types + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + //// ANCHOR: generic_types + pub fn pop(&mut self) -> T { + self.try_pop().unwrap() + //// ANCHOR: lookup_reference + } +} +//// ANCHOR_END: lookup_reference + +//// ANCHOR_END: generic_types +#[pure] +#[requires(index < link_len(link))] +//// ANCHOR: lookup_reference +fn link_lookup(link: &Link, index: usize) -> &T { // Return type is changed from `T` to `&T` + match link { + Some(node) => { + if index == 0 { + &node.elem // Here we return a reference to `elem` instead of the `elem` itself + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} +//// ANCHOR_END: lookup_reference + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), + } +} + +//// ANCHOR: generic_types +//// ANCHOR: lookup_reference +mod prusti_tests { + use super::*; + + //// ANCHOR_END: generic_types + fn _test_1(){ + let mut list = List::new(); + prusti_assert!(list.is_empty() && list.len() == 0); + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); + prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + // ... + //// ANCHOR_END: lookup_reference + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 + prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(*list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + //// ANCHOR: generic_types + fn _test_2(list_0: &mut List, list_1: &mut List) { + // ... + //// ANCHOR_END: generic_types + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(*list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0.lookup(0)))); + prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0.lookup(2)))); + prusti_assert!(x3 == old(snap(list_0.lookup(3)))); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1.lookup(0)))); + prusti_assert!(y0 == x3); + //// ANCHOR: generic_types + //// ANCHOR: lookup_reference + } +} +//// ANCHOR_END: generic_types +//// ANCHOR_END: lookup_reference \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/lib.rs b/docs/user-guide/src/tour/tour-src/src/lib.rs index ac2d5238191..a00771ef633 100644 --- a/docs/user-guide/src/tour/tour-src/src/lib.rs +++ b/docs/user-guide/src/tour/tour-src/src/lib.rs @@ -5,5 +5,7 @@ mod new; // mod push; // TOOD: fix extern_spec (new syntax) // mod pop; // mod testing; -mod option; -// mod generic; \ No newline at end of file +// mod option; +// mod generic; +// mod peek; +mod peek_mut; // pledges \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs new file mode 100644 index 00000000000..f699edee763 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs @@ -0,0 +1,294 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; +} +//// ANCHOR: pledge +impl List { + //// ANCHOR_END: pledge + #[pure] + pub fn len(&self) -> usize { + link_len(&self.head) + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> &T { + link_lookup(&self.head, index) + } + + #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(snap(self.lookup(0)) === elem)] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) === self.lookup(i + 1)))] + pub fn push(&mut self, elem: T) { + let new_node = Box::new(Node { + elem, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| // Every element will be shifted forwards by one + (1 <= i && i < prev.len()) + ==> prev.lookup(i) === self.lookup(i - 1)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(snap(old(snap(self)).lookup(0))) + )] + pub fn try_pop(&mut self) -> Option { + match self.head.take() { // Replace mem::swap with the buildin Option::take + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> T { + self.try_pop().unwrap() + } + + // // Not currently possible in Prusti + // pub fn try_peek(&mut self) -> Option<&T> { + // todo!() + // } + + #[pure] + #[requires(!self.is_empty())] + pub fn peek(&self) -> &T { + self.lookup(0) + } + + // #[requires(index < self.len())] + // pub fn lookup_mut(&mut self, index: usize) -> &mut T { + // let mut curr_node = &mut self.head; + // let mut index = index; // Workaround for Prusti not supporting mutable fn arguments + // while index != 0 { + // body_invariant!(true); + // if let Some(next_node) = curr_node { // reference in enum + // curr_node = &mut next_node.next; + // } else { + // unreachable!(); + // } + // index -= 1; + // } + // if let Some(node) = curr_node { // ERROR: [Prusti: unsupported feature] the creation of loans in this loop is not supported (ReborrowingDagHasNoMagicWands) + // &mut node.elem + // } else { + // unreachable!() + // } + // } + + #[trusted] // required due to unsupported reference in enum + #[requires(!self.is_empty())] + #[ensures(snap(result) === old(snap(self.peek())))] + //// ANCHOR: pledge + #[after_expiry( + old(self.len()) === self.len() // (1.) + && forall(|i: usize| 1 <= i && i < self.len() // (2.) + ==> old(snap(self.lookup(i))) === snap(self.lookup(i))) + && snap(self.peek()) === before_expiry(snap(result)) // (3.) + )] + pub fn peek_mut(&mut self) -> &mut T { + //// ANCHOR_END: pledge + // This does not work in Prusti at the moment: + // "&mut self.head" has type "&mut Option" + // this gets auto-dereferenced by Rust into type: "Option<&mut T>" + // this then gets matched to "Some(node: &mut T)" + // References in enums are not yet supported, so this cannot be verified by Prusti + if let Some(node) = &mut self.head { + &mut node.elem + } else { + unreachable!() + } + //// ANCHOR: pledge + // ... + } +} +//// ANCHOR_END: pledge + +#[pure] +#[requires(index < link_len(link))] +fn link_lookup(link: &Link, index: usize) -> &T { + match link { + Some(node) => { + if index == 0 { + &node.elem + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), + } +} + +mod prusti_tests { + use super::*; + + fn _test_1(){ + let mut list = List::new(); + prusti_assert!(list.is_empty() && list.len() == 0); + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); + prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 + prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(*list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(*list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0.lookup(0)))); + prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0.lookup(2)))); + prusti_assert!(x3 == old(snap(list_0.lookup(3)))); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1.lookup(0)))); + prusti_assert!(y0 == x3); + } + + fn _test_3() { + let mut list = List::new(); + list.push(16); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + + list.push(5); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 5); + + list.pop(); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + } + + fn _test_4() { + let mut list = List::new(); + list.push(8); + list.push(16); + prusti_assert!(*list.lookup(0) == 16); + prusti_assert!(*list.lookup(1) == 8); + prusti_assert!(list.len() == 2); + + { + let first = list.peek_mut(); + // `first` is a mutable reference to the first element of the list + // for as long as `first` is live, `list` cannot be accessed + prusti_assert!(*first == 16); + *first = 5; + prusti_assert!(*first == 5); + // `first` gets dropped here, `list` can be accessed again + } + prusti_assert!(list.len() == 2); + prusti_assert!(*list.lookup(0) == 5); + prusti_assert!(*list.lookup(1) == 8); + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs index f925fc1565c..e1f3f25f905 100644 --- a/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs @@ -1,3 +1,5 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing use prusti_contracts::*; pub struct List { @@ -11,16 +13,20 @@ struct Node { next: Link, } +//// ANCHOR: option_take_extern_spec #[extern_spec(std::mem)] #[ensures(snap(dest) === src)] #[ensures(result === old(snap(dest)))] fn replace(dest: &mut T, src: T) -> T; +//// ANCHOR_END: option_take_extern_spec // Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): // https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 +//// ANCHOR: option_take_extern_spec #[extern_spec] impl std::option::Option { + //// ANCHOR_END: option_take_extern_spec #[requires(self.is_some())] #[ensures(old(self) === Some(result))] pub fn unwrap(self) -> T; @@ -33,50 +39,23 @@ impl std::option::Option { #[ensures(result == matches!(self, Some(_)))] pub const fn is_some(&self) -> bool; + //// ANCHOR: option_take_extern_spec #[ensures(result === old(snap(self)))] #[ensures(self.is_none())] pub fn take(&mut self) -> Option; - - #[ensures(self.is_none() === result.is_none())] - #[ensures(self.is_some() ==> { - if let Some(inner) = snap(&self) { - result === Some(f(inner)) - } else { - unreachable!() - } - })] - pub fn map(self, f: F) -> Option - where - F: FnOnce(T) -> U; } +//// ANCHOR_END: option_take_extern_spec +//// ANCHOR: try_pop_rewrite +//// ANCHOR: rewrite_link_impl impl List { + //// ANCHOR_END: try_pop_rewrite #[pure] pub fn len(&self) -> usize { - Self::link_len(&self.head) - } - - - #[pure] - fn link_len(link: &Link) -> usize { - match link { - None => 0, - Some(node) => 1 + Self::link_len(&node.next), - } + link_len(&self.head) } - // #[pure] - // pub fn len(&self) -> usize { - // let mut curr = &self.head; - // let mut i = 0; - // while let Some(node) = curr { - // body_invariant!(true); - // i += 1; - // curr = &node.next; - // } - // i - // } - + //// ANCHOR_END: rewrite_link_impl #[pure] fn is_empty(&self) -> bool { matches!(self.head, None) @@ -87,43 +66,18 @@ impl List { List { head: None } } + //// ANCHOR: rewrite_link_impl #[pure] #[requires(index < self.len())] pub fn lookup(&self, index: usize) -> i32 { - // let mut curr = &self.head; - // let mut i = index; - // while let Some(node) = curr { - // body_invariant!(true); - // if i == 0 { - // return node.elem; - // } - // i -= 1; - // curr = &node.next; - // } - // unreachable!() - - - #[pure] - #[requires(index < List::link_len(curr))] - fn lookup_rec(curr: &Link, index: usize) -> i32 { - match curr { - Some(node) => { - if index == 0 { - node.elem - } else { - lookup_rec(&node.next, index - 1) - } - } - None => unreachable!(), - } - } - lookup_rec(&self.head, index) + link_lookup(&self.head, index) } + //// ANCHOR_END: rewrite_link_impl #[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)))] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) == self.lookup(i + 1)))] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { elem, @@ -137,9 +91,9 @@ impl List { // two-state predicate to check if the head of a list was correctly removed fn head_removed(&self, prev: &Self) -> bool { self.len() == prev.len() - 1 // The length will decrease by 1 - && forall(|i: usize| + && forall(|i: usize| // Every element will be shifted forwards by one (1 <= i && i < prev.len()) - ==> prev.lookup(i) == self.lookup(i - 1)) // Every element will be shifted forwards by one + ==> prev.lookup(i) == self.lookup(i - 1)) } } @@ -152,29 +106,67 @@ impl List { && result === Some(old(snap(self)).lookup(0)) )] + //// ANCHOR: try_pop_rewrite pub fn try_pop(&mut self) -> Option { - match self.head.take() { + match self.head.take() { // Replace mem::swap with the buildin Option::take None => None, Some(node) => { self.head = node.next; Some(node.elem) } } - /* // [Prusti: unsupported feature] unsuported creation of unique borrows (implicitly created in closure bindings) - self.head.take().map(|node| { - self.head = node.next; - node.elem - }) - */ } + + // // This will likely work in the future, but doesn't currently (even if you provide an `extern_spec` for `Option::map`): + // // Currently you get this error: + // // [Prusti: unsupported feature] unsupported creation of unique borrows (implicitly created in closure bindings) + // pub fn try_pop(&mut self) -> Option { + // let tmp = self.head.take(); + // tmp.map(move |node| { + // self.head = node.next; + // node.elem + // }) + // } + //// ANCHOR_END: try_pop_rewrite #[requires(!self.is_empty())] #[ensures(self.head_removed(&old(snap(self))))] #[ensures(result === old(snap(self)).lookup(0))] pub fn pop(&mut self) -> i32 { self.try_pop().unwrap() + //// ANCHOR: rewrite_len + } + //// ANCHOR: rewrite_link_impl + //// ANCHOR: try_pop_rewrite +} +//// ANCHOR_END: try_pop_rewrite +//// ANCHOR_END: rewrite_link_impl +//// ANCHOR_END: rewrite_len + +//// ANCHOR: rewrite_link_impl +#[pure] +#[requires(index < link_len(link))] +fn link_lookup(link: &Link, index: usize) -> i32 { + match link { + Some(node) => { + if index == 0 { + node.elem + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), } } +//// ANCHOR_END: rewrite_link_impl mod prusti_tests { use super::*; diff --git a/docs/user-guide/src/tour/tour-src/src/peek.rs b/docs/user-guide/src/tour/tour-src/src/peek.rs new file mode 100644 index 00000000000..979f797ca0e --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/peek.rs @@ -0,0 +1 @@ +mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs new file mode 100644 index 00000000000..247a3ec6606 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs @@ -0,0 +1,232 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; +} + +//// ANCHOR: implementation +impl List { + //// ANCHOR_END: implementation + #[pure] + pub fn len(&self) -> usize { + link_len(&self.head) + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> &T { + link_lookup(&self.head, index) + } + + #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(snap(self.lookup(0)) === elem)] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) === self.lookup(i + 1)))] + pub fn push(&mut self, elem: T) { + let new_node = Box::new(Node { + elem, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| // Every element will be shifted forwards by one + (1 <= i && i < prev.len()) + ==> prev.lookup(i) === self.lookup(i - 1)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(snap(old(snap(self)).lookup(0))) + )] + pub fn try_pop(&mut self) -> Option { + match self.head.take() { // Replace mem::swap with the buildin Option::take + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> T { + self.try_pop().unwrap() + } + + //// ANCHOR: implementation + // // Not currently possible in Prusti + // pub fn try_peek(&mut self) -> Option<&T> { + // todo!() + // } + + #[pure] + #[requires(!self.is_empty())] + pub fn peek(&self) -> &T { + self.lookup(0) + } +} +//// ANCHOR_END: implementation + +#[pure] +#[requires(index < link_len(link))] +fn link_lookup(link: &Link, index: usize) -> &T { + match link { + Some(node) => { + if index == 0 { + &node.elem + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), + } +} + +//// ANCHOR: test_peek +mod prusti_tests { + use super::*; + //// ANCHOR_END: test_peek + + fn _test_1(){ + let mut list = List::new(); + prusti_assert!(list.is_empty() && list.len() == 0); + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); + prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 + prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(*list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(*list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0.lookup(0)))); + prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0.lookup(2)))); + prusti_assert!(x3 == old(snap(list_0.lookup(3)))); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1.lookup(0)))); + prusti_assert!(y0 == x3); + } + + //// ANCHOR: test_peek + fn _test_3() { + let mut list = List::new(); + list.push(16); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + + list.push(5); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 5); + + list.pop(); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + } +} +//// ANCHOR_END: test_peek \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut.rs b/docs/user-guide/src/tour/tour-src/src/peek_mut.rs new file mode 100644 index 00000000000..bfb3faf1ad3 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/peek_mut.rs @@ -0,0 +1,3 @@ +// mod initial_code; // should fail +// mod final_code; +mod assert_on_expiry; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs b/docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs new file mode 100644 index 00000000000..fb7ba403db8 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs @@ -0,0 +1,319 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; +} +//// ANCHOR: pledge +impl List { + //// ANCHOR_END: pledge + #[pure] + pub fn len(&self) -> usize { + link_len(&self.head) + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> &T { + link_lookup(&self.head, index) + } + + #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(snap(self.lookup(0)) === elem)] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) === self.lookup(i + 1)))] + pub fn push(&mut self, elem: T) { + let new_node = Box::new(Node { + elem, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| // Every element will be shifted forwards by one + (1 <= i && i < prev.len()) + ==> prev.lookup(i) === self.lookup(i - 1)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(snap(old(snap(self)).lookup(0))) + )] + pub fn try_pop(&mut self) -> Option { + match self.head.take() { // Replace mem::swap with the buildin Option::take + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> T { + self.try_pop().unwrap() + } + + // // Not currently possible in Prusti + // pub fn try_peek(&mut self) -> Option<&T> { + // todo!() + // } + + #[pure] + #[requires(!self.is_empty())] + pub fn peek(&self) -> &T { + self.lookup(0) + } + + #[trusted] // required due to unsupported reference in enum + #[requires(!self.is_empty())] + #[ensures(snap(result) === old(snap(self.peek())))] + //// ANCHOR: pledge + #[after_expiry( + old(self.len()) === self.len() // (1.) + && forall(|i: usize| 1 <= i && i < self.len() // (2.) + ==> old(snap(self.lookup(i))) === snap(self.lookup(i))) + && snap(self.peek()) === before_expiry(snap(result)) // (3.) + )] + pub fn peek_mut(&mut self) -> &mut T { + //// ANCHOR_END: pledge + // This does not work in Prusti at the moment: + // "&mut self.head" has type "&mut Option" + // this gets auto-dereferenced by Rust into type: "Option<&mut T>" + // this then gets matched to "Some(node: &mut T)" + // References in enums are not yet supported, so this cannot be verified by Prusti + if let Some(node) = &mut self.head { + &mut node.elem + } else { + unreachable!() + } + //// ANCHOR: pledge + // ... + } +} +//// ANCHOR_END: pledge + +//// ANCHOR: assert_on_expiry +impl List { + predicate!{ + fn is_valid(&self) -> bool { + forall(|i: usize| i < self.len() + ==> *self.lookup(i) >= 0 && *self.lookup(i) < 16) + } + } + // ==> { let value = *self.lookup(i); + // 0 <= value && value < 16 }) + + #[trusted] // required due to unsupported reference in enum + #[requires(!self.is_empty())] + #[requires(self.is_valid())] + #[ensures(snap(result) === old(snap(self.peek())))] + #[assert_on_expiry( + 0 <= *result && *result < 16, + self.is_valid() + && old(self.len()) === self.len() // (1.) + && forall(|i: usize| 1 <= i && i < self.len() // (2.) + ==> old(snap(self.lookup(i))) === snap(self.lookup(i))) + && snap(self.peek()) === before_expiry(snap(result)) // (3.) + )] + pub fn peek_mut_16(&mut self) -> &mut i32 { + if let Some(node) = &mut self.head { + &mut node.elem + } else { + unreachable!() + } + } +} +//// ANCHOR_END: assert_on_expiry + +fn test_assert_on_expiry() { + let mut list = List::new(); + list.push(2); + list.push(1); + list.push(0); + { + let first = list.peek_mut_16(); + // *first = 16; // This gives an error: [Prusti: verification error] obligation might not hold on borrow expiry + *first = 15; + } +} + +#[pure] +#[requires(index < link_len(link))] +fn link_lookup(link: &Link, index: usize) -> &T { + match link { + Some(node) => { + if index == 0 { + &node.elem + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), + } +} + +mod prusti_tests { + use super::*; + + fn _test_1(){ + let mut list = List::new(); + prusti_assert!(list.is_empty() && list.len() == 0); + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); + prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 + prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(*list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(*list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0.lookup(0)))); + prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0.lookup(2)))); + prusti_assert!(x3 == old(snap(list_0.lookup(3)))); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1.lookup(0)))); + prusti_assert!(y0 == x3); + } + + fn _test_3() { + let mut list = List::new(); + list.push(16); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + + list.push(5); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 5); + + list.pop(); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + } + + fn _test_4() { + let mut list = List::new(); + list.push(8); + list.push(16); + prusti_assert!(*list.lookup(0) == 16); + prusti_assert!(*list.lookup(1) == 8); + prusti_assert!(list.len() == 2); + + { + let first = list.peek_mut(); + // `first` is a mutable reference to the first element of the list + // for as long as `first` is live, `list` cannot be accessed + prusti_assert!(*first == 16); + *first = 5; + prusti_assert!(*first == 5); + // `first` gets dropped here, `list` can be accessed again + } + prusti_assert!(list.len() == 2); + prusti_assert!(*list.lookup(0) == 5); + prusti_assert!(*list.lookup(1) == 8); + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs b/docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs new file mode 100644 index 00000000000..eec2b941e93 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs @@ -0,0 +1,274 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; +} +//// ANCHOR: pledge +impl List { + //// ANCHOR_END: pledge + #[pure] + pub fn len(&self) -> usize { + link_len(&self.head) + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> &T { + link_lookup(&self.head, index) + } + + #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(snap(self.lookup(0)) === elem)] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) === self.lookup(i + 1)))] + pub fn push(&mut self, elem: T) { + let new_node = Box::new(Node { + elem, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| // Every element will be shifted forwards by one + (1 <= i && i < prev.len()) + ==> prev.lookup(i) === self.lookup(i - 1)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(snap(old(snap(self)).lookup(0))) + )] + pub fn try_pop(&mut self) -> Option { + match self.head.take() { // Replace mem::swap with the buildin Option::take + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> T { + self.try_pop().unwrap() + } + + // // Not currently possible in Prusti + // pub fn try_peek(&mut self) -> Option<&T> { + // todo!() + // } + + #[pure] + #[requires(!self.is_empty())] + pub fn peek(&self) -> &T { + self.lookup(0) + } + + #[trusted] // required due to unsupported reference in enum + #[requires(!self.is_empty())] + #[ensures(snap(result) === old(snap(self.peek())))] + //// ANCHOR: pledge + #[after_expiry( + old(self.len()) === self.len() // (1.) + && forall(|i: usize| 1 <= i && i < self.len() // (2.) + ==> old(snap(self.lookup(i))) === snap(self.lookup(i))) + && snap(self.peek()) === before_expiry(snap(result)) // (3.) + )] + pub fn peek_mut(&mut self) -> &mut T { + //// ANCHOR_END: pledge + // This does not work in Prusti at the moment: + // "&mut self.head" has type "&mut Option" + // this gets auto-dereferenced by Rust into type: "Option<&mut T>" + // this then gets matched to "Some(node: &mut T)" + // References in enums are not yet supported, so this cannot be verified by Prusti + if let Some(node) = &mut self.head { + &mut node.elem + } else { + unreachable!() + } + //// ANCHOR: pledge + // ... + } +} +//// ANCHOR_END: pledge + +#[pure] +#[requires(index < link_len(link))] +fn link_lookup(link: &Link, index: usize) -> &T { + match link { + Some(node) => { + if index == 0 { + &node.elem + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), + } +} + +mod prusti_tests { + use super::*; + + fn _test_1(){ + let mut list = List::new(); + prusti_assert!(list.is_empty() && list.len() == 0); + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); + prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 + prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(*list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(*list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0.lookup(0)))); + prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0.lookup(2)))); + prusti_assert!(x3 == old(snap(list_0.lookup(3)))); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1.lookup(0)))); + prusti_assert!(y0 == x3); + } + + fn _test_3() { + let mut list = List::new(); + list.push(16); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + + list.push(5); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 5); + + list.pop(); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + } + + fn _test_4() { + let mut list = List::new(); + list.push(8); + list.push(16); + prusti_assert!(*list.lookup(0) == 16); + prusti_assert!(*list.lookup(1) == 8); + prusti_assert!(list.len() == 2); + + { + let first = list.peek_mut(); + // `first` is a mutable reference to the first element of the list + // for as long as `first` is live, `list` cannot be accessed + prusti_assert!(*first == 16); + *first = 5; + prusti_assert!(*first == 5); + // `first` gets dropped here, `list` can be accessed again + } + prusti_assert!(list.len() == 2); + prusti_assert!(*list.lookup(0) == 5); + prusti_assert!(*list.lookup(1) == 8); + } +} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs new file mode 100644 index 00000000000..2725fc0c903 --- /dev/null +++ b/docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs @@ -0,0 +1,289 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +use prusti_contracts::*; + +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; + +// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): +// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 + +#[extern_spec] +impl std::option::Option { + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + pub fn unwrap(self) -> T; + + #[pure] + #[ensures(result == matches!(self, None))] + pub const fn is_none(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Some(_)))] + pub const fn is_some(&self) -> bool; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + pub fn take(&mut self) -> Option; +} +//// ANCHOR: peek_mut_code +impl List { + //// ANCHOR_END: peek_mut_code + #[pure] + pub fn len(&self) -> usize { + link_len(&self.head) + } + + #[pure] + fn is_empty(&self) -> bool { + matches!(self.head, None) + } + + #[ensures(result.len() == 0)] + pub fn new() -> Self { + List { head: None } + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> &T { + link_lookup(&self.head, index) + } + + #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(snap(self.lookup(0)) === elem)] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) === self.lookup(i + 1)))] + pub fn push(&mut self, elem: T) { + let new_node = Box::new(Node { + elem, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + predicate! { + // two-state predicate to check if the head of a list was correctly removed + fn head_removed(&self, prev: &Self) -> bool { + self.len() == prev.len() - 1 // The length will decrease by 1 + && forall(|i: usize| // Every element will be shifted forwards by one + (1 <= i && i < prev.len()) + ==> prev.lookup(i) === self.lookup(i - 1)) + } + } + + #[ensures(old(self.is_empty()) ==> + result.is_none() && + self.is_empty() + )] + #[ensures(!old(self.is_empty()) ==> + self.head_removed(&old(snap(self))) + && + result === Some(snap(old(snap(self)).lookup(0))) + )] + pub fn try_pop(&mut self) -> Option { + match self.head.take() { // Replace mem::swap with the buildin Option::take + None => None, + Some(node) => { + self.head = node.next; + Some(node.elem) + } + } + } + + #[requires(!self.is_empty())] + #[ensures(self.head_removed(&old(snap(self))))] + #[ensures(result === old(snap(self)).lookup(0))] + pub fn pop(&mut self) -> T { + self.try_pop().unwrap() + } + + // // Not currently possible in Prusti + // pub fn try_peek(&mut self) -> Option<&T> { + // todo!() + // } + + #[pure] + #[requires(!self.is_empty())] + pub fn peek(&self) -> &T { + self.lookup(0) + } + + // #[requires(index < self.len())] + // pub fn lookup_mut(&mut self, index: usize) -> &mut T { + // let mut curr_node = &mut self.head; + // let mut index = index; // Workaround for Prusti not supporting mutable fn arguments + // while index != 0 { + // body_invariant!(true); + // if let Some(next_node) = curr_node { // reference in enum + // curr_node = &mut next_node.next; + // } else { + // unreachable!(); + // } + // index -= 1; + // } + // if let Some(node) = curr_node { // ERROR: [Prusti: unsupported feature] the creation of loans in this loop is not supported (ReborrowingDagHasNoMagicWands) + // &mut node.elem + // } else { + // unreachable!() + // } + // } + + //// ANCHOR: peek_mut_code + #[trusted] // required due to unsupported reference in enum + #[requires(!self.is_empty())] + #[ensures(snap(result) === old(snap(self.peek())))] + pub fn peek_mut(&mut self) -> &mut T { + // This does not work in Prusti at the moment: + // "&mut self.head" has type "&mut Option" + // this gets auto-dereferenced by Rust into type: "Option<&mut T>" + // this then gets matched to "Some(node: &mut T)" + // References in enums are not yet supported, so this cannot be verified by Prusti + if let Some(node) = &mut self.head { + &mut node.elem + } else { + unreachable!() + } + } +} +//// ANCHOR_END: peek_mut_code + +#[pure] +#[requires(index < link_len(link))] +fn link_lookup(link: &Link, index: usize) -> &T { + match link { + Some(node) => { + if index == 0 { + &node.elem + } else { + link_lookup(&node.next, index - 1) + } + } + None => unreachable!(), + } +} + +#[pure] +fn link_len(link: &Link) -> usize { + match link { + None => 0, + Some(node) => 1 + link_len(&node.next), + } +} + +//// ANCHOR: test_peek_mut +mod prusti_tests { + use super::*; + //// ANCHOR_END: test_peek_mut + + fn _test_1(){ + let mut list = List::new(); + prusti_assert!(list.is_empty() && list.len() == 0); + + list.push(5); + prusti_assert!(!list.is_empty() && list.len() == 1); + prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + + list.push(10); + prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 + prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + + let x = list.pop(); + prusti_assert!(!list.is_empty() && list.len() == 1); // length correct + prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again + prusti_assert!(x == 10); // pop returns the value that was added last + + if let Some(y) = list.try_pop() { + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(y == 5); // correct value inside the `Some` + } else { + unreachable!() // This should not happen, since `try_pop` never returns `None` + } + + let z = list.try_pop(); + prusti_assert!(list.is_empty() && list.len() == 0); // length correct + prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + } + + #[requires(list_0.len() >= 4)] + #[requires(!list_1.is_empty())] + #[requires(*list_0.lookup(1) == 42)] + #[requires(list_0.lookup(3) == list_1.lookup(0))] + fn _test_2(list_0: &mut List, list_1: &mut List) { + let x0 = list_0.pop(); + + list_0.push(10); + prusti_assert!(list_0.len() >= 4); + prusti_assert!(*list_0.lookup(1) == 42); + prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); + prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); + prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); + assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + + let x1 = list_0.pop(); + let x2 = list_0.pop(); + let x3 = list_0.pop(); + prusti_assert!(x0 == old(snap(list_0.lookup(0)))); + prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); + prusti_assert!(x2 == old(snap(list_0.lookup(2)))); + prusti_assert!(x3 == old(snap(list_0.lookup(3)))); + + let y0 = list_1.pop(); + prusti_assert!(y0 == old(snap(list_1.lookup(0)))); + prusti_assert!(y0 == x3); + } + + fn _test_3() { + let mut list = List::new(); + list.push(16); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + + list.push(5); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 5); + + list.pop(); + prusti_assert!(list.peek() === list.lookup(0)); + prusti_assert!(*list.peek() == 16); + } + + //// ANCHOR: test_peek_mut + fn _test_4() { + let mut list = List::new(); + list.push(8); + list.push(16); + prusti_assert!(*list.lookup(0) == 16); + prusti_assert!(*list.lookup(1) == 8); + prusti_assert!(list.len() == 2); + + { + let first = list.peek_mut(); + // `first` is a mutable reference to the first element of the list + // for as long as `first` is live, `list` cannot be accessed + prusti_assert!(*first == 16); + *first = 5; + prusti_assert!(*first == 5); + // `first` gets dropped here, `list` can be accessed again + } + prusti_assert!(list.len() == 2); // Fails + prusti_assert!(*list.lookup(0) == 5); // Fails + prusti_assert!(*list.lookup(1) == 8); // Fails + } +} +//// ANCHOR_END: test_peek_mut \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs b/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs index 57053d56086..f0f548a4834 100644 --- a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs @@ -73,8 +73,8 @@ impl List { #[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)))] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) == self.lookup(i + 1)))] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { elem, @@ -89,9 +89,9 @@ impl List { // two-state predicate to check if the head of a list was correctly removed fn head_removed(&self, prev: &Self) -> bool { self.len() == prev.len() - 1 // The length will decrease by 1 - && forall(|i: usize| + && forall(|i: usize| // Every element will be shifted forwards by one (1 <= i && i < prev.len()) - ==> prev.lookup(i) == self.lookup(i - 1)) // Every element will be shifted forwards by one + ==> prev.lookup(i) == self.lookup(i - 1)) } } //// ANCHOR_END: two_state_predicate diff --git a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs index d6239207bfe..b5246c26c0b 100644 --- a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs @@ -48,8 +48,8 @@ impl List { #[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)))] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) == self.lookup(i + 1)))] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { elem, diff --git a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs b/docs/user-guide/src/tour/tour-src/src/push/final_code.rs index 56c314b6ec2..6ce233e9bb9 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/push/final_code.rs @@ -1,4 +1,5 @@ -//// ANCHOR: all +//// ANCHOR: nothing +//// ANCHOR_END: nothing use prusti_contracts::*; pub struct List { @@ -32,8 +33,8 @@ impl List { #[ensures(self.len() == old(self.len()) + 1)] // 1. Property #[ensures(self.lookup(0) == elem)] // 2. Property //// ANCHOR: shifted_back - #[ensures(forall(|i: usize| (1 <= i && i < self.len()) ==> - old(self.lookup(i - 1)) == self.lookup(i)))] // 3. Property + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) == self.lookup(i + 1)))] // 3. Property pub fn push(&mut self, elem: i32) { // ... //// ANCHOR_END: shifted_back @@ -88,5 +89,4 @@ impl Link { //// ANCHOR: bounds } } -//// ANCHOR_END: bounds -//// ANCHOR_END: all +//// ANCHOR_END: bounds \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs index bbb962f9cd9..6f2eacb68dd 100644 --- a/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs +++ b/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs @@ -61,8 +61,8 @@ impl List { #[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)))] + #[ensures(forall(|i: usize| (i < old(self.len())) ==> + old(self.lookup(i)) == self.lookup(i + 1)))] pub fn push(&mut self, elem: i32) { let new_node = Box::new(Node { elem, @@ -76,9 +76,9 @@ impl List { // two-state predicate to check if the head of a list was correctly removed fn head_removed(&self, prev: &Self) -> bool { self.len() == prev.len() - 1 // The length will decrease by 1 - && forall(|i: usize| + && forall(|i: usize| // Every element will be shifted forwards by one (1 <= i && i < prev.len()) - ==> prev.lookup(i) == self.lookup(i - 1)) // Every element will be shifted forwards by one + ==> prev.lookup(i) == self.lookup(i - 1)) } } @@ -186,10 +186,7 @@ mod prusti_tests { list_0.push(10); prusti_assert!(list_0.len() >= 4); prusti_assert!(list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Note: This cannot be a `prusti_assert`, since `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); diff --git a/docs/user-guide/src/verify/assert_refute_assume.md b/docs/user-guide/src/verify/assert_refute_assume.md index 175be7fb244..464ecc1c270 100644 --- a/docs/user-guide/src/verify/assert_refute_assume.md +++ b/docs/user-guide/src/verify/assert_refute_assume.md @@ -97,6 +97,6 @@ following function: ```rust,noplaypen #[ensures(p == np)] fn proof(p: u32, np: u32) { - prusti_assume!(false); + prusti_assume!(false); } ``` diff --git a/docs/user-guide/src/verify/closure.md b/docs/user-guide/src/verify/closure.md index 17e24ff0f79..615893297f9 100644 --- a/docs/user-guide/src/verify/closure.md +++ b/docs/user-guide/src/verify/closure.md @@ -1,6 +1,8 @@ # Closures > **NOT YET SUPPORTED:** This feature is not yet supported in Prusti. See [PR #138](https://github.com/viperproject/prusti-dev/pull/138) for the status of this feature as well as a prototype. The syntax described here is subject to change. +> +> **NOTE: The syntax for attaching specifications to closures is currently not working** [Rust closures](https://doc.rust-lang.org/book/ch13-01-closures.html) can be given a specification using the `closure!(...)` syntax: From cb1799895ac20c7a8eaf35f492a078f2455a6509 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 21 Feb 2023 14:33:00 +0100 Subject: [PATCH 06/31] Fix repository links --- prusti-contracts/prusti-contracts-proc-macros/Cargo.toml | 2 +- prusti-contracts/prusti-contracts/Cargo.toml | 2 +- prusti-contracts/prusti-specs/Cargo.toml | 2 +- prusti-contracts/prusti-std/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml index 94f64b0e047..5823cf59d92 100644 --- a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml +++ b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "MPL-2.0" description = "Internal `proc-macro` Prusti crate" homepage = "https://www.pm.inf.ethz.ch/research/prusti.html" -repository = "https://github.com/viperproject/prusti-dev/prusti-contracts/prusti-contracts-proc-macros/" +repository = "https://github.com/viperproject/prusti-dev/tree/master/prusti-contracts/prusti-contracts-proc-macros/" readme = "README.md" keywords = ["prusti"] categories = ["development-tools", "development-tools::testing"] diff --git a/prusti-contracts/prusti-contracts/Cargo.toml b/prusti-contracts/prusti-contracts/Cargo.toml index 48598dd507e..93bd3f6dff7 100644 --- a/prusti-contracts/prusti-contracts/Cargo.toml +++ b/prusti-contracts/prusti-contracts/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "MPL-2.0" description = "Tools for specifying contracts with Prusti" homepage = "https://www.pm.inf.ethz.ch/research/prusti.html" -repository = "https://github.com/viperproject/prusti-dev/prusti-contracts/prusti-contracts/" +repository = "https://github.com/viperproject/prusti-dev/tree/master/prusti-contracts/prusti-contracts/" readme = "README.md" keywords = ["prusti", "contracts", "verification", "formal", "specifications"] categories = ["development-tools", "development-tools::testing"] diff --git a/prusti-contracts/prusti-specs/Cargo.toml b/prusti-contracts/prusti-specs/Cargo.toml index 2e4a1f7ea91..0a8f68b301d 100644 --- a/prusti-contracts/prusti-specs/Cargo.toml +++ b/prusti-contracts/prusti-specs/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "MPL-2.0" description = "Internal Prusti crate for parsing specifications" homepage = "https://www.pm.inf.ethz.ch/research/prusti.html" -repository = "https://github.com/viperproject/prusti-dev/prusti-contracts/prusti-specs/" +repository = "https://github.com/viperproject/prusti-dev/tree/master/prusti-contracts/prusti-specs" readme = "README.md" keywords = ["prusti"] categories = ["development-tools", "development-tools::testing"] diff --git a/prusti-contracts/prusti-std/Cargo.toml b/prusti-contracts/prusti-std/Cargo.toml index 22179bce1fb..1b12f2a0235 100644 --- a/prusti-contracts/prusti-std/Cargo.toml +++ b/prusti-contracts/prusti-std/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "MPL-2.0" description = "External specifications for items in std for Prusti" homepage = "https://www.pm.inf.ethz.ch/research/prusti.html" -repository = "https://github.com/viperproject/prusti-dev/prusti-contracts/prusti-std/" +repository = "https://github.com/viperproject/prusti-dev/tree/master/prusti-contracts/prusti-std/" readme = "README.md" keywords = ["prusti", "contracts", "verification", "formal", "specifications"] categories = ["development-tools", "development-tools::testing"] From 34cc7bb88ca21a3444b3337f1ae45f3eb16f6de9 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 28 Feb 2023 15:44:28 +0100 Subject: [PATCH 07/31] Move user-guide code to testing directory and fix mdbook doctest --- docs/user-guide/src/SUMMARY.md | 3 +- docs/user-guide/src/basic.md | 10 + docs/user-guide/src/install.md | 1 + docs/user-guide/src/intro.md | 1 - docs/user-guide/src/syntax.md | 19 +- docs/user-guide/src/tour/code.md | 123 -------- docs/user-guide/src/tour/counterexamples.md | 36 +++ docs/user-guide/src/tour/final.md | 9 +- docs/user-guide/src/tour/generics.md | 14 +- docs/user-guide/src/tour/getting-started.md | 17 +- docs/user-guide/src/tour/loop_invariants.md | 25 ++ docs/user-guide/src/tour/new.md | 37 ++- docs/user-guide/src/tour/option.md | 57 +--- docs/user-guide/src/tour/peek.md | 8 +- docs/user-guide/src/tour/pledges.md | 16 +- docs/user-guide/src/tour/pop.md | 80 +++-- docs/user-guide/src/tour/push.md | 97 +++--- docs/user-guide/src/tour/setup.md | 28 +- docs/user-guide/src/tour/summary.md | 42 ++- docs/user-guide/src/tour/testing.md | 38 ++- docs/user-guide/src/tour/tour-src/Cargo.toml | 11 - docs/user-guide/src/tour/tour-src/Prusti.toml | 2 - .../src/tour/tour-src/src/01-chapter-2-1.rs | 17 - .../src/tour/tour-src/src/02-chapter-2-1.rs | 63 ---- .../src/tour/tour-src/src/04-chapter-2-2.rs | 28 -- .../src/tour/tour-src/src/05-chapter-2-3.rs | 63 ---- .../src/tour/tour-src/src/06-chapter-2-4.rs | 95 ------ .../src/tour/tour-src/src/07-chapter-2-4.rs | 84 ----- .../src/tour/tour-src/src/08-chapter-2-4.rs | 103 ------ .../src/tour/tour-src/src/09-chapter-2-4.rs | 107 ------- .../src/tour/tour-src/src/10-chapter-2-5.rs | 133 -------- .../src/tour/tour-src/src/11-chapter-2-5.rs | 165 ---------- .../src/tour/tour-src/src/12-chapter-2-6.rs | 208 ------------- .../tour-src/src/13-too-many-lists-final.rs | 218 ------------- .../src/tour/tour-src/src/generic.rs | 1 - .../src/tour/tour-src/src/getting_started.rs | 2 - .../tour-src/src/getting_started/failing.rs | 35 --- .../tour-src/src/getting_started/working.rs | 24 -- docs/user-guide/src/tour/tour-src/src/lib.rs | 11 - .../src/loop_invariants/initial_code.rs | 294 ------------------ docs/user-guide/src/tour/tour-src/src/new.rs | 6 - .../src/tour/tour-src/src/option.rs | 1 - docs/user-guide/src/tour/tour-src/src/peek.rs | 1 - .../src/tour/tour-src/src/peek_mut.rs | 3 - docs/user-guide/src/tour/tour-src/src/pop.rs | 2 - docs/user-guide/src/tour/tour-src/src/push.rs | 5 - docs/user-guide/src/tour/tour-src/src/test.rs | 19 -- .../src/tour/tour-src/src/testing.rs | 1 - .../src/verify/assert_refute_assume.md | 6 +- docs/user-guide/src/verify/closure.md | 2 +- docs/user-guide/src/verify/external.md | 4 +- docs/user-guide/src/verify/loop.md | 4 +- docs/user-guide/src/verify/overflow.md | 4 +- docs/user-guide/src/verify/panic.md | 4 +- docs/user-guide/src/verify/pledge.md | 2 +- docs/user-guide/src/verify/predicate.md | 4 +- docs/user-guide/src/verify/prepost.md | 2 +- .../src/verify/print_counterexample.md | 4 +- docs/user-guide/src/verify/pure.md | 2 +- docs/user-guide/src/verify/spec_ent.md | 2 +- docs/user-guide/src/verify/trusted.md | 4 +- docs/user-guide/src/verify/type-models.md | 16 +- docs/user-guide/src/verify/type_cond_spec.md | 4 +- .../tests/verify/fail/user-guide/README.md | 1 + .../verify/fail/user-guide/counterexample.rs | 24 ++ .../user-guide/getting_started_failing.rs | 4 +- .../verify/fail/user-guide/impl_new_spec_1.rs | 10 +- .../verify/fail/user-guide/impl_new_spec_2.rs | 7 +- .../verify/fail/user-guide/impl_new_spec_3.rs | 10 +- .../verify/fail/user-guide/loop_invariants.rs | 19 ++ .../user-guide/option_loops_in_pure_fn.rs | 50 +++ .../fail/user-guide/peek_mut_pledges.rs | 21 +- .../tests/verify/fail/user-guide/pop.rs | 10 +- .../push_property_2_missing_bounds.rs | 8 +- .../user-guide/testing_incorrect_specs.rs | 18 ++ .../fail/user-guide/testing_restrictive_fn.rs | 21 ++ .../tests/verify/pass/user-guide/README.md | 1 + .../pass/user-guide}/assert_on_expiry.rs | 6 +- .../tests/verify/pass/user-guide/generic.rs | 26 +- .../user-guide/getting_started_working.rs | 4 + .../tests/verify/pass/user-guide}/impl_new.rs | 2 + .../pass/user-guide/impl_new_full_code.rs | 13 +- .../pass/user-guide/impl_new_spec_fixed.rs | 4 + .../pass/user-guide/loop_invariants_final.rs | 22 ++ .../tests/verify/pass/user-guide/option.rs | 14 +- .../tests/verify/pass/user-guide/peek.rs | 14 +- .../pass/user-guide/peek_mut_pledges.rs | 25 +- .../tests/verify/pass/user-guide/pop.rs | 10 +- .../verify/pass/user-guide/push_final_code.rs | 4 + .../pass/user-guide/push_initial_code.rs | 4 + .../verify/pass/user-guide/push_property_1.rs | 8 +- .../user-guide/push_property_2_with_bounds.rs | 4 + .../pass/user-guide/testing_initial_code.rs | 16 +- 93 files changed, 666 insertions(+), 2141 deletions(-) delete mode 100644 docs/user-guide/src/tour/code.md create mode 100644 docs/user-guide/src/tour/counterexamples.md create mode 100644 docs/user-guide/src/tour/loop_invariants.md delete mode 100644 docs/user-guide/src/tour/tour-src/Cargo.toml delete mode 100644 docs/user-guide/src/tour/tour-src/Prusti.toml delete mode 100644 docs/user-guide/src/tour/tour-src/src/01-chapter-2-1.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/02-chapter-2-1.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/04-chapter-2-2.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/05-chapter-2-3.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/06-chapter-2-4.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/07-chapter-2-4.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/08-chapter-2-4.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/09-chapter-2-4.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/10-chapter-2-5.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/11-chapter-2-5.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/12-chapter-2-6.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/13-too-many-lists-final.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/generic.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/getting_started.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/getting_started/working.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/lib.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/new.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/option.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/peek.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/peek_mut.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/pop.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/push.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/test.rs delete mode 100644 docs/user-guide/src/tour/tour-src/src/testing.rs create mode 100644 prusti-tests/tests/verify/fail/user-guide/README.md create mode 100644 prusti-tests/tests/verify/fail/user-guide/counterexample.rs rename docs/user-guide/src/tour/tour-src/src/03-fail.rs => prusti-tests/tests/verify/fail/user-guide/getting_started_failing.rs (77%) rename docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs => prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs (65%) rename docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs => prusti-tests/tests/verify/fail/user-guide/impl_new_spec_2.rs (70%) rename docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs => prusti-tests/tests/verify/fail/user-guide/impl_new_spec_3.rs (52%) create mode 100644 prusti-tests/tests/verify/fail/user-guide/loop_invariants.rs create mode 100644 prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs rename docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs => prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs (93%) rename docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs => prusti-tests/tests/verify/fail/user-guide/pop.rs (90%) rename docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs => prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs (83%) create mode 100644 prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs create mode 100644 prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs create mode 100644 prusti-tests/tests/verify/pass/user-guide/README.md rename {docs/user-guide/src/tour/tour-src/src/peek_mut => prusti-tests/tests/verify/pass/user-guide}/assert_on_expiry.rs (97%) rename docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs => prusti-tests/tests/verify/pass/user-guide/generic.rs (88%) rename docs/user-guide/src/tour/tour-src/src/03-chapter-2-1.rs => prusti-tests/tests/verify/pass/user-guide/getting_started_working.rs (77%) rename {docs/user-guide/src/tour/tour-src/src/new => prusti-tests/tests/verify/pass/user-guide}/impl_new.rs (94%) rename docs/user-guide/src/tour/tour-src/src/new/full_code.rs => prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs (73%) rename docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs => prusti-tests/tests/verify/pass/user-guide/impl_new_spec_fixed.rs (80%) create mode 100644 prusti-tests/tests/verify/pass/user-guide/loop_invariants_final.rs rename docs/user-guide/src/tour/tour-src/src/option/initial_code.rs => prusti-tests/tests/verify/pass/user-guide/option.rs (95%) rename docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs => prusti-tests/tests/verify/pass/user-guide/peek.rs (95%) rename docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs => prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs (93%) rename docs/user-guide/src/tour/tour-src/src/pop/final_code.rs => prusti-tests/tests/verify/pass/user-guide/pop.rs (95%) rename docs/user-guide/src/tour/tour-src/src/push/final_code.rs => prusti-tests/tests/verify/pass/user-guide/push_final_code.rs (94%) rename docs/user-guide/src/tour/tour-src/src/push/initial_code.rs => prusti-tests/tests/verify/pass/user-guide/push_initial_code.rs (89%) rename docs/user-guide/src/tour/tour-src/src/push/property_1.rs => prusti-tests/tests/verify/pass/user-guide/push_property_1.rs (91%) rename docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs => prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs (93%) rename docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs => prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs (93%) diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index 00f4f342856..ac72033b072 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -1,6 +1,5 @@ # Summary -- [TODO: Remove this after writing The guided tour](tour/code.md) - [Introduction](intro.md) - [Installation](install.md) - [Basic Usage](basic.md) @@ -16,6 +15,8 @@ - [Peek](tour/peek.md) - [Pledges (mutable peek)](tour/pledges.md) - [Final Code](tour/final.md) + - [Loop Invariants](tour/loop_invariants.md) + - [Counterexample](tour/counterexamples.md) - [Verification Features](verify/summary.md) - [Absence of panics](verify/panic.md) - [Overflow checks](verify/overflow.md) diff --git a/docs/user-guide/src/basic.md b/docs/user-guide/src/basic.md index 934f110e12f..f4eaf0ce792 100644 --- a/docs/user-guide/src/basic.md +++ b/docs/user-guide/src/basic.md @@ -49,6 +49,8 @@ To also verify that `max` indeed always returns the maximum of its two inputs, w that the return value of `max` is at least as large as both `a` and `b` and, additionally, coincides with `a` or `b`: ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; use prusti_contracts::*; #[ensures(result >= a && result >= b)] @@ -75,6 +77,8 @@ Notice that Prusti assumes by default that integer types are bounded; it thus pe Next, we add a second function `max3` which returns the maximum of three instead of two integers; we reuse the already verified function `max` in the new function's specification to show that this function is implemented correctly. ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; use prusti_contracts::*; #[pure] @@ -110,6 +114,10 @@ So far, we only considered programs that meet their specification and that, cons To conclude this example, assume we accidentally return `c` instead of `b` if `b > c` holds: ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[ensures(result == max(a, max(b, c)))] fn max3(a: i32, b: i32, c: i32) -> i32 { if a > b && a > c { @@ -129,6 +137,8 @@ In this case, Prusti will highlight the line with the error and report that the For debugging purposes, it is often useful to add `assert!(...)` macros to our code to locate the issue. For example, in the code below, we added an assertion that fails because `b > c` and thus the maximum of `b` and `c` is `b` instead of `c`. ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; use prusti_contracts::*; #[pure] diff --git a/docs/user-guide/src/install.md b/docs/user-guide/src/install.md index 373982adfc4..6ecd4b71ad8 100644 --- a/docs/user-guide/src/install.md +++ b/docs/user-guide/src/install.md @@ -13,6 +13,7 @@ The easiest way to try out Prusti is by using the ["Prusti Assistant"](https://m > `File → Preferences → Settings → Prusti Assistant → Build Channel`. In case you experience difficulties or encounter bugs while using Prusti Assistant, please [open an issue in Prusti Assistant's repository](https://github.com/viperproject/prusti-assistant/issues) or contact us in the [Zulip chat](https://prusti.zulipchat.com/). +Bugs with Prusti itself can be reported on the [prusti-dev repository](https://github.com/viperproject/prusti-dev/issues). ## Command-line setup diff --git a/docs/user-guide/src/intro.md b/docs/user-guide/src/intro.md index 333ee12e633..7e2113cdeae 100644 --- a/docs/user-guide/src/intro.md +++ b/docs/user-guide/src/intro.md @@ -1,4 +1,3 @@ # Introduction This is the user guide for [Prusti](https://github.com/viperproject/prusti-dev/) - a Rust verifier built upon the the [Viper verification infrastructure](https://www.pm.inf.ethz.ch/research/viper.html). -This guide replaces the [wiki-based tutorial](https://github.com/viperproject/prusti-dev/wiki/Tutorial) from the GitHub repository. diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index 92eb5ae74d6..7eee0239d34 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -21,7 +21,7 @@ When using Prusti, `result` is used to refer to what a function returns. `result` can only be used inside a postcondition, meaning that variables called `result` used in a function need to be renamed. Here is an example for returning an integer: -``` +```rust,noplaypen,ignore use prusti_contracts::*; #[ensures(result == 5)] @@ -31,7 +31,7 @@ fn five() -> i32 { ``` And an example for returning a tuple and accessing individual fields: -``` +```rust,noplaypen,ignore use prusti_contracts::*; #[ensures(result.0 / 2 == result.1 && result.2 == 'a')] @@ -45,8 +45,7 @@ fn tuple() -> (i32, i32, char) { Old expressions are used to refer to the value that a memory location pointed at by a mutable reference had at the beginning of the function: -```rust,noplaypen -# extern crate prusti_contracts; +```rust,noplaypen,ignore use prusti_contracts::*; #[ensures(*x == old(*x) + 1)] @@ -61,7 +60,6 @@ pub fn inc(x: &mut u32) { Implications express a [relationship](https://en.wikipedia.org/wiki/Material_conditional) between two Boolean expressions: ```rust,noplaypen,ignore -# extern crate prusti_contracts; # use prusti_contracts::*; # #[pure] @@ -73,7 +71,6 @@ pub fn is_empty(&self) -> bool; `a ==> b` is equivalent to `!a || b` and `!(a && !b)`. This also extends to the short-circuiting behaviour: if `a` is not true, `b` is not evaluated. ```rust,noplaypen,ignore -# extern crate prusti_contracts; # use prusti_contracts::*; # #[pure] @@ -84,7 +81,9 @@ pub fn is_empty(&self) -> bool; There is also syntax for biconditionals ("if and only if"): -```rust,noplaypen +```rust,noplaypen,ignore +# use prusti_contracts::*; +# #[pure] #[ensures(self.len() == 0 <==> result)] pub fn is_empty(&self) -> bool; @@ -106,7 +105,6 @@ Nonetheless, snapshot equality could be used to compare values of such types, as in the following code: ```rust,noplaypen,ignore -# extern crate prusti_contracts; # use prusti_contracts::*; # #[requires(a === b)] @@ -128,7 +126,7 @@ The function `snap` can be used to take a snapshot of a reference in specificati Its functionality is similar to the `clone` function, but `snap` is only intended for use in specifications. It also does not require the type behind the reference to implement the `Clone` trait. The `snap` function enables writing specifications that would otherwise break Rusts ownership rules: -```rust,noplaypen +```rust,noplaypen,ignore # use prusti_contracts::*; # struct NonCopyInt { @@ -148,8 +146,7 @@ TODO: avoid snap Quantifiers are typically used for describing how a method call changes a container such as a vector: -```rust,noplaypen -# extern crate prusti_contracts; +```rust,noplaypen,ignore # use prusti_contracts::*; # #[requires(0 <= index && index < self.len())] diff --git a/docs/user-guide/src/tour/code.md b/docs/user-guide/src/tour/code.md deleted file mode 100644 index 74d5f5c7ae9..00000000000 --- a/docs/user-guide/src/tour/code.md +++ /dev/null @@ -1,123 +0,0 @@ -# Guided Tour - -> **Disclaimer:** All code shown in this tutorial has been tested with -> [Prusti v-2021-11-22-1738](https://github.com/viperproject/prusti-dev/tree/v-2021-11-22-1738). - -In this chapter, we demonstrate Prusti's capabilities. -As a running example, we roughly follow the first chapters of the Rust tutorial -[Learn Rust with Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). -Linked lists turn out to be sufficiently awful that their implementation and verification -covers most of Rust's and Prusti's essential concepts. -While the above tutorial explains in detail how linked lists are implemented in Rust, -we additionally aim to verify that the implemented list operations are functionally -correct. - -While we assume basic familiarity with Rust, it is possible to learn both -Rust and Prusti at the same time by reading this tour and the -[Rust tutorial](https://rust-unofficial.github.io/too-many-lists/) -that inspired it in an interleaved fashion. -We will provide pointers to the Rust tutorial for beginners to catch up where appropriate. - -Throughout this tour, we will cover the following Prusti concepts: - -- How Prusti reports errors and integrates into the development process -- Runtime errors detected by Prusti -- Writing function-modular specifications -- Using *pure* functions in specifications -- Writing loop invariants -- Using *trusted wrappers* to deal with library code -- Basic troubleshooting - - -The individual chapters are found in the sidebar, which may be collapsed on mobile -devices. -As a quick reference, the main steps of this tour and the involved Prusti features -are as follows: - -1. [Getting Started](getting-started.md): Simple runtime errors catched by Prusti -2. [New](new.md): Postconditions, pure functions -3. [Push](push.md): Preconditions, trusted functions, old expressions, quantifiers -4. [Pop](pop.md): Exercise with similar concepts as for push -5. [Testing](testing.md): More runtime errors catched by Prusti -6. [A Bad Stack](bad-stack.md): Wrap-up of the second chapter of - [Learn Rust with Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). -7. [Options](options.md): Verification with option types -7. [Generics](generics.md): Prusti and generics -8. [Peek](peek.md): Exercise -9. [Final Code](final.md): Final code with solution of exercise -10. [Pledges](pledges.md): Bonus demonstrating Prusti's pledges - - -## Getting started - -Our starting -That is, we implement a simple singly-linked stack and additionally verify -that the involved operations are functionally correct. - - -```bash -$ prusti-rustc --edition=2018 path/to/file.rs -``` - -```rust,noplaypen -#fn max(a: i32, b: i32) -> i32 { - if a > b { - a - } else { - b - } -#} -``` - -```rust,noplaypen -{{#include tour-src/src/01-chapter-2-1.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/02-chapter-2-1.rs:1:}} -``` - - - -```rust,noplaypen -{{#include tour-src/src/04-chapter-2-2.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/05-chapter-2-3.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/06-chapter-2-4.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/07-chapter-2-4.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/08-chapter-2-4.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/09-chapter-2-4.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/10-chapter-2-5.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/11-chapter-2-5.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/12-chapter-2-6.rs:1:}} -``` - -```rust,noplaypen -{{#include tour-src/src/13-too-many-lists-final.rs:1:}} -``` - diff --git a/docs/user-guide/src/tour/counterexamples.md b/docs/user-guide/src/tour/counterexamples.md new file mode 100644 index 00000000000..56d090a85cf --- /dev/null +++ b/docs/user-guide/src/tour/counterexamples.md @@ -0,0 +1,36 @@ +# Counterexamples + +Let's take the summation function from the last chapter, which adds up all the numbers from 1 to `x`. Let's suppose we forgot to add the non-negative postcondition for `x`: + +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/counterexample.rs:code}} +``` + +Attempting to verify this file will result in an error: +```plain +[Prusti: verification error] postcondition might not hold. +``` + +One way to help with debugging such a verification failure, is to have Prusti print a **counterexample**. This can be enabled by adding the `counterexample = true` flag in your `Prusti.toml` file. + +A counterexample is any combination of values, which will cause some postcondition or assertion to fail (there are no guarantees on which values get chosen). + +After running Prusti again with the new setting, we will get an error message like this one: +```plain +[Prusti: verification error] postcondition might not hold. + final_code.rs(12, 1): the error originates here + final_code.rs(12, 14): counterexample for "x" + initial value: ? + final value: -2 + final_code.rs(13, 9): counterexample for "i" + final value: 1 + final_code.rs(14, 9): counterexample for "sum" + final value: 0 + final_code.rs(12, 25): counterexample for result + final value: 0 +``` + +Here we can see that the postcondition does not hold for `x == -2`, which will result in final values of `i == 1`, `sum == 0` and `result == 0`. This should help with finding wrong specifications or bugs in the code, which in this case is allowing negative numbers as an input. + + +Note that verification with `counterexample = true` is slower than normal verification. diff --git a/docs/user-guide/src/tour/final.md b/docs/user-guide/src/tour/final.md index d405a3fc3ec..48b311e76b3 100644 --- a/docs/user-guide/src/tour/final.md +++ b/docs/user-guide/src/tour/final.md @@ -1,3 +1,10 @@ # Final List Code -**TODO** +## Full Code + +```rust,noplaypen +// Expand to see the full code +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs:nothing}} +``` + +The next 2 chapters will explain loop invariants and counterexamples, but we will not use the linked list from our previous chapters. \ No newline at end of file diff --git a/docs/user-guide/src/tour/generics.md b/docs/user-guide/src/tour/generics.md index f20aa53f39e..f56e6ac5f48 100644 --- a/docs/user-guide/src/tour/generics.md +++ b/docs/user-guide/src/tour/generics.md @@ -8,12 +8,12 @@ If you do this process with Prusti, at some point you will encounter the followi ```plain [E0369] binary operation `==` cannot be applied to type `T`. ``` -This is because the generic type `T` might not have an equality function that could be called on it like `i32` does. Since we only used `==` inside of specifications, we can fix this problems by using [snapshot equality `===`](../syntax.md#snapshot-equality) instead. +This is because the generic type `T` might not implement [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) and thus not have an equality function `==` that could be called on it like `i32` does. Since we only used `==` inside of specifications, we can fix this problems by using [snapshot equality `===`](../syntax.md#snapshot-equality) instead. Here you can see where some of the changes where done (expand to see full changes): ```rust,noplaypen -{{#rustdoc_include tour-src/src/generic/initial_code.rs:generic_types}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/generic.rs:generic_types}} ``` This code still fails to compile, this time with an error from the function `link_lookup`: @@ -22,18 +22,18 @@ This code still fails to compile, this time with an error from the function `lin [Note] move occurs because `node.elem` has type `T`, which does not implement the `Copy` trait ``` -To fix this, we will change `List::lookup` and `link_lookup` to return a reference to the element at index `i`, instead of the element itself. This was not needed for `i32`, since it implements the [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) trait. By returning a reference instead, the lookups will work for any type `T`, even if it is not `Copy`! +To fix this, we will change `List::lookup` and `link_lookup` to return a reference to the element at index `i`, instead of the element itself. This was not needed for `i32`, since it implements the [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) trait. For a general type `T`, returning it by value would move it out of the list, which we don't want. By returning a reference instead, the lookups will work for any type `T`, because the element stays in the list. -In addition to returning a reference, we will have to adjust some of the places where `lookup` is used, mostly by dereferencing or using `snap` on the reference: +In addition to returning a reference, we will have to adjust some of the places where `lookup` is used, mostly by dereferencing or using `snap` on the returned reference: ```rust,noplaypen -{{#rustdoc_include tour-src/src/generic/initial_code.rs:lookup_reference}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/generic.rs:lookup_reference}} ``` -After all these changes, Prusti is able to verify the code again, so we now have a linked list that can store any type, not just `i32`! +After all these changes, Prusti is able to verify the code again, so we now our linked list can be used to store elements of any type, not just `i32`! If you want to see the full code after all the changes, expand the following code block. ```rust,noplaypen // Expand to see full code up to this chapter -{{#rustdoc_include tour-src/src/generic/initial_code.rs:nothing}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/generic.rs:nothing}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/getting-started.md b/docs/user-guide/src/tour/getting-started.md index ed82b8eb885..deb2dc234ff 100644 --- a/docs/user-guide/src/tour/getting-started.md +++ b/docs/user-guide/src/tour/getting-started.md @@ -19,14 +19,14 @@ For example, reading up on possible data layouts for lists might be useful for b > > Discusses potential pitfalls and errors when setting up a singly-linked data structure in Rust. -## Stack Layout +## Stack layout Our naïve singly-linked stack is composed of a public structure `List` storing the head of the list, an enum `Link` representing either an empty list or a heap-allocated Node storing the payload—an integer—and the link to the next node: ```rust,noplaypen -{{#include tour-src/src/getting_started/working.rs:1:13}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/getting_started_working.rs:types}} // Prusti: VERIFIES ``` @@ -34,7 +34,7 @@ As explained in the chapter [2.1: Basic Data Layout](https://rust-unofficial.git Moreover, it benefits from the Rust compiler's [null-pointer optimization](https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#discriminant-elision-on-option-like-enums) and makes sure that all list elements are uniformly allocated on the heap. -## Absence of Runtime Errors +## Absence of runtime errors Prusti automatically checks that no statement or macro that causes an explicit runtime error, such as @@ -43,13 +43,13 @@ an explicit runtime error, such as [`unimplemented`](https://doc.rust-lang.org/std/macro.unimplemented.html), [`todo`](https://doc.rust-lang.org/std/macro.todo.html), or possibly a failing [assertion](https://doc.rust-lang.org/std/macro.assert.html), -is reachable. [Prusti assertions](../syntax.md#prusti-assertions) are also checked. These are like the normal `assert` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks when compiled normally. +is reachable. [Prusti assertions](../verify/assert_assume.md) are also checked. These are like the normal `assert` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks or code when compiled normally. For example, the following test function creates a node with no successor and panics if the node's payload is greater than 23: ```rust,noplaypen -{{#rustdoc_include tour-src/src/getting_started/working.rs:15:24}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/getting_started_working.rs:code}} // Prusti: VERIFIES ``` Prusti successfully verifies the above function @@ -59,7 +59,7 @@ whenever execution reaches the `if` statement. This is not the case for the following function in which the test node is initialized with an arbitrary integer: ```rust,noplaypen -{{#rustdoc_include tour-src/src/getting_started/failing.rs:26:35}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/getting_started_failing.rs:failing_code}} // Prusti: FAILS ``` @@ -68,12 +68,9 @@ Prusti reports errors in the same fashion as the Rust compiler (although with th is: ```plain -error: [Prusti: verification error] panic!(..) statement might be reachable +error: [Prusti: verification error] statement might panic --> getting_started_failing.rs:33:9 | 33 | panic!() | ^^^^^^^^ - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -Verification failed ``` diff --git a/docs/user-guide/src/tour/loop_invariants.md b/docs/user-guide/src/tour/loop_invariants.md new file mode 100644 index 00000000000..f3690e267b4 --- /dev/null +++ b/docs/user-guide/src/tour/loop_invariants.md @@ -0,0 +1,25 @@ +# Loop Invariants + +To show how to verify loops, we will use a different example than our linked list for simplicity. +We will write and verify a function that can add some value to every element of an array slice. + +Lets write a function that takes an integer `x` and sums up all values from 0 to that value in a loop. +For non-negative inputs, the result will be equal to `x * (x + 1) / 2`: + +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/loop_invariants.rs:specification}} +// Prusti: fails +``` + +We cannot verified this code yet, because Prusti does not know what the `while` loop does to `sum` and `i`. For that, we need to add a [`body_invariant`](../verify/loop.md). Body invariants are expressions that always hold at the beginning and end of the loop body. In our case, the invariant is that `sum` contains the sum of all values between 1 and `i`. Since `i` starts at 1 and not at 0, we have to slightly adjust the formula by using `i - 1` instead of `i`, so we get: `sum == (i - 1) * i / 2`. + +After adding the `body_invariant`, we get this code: + +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/loop_invariants_final.rs:specification}} +// Prusti: verifies +``` + +This body invariant is enough to verify the postcondition. After the loop, `i == x + 1` will hold. Plugging this into our `body_invariant!(sum == (i - 1) * i / 2)`, we get `sum == x * (x + 1) / 2`, which is our postcondition. + +Note that we did not have to add `body_invariant!(1 <= i && i <= x)`. Prusti can sometimes figure out the correct range for `i` by itself, as long as at least one other `body_invariant` is present in the loop. diff --git a/docs/user-guide/src/tour/new.md b/docs/user-guide/src/tour/new.md index 61813fb6ae2..175f021d625 100644 --- a/docs/user-guide/src/tour/new.md +++ b/docs/user-guide/src/tour/new.md @@ -8,16 +8,16 @@ > writing simple static functions; > Rust's ownership system. -## Implementing New +## Implementing `new` We first provide a static function to create empty lists: ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/impl_new.rs:impl_new}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new.rs:impl_new}} // Prusti: VERIFIES ``` -## A First Specification +## A first specification What would be a sensible first specification for `new()`? We could attempt to verify the list returned by `new()` is always empty. @@ -25,10 +25,10 @@ In other words, the length of the returned list is always zero. To express this property, we first implement a length method for lists which itself calls an auxiliary length method implemented for `Link`. For simplicity, we will not actually compute the length of a `Link` yet. -Rather, we will just always return 0. +Rather, we will just always return 0. The return type for the `len` functions is [`usize`](https://doc.rust-lang.org/std/primitive.usize.html), which is a pointer-sized unsigned integer (e.g., 64 bits on a 64-bit computer). `usize` is also used in the [`Vec::len`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.len) function in the standard library. ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/spec_failing_1.rs:first_spec_1}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs:first_spec_1}} // Prusti: VERIFIES ``` @@ -39,7 +39,7 @@ That is, we attach the [postcondition](../verify/prepost.md) `result.len() == 0` to the function `new()`. The special variable [`result`](../syntax.md#result-variable) is used in Prusti specifications to refer to the value that is returned by a function: ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/spec_failing_1.rs:first_spec_2}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs:first_spec_2}} ``` Unfortunately, Prusti—or rather: the Rust compiler—will complain about @@ -60,7 +60,7 @@ Before we can use these specifications, we need to make the path to these macros and attributes visible: ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/spec_failing_2.rs:import_prusti}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/impl_new_spec_2.rs:import_prusti}} ``` Declaring that we use the `prusti_contracts` crate removes the compiler error but @@ -86,13 +86,13 @@ After adding the `#[pure]` attribute to our `List::len()` method, it is allowed appear in Prusti specifications: ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/spec_failing_3.rs:pure_annotation}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/impl_new_spec_3.rs:pure_annotation}} ``` However, Prusti still won't verify! It produces the same error but now it refers to the *body* of `len()`: -```markdown +```plain error: [Prusti: invalid specification] use of impure function "Link::len" in pure code is not allowed --> list.rs:30:9 | @@ -102,18 +102,18 @@ error: [Prusti: invalid specification] use of impure function "Link::len" in pur Whenever we add the attribute `#[pure]` to a function, Prusti will check whether that function is indeed deterministic and side-effect free -(notice that termination is *not* checked); otherwise, it complains. +(notice that [termination](../limitations.md#termination-checks-total-correctness-missing) is *not* checked); otherwise, it complains. In this case, Prusti complains because we call an impure function, namely `Link::len()`, within the body of the pure function `List::len()`. To fix this issue, it suffices to mark `Link::len()` as pure as well. ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/spec_fixed.rs:pure_annotation}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new_spec_fixed.rs:pure_annotation}} // Prusti: VERIFIES ``` -```markdown +```plain $ cargo-prusti // ... Successful verification of 4 items @@ -128,20 +128,22 @@ this is hardly surprising since `len()` ultimately always returns 0.) We will now properly implement `len()`, and while we're at it, `is_empty()` for `Link`. Both of them are pure functions, so we will add the `#[pure]` annotation. Both functions can be called without any restrictions, so they have the default postcondition `#[requires(true)]`, which we don't have to add manually. We also don't need to add any additional postconditions, since pure functions will be inlined wherever they are used during verification. ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/full_code.rs:31:44}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs:implementation}} ``` +Here we use the [`matches` macro](https://doc.rust-lang.org/std/macro.matches.html) in `is_empty`, which is true if and only if the first argument matches the pattern in the second argument. + We can now check if the specification is working, by writing a function that panics if the specification is wrong: ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/full_code.rs:46:50}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs:test_len}} ``` The last line asserts, that the `is_empty` function only returns `true`, if the `len` function returns `0`. -And Prusti can verify it! Now we know that this assert statement can never fail, no matter what `Link` is passed to the test function. +And Prusti can verify it! Now we know that this assert statement holds for any `link` that is passed to the `test_len` function. ### Overflow checks -Here you can also see why we disabled overflow checking for this tutorial. If you remove the `check_overflows = false` setting in the `Prusti.toml` file, and then try to verify the crate again, you will get an error: +Here you can also see why we disabled overflow checking for this tutorial. If you remove the `check_overflows = false` flag in the `Prusti.toml` file, and then try to verify the crate again, you will get an error: ```plain [Prusti: verification error] assertion might fail with "attempt to add with overflow" Link::More(node) => 1 + node.next.len(), @@ -156,5 +158,6 @@ It should successfully verify with Prusti and we will further extend it througho the next four chapters. ```rust,noplaypen -{{#rustdoc_include tour-src/src/new/full_code.rs}} +// Expand to see full code up to this chapter +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs:nothing}} ``` diff --git a/docs/user-guide/src/tour/option.md b/docs/user-guide/src/tour/option.md index 296faf6ed96..e022c504d3f 100644 --- a/docs/user-guide/src/tour/option.md +++ b/docs/user-guide/src/tour/option.md @@ -13,67 +13,24 @@ type Link = Option>; In order to use the `Option::take` function, we also have to implement the `extern_spec` for it. As you can see, it is quite similar to the `extern_spec` for `mem::replace`, since `take` does the same as `replace(&mut self, None)`: ```rust,noplaypen -{{#rustdoc_include tour-src/src/option/initial_code.rs:option_take_extern_spec}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:option_take_extern_spec}} ``` -These changes require some adjustments of the code and specifications. With the new type alias for `Link`, we cannot have an `impl` block anymore, so our `lookup` and `len` functions on `Link` are now normal, non-associated functions: +Changing the `Link` type requires some adjustments of the code and specifications. With the new type alias for `Link`, we cannot have an `impl Link` block anymore, so our `lookup` and `len` functions on `Link` are now normal, non-associated functions: ```rust,noplaypen -{{#rustdoc_include tour-src/src/option/initial_code.rs:rewrite_link_impl}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:rewrite_link_impl}} ``` -Due to current [limitations of Prusti](../limitations.md), we cannot replace our `len` and `lookup` functions with loops: +Due to current [limitations of Prusti](../limitations.md#loops-in-pure-functions-unsupported), we cannot replace our `link_len` and `link_lookup` functions with loops: ```rust,noplaypen,ignore -# use prusti_contracts::*; -# -# pub struct List { -# head: Link, -# } -# -# type Link = Option>; -# -# struct Node { -# elem: i32, -# next: Link, -# } -# -impl List { - // Prusti cannot verify these functions at the moment, - // since loops in pure functions are not yet supported: - #[pure] - pub fn len(&self) -> usize { - let mut curr = &self.head; - let mut i = 0; - while let Some(node) = curr { -# body_invariant!(true); - i += 1; - curr = &node.next; - } - i - } - - #[pure] - #[requires(index < self.len())] - pub fn lookup(&self, index: usize) -> i32 { - let mut curr = &self.head; - let mut i = index; - while let Some(node) = curr { -# body_invariant!(true); - if i == 0 { - return node.elem; - } - i -= 1; - curr = &node.next; - } - unreachable!() - } -} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs:code}} ``` Since Prusti doesn't fully support closures yet, we also cannot do the rewrite to use the `Option::map` function: ```rust,noplaypen -{{#rustdoc_include tour-src/src/option/initial_code.rs:try_pop_rewrite}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:try_pop_rewrite}} ``` After all the changes done in this chapter, Prusti is still be able to verify the code, so we didn't break anything. @@ -81,5 +38,5 @@ If you want to see the full code after all the changes, expand the following cod ```rust,noplaypen // Expand to see full code up to this chapter -{{#rustdoc_include tour-src/src/option/initial_code.rs:nothing}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:nothing}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/peek.md b/docs/user-guide/src/tour/peek.md index 364ac321cb9..833b3930ff0 100644 --- a/docs/user-guide/src/tour/peek.md +++ b/docs/user-guide/src/tour/peek.md @@ -8,21 +8,21 @@ Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and We can still implement `peek`, but we just cannot do it by using `try_peek` like before in `pop`. Instead, we can reuse th already implemented and verified `lookup` function! Since `lookup` can return a reference to any element of the list, we can just call `self.lookup(0)` inside of `peek`: ```rust,noplaypen -{{#rustdoc_include tour-src/src/peek/initial_code.rs:implementation}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek.rs:implementation}} ``` We can also write a test again, to see if our specification holds up in actual code: ```rust,noplaypen -{{#rustdoc_include tour-src/src/peek/initial_code.rs:test_peek}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek.rs:test_peek}} ``` This verifies too, so it appears our implementation of `peek` is correct. -The `peek` method only returns an immutable reference, but what if you want to change elements of the list? We will see how in the next chapter. +The `peek` method only returns an immutable reference, but what if you want to get a mutable reference? We will see how in the next chapter. Here you can see the full code we have now: ```rust,noplaypen // Expand to see full code up to this chapter -{{#rustdoc_include tour-src/src/peek/initial_code.rs:nothing}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek.rs:nothing}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/pledges.md b/docs/user-guide/src/tour/pledges.md index b5171cab559..53ac65f812b 100644 --- a/docs/user-guide/src/tour/pledges.md +++ b/docs/user-guide/src/tour/pledges.md @@ -1,7 +1,7 @@ # Pledges -Now we will look at [`pledges`](../verify/pledge.md). Pledges are used for functions that return mutable reference into some datastructure. -They explain to Prusti how the original object gets affected by changes to the returned reference. +Now we will look at [`pledges`](../verify/pledge.md). Pledges are used for functions that return mutable references into some datastructure. +With a pledge you can explain to Prusti how the original object gets affected by changes to the returned reference. We will demonstrate it by implementing a function that gives you a mutable reference to the first element in the list: ## Implementing `peek_mut` @@ -12,7 +12,7 @@ As a first postcondition, we want to ensure that the `result` of `peek_mut` poin In the code, we need to get a mutable reference to the type inside the `Link = Option>>`. This requires the use of the type `Option<&mut T>`, which is a structure containing a reference, so it is not yet supported by Prusti (see [limitations chapter](../limitations.md)). To still be able to verify `peek_mut`, we mark it as `trusted` for now: ```rust,noplaypen -{{#rustdoc_include tour-src/src/peek_mut/initial_code.rs:peek_mut_code}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs:peek_mut_code}} ``` Note that `peek_mut` cannot be `#[pure]`, since it returns a mutable reference. @@ -29,10 +29,10 @@ Lets write a test to see if our specification works: - Is the second element 8? ```rust,noplaypen -{{#rustdoc_include tour-src/src/peek_mut/initial_code.rs:test_peek_mut}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs:test_peek_mut}} ``` -But this fails, Prusti cannot verify any of our last three `prusti_assert` statements. This is where `pledges` come in. We have to tell Prusti how the `result` affects the original list. Without this, Prusti assumes that the reference can change anything about the original list, so nothing can be known about it after the reference gets dropped. +But this fails, Prusti cannot verify any of our last three `prusti_assert` statements. This is where `pledges` come in. We have to tell Prusti how the `result` affects the original list. Without this, Prusti assumes that changes to the reference can change every property of the original list, so nothing can be known about it after the reference gets dropped. ## Writing the pledge @@ -40,7 +40,7 @@ The pledge gets written with an annotation like for `ensures` and `requires`, bu Inside we have all the conditions that hold after the returned reference gets dropped: ```rust,noplaypen -{{#rustdoc_include tour-src/src/peek_mut/final_code.rs:pledge}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs:pledge}} ``` We have 3 conditions here: @@ -58,7 +58,7 @@ As an example, we could use this to make sure that our list of `i32` can only co Given that this invariant held before the reference was given out, it will hold again if the changed element is still in the correct range: ```rust,noplaypen,ignore -{{#rustdoc_include tour-src/src/peek_mut/assert_on_expiry.rs:assert_on_expiry}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs:assert_on_expiry}} ``` The syntax here is `#[assert_on_expiry(condition, invariant)]`. This means that the `invariant` holds, given that `condition` is true when the reference expires. @@ -69,5 +69,5 @@ Note that for some condition `A`, `after_expiry(A)` is equal to `assert_one_expi ```rust,noplaypen // Expand to see full code up to this chapter -{{#rustdoc_include tour-src/src/peek_mut/final_code.rs:nothing}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs:nothing}} ``` diff --git a/docs/user-guide/src/tour/pop.md b/docs/user-guide/src/tour/pop.md index f142cb82ecb..584b91c75e8 100644 --- a/docs/user-guide/src/tour/pop.md +++ b/docs/user-guide/src/tour/pop.md @@ -3,16 +3,17 @@ > **Recommended reading:** > [2.5: Pop](https://rust-unofficial.github.io/too-many-lists/first-pop.html), -Let's continue with a function to remove and return one element from the top of a list. The way to write such a function is described in [2.5: Pop](https://rust-unofficial.github.io/too-many-lists/first-pop.html), we will focus on the verification in this chapter. +Let's continue with a function to remove and return the element at the head of a list. The way to write such a function is described in [2.5: Pop](https://rust-unofficial.github.io/too-many-lists/first-pop.html), we will focus on the verification in this chapter. -We will rename the `pop` function to `try_pop`. The return type is still `Option` and `try_pop` will only return `Some(item)` if the list has elements, and `None` otherwise. We will then add a new `pop` function, which has the return type `i32`, and will panic if it is called with an empty list. However, by using the correct precondition, we can prevent the `pop` function to ever panic! Here is the code: +There is one change to the code from the original tutorial: +We will rename the `pop` function to `try_pop`. The return type is still `Option` and `try_pop` will return `Some(item)` if the list has elements, and `None` otherwise. We will then add a new `pop` function, which has the return type `i32`, and will panic if it is called with an empty list. However, by using the correct precondition, we can prevent the `pop` function from ever panicking! Here is the code: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/initial_code.rs:initial}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/pop.rs:initial}} ``` -For the implementation of our `pop` method, we can reuse the implementation of `try_pop`. We call `try_pop` on the list, then call `unwrap` on the result. `unwrap` will return the inner value of the `Option` if it is `Some`, and will panic otherwise. -Normally, it is bad form to use `unwrap` in production code, where you should handle potential errors, instead of just panicing. +For the implementation of our `pop` method, we can reuse the implementation of `try_pop`. We call `try_pop` on the list, then call [`unwrap`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap) on the result. `unwrap` will return the inner value of the `Option` if it is `Some`, and will panic otherwise. +Normally, it is bad form to use `unwrap` in production code, where you should handle potential errors, instead of just panicking. But, since we are verifying that there will never be `None` passed to `unwrap`, we should be able to get away with it here. ## Properties of `try_pop` @@ -21,14 +22,13 @@ Lets start by (informally) listing the properties we want our `try_pop` method t We do not need a precondition for `try_pop`, since it can be called on any list. This just leaves all the postconditions, which can be put into two categories: -- The input list is empty before the call +- If the input list is empty before the call: 1. The `result` will be `None` - 2. The list will still be empty -- The input list is not empty before the call - 1. The `result` will not be `None` - 2. The `result` will contain the first value of the old list - 3. The length will get reduced by one - 4. All elements will be shifted forwards by one + 2. The list will still be empty afterwards +- If the input list is not empty before the call: + 1. The `result` will be `Some(value)` and `value` is the element that was the first element of the list + 2. The length will get reduced by one + 3. All elements will be shifted forwards by one ## Properties of `pop` @@ -39,51 +39,65 @@ The postconditions are similar to those of `try_pop`, but we can skip all those 2. The length will get reduced by one 3. All elements will be shifted forwards by one -## Implementing the specification +## Preparations ### Adding `List::is_empty` -Since we will need to check if a list is empty, we can implement a `#[pure]` `is_empty` function for List. It can just call the `is_empty` function on `self.head`: +Since we will need to check if a list is empty, we can implement a `#[pure]` function `is_empty` for `List`. It can just call the `is_empty` function on `self.head`: + +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/pop.rs:is_empty}} +``` + +### Writing the external specifications for `Option` + +Since we use `Option::unwrap`, we will need an external specification for it. While we're at it, lets also write the `#[extern_spec]` for `Option::is_some` and `Option::is_none`: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/initial_code.rs:is_empty}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:extern_spec}} ``` -### Precondition of `pop` +The syntax for writing external specifications for functions associated with `Option` is slightly different to that of `std::mem::replace`, which was a standalone function. + +Note: In the future, you should just be able to import these external specifications using the [`prusti-std` crate](https://crates.io/crates/prusti-std). It should be available after [this PR](https://github.com/viperproject/prusti-dev/pull/1249) is merged. Until then, you can find the work in progress specifications in the PR (e.g., for [`Option::unwrap`](https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49)). + +## Implementing the specification + +### Writing the precondition Let's start our specification with the precondition of `pop`. Since the `unwrap` will panic if it is passed `None`, and `try_pop` returns `None` if the list is empty, we have to ensure that `pop` is only called on non-empty lists. Therefore we add it as a precondition: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/initial_code.rs:pop_precondition}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/pop.rs:pop_precondition}} ``` -`try_pop` does not require a precondition, since it will not panic on an empty list, but instead just return `None`. +`try_pop` does not require a precondition, since it will never panic. ### `try_pop` postcondition for empty lists Now we will implement the two conditions that hold for `try_pop` if you pass an empty list to it. -To ensures that these are only checked for empty lists, we use the implication operator `==>`: +To ensures that these are only checked for empty lists, we use the implication operator [`==>`](../syntax.md#implications): ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/final_code.rs:try_pop_empty}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:try_pop_empty}} ``` This specification ensures that for empty lists, the list will still be empty afterwards, and `try_pop` will return `None`. ### Checking if the correct result is returned -Now we can add the specification for checking if the correct result is returned. Like with `push`, we will use the `lookup` function for checking that the `result` is the old head of the list. For this we call `lookup(0)` on a snapshot of `self` before the function call: `old(snap(self)).lookup(0)`. +Now we can add the specification for checking if the correct result is returned. Like with `push`, we will use the `lookup` function to check that `result` was the first element of the list. For this we call `lookup(0)` on a snapshot of `self` before the function call: `old(snap(self)).lookup(0)`. -We can check this condition for snapshot equality with `result`. This will always hold for `pop`, since the list is never empty: +We can check this condition for snapshot equality ([`===`](../syntax.md#snapshot-equality)) with `result`. This will always hold for `pop`, since the list is never empty: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/final_code.rs:pop_result_correct}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:pop_result_correct}} ``` For `try_pop`, the condition only holds if the list was *not* empty before the call. In addition, the `result` is an `Option::Some`, so we will have to include this in our postcondition: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/final_code.rs:try_pop_result_correct}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:try_pop_result_correct}} ``` @@ -94,6 +108,8 @@ You may have noticed that the last two conditions for `pop` are the same as the A `predicate` is basically just a [`pure`](../verify/pure.md) function that returns a `bool`, but it can use all the additional syntax available in Prusti specifications. Lets look at an example: ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; # use prusti_contracts::*; # predicate! { @@ -134,7 +150,7 @@ For this we need check two properties: We combine these two properties into a single expression using `&&`: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/final_code.rs:two_state_predicate}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:two_state_predicate}} ``` Here we are able to call `.len()` and `.lookup()` on both references, because they are pure functions. @@ -142,19 +158,15 @@ Here we are able to call `.len()` and `.lookup()` on both references, because th To use this predicate, we call it on the list `self`, and then pass in a snapshot of the `self` from before the function call. Like with the condition for correctness of the `result`, we can just add this `predicate` to `pop`, but we need to restrict it to non-empty lists for `try_pop`: ```rust,noplaypen -{{#rustdoc_include tour-src/src/pop/final_code.rs:predicate_use}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:predicate_use}} ``` -## Summary - -We have now implemented the entire specification we formulated at the start of this chapter! -The list should now be able to correctly `push`, `pop`, `try_pop` elements, as described in our specifications. - -But how do we know that our specifications match the behavior of the code? We will look at this in the next chapter. +Prusti can now successfully verify the postconditions of both `try_pop` and `pop`, and ensure that they will not panic! +But there might still be a chance that our specifications don't fully match what we expect the code to do, so we will look at how to test your specifications in the next chapter. -If you want the full code we have now, expand the code block below: +## Full Code Listing ```rust,noplaypen // Expand to see full code up to this chapter -{{#rustdoc_include tour-src/src/pop/final_code.rs:none}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:none}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/push.md b/docs/user-guide/src/tour/push.md index 19a695214c3..8d41e9636eb 100644 --- a/docs/user-guide/src/tour/push.md +++ b/docs/user-guide/src/tour/push.md @@ -23,9 +23,9 @@ Since `push` modifies `self`, it cannot be marked as a `#[pure]` function. This Before we implement `push`, let us briefly think of possible specifications. Ideally, our implementation satisfies at least the following properties: -1. Executing `push` increases the length of the underlying list by one. -2. After `push(elem)` the first element of the list stores the value `elem`. -3. After executing `push(elem)`, the elements of the original list remain unchanged (just moved back by 1). +1. Executing `push` increases the length of the underlying list by one. [(Chapter link)](push.md#first-property) +2. After `push(elem)` the first element of the list stores the value `elem`. [(Chapter link)](push.md#second-property) +3. After executing `push(elem)`, the elements of the original list remain unchanged (just moved back by 1). [(Chapter link)](push.md#third-property) ## Initial code @@ -33,8 +33,8 @@ We start out with an implementation of `push`. If you want to learn more about h Here is our initial code: -```rust,noplaypen,ignore -{{#rustdoc_include tour-src/src/push/initial_code.rs:initial_code}} +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_initial_code.rs:initial_code}} ``` ## First property @@ -43,7 +43,7 @@ The first property can easily be expressed as a postcondition that uses the pure method `len` introduced in the [previous chapter](new.md): ```rust,noplaypen -{{#rustdoc_include tour-src/src/push/property_1.rs:property_1}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:property_1}} ``` Even though the above implementation of `push` is correct, attempting to verify it with Prusti still yields a verification error: @@ -63,33 +63,37 @@ function's implementation) whenever it encounters a function call. The only exception are *pure* functions, such as `len`, where Prusti also takes the function body into account. + + +### Adding external specifications to library code + In our case, the function `std::mem::replace` is neither marked as `pure` nor does it come with a specification. Hence, Prusti assumes that it is memory safe and nothing else. -That is, Prusti uses `true` as both pre- and postcondition of `std::mem::replace`, -which is too weak to prove the specification of `push`: according to its specification, -`std::mem::replace` could arbitrarily change the original list and thus also its length. +That is, Prusti uses `true` as both pre- and postcondition of `replace`, +which is too weak to prove the specification of `push`. According to its specification, +`replace` could arbitrarily change the original list and thus also its length. Hence, we cannot conclude that the length the list returned by -`mem::replace(&mut self.head, Link::Empty)` coincides with the length of the original +`replace(&mut self.head, Link::Empty)` coincides with the length of the original list. -We can remedy this issue by strengthening the specification of `std::mem::replace`. +We can remedy this issue by strengthening the specification of `replace`. In this tutorial, we will assume that the standard library is correct, that is, we do not attempt to verify specifications for functions in external crates, -like `std::mem::replace`. To this end, we have to add the specification to the function. +like `replace`. To this end, we have to add the specification to the function. This can be done with another piece of Prusti syntax, the [extern_spec](../verify/external.md): -```rust,noplaypen,ignore -{{#rustdoc_include tour-src/src/push/property_1.rs:extern_spec}} +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:extern_spec}} ``` -Lets break this code down step by step. +Lets break this code down step by step: - First we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. This requires `prusti_contracts::*` to be imported first. - Next we need to figure out where the function is located. In this case it is `std::mem`, which we then put as the parameter in `#[extern_spec(std::mem)]` - After a quick search for *\"rust std mem replace\"* we can find the [documentation for std::mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html). Here we can get the function signature: `pub fn replace(dest: &mut T, src: T) -> T`. We then write down the signature in the inner module, followed by a `;`. - Since there are no preconditions to `replace`, we can use the (implicit) default `#[requires(true)]`. - For writing the postcondition, we use four pieces of Prusti syntax: - - `===` is called **snapshot equality** or **logical equality**. Is essentially checks if the left and right sides are structurally equal. More details can be seen [here](../syntax.md#snapshot-equality). `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. - - The [`snap()`](../syntax.md#snap-function) function takes a snapshot of a reference. It has a similar functionality to the `clone()` method, but does not require the type of the reference it is called on to implement the `Clone` trait. `snap` should only be used in specifications, since it ignores the borrow checker. + - [`===`](../syntax.md#snapshot-equality) is called **snapshot equality** or **logical equality**. Is essentially checks if the left and right sides are structurally equal. `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. + - The [`snap()`](../syntax.md#snap-function) function takes a snapshot of a reference. It has a similar functionality to the [`clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) method, but does not require the type of the reference it is called on to implement the `Clone` trait. `snap` should only be used in specifications, since it ignores the borrow checker. - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state of `snap(dest)` from before the function was called. - The identifier [`result`](../syntax.md#result-variable) is used to refer to the return parameter of the function. - The postcondition consists of two parts, which can either be written in one condition with `&&`, or in multiple `#[ensures(...)]` annotations like in the example above. @@ -99,21 +103,27 @@ Lets break this code down step by step. Since `result` is structurally equal to `dest` from before the function call, Prusti knows that the pure function `len()` called on `result` returns the same value as it would have for `dest`. -An important thing to note here is the Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. +An important thing to note here is that Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. ### Future -There is currently new functionality planed for Prusti-assistant, which should enable the user to automatically generate parts of the `extern_spec` syntax. +There is currently new functionality planned for Prusti-assistant, which should enable the user to automatically generate parts of the `extern_spec` syntax. There is also work being done for providing external specifications for the Rust standard library. Depending on when you are reading this, the `std::mem::replace` function might be annotated already, in that case this `extern_spec` may not be needed anymore. -You can track the progress and find some already completed specifications [in this Pull Request](https://github.com/viperproject/prusti-dev/pull/1249), +You can track the progress and find some already completed specifications [in this Pull Request](https://github.com/viperproject/prusti-dev/pull/1249). -## Trusted Functions +Specifications for the standard library should eventually be available in the [prusti-std crate](https://crates.io/crates/prusti-std). Any specifications in this crate will be available by adding it to your project's dependencies. + +## Trusted functions As mentioned above, `extern_specs` are implicitly `#[trusted]` by Prusti. Trusted functions can be used for verifying projects containing external code that does not have Prusti annotations, or projects using Rust features that are not yet supported by Prusti. An example is printing a string slice (not supported yet): ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[trusted] fn print(s: &str) { println!("{s}"); @@ -125,6 +135,8 @@ require external justification. For example, the following function will not cause the verification to fail: ```rust,noplaypen,norun +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; # use prusti_contracts::*; # #[trusted] @@ -133,10 +145,12 @@ fn incorrect_function() -> i32 { } ``` -This one is even worse, this will enable anything to be verified: +This one is even worse, it will enable anything to be verified: ```rust,noplaypen,norun +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; # use prusti_contracts::*; -# +# #[trusted] #[ensures(false)] fn wrong() {} @@ -144,19 +158,18 @@ fn wrong() {} ### Checking the `extern_spec` -Let's get back to our code. After adding the external specification for `std::mem::replace`, we can finally verify the `push` function: +Let's get back to our code. After adding the external specification for `std::mem::replace`, we can finally verify the first property of our `push` function: ```rust,noplaypen -{{#rustdoc_include tour-src/src/push/property_1.rs:property_1}} - +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:property_1}} // Prusti: Verifies ``` -With this, the first or our three property of `push` is verified, but we still have 2 more to prove. +With this, the first of our three property of `push` is verified, but we still have 2 more to prove. ## Second property -Recall that the second property of our specification informally reads as follows: +Recall the second property of our specification: > 2. After `push(elem)` the first element of the list stores the value `elem`. @@ -166,27 +179,27 @@ Our second desired property then corresponds to the postcondition `self.lookup(0) == elem`. ```rust,noplaypen -{{#rustdoc_include tour-src/src/push/property_2_missing_bounds.rs:lookup}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs:lookup}} ``` Consider the `match` statement in the last function. The Rust compiler will complain if we attempt to omit the case `Link::Empty`. -Since there is no sensible implementation of `lookup` if the underlying list is empty, -we used the macro `unreachable!()`, which will crash the program with a panic. +We have no sensible implementation of `lookup` if the underlying list is empty, +so we used the macro `unreachable!()`, which will crash the program with a panic. Since nothing prevents us from calling `lookup` on an empty list, Prusti complains: ```plain -unreachable!(..) statement in pure function might be reachable +unreachable!(..) statement might be reachable ``` -We can specify that `lookup` should only be called with an `index` which is between `0` and `self.len()` (which implies that we cannot call lookup on an empty list). We do this by adding the precondition `index < self.len()` to *both* `lookup` functions. This is -sufficient to verify our second property for `push`: +We can specify that `lookup` should only be called with an `index` between `0` and `self.len()` (which implies that we cannot call lookup on an empty list: `0 <= index < self.len()` implies `0 < self.len()`). We do this by adding the precondition `index < self.len()` to *both* `lookup` functions. +This is sufficient to verify our second property for `push`: ```rust,noplaypen -{{#rustdoc_include tour-src/src/push/property_2_with_bounds.rs:bounds}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs:bounds}} ``` -We don't need to add the condition `0 <= index` to the precondition, since `index` has the type `usize`, and unsigned integers are always non-negative. (If you don't want Prusti to add this condition automatically, you can add the line `encode_unsigned_num_constraint = false` to your `Prusti.toml` file). +We don't need to add the condition `0 <= index`, since `index` has the type `usize`, and unsigned integers are always non-negative. (If you don't want Prusti to add this condition automatically, you can add the line `encode_unsigned_num_constraint = false` to your `Prusti.toml` file). After these changes, Prusti can successfully verify the code, so the first two properties of `push` are correct. @@ -204,18 +217,18 @@ To formalize the above property, we can reuse our pure function `lookup`, [quantifiers](../syntax.md#quantifiers), and [old expressions](../syntax.md#old-expressions), that is, we add the postcondition: -```rust,noplaypen,ignore -{{#rustdoc_include tour-src/src/push/final_code.rs:shifted_back}} +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_final_code.rs:shifted_back}} ``` Lets break this expression down like before: - We start with the `ensures` annotation, to denote a postcondition. -- `forall(..)` denotes a quantifier, and it takes a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html). -- The closure is denoted by the two vertical bars: `||`, which contain the parameters it the closure takes. Here we only have one parameter `i: usize`. The return type of the closure is `bool`. You can think of the `forall` expression as follows: *Any parameter passed to the closure makes it return `true`*. +- `forall(..)` denotes a [quantifier](../syntax.md#quantifiers), and it takes a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html). +- The closure is denoted by the two vertical bars: `||`, which contain the parameters that the closure takes. Here we only have one parameter `i: usize`. The return type of the closure is `bool`. You can think of the `forall` expression as follows: *Any parameter passed to the closure makes it return `true`*. - Closures in a `forall` expression can take any number of parameters, separated by a comma: `|i: usize, j: usize|`. - In this case, the closure uses the [implication operator `==>`](../syntax.md#implications). It takes a left and right argument of type `bool` and is true, if the left side is false, or both sides are true. - The left side of the implication is `(1 <= i && i < self.len())`, which is the range where the right side must hold. If the index `i` is outside of this range, we don't care about it, so the condition will be false, making the entire implication true. - - The right side is the condition for everything being shifted back by one element: `old(self.lookup(i - 1)) == self.lookup(i)))`. + - The right side is the condition for everything being shifted back by one element: `old(self.lookup(i - 1)) == self.lookup(i)))`. Note that the right side is only evaluated if the left side is true, otherwise you could get an overflow in `i - 1` for `i == 0`, or a panic if `i` is out of bounds. This code is verified successfully by Prusti, so we know that the `lookup` function correctly implements the three postconditions! @@ -224,5 +237,5 @@ This code is verified successfully by Prusti, so we know that the `lookup` funct ```rust,noplaypen // Expand to see full code up to this chapter -{{#rustdoc_include tour-src/src/push/final_code.rs:nothing}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_final_code.rs:nothing}} ``` \ No newline at end of file diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md index 5febce45cdc..3daaa33751e 100644 --- a/docs/user-guide/src/tour/setup.md +++ b/docs/user-guide/src/tour/setup.md @@ -1,6 +1,6 @@ # Setup -To get started, you can use an existing Rust project or create a new one with Cargo: +To get started, you can use an existing Rust project or create a new one with [Cargo](https://doc.rust-lang.org/cargo/): ```sh cargo new prusti_tutorial @@ -18,11 +18,10 @@ cargo add prusti-contracts For older versions of Rust, you can manually add the dependency in your Cargo.toml file: ```toml [dependencies] -prusti-contracts = "0.1.3" +prusti-contracts = "0.1.4" ``` - -To use prusti-contracts in a Rust file, just add the following line: +To use prusti-contracts in a Rust code file, just add the following line: ```rust,ignore use prusti_contracts::*; ``` @@ -34,7 +33,20 @@ In this file, you can set [configuration flags](https://viperproject.github.io/p check_overflows = false ``` -TODO: -- nightly compiler needed(?) -- mention how to run Prusti on the created project -- mention that strings are not supported (remove print from generated main fn) \ No newline at end of file +**Note**: Creating a new project will create a `main.rs` file containing a `Hello World` program. Since Prusti does not yet support Strings (see [Prusti Limitations](../limitations.md#strings-and-string-slices) chapter), verification will fail on `main.rs`. To still verify the code, remove the line `println!("Hello, world!");`. + + +## Standard library annotations + +Annotations for functions and types in the Rust standard library will be available in the [prusti-std crate](https://crates.io/crates/prusti-std) after [this PR](https://github.com/viperproject/prusti-dev/pull/1249) is merged. + +Adding this crate works the same as for the `prusti-contracts` crate: +```sh +cargo add prusti-std +``` +or: +```toml +[dependencies] +prusti-std = "0.1.4" +``` +You do not need to import anything to use the annotations in this crate in a file. diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index 0d41a85eeb5..4888c2230d2 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -1,7 +1,7 @@ # Guided Tour > **Disclaimer:** All code shown in this tutorial has been tested with -> [Prusti v-2021-11-22-1738](https://github.com/viperproject/prusti-dev/tree/v-2021-11-22-1738). +> [Prusti version 0.2.1, 2023-01-26](https://github.com/viperproject/prusti-dev/releases/tag/v-2023-01-26-1935) > > Unless stated otherwise, all code listings should be put in their own file > and can be verified with @@ -10,11 +10,8 @@ In this chapter, we demonstrate Prusti's capabilities. As a running example, we roughly follow the first chapters of the Rust tutorial [Learn Rust with Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). -Linked lists turn out to be sufficiently complex that their implementation and verification -covers most of Rust's and Prusti's essential concepts. -While the above tutorial explains in detail how linked lists are implemented in Rust, -we additionally aim to verify that the implemented list operations are functionally -correct. +Linked lists turn out to be sufficiently complex that their implementation and verification covers most of Rust's and Prusti's essential concepts. +While the above tutorial explains in detail how linked lists are implemented in Rust, we additionally aim to verify that the implemented list operations are functionally correct. While we assume basic familiarity with Rust, it is possible to learn both Rust and Prusti at the same time by reading this tour and the @@ -27,9 +24,10 @@ Throughout this tour, we will cover the following Prusti concepts: - How Prusti reports errors and integrates into the development process - Runtime errors detected by Prusti - Writing function-modular specifications -- Using *pure* functions in specifications -- Writing loop invariants +- Using *pure* functions and predicates in specifications +- Writing loop invariants - Using *extern_spec* to deal with library code +- Writing pledges for functions that return mutable references - Basic troubleshooting @@ -38,18 +36,16 @@ devices. As a quick reference, the main steps of this tour and the involved Prusti features are as follows: -# TODO: Update <====== - -0. [Setup](setup.md) How to add Prusti to a Rust project -1. [Getting Started](getting-started.md): Simple runtime errors caught by Prusti -2. [New](new.md): Postconditions, pure functions -3. [Push](push.md): Preconditions, trusted functions, old expressions, quantifiers -4. [Pop](pop.md): Exercise with similar concepts as for push -5. [Testing](testing.md): More runtime errors caught by Prusti -6. --> [A Bad Stack](bad-stack.md): Wrap-up of the second chapter of - [Learn Rust with Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). -7. [Options](options.md): Verification with option types -7. [Generics](generics.md): Prusti and generics -8. [Peek](peek.md): Exercise -9. [Final Code](final.md): Final code with solution of exercise -10. [Pledges](pledges.md): Bonus demonstrating Prusti's pledges +1. [Setup](setup.md) How to add Prusti to a Rust project +2. [Getting Started](getting-started.md): Simple runtime errors caught by Prusti +3. [New](new.md): Postconditions, pure functions +4. [Push](push.md): Preconditions, external specifications, trusted functions, old expressions, quantifiers, snapshots, structural/snapshot equality +5. [Pop](pop.md): Similar concepts as in [Push](push.md), predicates +6. [Testing](testing.md): Testing specifications, differences to normal tests +7. [Option](option.md): Changing `Link` to use `Option` type +8. [Generics](generics.md): Prusti and generics +9. [Peek](peek.md): Verifying a `peek` function +10. [Pledges (mutable peek)](pledges.md): Demonstrate Prusti's pledges for functions returning mutable references +11. [Final Code](final.md): Final code for the verified linked list +12. [Loop Invariants](loop_invariants.md): Verifying code containing loops by writing loop invariants +13. [Counterexamples](counterexamples.md): Get a counterexample for a failing assertions diff --git a/docs/user-guide/src/tour/testing.md b/docs/user-guide/src/tour/testing.md index 17af4d3681f..8d736e75971 100644 --- a/docs/user-guide/src/tour/testing.md +++ b/docs/user-guide/src/tour/testing.md @@ -5,25 +5,45 @@ The linked chapter in the "Learning Rust With Entirely Too Many Linked Lists" tutorial explains how testing normal Rust code works. In this chapter we will check both the code and the specifications that we added in the previous chapters. -Note: Normal tests (marked by `#[cfg(test)]`) are currently ***not*** checked by Prusti, but this may be added in the future. +Note: Normal tests (marked with `#[cfg(test)]` or `#[test]`) are currently ***not*** checked by Prusti, but this may be added in the future. Without the `test` markers, Prusti will check any test like a normal function. -To check our specifications and code, we can write a function that relies on the expected behavior. Just like in the `Testing` chapter, we can create a new namespace for the test: +We have written some specifications in the previous chapters, but we didn't check if they are actually correct or useful. For example, a function that has a too restrictive precondition may be verified successfully by itself, but cannot be used: ```rust,noplaypen -{{#rustdoc_include tour-src/src/testing/initial_code.rs:test_1}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs:code}} +``` + +This function is correct (ignoring potential overflows), but it is not useful, since the input must be `10`. + +Another potential problem could be a disconnect in what the specification says, and what the programmer wants a function to do: + +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs:code}} +``` + +There is not error shown on the square function, since it will never panic (ignoring overflows again), and it does exactly what the specification says it does. Checking the specification with a test function shows however, that the function calculates `x` cubed instead of `x` squared. + +For internal functions, you will likely notice mistakes in the specification when you use it in other code in your project. When you have public functions however, like for example in a library, you might want to write some tests for your specification to ensure that they are correct and useful. + +## Testing our linked list specifications + +To check our specifications and code, we can write a function that relies on the expected behavior. We can create a new namespace for the test, here we call it `prusti_tests`: + +```rust,noplaypen +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs:test_1}} // Prusti: verifies ``` We can also have tests that take arguments and also have pre- and postconditions: ```rust,noplaypen -{{#rustdoc_include tour-src/src/testing/initial_code.rs:test_2}} +{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs:test_2}} // Prusti: verifies ``` -This passes as well. In this test we can see that we can call `pop` at least 4 times without the possibility of a panic, since the length of `list_0` is at least 4. +If you change any parts of this test, you will get a verification error, e.g., testing for any different lengths will cause the verification to fail. +Since this code can be verified, it appears that our specification matches our expectation for what the code does. -Running Prusti on the project again should still verify. If you change any parts of this test, you will get a verification error, e.g., testing for any different lengths will cause the verification to fail. +## Note on differences between verification and unit tests -Verification should not completely replace normal testing. -A passing verification just means that the code will not panic and that it does exactly what the specifications describe. -Just like in normal code, what the code and specification encode may not be the same as what the programmer wants, and testing can help catch such missalignments. \ No newline at end of file +Static verification is stronger than unit testing, since unit tests will not be able to check the entire space of possible inputs for a function. +It might still be worth writing some unit tests even for verified code, to catch errors in the specifications. As noted above, Prusti will be able to check unit tests in the future. \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/Cargo.toml b/docs/user-guide/src/tour/tour-src/Cargo.toml deleted file mode 100644 index 15cf08a8580..00000000000 --- a/docs/user-guide/src/tour/tour-src/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -authors = ["Prusti Devs "] -name = "tour-src" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -prusti-contracts = "0.1.3" -# prusti-contracts = { path = "../../../../../prusti-contracts/prusti-contracts/" } diff --git a/docs/user-guide/src/tour/tour-src/Prusti.toml b/docs/user-guide/src/tour/tour-src/Prusti.toml deleted file mode 100644 index e53117479e5..00000000000 --- a/docs/user-guide/src/tour/tour-src/Prusti.toml +++ /dev/null @@ -1,2 +0,0 @@ -check_overflows = false -# counterexample = true \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/01-chapter-2-1.rs b/docs/user-guide/src/tour/tour-src/src/01-chapter-2-1.rs deleted file mode 100644 index 14af5689a5b..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/01-chapter-2-1.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* - Capter 2 - A Bad Singly-Linked Stack - - Chapter 2.1 - Basic Data Layout - - > List a = Empty | Elem a (List a) - - A list is a sum type (Rust: enum) - - For now, we only support storing 32-bit integers -*/ - -// pub allows using List outside this module (not necessary here) -pub enum List { - Empty, - Elem(i32, List), -} diff --git a/docs/user-guide/src/tour/tour-src/src/02-chapter-2-1.rs b/docs/user-guide/src/tour/tour-src/src/02-chapter-2-1.rs deleted file mode 100644 index d46f87aadc0..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/02-chapter-2-1.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - Chapter 2.1 - Basic Data Layout - -error[E0072]: recursive type `List` has infinite size - --> .\01-chapter-2-1.rs:12:1 - | -12 | pub enum List { - | ^^^^^^^^^^^^^ recursive type has infinite size -13 | Empty, -14 | Elem(i32, List), - | ---- recursive without indirection - | -help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable - | -14 | Elem(i32, Box), - | - - [Module std::boxed](https://doc.rust-lang.org/std/boxed/index.html): - > Box, casually referred to as a ‘box’, provides the simplest form of heap allocation in Rust. - > Boxes provide ownership for this allocation, and drop their contents when they go out of scope. -*/ - - -pub enum List { - Empty, - Elem(i32, Box), -} - -/* - Rust tutorial: Hey it built! ...but this is actually a really foolish definition of a List - - Notation: [] = Stack () = Heap - - A list with three elements looks as follows: - - > [Elem A, ptr] -> (Elem B, ptr) -> (Elem C, ptr) -> (Empty *junk*) - - - The last node is just overhead, the first node is not on the heap - - Empty consumes space for a full pointer and an element - - This propagates e.g. when splitting off C: - - > [Elem A, ptr] -> (Elem B, ptr) -> (Empty *junk*) - > [Elem C, ptr] -> (Empty *junk*) - - We would rather like to have a Java/C-style pointer that is nullable: - - > [ptr] -> (Elem A, ptr) -> (Elem B, ptr) -> (Elem C, *null*) - - Splitting off C then yields: - - > [ptr] -> (Elem A, ptr) -> (Elem B, *null*) - > [ptr] -> (Elem C, *null*) - - Rust optimises the layout of enums such that the junk node (Empty) causes no overhead if the enum has the form - - > enum Foo { - > A, - > B(ContainsANonNullPtr), - > } - - To profit from this null pointer optimisation, we thus move all data of a list node into a single C-style struct. -*/ diff --git a/docs/user-guide/src/tour/tour-src/src/04-chapter-2-2.rs b/docs/user-guide/src/tour/tour-src/src/04-chapter-2-2.rs deleted file mode 100644 index 6284aedadeb..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/04-chapter-2-2.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -impl List { - // Self is an alias for the type that we are currently implementing - pub fn new() -> Self { - List { - head: Link::Empty, - } - } -} - -impl Link { - fn len(&self) -> usize { - 0 - } -} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/05-chapter-2-3.rs b/docs/user-guide/src/tour/tour-src/src/05-chapter-2-3.rs deleted file mode 100644 index efec168c74f..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/05-chapter-2-3.rs +++ /dev/null @@ -1,63 +0,0 @@ -#![feature(box_patterns)] // convenience box syntax - -/* - We could verify that the list returned by new() is empty, i.e., is of length 0. - To this end, we also need to implement a length function for links -*/ - -// (1) simplifies writing Prusti's annotations (implemented as Rust macros/attributes) -use prusti_contracts::*; - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -impl List { - - // (3) add a method for length without annotations - // Methods are a special case of function in Rust because of the self argument, - // which doesn't have a declared type. - // There are 3 primary forms that self can take: self, &mut self, and &self - // We choose &self since computing the length only requires immutable access. - #[pure] // (4) - pub fn len(&self) -> usize { - //0 // only for (3) and (4) - self.head.len() // (5) - } - - // (2) add postcondition - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { - head: Link::Empty, - } - } - -} - -impl Link { - - // (5) add length method for Links - #[pure] - fn len(&self) -> usize { - 0/* - match self { - Link::Empty => 0, - Link::More(box node) => 1 + node.next.len(), - } - */ - } - -} - -fn main() {} diff --git a/docs/user-guide/src/tour/tour-src/src/06-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/06-chapter-2-4.rs deleted file mode 100644 index 62249419133..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/06-chapter-2-4.rs +++ /dev/null @@ -1,95 +0,0 @@ -#![feature(box_patterns)] -use prusti_contracts::*; - -use std::mem; // (4) - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -/* - Chapter 2.4 - Push - - So let's write pushing a value onto a list. - push mutates the list, so we'll want to take &mut self -*/ - -impl List { - - #[pure] - pub fn len(&self) -> usize { - self.head.len() - } - - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { - head: Link::Empty, - } - } - - /* - // (2) compile this before proceeding - pub fn push(&mut self, elem: i32) { - - let new_node = Node { - elem: elem, - next: self.head, // this should be the old list - // this does not work: - // once the self borrow expired, the old List would only be - // partially initialized because we moved self.head - // and that cannot be copied. - }; - } - */ - - /* - // (3) What if we put something back? Namely, the node that we're creating. - // Rust does not accept this for exception safety. - pub fn push(&mut self, elem: i32) { - let new_node = Box::new(Node { - elem: elem, - next: self.head, - }); - - self.head = Link::More(new_node); - } - */ - - // (4) A correct solution would be to first steal a value out of a borrow and - // replace it with another one - // We use std::mem:replace for that (add use directive at the top) - pub fn push(&mut self, elem: i32) { - let new_node = Box::new(Node { - elem: elem, - next: mem::replace(&mut self.head, Link::Empty), - }); - - self.head = Link::More(new_node); - } - - // (5) this verifies! - // TODO: What would be reasonable first specs for push? -} - -impl Link { - - #[pure] - fn len(&self) -> usize { - match self { - Link::Empty => 0, - Link::More(box node) => 1 + node.next.len(), - } - } - -} diff --git a/docs/user-guide/src/tour/tour-src/src/07-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/07-chapter-2-4.rs deleted file mode 100644 index 405732ddb41..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/07-chapter-2-4.rs +++ /dev/null @@ -1,84 +0,0 @@ -#![feature(box_patterns)] -use prusti_contracts::*; - -use std::mem; - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -// (3) add a trusted wrapper such that we can come up with a spec -// such wrappers are common for frontends when dealing with functions -// that use, for example, the operating system or unsafe/too complicated code -#[trusted] -// (4) Wait wait, this spec is not checked and looks very dangerous! -// We should at least requiree that it is only called with an empty link -// We can then also ensure that the replaced list will be that empty link -#[requires(src.is_empty())] // (4) also requires defining is_empty() below -#[ensures(dest.is_empty())] // (4) -#[ensures(old(dest.len()) == result.len())] // (3) -fn replace(dest: &mut Link, src: Link) -> Link { - mem::replace(dest, src) -} - - -impl List { - - #[pure] - pub fn len(&self) -> usize { - self.head.len() - } - - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { - head: Link::Empty, - } - } - - // (1) Let's collect some possible specs: - // a) The length of the list increases by one (3): check - // b) The first element is the pushed one - // c) All other elements have not been changed - #[ensures(self.len() == old(self.len()) + 1)] // (2) Why does this not hold? mem::replace has no spec! - pub fn push(&mut self, elem: i32) { - let new_node = Box::new(Node { - elem: elem, - // (1) next: mem::replace(&mut self.head, Link::Empty), - next: replace(&mut self.head, Link::Empty), // (3) - }); - - self.head = Link::More(new_node); - } -} - -impl Link { - - #[pure] - fn len(&self) -> usize { - match self { - Link::Empty => 0, - Link::More(box node) => 1 + node.next.len(), - } - } - - // (4) - #[pure] - fn is_empty(&self) -> bool { - match self { - Link::Empty => true, - Link::More(box node) => false, - } - } - -} diff --git a/docs/user-guide/src/tour/tour-src/src/08-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/08-chapter-2-4.rs deleted file mode 100644 index 80955b51a07..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/08-chapter-2-4.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![feature(box_patterns)] -use prusti_contracts::*; - -use std::mem; - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -#[trusted] -#[requires(src.is_empty())] -#[ensures(dest.is_empty())] -#[ensures(old(dest.len()) == result.len())] -fn replace(dest: &mut Link, src: Link) -> Link { - mem::replace(dest, src) -} - - -impl List { - - #[pure] - pub fn len(&self) -> usize { - self.head.len() - } - - // (3) - #[pure] - #[requires(0 <= index && index < self.len())] // (9) - pub fn lookup(&self, index: usize) -> i32 { - self.head.lookup(index) - } - - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { - head: Link::Empty, - } - } - - // (1) Let's collect some possible specs: - // a) The length of the list increases by one: check - // b) The first element is the pushed one - // c) All other elements have not been changed - #[ensures(self.len() == old(self.len()) + 1)] - #[ensures(self.lookup(0) == elem)] // (2) express property b) - 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); - } -} - -impl Link { - - #[pure] - #[ensures(!self.is_empty() ==> result > 0)] // (7) - #[ensures(result >= 0)] // (8) - 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, - } - } - - // (4) - #[pure] - #[requires(0 <= index && index < self.len())] // (6) no fix as there's no - // relation between being empty and length 0 - pub fn lookup(&self, index: usize) -> i32 { - match self { - Link::Empty => unreachable!(), // (5) reachable because list might be empty - Link::More(box node) => { - if index == 0 { - node.elem - } else { - node.next.lookup(index - 1) - } - } - } - } - -} diff --git a/docs/user-guide/src/tour/tour-src/src/09-chapter-2-4.rs b/docs/user-guide/src/tour/tour-src/src/09-chapter-2-4.rs deleted file mode 100644 index 42144399bce..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/09-chapter-2-4.rs +++ /dev/null @@ -1,107 +0,0 @@ -#![feature(box_patterns)] -use prusti_contracts::*; - -use std::mem; - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -#[trusted] -#[requires(src.is_empty())] -#[ensures(dest.is_empty())] -#[ensures(old(dest.len()) == result.len())] -// (5) make sure that replace leaves elements in the result untouched -//#[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, - } - } - - // (1) Let's collect some possible specs: - // a) The length of the list increases by one: check - // b) The first element is the pushed one: check - // c) All other elements have not been changed - #[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)))] // (2) specify property c) - // Why does it fail? - // The spec for replace is not strong enough! - 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); - } -} - -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, - } - } - - #[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) - } - } - } - } - -} diff --git a/docs/user-guide/src/tour/tour-src/src/10-chapter-2-5.rs b/docs/user-guide/src/tour/tour-src/src/10-chapter-2-5.rs deleted file mode 100644 index c0ca264f3ef..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/10-chapter-2-5.rs +++ /dev/null @@ -1,133 +0,0 @@ -/* - Chapter 2.5 Pop - - Like push, pop wants to mutate the list. - Unlike push, we actually want to return something. - But pop also has to deal with a tricky corner case: what if the list is empty? - To represent this case, we introduce an Option type. - (Option is actually in the standard library but we write our own to assign specs to functions) -*/ - -#![feature(box_patterns)] -use prusti_contracts::*; - -use std::mem; - -// (1) alternative would be to introduce trusted functions again... -pub enum TrustedOption { - Some(i32), - None, -} - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -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); - } - - // (6) TODO: What would be sensible specifications for pop()? - // (2): introduce function below - pub fn pop(&mut self) -> TrustedOption { - // match self.head { // (2) move out of borrow - //match &self.head { // (3) use shared reborrow - match replace(&mut self.head, Link::Empty) { // (5) - Link::Empty => { - TrustedOption::None // (4) - } - Link::More(node) => { - self.head = node.next; // (4) cannot assign, we just borrowed self.head! - TrustedOption::Some(node.elem) // (4) - } - } - // unimplemented!() // (2), remove at (4) - } -} - -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, - } - } - - #[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) - } - } - } - } - -} diff --git a/docs/user-guide/src/tour/tour-src/src/11-chapter-2-5.rs b/docs/user-guide/src/tour/tour-src/src/11-chapter-2-5.rs deleted file mode 100644 index b3cd07fb980..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/11-chapter-2-5.rs +++ /dev/null @@ -1,165 +0,0 @@ -#![feature(box_patterns)] -use prusti_contracts::*; - -use std::mem; - -pub enum TrustedOption { - Some(i32), - None, -} - -// (3) add implementation for TrustedOption -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() - } - - // (6) add method - #[pure] - #[requires(self.is_some())] // (7) - pub fn peek(&self) -> i32 { - match self { - TrustedOption::Some(val) => *val, - TrustedOption::None => unreachable!(), - } - } - -} - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -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); - } - - // (1) TODO: What would be sensible specifications for pop()? - // a) empty list means we return None - // b) non-empty list means we return Some(_) - // c) lengths are changed correctly - // d) the returned value is the first element of the old list - // e) all elements except the first one remain unchanged - - // (2) spec a/b) we could add explicit matches but that would be silly - #[ensures(old(self.len()) == 0 ==> result.is_none())] // (2) spec a) - - #[ensures(old(self.len()) > 0 ==> result.is_some())] // (2) spec b) - // (4) specification c) - #[ensures(old(self.len()) == 0 ==> self.len() == 0)] // (4) empty lists remain empty - #[ensures(old(self.len()) > 0 ==> self.len() == old(self.len()-1))] // (4) non-empty lists are 1 smaller - // (5) specification d) - #[ensures(old(self.len()) > 0 ==> result.peek() == old(self.lookup(0)))] // (5) - // (6) specification e) - #[ensures(old(self.len()) > 0 ==> // (6) - forall(|i: usize| (0 <= i && i < self.len()) ==> // (6) - old(self.lookup(i+1)) == self.lookup(i)))] // (6) - 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(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) - } - } - } - } - -} diff --git a/docs/user-guide/src/tour/tour-src/src/12-chapter-2-6.rs b/docs/user-guide/src/tour/tour-src/src/12-chapter-2-6.rs deleted file mode 100644 index 46fbadf1b58..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/12-chapter-2-6.rs +++ /dev/null @@ -1,208 +0,0 @@ -/* - Chapter 2.6 - Testing - Alright, so we've got push and pop written, now we can actually test out our stack! - Rust and cargo support testing as a first-class feature, - so this will be super easy. - All we have to do is write a function, and annotate it with #[test]. -*/ - -#![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), -} - -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(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) - } - } - } - } - -} - - -// (1) -pub mod test { - use super::{List, TrustedOption}; - - pub fn basics() { - let mut list = List::new(); - - // (1) Check empty list behaves right - assert!(list.pop().is_none()); - - // (2) Populate list - list.push(1); - list.push(2); - list.push(3); - - // (2) Check normal removal - match list.pop() { - TrustedOption::Some(val) => assert!(val == 3), - _ => unreachable!(), - } - match list.pop() { - TrustedOption::Some(val) => assert!(val == 2), - _ => unreachable!(), - } - - // (3) Push some more just to make sure nothing's corrupted - list.push(4); - list.push(5); - - // (3) Check normal removal - match list.pop() { - TrustedOption::Some(val) => assert!(val == 5), - _ => unreachable!(), - } - match list.pop() { - TrustedOption::Some(val) => assert!(val == 4), - _ => unreachable!(), - } - - // (4) Check exhaustion - match list.pop() { - TrustedOption::Some(val) => assert!(val == 1), - _ => unreachable!(), - } - assert!(list.pop().is_none()); - } -} diff --git a/docs/user-guide/src/tour/tour-src/src/13-too-many-lists-final.rs b/docs/user-guide/src/tour/tour-src/src/13-too-many-lists-final.rs deleted file mode 100644 index 6ba6d051ccd..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/13-too-many-lists-final.rs +++ /dev/null @@ -1,218 +0,0 @@ -#![feature(box_patterns)] // convenience box syntax - -//! An adaptation of the example from -//! https://rust-unofficial.github.io/too-many-lists/first-final.html -//! -//! Proven properties: -//! + Absence of panics. -//! + Push and pop behaves correctly. - -use prusti_contracts::*; -use std::mem; - -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -impl Link { - #[pure] - fn is_empty(&self) -> bool { - match self { - Link::Empty => true, - Link::More(box node) => false, - } - } - #[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] - #[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) - } - } - } - } -} - -struct Node { - elem: i32, - next: Link, -} - -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 { - match self { - TrustedOption::Some(_) => true, - TrustedOption::None => false, - } - } - #[pure] - #[requires(self.is_some())] - pub fn peek(&self) -> i32 { - match self { - TrustedOption::Some(val) => *val, - TrustedOption::None => unreachable!(), - } - } -} - -#[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 old_len = self.head.len(); - 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 ==> self.len() == 0)] - #[ensures(old(self.len()) > 0 ==> result.is_some())] - #[ensures(old(self.len()) > 0 ==> result.peek() == old(self.lookup(0)))] - #[ensures(old(self.len()) > 0 ==> self.len() == old(self.len()-1))] - #[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) - } - } - } -} - -// added in chapter 2.7 -impl Drop for List { - fn drop(&mut self) { - let mut cur_link = replace(&mut self.head, Link::Empty); - - let mut continue_loop = true; - while continue_loop { - if let Link::More(mut boxed_node) = cur_link { - cur_link = replace(&mut boxed_node.next, Link::Empty); - } else { - continue_loop = false; - } - } - - } -} - -pub mod test { - use super::{List, TrustedOption}; - - pub fn basics() { - let mut list = List::new(); - - // Check empty list behaves right - assert!(list.pop().is_none()); - - // Populate list - list.push(1); - list.push(2); - list.push(3); - - // Check normal removal - match list.pop() { - TrustedOption::Some(val) => assert!(val == 3), - _ => unreachable!(), - } - match list.pop() { - TrustedOption::Some(val) => assert!(val == 2), - _ => unreachable!(), - } - - // Push some more just to make sure nothing's corrupted - list.push(4); - list.push(5); - - // Check normal removal - match list.pop() { - TrustedOption::Some(val) => assert!(val == 5), - _ => unreachable!(), - } - match list.pop() { - TrustedOption::Some(val) => assert!(val == 4), - _ => unreachable!(), - } - - // Check exhaustion - match list.pop() { - TrustedOption::Some(val) => assert!(val == 1), - _ => unreachable!(), - } - assert!(list.pop().is_none()); - } -} - -fn main() {} diff --git a/docs/user-guide/src/tour/tour-src/src/generic.rs b/docs/user-guide/src/tour/tour-src/src/generic.rs deleted file mode 100644 index 979f797ca0e..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/generic.rs +++ /dev/null @@ -1 +0,0 @@ -mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/getting_started.rs b/docs/user-guide/src/tour/tour-src/src/getting_started.rs deleted file mode 100644 index e5447ac113d..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/getting_started.rs +++ /dev/null @@ -1,2 +0,0 @@ -// mod failing; -mod working; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs b/docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs deleted file mode 100644 index 4585a7f066a..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/getting_started/failing.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -fn main() { - let test = Node { - elem: 17, - next: Link::Empty, - }; - - if test.elem > 23 { - panic!() // unreachable - } -} - -fn test(x: i32) { - let test = Node { - elem: x, // unknown value - next: Link::Empty, - }; - - if test.elem > 23 { - panic!() - } -} diff --git a/docs/user-guide/src/tour/tour-src/src/getting_started/working.rs b/docs/user-guide/src/tour/tour-src/src/getting_started/working.rs deleted file mode 100644 index e76fd5e8dd1..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/getting_started/working.rs +++ /dev/null @@ -1,24 +0,0 @@ -pub struct List { - head: Link, -} - -enum Link { - Empty, - More(Box), -} - -struct Node { - elem: i32, - next: Link, -} - -fn main() { - let test = Node { - elem: 17, - next: Link::Empty, - }; - - if test.elem > 23 { - panic!() // unreachable - } -} diff --git a/docs/user-guide/src/tour/tour-src/src/lib.rs b/docs/user-guide/src/tour/tour-src/src/lib.rs deleted file mode 100644 index a00771ef633..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![allow(dead_code, unused)] - -mod getting_started; -mod new; -// mod push; // TOOD: fix extern_spec (new syntax) -// mod pop; -// mod testing; -// mod option; -// mod generic; -// mod peek; -mod peek_mut; // pledges \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs b/docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs deleted file mode 100644 index f699edee763..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/loop_invariants/initial_code.rs +++ /dev/null @@ -1,294 +0,0 @@ -//// ANCHOR: nothing -//// ANCHOR_END: nothing -use prusti_contracts::*; - -pub struct List { - head: Link, -} - -type Link = Option>>; - -struct Node { - elem: T, - next: Link, -} - -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} -//// ANCHOR: pledge -impl List { - //// ANCHOR_END: pledge - #[pure] - pub fn len(&self) -> usize { - link_len(&self.head) - } - - #[pure] - fn is_empty(&self) -> bool { - matches!(self.head, None) - } - - #[ensures(result.len() == 0)] - pub fn new() -> Self { - List { head: None } - } - - #[pure] - #[requires(index < self.len())] - pub fn lookup(&self, index: usize) -> &T { - link_lookup(&self.head, index) - } - - #[ensures(self.len() == old(self.len()) + 1)] - #[ensures(snap(self.lookup(0)) === elem)] - #[ensures(forall(|i: usize| (i < old(self.len())) ==> - old(self.lookup(i)) === self.lookup(i + 1)))] - pub fn push(&mut self, elem: T) { - let new_node = Box::new(Node { - elem, - next: self.head.take(), - }); - - self.head = Some(new_node); - } - - predicate! { - // two-state predicate to check if the head of a list was correctly removed - fn head_removed(&self, prev: &Self) -> bool { - self.len() == prev.len() - 1 // The length will decrease by 1 - && forall(|i: usize| // Every element will be shifted forwards by one - (1 <= i && i < prev.len()) - ==> prev.lookup(i) === self.lookup(i - 1)) - } - } - - #[ensures(old(self.is_empty()) ==> - result.is_none() && - self.is_empty() - )] - #[ensures(!old(self.is_empty()) ==> - self.head_removed(&old(snap(self))) - && - result === Some(snap(old(snap(self)).lookup(0))) - )] - pub fn try_pop(&mut self) -> Option { - match self.head.take() { // Replace mem::swap with the buildin Option::take - None => None, - Some(node) => { - self.head = node.next; - Some(node.elem) - } - } - } - - #[requires(!self.is_empty())] - #[ensures(self.head_removed(&old(snap(self))))] - #[ensures(result === old(snap(self)).lookup(0))] - pub fn pop(&mut self) -> T { - self.try_pop().unwrap() - } - - // // Not currently possible in Prusti - // pub fn try_peek(&mut self) -> Option<&T> { - // todo!() - // } - - #[pure] - #[requires(!self.is_empty())] - pub fn peek(&self) -> &T { - self.lookup(0) - } - - // #[requires(index < self.len())] - // pub fn lookup_mut(&mut self, index: usize) -> &mut T { - // let mut curr_node = &mut self.head; - // let mut index = index; // Workaround for Prusti not supporting mutable fn arguments - // while index != 0 { - // body_invariant!(true); - // if let Some(next_node) = curr_node { // reference in enum - // curr_node = &mut next_node.next; - // } else { - // unreachable!(); - // } - // index -= 1; - // } - // if let Some(node) = curr_node { // ERROR: [Prusti: unsupported feature] the creation of loans in this loop is not supported (ReborrowingDagHasNoMagicWands) - // &mut node.elem - // } else { - // unreachable!() - // } - // } - - #[trusted] // required due to unsupported reference in enum - #[requires(!self.is_empty())] - #[ensures(snap(result) === old(snap(self.peek())))] - //// ANCHOR: pledge - #[after_expiry( - old(self.len()) === self.len() // (1.) - && forall(|i: usize| 1 <= i && i < self.len() // (2.) - ==> old(snap(self.lookup(i))) === snap(self.lookup(i))) - && snap(self.peek()) === before_expiry(snap(result)) // (3.) - )] - pub fn peek_mut(&mut self) -> &mut T { - //// ANCHOR_END: pledge - // This does not work in Prusti at the moment: - // "&mut self.head" has type "&mut Option" - // this gets auto-dereferenced by Rust into type: "Option<&mut T>" - // this then gets matched to "Some(node: &mut T)" - // References in enums are not yet supported, so this cannot be verified by Prusti - if let Some(node) = &mut self.head { - &mut node.elem - } else { - unreachable!() - } - //// ANCHOR: pledge - // ... - } -} -//// ANCHOR_END: pledge - -#[pure] -#[requires(index < link_len(link))] -fn link_lookup(link: &Link, index: usize) -> &T { - match link { - Some(node) => { - if index == 0 { - &node.elem - } else { - link_lookup(&node.next, index - 1) - } - } - None => unreachable!(), - } -} - -#[pure] -fn link_len(link: &Link) -> usize { - match link { - None => 0, - Some(node) => 1 + link_len(&node.next), - } -} - -mod prusti_tests { - use super::*; - - fn _test_1(){ - let mut list = List::new(); - prusti_assert!(list.is_empty() && list.len() == 0); - - list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); - prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` - - list.push(10); - prusti_assert!(!list.is_empty() && list.len() == 2); // length correct - prusti_assert!(*list.lookup(0) == 10); // head is 10 - prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly - - let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again - prusti_assert!(x == 10); // pop returns the value that was added last - - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` - } - - let z = list.try_pop(); - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` - } - - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(*list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(*list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0.lookup(0)))); - prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0.lookup(2)))); - prusti_assert!(x3 == old(snap(list_0.lookup(3)))); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1.lookup(0)))); - prusti_assert!(y0 == x3); - } - - fn _test_3() { - let mut list = List::new(); - list.push(16); - prusti_assert!(list.peek() === list.lookup(0)); - prusti_assert!(*list.peek() == 16); - - list.push(5); - prusti_assert!(list.peek() === list.lookup(0)); - prusti_assert!(*list.peek() == 5); - - list.pop(); - prusti_assert!(list.peek() === list.lookup(0)); - prusti_assert!(*list.peek() == 16); - } - - fn _test_4() { - let mut list = List::new(); - list.push(8); - list.push(16); - prusti_assert!(*list.lookup(0) == 16); - prusti_assert!(*list.lookup(1) == 8); - prusti_assert!(list.len() == 2); - - { - let first = list.peek_mut(); - // `first` is a mutable reference to the first element of the list - // for as long as `first` is live, `list` cannot be accessed - prusti_assert!(*first == 16); - *first = 5; - prusti_assert!(*first == 5); - // `first` gets dropped here, `list` can be accessed again - } - prusti_assert!(list.len() == 2); - prusti_assert!(*list.lookup(0) == 5); - prusti_assert!(*list.lookup(1) == 8); - } -} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new.rs b/docs/user-guide/src/tour/tour-src/src/new.rs deleted file mode 100644 index e22c73724c0..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/new.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod impl_new; -// mod spec_failing_1; -// mod spec_failing_2; -// mod spec_failing_3; -mod spec_fixed; -mod full_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/option.rs b/docs/user-guide/src/tour/tour-src/src/option.rs deleted file mode 100644 index 979f797ca0e..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/option.rs +++ /dev/null @@ -1 +0,0 @@ -mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek.rs b/docs/user-guide/src/tour/tour-src/src/peek.rs deleted file mode 100644 index 979f797ca0e..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/peek.rs +++ /dev/null @@ -1 +0,0 @@ -mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut.rs b/docs/user-guide/src/tour/tour-src/src/peek_mut.rs deleted file mode 100644 index bfb3faf1ad3..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/peek_mut.rs +++ /dev/null @@ -1,3 +0,0 @@ -// mod initial_code; // should fail -// mod final_code; -mod assert_on_expiry; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/pop.rs b/docs/user-guide/src/tour/tour-src/src/pop.rs deleted file mode 100644 index b67069a7559..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/pop.rs +++ /dev/null @@ -1,2 +0,0 @@ -// mod initial_code; // should fail -mod final_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/push.rs b/docs/user-guide/src/tour/tour-src/src/push.rs deleted file mode 100644 index ebc97b0e627..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/push.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod initial_code; -mod property_1; -mod property_2_missing_bounds; -mod property_2_with_bounds; -mod final_code; \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/test.rs b/docs/user-guide/src/tour/tour-src/src/test.rs deleted file mode 100644 index b8983090848..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/test.rs +++ /dev/null @@ -1,19 +0,0 @@ -// TODO: DELETE THIS FILE - -fn test() { - //// ANCHOR: code_1 - // Comment 1 - // Comment 2 - let code_1 = 6; - //// ANCHOR_END: code_1 - - // Comment 3 - // Comment 4 - let code_2 = 8 - //// ANCHOR: code_1 - - // Comment 5 - // Comment 6 - let code_3 = 10 - //// ANCHOR_END: code_1 -} \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/testing.rs b/docs/user-guide/src/tour/tour-src/src/testing.rs deleted file mode 100644 index 979f797ca0e..00000000000 --- a/docs/user-guide/src/tour/tour-src/src/testing.rs +++ /dev/null @@ -1 +0,0 @@ -mod initial_code; \ No newline at end of file diff --git a/docs/user-guide/src/verify/assert_refute_assume.md b/docs/user-guide/src/verify/assert_refute_assume.md index 464ecc1c270..a55fb7d31cc 100644 --- a/docs/user-guide/src/verify/assert_refute_assume.md +++ b/docs/user-guide/src/verify/assert_refute_assume.md @@ -9,7 +9,7 @@ function (via an assumption). The macros `prusti_assert!`, `prusti_assert_eq` and `prusti_assert_ne` instruct Prusti to verify that a certain property holds at a specific point within the body of a function. In contrast to the `assert!`, `assert_eq` and `assert_ne` macros, which only accept Rust expressions, the Prusti variants accept [specification](../syntax.md) expressions as arguments. Therefore, [quantifiers](../syntax.md#quantifiers), [`old`](../syntax.md#old-expressions) expressions and other Prusti specification syntax is allowed within a call to `prusti_assert!`, as in the following example: -```rust,noplaypen +```rust,noplaypen,ignore # use prusti_contracts::*; # #[requires(*x != 2)] @@ -21,7 +21,7 @@ fn go(x: &mut u32) { The two macros `prusti_assert_eq` and `prusti_assert_ne` are also slightly different than their standard counterparts, in that they use [snapshot equality](syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. -```rust +```rust,noplaypen,ignore # use prusti_contracts::*; # #[requires(a === b)] @@ -94,7 +94,7 @@ holds at a point within the body of a function. Of course, if used improperly, this can be used to introduce unsoundness. For example, Prusti would verify the following function: -```rust,noplaypen +```rust,noplaypen,ignore #[ensures(p == np)] fn proof(p: u32, np: u32) { prusti_assume!(false); diff --git a/docs/user-guide/src/verify/closure.md b/docs/user-guide/src/verify/closure.md index 615893297f9..221dee6fa11 100644 --- a/docs/user-guide/src/verify/closure.md +++ b/docs/user-guide/src/verify/closure.md @@ -6,7 +6,7 @@ [Rust closures](https://doc.rust-lang.org/book/ch13-01-closures.html) can be given a specification using the `closure!(...)` syntax: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; fn main() { diff --git a/docs/user-guide/src/verify/external.md b/docs/user-guide/src/verify/external.md index 147c80ef212..e0dd156cfdc 100644 --- a/docs/user-guide/src/verify/external.md +++ b/docs/user-guide/src/verify/external.md @@ -4,7 +4,7 @@ Since the Rust standard library and external libraries do not specify contracts The standard library type `std::option::Option` could be specified as follows: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[extern_spec] @@ -28,7 +28,7 @@ Any function in an external specification is implicitly [trusted](trusted.md) (a Module functions can be specified using a nested `mod` syntax: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[extern_spec] diff --git a/docs/user-guide/src/verify/loop.md b/docs/user-guide/src/verify/loop.md index f859629804a..61e1ef27b9d 100644 --- a/docs/user-guide/src/verify/loop.md +++ b/docs/user-guide/src/verify/loop.md @@ -11,7 +11,7 @@ To verify loops, including loops in which the loop condition has side effects, P In general, given the loop: -```rust +```rust,noplaypen,ignore while { G; // possibly side-effectful g // loop condition @@ -35,7 +35,7 @@ Finally, the loop body invariant is not enforced when exiting from a loop with a As an example, consider the following program. The loop condition calls `test_and_increment`, and the call has side effects: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[ensures(result == (old(*i) >= 0))] diff --git a/docs/user-guide/src/verify/overflow.md b/docs/user-guide/src/verify/overflow.md index d146bd47f63..9c8b0daa64e 100644 --- a/docs/user-guide/src/verify/overflow.md +++ b/docs/user-guide/src/verify/overflow.md @@ -4,6 +4,8 @@ Overflow checks are enabled by default. When overflow checks are enabled, Prusti models integers as bounded values with a range that depends on the type of the integer. Values of `u32` types, for example, would be modeled to be between `0` and `2^32 - 1`. -When overflow checks are disabled, Prusti models each integer type as an unbounded integer. +When overflow checks are disabled, Prusti models signed integers as unbounded integers. Overflow checks can be disabled by setting the [`check_overflows`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#check_overflows) flag to `false`. See [Providing Flags](https://viperproject.github.io/prusti-dev/dev-guide/config/providing.html) in the developer guide for details. + +By default, unsigned integers are modeled as being non-negative (`0 <= i`), even with overflow checks disabled. They can also be modeled as unbounded integers by setting the [`encode_unsigned_num_constraint`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#encode_unsigned_num_constraint) flag to `false`. diff --git a/docs/user-guide/src/verify/panic.md b/docs/user-guide/src/verify/panic.md index 53ff8f89b26..c93970d88eb 100644 --- a/docs/user-guide/src/verify/panic.md +++ b/docs/user-guide/src/verify/panic.md @@ -2,7 +2,7 @@ With the default settings, Prusti checks absence of panics. For example, consider the following program which always panics when executed: -```rust +```rust,noplaypen,ignore pub fn main() { unreachable!(); } @@ -24,7 +24,7 @@ This message correctly points out that the `unreachable!()` statement might actu The message says "might" because Prusti is conservative, i.e., it reports a verification error *unless* it can prove that the statement is unreachable. Hence, Prusti successfully the example below as it can rule out that the condition in the conditional statement, `a <= 0`, holds. -```rust +```rust,noplaypen,ignore pub fn main() { let a = 5; if a <= 0 { diff --git a/docs/user-guide/src/verify/pledge.md b/docs/user-guide/src/verify/pledge.md index 9b1e556d1ab..6fbcf573183 100644 --- a/docs/user-guide/src/verify/pledge.md +++ b/docs/user-guide/src/verify/pledge.md @@ -4,7 +4,7 @@ Pledges are a construct that can be used to specify the behaviour of functions t As a full example, a wrapper around Rust `Vec` could be implemented as follows: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; pub struct VecWrapperI32 { diff --git a/docs/user-guide/src/verify/predicate.md b/docs/user-guide/src/verify/predicate.md index 3fdfb1b139f..162b8344de8 100644 --- a/docs/user-guide/src/verify/predicate.md +++ b/docs/user-guide/src/verify/predicate.md @@ -6,7 +6,7 @@ They are more powerful than pure functions: inside predicate bodies the full [Pr Predicates are declared using the `predicate!` macro on a function: -```rust +```rust,noplaypen,ignore predicate! { fn all_zeroes(a: &MyArray) -> bool { forall(|i: usize| @@ -17,7 +17,7 @@ predicate! { Within specifications, predicates can be called just like pure functions: -```rust +```rust,noplaypen,ignore #[ensures(all_zeros(a))] fn zero(a: &mut MyArray) { ... } ``` diff --git a/docs/user-guide/src/verify/prepost.md b/docs/user-guide/src/verify/prepost.md index b084742a674..e5d47ac9d4d 100644 --- a/docs/user-guide/src/verify/prepost.md +++ b/docs/user-guide/src/verify/prepost.md @@ -2,7 +2,7 @@ In Prusti, the externally observable behaviour of a function can be specified with preconditions and postconditions. They can be provided using [Rust attributes](https://doc.rust-lang.org/reference/attributes.html): -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[requires(...)] diff --git a/docs/user-guide/src/verify/print_counterexample.md b/docs/user-guide/src/verify/print_counterexample.md index 85f4bbb724b..f031bf4957f 100644 --- a/docs/user-guide/src/verify/print_counterexample.md +++ b/docs/user-guide/src/verify/print_counterexample.md @@ -6,7 +6,7 @@ A counterexample for structs and enums can be formatted by annotating the type w If a struct is annotated, the macro must have at least one argument and the first argument must be of type String and can contain an arbitrary number of curly brackets. The number of curly brackets must match the number of the remaining arguments. The remaining arguments must either be a field name, if the fields are named, or an index, if the fields are unnamed. A field can be used multiple times. -```rust +```rust,noplaypen,ignore #[print_counterexample("Custom message: {}, {}", field_1, field_2) ] struct X { field_1: i32, @@ -18,7 +18,7 @@ struct X { If an enum is annotated, the macro must not contain any arguments. Each variant can be annotated in the exact same way as previously described. Only annotating a variant without the enum itself will result in a compile time error. -```rust +```rust,noplaypen,ignore #[print_counterexample()] enum X { #[print_counterexample("Custom message: {}, {}", 0, 1)] diff --git a/docs/user-guide/src/verify/pure.md b/docs/user-guide/src/verify/pure.md index e92150e47d3..a74c64898b7 100644 --- a/docs/user-guide/src/verify/pure.md +++ b/docs/user-guide/src/verify/pure.md @@ -4,7 +4,7 @@ Pure functions are functions which are deterministic and side-effect free. In Pr At the moment, it is up to the user to ensure that functions annotated with `#[pure]` always terminate. Non-terminating pure functions would allow to infer `false`. -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[pure] diff --git a/docs/user-guide/src/verify/spec_ent.md b/docs/user-guide/src/verify/spec_ent.md index 6c2da0b1bbf..8da85f89949 100644 --- a/docs/user-guide/src/verify/spec_ent.md +++ b/docs/user-guide/src/verify/spec_ent.md @@ -4,7 +4,7 @@ The contract for a closure or function pointer variable can be given using the specification entailment syntax: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[requires( diff --git a/docs/user-guide/src/verify/trusted.md b/docs/user-guide/src/verify/trusted.md index 617f5360adc..b66955f6eca 100644 --- a/docs/user-guide/src/verify/trusted.md +++ b/docs/user-guide/src/verify/trusted.md @@ -2,7 +2,7 @@ Sometimes specifications express a fact which is true about a function, but the verifier cannot prove it automatically, or it uses features not yet supported by Prusti. In such cases, it is possible to mark a function as `#[trusted]`: -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[trusted] @@ -24,7 +24,7 @@ When declaring a function as `#[trusted]`, Prusti ignores the function's body an As the example below demonstrates, a single wrong, yet trusted, specification may lead to wrong and unexpected verification results. Hence, some care is needed to ensure that the specifications of trusted functions are indeed correct. -```rust +```rust,noplaypen,ignore use prusti_contracts::*; #[trusted] diff --git a/docs/user-guide/src/verify/type-models.md b/docs/user-guide/src/verify/type-models.md index 9806181a6fb..b4a31b4ea5b 100644 --- a/docs/user-guide/src/verify/type-models.md +++ b/docs/user-guide/src/verify/type-models.md @@ -5,7 +5,7 @@ To provide a specification for such structs, you can add model fields to be used model for a struct, simply annotate it with the `#[model]` macro and declare the to-be-modelled fields inside the struct: -```rust +```rust,noplaypen,ignore #[model] struct SomeStruct { some_i32: i32, @@ -15,7 +15,7 @@ struct SomeStruct { You then can use the model of `SomeStruct` inside specifications via the `.model()` function: -```rust +```rust,noplaypen,ignore #[requires(some_struct.model().some_i32 == 42)] fn some_method(some_struct: &SomeStruct) { // ... @@ -25,7 +25,7 @@ fn some_method(some_struct: &SomeStruct) { A model cannot be used outside of specification code, that is the following code will emit an error in Prusti and panic when executed: -```rust +```rust,noplaypen,ignore fn some_client(some_struct: &mut SomeStruct) { some_struct.model().some_i32 = 42; } @@ -47,7 +47,7 @@ Different (generic) models for the same type can have different fields. In order needs attributes attached to the generics. A type argument needs to be attributed with `#[concrete]`, a type parameter with `#[generic]`. In the last example above, we would create a model with: -```rust +```rust,noplaypen,ignore #[model] struct SomeGenericStruct<#[generic] A: Copy, #[concrete] i32> { field_a: A @@ -68,7 +68,7 @@ Note: If you create ambiguous models, you can get a compile error when accessing Using models on types without fields can have unexpected verification behavior as shown in the code snippet below: -```rust +```rust,noplaypen,ignore struct A; // no fields @@ -104,7 +104,7 @@ field `val` for the *same* model is `42` and `43`, a contradiction. An example where a type model comes in handy is the `std::slice::Iter` struct from the standard library. We would like to provide a specification for the `Iterator`: -```rust +```rust,noplaypen,ignore impl Iterator<'a, T> for std::slice::Iter<'a, T> { // ??? spec involving Iter ??? fn next(&mut self) -> Option<&'a T>; @@ -116,7 +116,7 @@ a [straightforward specification](https://doc.rust-lang.org/src/core/slice/iter. We can instead provide a model for `Iter` in the following way, using the `#[model]` macro: -```rust +```rust,noplaypen,ignore use std::slice::Iter; #[model] @@ -131,7 +131,7 @@ This allows an instance of `Iter<'_, T>` to be modelled by the fields `position` The model can then be used in specifications: -```rust +```rust,noplaypen,ignore #[ensures(result.model().position == 0)] #[ensures(result.model().len == slice.len())] #[trusted] diff --git a/docs/user-guide/src/verify/type_cond_spec.md b/docs/user-guide/src/verify/type_cond_spec.md index ff8a9c0e198..9668987d9c3 100644 --- a/docs/user-guide/src/verify/type_cond_spec.md +++ b/docs/user-guide/src/verify/type_cond_spec.md @@ -4,7 +4,7 @@ When specifying trait methods or generic functions, there is often a special cas For example, one could use this to specify a function like `core::mem::size_of` by defining a trait for types whose size we'd like to specify: -```rust +```rust,noplaypen,ignore #[pure] #[refine_spec(where T: KnownSize, [ ensures(result == T::size()), @@ -21,7 +21,7 @@ pub trait KnownSize { There are some marker traits which simply modify the behavior of methods in their super-traits. For instance, consider the `PartialEq` and `Eq` traits. In order to consider this additional behavior for verification, we can refine the contract of `PartialEq::eq` when the type is known to be marked `Eq`: -```rust +```rust,noplaypen,ignore pub trait PartialEq { #[refine_spec(where Self: Eq, [ ensures(self == self), // reflexive diff --git a/prusti-tests/tests/verify/fail/user-guide/README.md b/prusti-tests/tests/verify/fail/user-guide/README.md new file mode 100644 index 00000000000..29875ce189e --- /dev/null +++ b/prusti-tests/tests/verify/fail/user-guide/README.md @@ -0,0 +1 @@ +**The files in this directory are used in the Prusti user guide (prusti-dev/docs/user-guide). If you move or rename any files in this directory, remember to also adjust the user-guide.** \ No newline at end of file diff --git a/prusti-tests/tests/verify/fail/user-guide/counterexample.rs b/prusti-tests/tests/verify/fail/user-guide/counterexample.rs new file mode 100644 index 00000000000..aa28b52f9f1 --- /dev/null +++ b/prusti-tests/tests/verify/fail/user-guide/counterexample.rs @@ -0,0 +1,24 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; +use prusti_contracts::*; + +fn main() {} + +// Note: counterexample = true + +//// ANCHOR: code +// #[requires(x >= 0)] // Forgot to add this condition +#[ensures(result == x * (x + 1) / 2)] //~ ERROR postcondition might not hold +fn summation(x: i32) -> i32 { + let mut i = 1; + let mut sum = 0; + while i <= x { + body_invariant!(sum == (i - 1) * i / 2); + sum += i; + i += 1; + } + sum +} +//// ANCHOR_END: code \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/03-fail.rs b/prusti-tests/tests/verify/fail/user-guide/getting_started_failing.rs similarity index 77% rename from docs/user-guide/src/tour/tour-src/src/03-fail.rs rename to prusti-tests/tests/verify/fail/user-guide/getting_started_failing.rs index 4585a7f066a..1f190ce8317 100644 --- a/docs/user-guide/src/tour/tour-src/src/03-fail.rs +++ b/prusti-tests/tests/verify/fail/user-guide/getting_started_failing.rs @@ -23,6 +23,7 @@ fn main() { } } +//// ANCHOR: failing_code fn test(x: i32) { let test = Node { elem: x, // unknown value @@ -30,6 +31,7 @@ fn test(x: i32) { }; if test.elem > 23 { - panic!() + panic!() //~ ERROR panic!(..) statement might be reachable } } +//// ANCHOR_END: failing_code diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs b/prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs similarity index 65% rename from docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs rename to prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs index 2f8a548f7e0..e50df4fa1c1 100644 --- a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_1.rs +++ b/prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -15,20 +19,22 @@ struct Node { } //// ANCHOR: first_spec_1 +//// ANCHOR: first_spec_2 impl List { + //// ANCHOR_END: first_spec_2 pub fn len(&self) -> usize { self.head.len() } //// ANCHOR_END: first_spec_1 //// ANCHOR: first_spec_2 - #[ensures(result.len() == 0)] + #[ensures(result.len() == 0)] //~ ERROR use of impure function "List::len" in pure code is not allowed pub fn new() -> Self { List { head: Link::Empty } } -//// ANCHOR_END: first_spec_2 //// ANCHOR: first_spec_1 } +//// ANCHOR_END: first_spec_2 impl Link { fn len(&self) -> usize { diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs b/prusti-tests/tests/verify/fail/user-guide/impl_new_spec_2.rs similarity index 70% rename from docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs rename to prusti-tests/tests/verify/fail/user-guide/impl_new_spec_2.rs index 72fe80d45a0..d2a900180c1 100644 --- a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_2.rs +++ b/prusti-tests/tests/verify/fail/user-guide/impl_new_spec_2.rs @@ -1,7 +1,12 @@ + +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; //// ANCHOR: import_prusti use prusti_contracts::*; //// ANCHOR_END: import_prusti +fn main() {} + pub struct List { head: Link, } @@ -24,7 +29,7 @@ impl List { } //// ANCHOR: import_prusti - #[ensures(result.len() == 0)] + #[ensures(result.len() == 0)] //~ ERROR use of impure function "List::len" in pure code is not allowed pub fn new() -> Self { List { head: Link::Empty } } diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs b/prusti-tests/tests/verify/fail/user-guide/impl_new_spec_3.rs similarity index 52% rename from docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs rename to prusti-tests/tests/verify/fail/user-guide/impl_new_spec_3.rs index 6df329e0324..352716991f3 100644 --- a/docs/user-guide/src/tour/tour-src/src/new/spec_failing_3.rs +++ b/prusti-tests/tests/verify/fail/user-guide/impl_new_spec_3.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -18,15 +22,17 @@ struct Node { impl List { #[pure] pub fn len(&self) -> usize { - self.head.len() + self.head.len() //~ ERROR use of impure function "Link::len" in pure code is not allowed } //// ANCHOR_END: pure_annotation - #[ensures(result.len() == 0)] + #[ensures(result.len() == 0)] //~ ERROR precondition of pure function call might not hold. pub fn new() -> Self { List { head: Link::Empty } } + //// ANCHOR: pure_annotation } +//// ANCHOR_END: pure_annotation impl Link { fn len(&self) -> usize { diff --git a/prusti-tests/tests/verify/fail/user-guide/loop_invariants.rs b/prusti-tests/tests/verify/fail/user-guide/loop_invariants.rs new file mode 100644 index 00000000000..9d3c1623526 --- /dev/null +++ b/prusti-tests/tests/verify/fail/user-guide/loop_invariants.rs @@ -0,0 +1,19 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; +use prusti_contracts::*; + +fn main() {} + +//// ANCHOR: specification +#[requires(x >= 0)] +#[ensures(result == x * (x + 1) / 2)] //~ ERROR postcondition might not hold +fn summation(x: i32) -> i32 { + let mut i = 1; + let mut sum = 0; + while i <= x { + sum += i; + i += 1; + } + sum +} +//// ANCHOR_END: specification \ No newline at end of file diff --git a/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs b/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs new file mode 100644 index 00000000000..a2fbcb80d13 --- /dev/null +++ b/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs @@ -0,0 +1,50 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; +use prusti_contracts::*; + +fn main() {} + +pub struct List { + head: Link, +} + +type Link = Option>; + +struct Node { + elem: i32, + next: Link, +} + +//// ANCHOR: code +impl List { + // Prusti cannot verify these functions at the moment, + // since loops in pure functions are not yet supported: + #[pure] + pub fn len(&self) -> usize { + let mut curr = &self.head; + let mut i = 0; + while let Some(node) = curr { + body_invariant!(true); + i += 1; + curr = &node.next; + } + i + } + + #[pure] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + let mut curr = &self.head; + let mut i = index; + while let Some(node) = curr { + body_invariant!(true); + if i == 0 { + return node.elem; + } + i -= 1; + curr = &node.next; + } + unreachable!() + } +} +//// ANCHOR_END: code \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs similarity index 93% rename from docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs rename to prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs index 2725fc0c903..ed59b7de7ed 100644 --- a/docs/user-guide/src/tour/tour-src/src/peek_mut/initial_code.rs +++ b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs @@ -1,7 +1,11 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -91,12 +95,11 @@ impl List { self.is_empty() )] #[ensures(!old(self.is_empty()) ==> - self.head_removed(&old(snap(self))) - && + self.head_removed(&old(snap(self))) && result === Some(snap(old(snap(self)).lookup(0))) )] pub fn try_pop(&mut self) -> Option { - match self.head.take() { // Replace mem::swap with the buildin Option::take + match self.head.take() { None => None, Some(node) => { self.head = node.next; @@ -190,7 +193,7 @@ mod prusti_tests { use super::*; //// ANCHOR_END: test_peek_mut - fn _test_1(){ + fn test_1(){ let mut list = List::new(); prusti_assert!(list.is_empty() && list.len() == 0); @@ -224,7 +227,7 @@ mod prusti_tests { #[requires(!list_1.is_empty())] #[requires(*list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { + fn test_2(list_0: &mut List, list_1: &mut List) { let x0 = list_0.pop(); list_0.push(10); @@ -233,7 +236,7 @@ mod prusti_tests { prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); @@ -281,9 +284,9 @@ mod prusti_tests { prusti_assert!(*first == 5); // `first` gets dropped here, `list` can be accessed again } - prusti_assert!(list.len() == 2); // Fails - prusti_assert!(*list.lookup(0) == 5); // Fails - prusti_assert!(*list.lookup(1) == 8); // Fails + prusti_assert!(list.len() == 2); //~ ERROR the asserted expression might not hold + prusti_assert!(*list.lookup(0) == 5); // Fails too + prusti_assert!(*list.lookup(1) == 8); // Fails too } } //// ANCHOR_END: test_peek_mut \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs b/prusti-tests/tests/verify/fail/user-guide/pop.rs similarity index 90% rename from docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs rename to prusti-tests/tests/verify/fail/user-guide/pop.rs index b5246c26c0b..95c2eec5369 100644 --- a/docs/user-guide/src/tour/tour-src/src/pop/initial_code.rs +++ b/prusti-tests/tests/verify/fail/user-guide/pop.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -46,7 +50,7 @@ impl List { self.head.lookup(index) } - #[ensures(self.len() == old(self.len()) + 1)] + #[ensures(self.len() == old(self.len()) + 1)] //~ ERROR postcondition might not hold #[ensures(self.lookup(0) == elem)] #[ensures(forall(|i: usize| (i < old(self.len())) ==> old(self.lookup(i)) == self.lookup(i + 1)))] @@ -69,9 +73,11 @@ impl List { }, } } - + + //// ANCHOR_END: initial //// ANCHOR: pop_precondition #[requires(!self.is_empty())] + //// ANCHOR: initial pub fn pop(&mut self) -> i32 { self.try_pop().unwrap() } diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs b/prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs similarity index 83% rename from docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs rename to prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs index 1b83e3a7d4b..fbbeec1a421 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/property_2_missing_bounds.rs +++ b/prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -27,7 +31,7 @@ impl List { } #[ensures(self.len() == old(self.len()) + 1)] - #[ensures(self.lookup(0) == elem)] // 2. Property + #[ensures(self.lookup(0) == elem)] // 2. Property //~ ERROR postcondition might not hold pub fn push(&mut self, elem: i32) { // ... //// ANCHOR_END: lookup @@ -62,7 +66,7 @@ impl Link { node.next.lookup(index - 1) } } - Link::Empty => unreachable!(), + Link::Empty => unreachable!(), //~ ERROR: unreachable!(..) statement might be reachable } } //// ANCHOR_END: lookup diff --git a/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs b/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs new file mode 100644 index 00000000000..97ead5a2379 --- /dev/null +++ b/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs @@ -0,0 +1,18 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; +use prusti_contracts::*; + +fn main() {} + +//// ANCHOR: code +#[ensures(result == x * x * x)] +fn square(x: i32) -> i32 { + x * x * x +} + +fn test() { + let x = 10; + let x_squared = square(x); + prusti_assert!(x_squared == 100); //~ ERROR the asserted expression might not hold +} +//// ANCHOR_END: code \ No newline at end of file diff --git a/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs b/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs new file mode 100644 index 00000000000..33120c4c23b --- /dev/null +++ b/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs @@ -0,0 +1,21 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; +use prusti_contracts::*; + +fn main() {} + +//// ANCHOR: code +#[requires(x == 10)] +#[ensures(result == x * x)] +fn restrictive_square(x: i32) -> i32 { + x * x +} + +fn test() { + let x = 10; + let x_squared = restrictive_square(x); + + let y = 5; + let y_squared = restrictive_square(y); //~ ERROR precondition might not hold. +} +//// ANCHOR_END: code \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/user-guide/README.md b/prusti-tests/tests/verify/pass/user-guide/README.md new file mode 100644 index 00000000000..29875ce189e --- /dev/null +++ b/prusti-tests/tests/verify/pass/user-guide/README.md @@ -0,0 +1 @@ +**The files in this directory are used in the Prusti user guide (prusti-dev/docs/user-guide). If you move or rename any files in this directory, remember to also adjust the user-guide.** \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs similarity index 97% rename from docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs rename to prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs index fb7ba403db8..04847549665 100644 --- a/docs/user-guide/src/tour/tour-src/src/peek_mut/assert_on_expiry.rs +++ b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs @@ -1,7 +1,11 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -265,7 +269,7 @@ mod prusti_tests { prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); diff --git a/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/generic.rs similarity index 88% rename from docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs rename to prusti-tests/tests/verify/pass/user-guide/generic.rs index 479810e6b16..85727ee2c21 100644 --- a/docs/user-guide/src/tour/tour-src/src/generic/initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/generic.rs @@ -1,8 +1,13 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + //// ANCHOR: generic_types +// Make the types generic: pub struct List { head: Link, } @@ -67,7 +72,8 @@ impl List { #[pure] #[requires(index < self.len())] //// ANCHOR: lookup_reference - pub fn lookup(&self, index: usize) -> &T { // Return type is changed from `T` to `&T` + // Return type is changed from `T` to `&T` + pub fn lookup(&self, index: usize) -> &T { link_lookup(&self.head, index) } //// ANCHOR_END: lookup_reference @@ -110,6 +116,7 @@ impl List { result === Some(snap(old(snap(self)).lookup(0))) )] //// ANCHOR: generic_types + // Return type changed from `Option` pub fn try_pop(&mut self) -> Option { // ... //// ANCHOR_END: generic_types @@ -128,6 +135,7 @@ impl List { #[ensures(self.head_removed(&old(snap(self))))] #[ensures(result === old(snap(self)).lookup(0))] //// ANCHOR: generic_types + // Return type changed from `i32` pub fn pop(&mut self) -> T { self.try_pop().unwrap() //// ANCHOR: lookup_reference @@ -139,11 +147,13 @@ impl List { #[pure] #[requires(index < link_len(link))] //// ANCHOR: lookup_reference -fn link_lookup(link: &Link, index: usize) -> &T { // Return type is changed from `T` to `&T` +// Return type is changed from `T` to `&T` +fn link_lookup(link: &Link, index: usize) -> &T { match link { Some(node) => { if index == 0 { - &node.elem // Here we return a reference to `elem` instead of the `elem` itself + // Here we return a reference to `elem` instead of the `elem` itself + &node.elem } else { link_lookup(&node.next, index - 1) } @@ -167,13 +177,14 @@ mod prusti_tests { use super::*; //// ANCHOR_END: generic_types - fn _test_1(){ + fn test_1(){ let mut list = List::new(); prusti_assert!(list.is_empty() && list.len() == 0); list.push(5); prusti_assert!(!list.is_empty() && list.len() == 1); - prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` + // Here we can dereference the lookup, since `i32` is `Copy`: + prusti_assert!(*list.lookup(0) == 5); // ... //// ANCHOR_END: lookup_reference @@ -204,7 +215,8 @@ mod prusti_tests { #[requires(*list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] //// ANCHOR: generic_types - fn _test_2(list_0: &mut List, list_1: &mut List) { + // For this test we keep `i32`, but we could make it generic too + fn test_2(list_0: &mut List, list_1: &mut List) { // ... //// ANCHOR_END: generic_types let x0 = list_0.pop(); @@ -215,7 +227,7 @@ mod prusti_tests { prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); diff --git a/docs/user-guide/src/tour/tour-src/src/03-chapter-2-1.rs b/prusti-tests/tests/verify/pass/user-guide/getting_started_working.rs similarity index 77% rename from docs/user-guide/src/tour/tour-src/src/03-chapter-2-1.rs rename to prusti-tests/tests/verify/pass/user-guide/getting_started_working.rs index e76fd5e8dd1..f2ceba6fe3c 100644 --- a/docs/user-guide/src/tour/tour-src/src/03-chapter-2-1.rs +++ b/prusti-tests/tests/verify/pass/user-guide/getting_started_working.rs @@ -1,3 +1,4 @@ +//// ANCHOR: types pub struct List { head: Link, } @@ -11,7 +12,9 @@ struct Node { elem: i32, next: Link, } +//// ANCHOR_END: types +//// ANCHOR: code fn main() { let test = Node { elem: 17, @@ -22,3 +25,4 @@ fn main() { panic!() // unreachable } } +//// ANCHOR_END: code diff --git a/docs/user-guide/src/tour/tour-src/src/new/impl_new.rs b/prusti-tests/tests/verify/pass/user-guide/impl_new.rs similarity index 94% rename from docs/user-guide/src/tour/tour-src/src/new/impl_new.rs rename to prusti-tests/tests/verify/pass/user-guide/impl_new.rs index 418acb8b70a..f7a9c635317 100644 --- a/docs/user-guide/src/tour/tour-src/src/new/impl_new.rs +++ b/prusti-tests/tests/verify/pass/user-guide/impl_new.rs @@ -1,3 +1,5 @@ +fn main() {} + pub struct List { head: Link, } diff --git a/docs/user-guide/src/tour/tour-src/src/new/full_code.rs b/prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs similarity index 73% rename from docs/user-guide/src/tour/tour-src/src/new/full_code.rs rename to prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs index d2c2433c188..1637e282dc9 100644 --- a/docs/user-guide/src/tour/tour-src/src/new/full_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -28,6 +32,7 @@ impl List { } } +//// ANCHOR: implementation impl Link { #[pure] fn len(&self) -> usize { @@ -42,11 +47,15 @@ impl Link { matches!(self, Link::Empty) } } +//// ANCHOR_END: implementation -fn _test_len(link: &Link) { +//// ANCHOR: test_len +fn test_len(link: &Link) { let link_is_empty = link.is_empty(); let link_len = link.len(); assert!(link_is_empty == (link_len == 0)); } +//// ANCHOR_END: test_len -fn main() {} +//// ANCHOR: nothing +//// ANCHOR_END: nothing \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs b/prusti-tests/tests/verify/pass/user-guide/impl_new_spec_fixed.rs similarity index 80% rename from docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs rename to prusti-tests/tests/verify/pass/user-guide/impl_new_spec_fixed.rs index 74876b12ee2..2bf00979329 100644 --- a/docs/user-guide/src/tour/tour-src/src/new/spec_fixed.rs +++ b/prusti-tests/tests/verify/pass/user-guide/impl_new_spec_fixed.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } diff --git a/prusti-tests/tests/verify/pass/user-guide/loop_invariants_final.rs b/prusti-tests/tests/verify/pass/user-guide/loop_invariants_final.rs new file mode 100644 index 00000000000..f5547ea0bc2 --- /dev/null +++ b/prusti-tests/tests/verify/pass/user-guide/loop_invariants_final.rs @@ -0,0 +1,22 @@ +//// ANCHOR: nothing +//// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; +use prusti_contracts::*; + +fn main() {} + +//// ANCHOR: specification +#[requires(x >= 0)] +#[ensures(result == x * (x + 1) / 2)] +fn summation(x: i32) -> i32 { + let mut i = 1; + let mut sum = 0; + while i <= x { + body_invariant!(sum == (i - 1) * i / 2); + sum += i; + i += 1; + } + sum +} +//// ANCHOR_END: specification \ No newline at end of file diff --git a/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/option.rs similarity index 95% rename from docs/user-guide/src/tour/tour-src/src/option/initial_code.rs rename to prusti-tests/tests/verify/pass/user-guide/option.rs index e1f3f25f905..ca44db8e10d 100644 --- a/docs/user-guide/src/tour/tour-src/src/option/initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/option.rs @@ -1,7 +1,11 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -171,12 +175,12 @@ fn link_len(link: &Link) -> usize { mod prusti_tests { use super::*; - fn _test_1(){ + fn test_1(){ let mut list = List::new(); // create an new, empty list - prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty and have 0 length + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); // now the list should not be empty and have a length of 1 + prusti_assert!(!list.is_empty() && list.len() == 1); // the list should have a length of 1 prusti_assert!(list.lookup(0) == 5); // the head of the list should be 5 list.push(10); @@ -205,7 +209,7 @@ mod prusti_tests { #[requires(!list_1.is_empty())] #[requires(list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { + fn test_2(list_0: &mut List, list_1: &mut List) { let x0 = list_0.pop(); list_0.push(10); @@ -214,7 +218,7 @@ mod prusti_tests { prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); diff --git a/docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/peek.rs similarity index 95% rename from docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs rename to prusti-tests/tests/verify/pass/user-guide/peek.rs index 247a3ec6606..f0bd5dd0338 100644 --- a/docs/user-guide/src/tour/tour-src/src/peek/initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek.rs @@ -1,7 +1,11 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -153,9 +157,9 @@ fn link_len(link: &Link) -> usize { //// ANCHOR: test_peek mod prusti_tests { use super::*; - //// ANCHOR_END: test_peek - fn _test_1(){ + //// ANCHOR_END: test_peek + fn test_1(){ let mut list = List::new(); prusti_assert!(list.is_empty() && list.len() == 0); @@ -189,7 +193,7 @@ mod prusti_tests { #[requires(!list_1.is_empty())] #[requires(*list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { + fn test_2(list_0: &mut List, list_1: &mut List) { let x0 = list_0.pop(); list_0.push(10); @@ -198,7 +202,7 @@ mod prusti_tests { prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); @@ -229,4 +233,4 @@ mod prusti_tests { prusti_assert!(*list.peek() == 16); } } -//// ANCHOR_END: test_peek \ No newline at end of file +//// ANCHOR_END: test_peek diff --git a/docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs similarity index 93% rename from docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs rename to prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs index eec2b941e93..9df1afdc007 100644 --- a/docs/user-guide/src/tour/tour-src/src/peek_mut/final_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs @@ -1,7 +1,11 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -91,12 +95,11 @@ impl List { self.is_empty() )] #[ensures(!old(self.is_empty()) ==> - self.head_removed(&old(snap(self))) - && + self.head_removed(&old(snap(self))) && result === Some(snap(old(snap(self)).lookup(0))) )] pub fn try_pop(&mut self) -> Option { - match self.head.take() { // Replace mem::swap with the buildin Option::take + match self.head.take() { None => None, Some(node) => { self.head = node.next; @@ -128,10 +131,10 @@ impl List { #[ensures(snap(result) === old(snap(self.peek())))] //// ANCHOR: pledge #[after_expiry( - old(self.len()) === self.len() // (1.) - && forall(|i: usize| 1 <= i && i < self.len() // (2.) + old(self.len()) === self.len() // (1. condition) + && forall(|i: usize| 1 <= i && i < self.len() // (2. condition) ==> old(snap(self.lookup(i))) === snap(self.lookup(i))) - && snap(self.peek()) === before_expiry(snap(result)) // (3.) + && snap(self.peek()) === before_expiry(snap(result)) // (3. condition) )] pub fn peek_mut(&mut self) -> &mut T { //// ANCHOR_END: pledge @@ -177,7 +180,7 @@ fn link_len(link: &Link) -> usize { mod prusti_tests { use super::*; - fn _test_1(){ + fn test_1(){ let mut list = List::new(); prusti_assert!(list.is_empty() && list.len() == 0); @@ -211,7 +214,7 @@ mod prusti_tests { #[requires(!list_1.is_empty())] #[requires(*list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { + fn test_2(list_0: &mut List, list_1: &mut List) { let x0 = list_0.pop(); list_0.push(10); @@ -220,7 +223,7 @@ mod prusti_tests { prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); @@ -235,7 +238,7 @@ mod prusti_tests { prusti_assert!(y0 == x3); } - fn _test_3() { + fn test_3() { let mut list = List::new(); list.push(16); prusti_assert!(list.peek() === list.lookup(0)); @@ -250,7 +253,7 @@ mod prusti_tests { prusti_assert!(*list.peek() == 16); } - fn _test_4() { + fn test_4() { let mut list = List::new(); list.push(8); list.push(16); diff --git a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs b/prusti-tests/tests/verify/pass/user-guide/pop.rs similarity index 95% rename from docs/user-guide/src/tour/tour-src/src/pop/final_code.rs rename to prusti-tests/tests/verify/pass/user-guide/pop.rs index f0f548a4834..b2026b18b3f 100644 --- a/docs/user-guide/src/tour/tour-src/src/pop/final_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/pop.rs @@ -1,7 +1,11 @@ //// ANCHOR: none //// ANCHOR_END: none +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -16,6 +20,7 @@ struct Node { next: Link, } +//// ANCHOR: extern_spec #[extern_spec(std::mem)] #[ensures(snap(dest) === src)] #[ensures(result === old(snap(dest)))] @@ -38,6 +43,7 @@ impl std::option::Option { #[ensures(result == matches!(self, Some(_)))] pub const fn is_some(&self) -> bool; } +//// ANCHOR_END: extern_spec //// ANCHOR: two_state_predicate //// ANCHOR: predicate_use @@ -131,10 +137,12 @@ impl List { //// ANCHOR_END: predicate_use #[requires(!self.is_empty())] - #[ensures(self.head_removed(&old(snap(self))))] //// ANCHOR: predicate_use + #[ensures(self.head_removed(&old(snap(self))))] + //// ANCHOR_END: predicate_use //// ANCHOR: pop_result_correct #[ensures(result === old(snap(self)).lookup(0))] + //// ANCHOR: predicate_use pub fn pop(&mut self) -> i32 { // ... //// ANCHOR_END: predicate_use diff --git a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs b/prusti-tests/tests/verify/pass/user-guide/push_final_code.rs similarity index 94% rename from docs/user-guide/src/tour/tour-src/src/push/final_code.rs rename to prusti-tests/tests/verify/pass/user-guide/push_final_code.rs index 6ce233e9bb9..30f3e9efaaf 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/final_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_final_code.rs @@ -1,7 +1,11 @@ //// ANCHOR: nothing //// ANCHOR_END: nothing +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } diff --git a/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/push_initial_code.rs similarity index 89% rename from docs/user-guide/src/tour/tour-src/src/push/initial_code.rs rename to prusti-tests/tests/verify/pass/user-guide/push_initial_code.rs index f62a0a93550..974cebcc457 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_initial_code.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_1.rs b/prusti-tests/tests/verify/pass/user-guide/push_property_1.rs similarity index 91% rename from docs/user-guide/src/tour/tour-src/src/push/property_1.rs rename to prusti-tests/tests/verify/pass/user-guide/push_property_1.rs index fcb6803f9ce..4eb11dd92cb 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/property_1.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_property_1.rs @@ -1,7 +1,9 @@ -//// ANCHOR: extern_spec +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; -//// ANCHOR_END: extern_spec +fn main() {} + pub struct List { head: Link, } @@ -17,13 +19,13 @@ struct Node { } //// ANCHOR: extern_spec -//// ANCHOR: property_1 #[extern_spec(std::mem)] #[ensures(snap(dest) === src)] #[ensures(result === old(snap(dest)))] fn replace(dest: &mut T, src: T) -> T; //// ANCHOR_END: extern_spec +//// ANCHOR: property_1 impl List { #[ensures(self.len() == old(self.len()) + 1)] // 1. Property pub fn push(&mut self, elem: i32) { diff --git a/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs b/prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs similarity index 93% rename from docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs rename to prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs index b567439775b..47c9baeae60 100644 --- a/docs/user-guide/src/tour/tour-src/src/push/property_2_with_bounds.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } diff --git a/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs similarity index 93% rename from docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs rename to prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs index 6f2eacb68dd..035b9b2ee34 100644 --- a/docs/user-guide/src/tour/tour-src/src/testing/initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs @@ -1,5 +1,9 @@ +// The next line is only required for doctests, you can ignore/remove it +extern crate prusti_contracts; use prusti_contracts::*; +fn main() {} + pub struct List { head: Link, } @@ -145,12 +149,12 @@ mod prusti_tests { use super::*; //// ANCHOR_END: test_2 - fn _test_1(){ + fn test_1(){ let mut list = List::new(); // create an new, empty list - prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty and have 0 length + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); // now the list should not be empty and have a length of 1 + prusti_assert!(!list.is_empty() && list.len() == 1); // the list should have a length of 1 prusti_assert!(list.lookup(0) == 5); // the head of the list should be 5 list.push(10); @@ -174,19 +178,20 @@ mod prusti_tests { prusti_assert!(list.is_empty() && list.len() == 0); // length correct prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` } + //// ANCHOR_END: test_1 //// ANCHOR: test_2 #[requires(list_0.len() >= 4)] #[requires(!list_1.is_empty())] #[requires(list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { + fn test_2(list_0: &mut List, list_1: &mut List) { let x0 = list_0.pop(); list_0.push(10); prusti_assert!(list_0.len() >= 4); prusti_assert!(list_0.lookup(1) == 42); - assert!(list_0.pop() == 10); // Note: This cannot be a `prusti_assert`, since `pop` changes the list + assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list let x1 = list_0.pop(); let x2 = list_0.pop(); @@ -200,6 +205,7 @@ mod prusti_tests { prusti_assert!(y0 == old(snap(list_1)).lookup(0)); prusti_assert!(y0 == x3); } + //// ANCHOR: test_1 } //// ANCHOR_END: test_1 //// ANCHOR_END: test_2 \ No newline at end of file From e661ee6165e4d69970e33a3bbf5bf5aca69fd337 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 28 Feb 2023 15:46:22 +0100 Subject: [PATCH 08/31] Fix code block formating --- docs/dev-guide/src/development/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-guide/src/development/build.md b/docs/dev-guide/src/development/build.md index ce1f0c7b2c6..a958e88ec18 100644 --- a/docs/dev-guide/src/development/build.md +++ b/docs/dev-guide/src/development/build.md @@ -2,7 +2,7 @@ Once the [development setup](setup.md) is complete, Prusti can be compiled. Using the Python wrapper script: -``` +```bash $ ./x.py build ``` From e790774a37febf8039befa0354602c63f7e520f9 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 8 Mar 2023 09:22:41 +0100 Subject: [PATCH 09/31] Update documentation instructions --- docs/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/README.md b/docs/README.md index 4b680925cc1..604e3443041 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,40 @@ # Documentation This is the source directory for Prusti's GitHub page (). Modifications should consist of pushes or pull requests to the `master` branch. Deployment on the `gh-pages` branch is done automatically via GitHub actions. + +The books in this documentation use [mdbook](https://rust-lang.github.io/mdBook/index.html). + +## Showing the book locally + +After [installing mdbook](https://rust-lang.github.io/mdBook/guide/installation.html) and cloning the docs onto your machine, go to the book directory you want to work on. + +By running `mdbook serve --open` (`mdbook.exe` on Windows), mdbooks will build the book and open it in your default browser. +On a file change, mdbook will rebuild the book and the browser window should refresh. + + +## Importing code files into the book + +Small code samples can be immediately put into a code block inline to the book. +Larger code samples and code that should be checked by Prusti for correctness should be put into the prusti testing directory (`../prusti-tests/tests/`). + +There you can put them in the corresponding directory, like is already done in `/verify/pass/user-guide/`. +These files will be automatically tested by the Prusti test-suite. + +Code blocks that are not supposed to be run by a user, please add `noplaypen` to the code block. + + +## Doctests + +The final book can be tested using `mdbook test` (see [here](https://rust-lang.github.io/mdBook/cli/test.html)). +This will compile and run your code samples using **standard rustc** (no Prusti). + +Code blocks that are not intended to be compiled should be marked with `ignore`. +Code blocks that should not run during doctests should be marked with `no_run`. + +See more on documentation tests [here](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html). + +Doctests should also run automatically as part of the Prusti CI. + +### TODO: Find better way to run doctest +To run doctests locally with code that uses `prusti_contracts`, you will have to supply mdbook with a path containing the needed dependencies. +One (not very nice way) to do this, is taking any Rust crate that uses `prusti_contracts`, building it, then passing the path to that crate to mdbook: mdbook test -L "(Path_to_crate)/target/debug/deps/"` From 2fbb33fae179f8b0830c43a04c9207a822c26233 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 8 Mar 2023 09:24:10 +0100 Subject: [PATCH 10/31] Disable failing test --- .../tests/verify/fail/user-guide/option_loops_in_pure_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs b/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs index a2fbcb80d13..b8b57165371 100644 --- a/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs +++ b/prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs @@ -1,4 +1,5 @@ -// The next line is only required for doctests, you can ignore/remove it +// ignore-test: This code causes Prusti to panic +// The next and previous line are only required for (doc)tests, you can ignore/remove it extern crate prusti_contracts; use prusti_contracts::*; From aa33bd941b1b1517302c25d856af354520e6bfce Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 8 Mar 2023 09:47:52 +0100 Subject: [PATCH 11/31] Provide a way to run mdbook test locally --- Cargo.toml | 2 +- docs/README.md | 11 +++- docs/dummy/Cargo.toml | 12 ++++ docs/dummy/src/lib.rs | 8 +++ docs/user-guide/src/SUMMARY.md | 2 +- docs/user-guide/src/syntax.md | 47 +++++++++++----- docs/user-guide/src/tour/new.md | 2 +- docs/user-guide/src/tour/option.md | 2 +- docs/user-guide/src/tour/peek.md | 2 +- docs/user-guide/src/tour/pledges.md | 2 +- docs/user-guide/src/tour/setup.md | 2 +- .../src/verify/assert_refute_assume.md | 2 +- docs/user-guide/src/verify/counterexample.md | 56 +++++++++++++++++++ .../src/verify/print_counterexample.md | 29 ---------- 14 files changed, 126 insertions(+), 53 deletions(-) create mode 100644 docs/dummy/Cargo.toml create mode 100644 docs/dummy/src/lib.rs create mode 100644 docs/user-guide/src/verify/counterexample.md delete mode 100644 docs/user-guide/src/verify/print_counterexample.md diff --git a/Cargo.toml b/Cargo.toml index 44ccc629e00..35b669b8d23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ "jni-gen/systest", ] exclude = [ - "docs/user-guide/src/tour/tour-src" + "docs/dummy" ] [profile.dev] diff --git a/docs/README.md b/docs/README.md index 604e3443041..67c3747fcc5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,6 +35,13 @@ See more on documentation tests [here](https://doc.rust-lang.org/rustdoc/write-d Doctests should also run automatically as part of the Prusti CI. -### TODO: Find better way to run doctest To run doctests locally with code that uses `prusti_contracts`, you will have to supply mdbook with a path containing the needed dependencies. -One (not very nice way) to do this, is taking any Rust crate that uses `prusti_contracts`, building it, then passing the path to that crate to mdbook: mdbook test -L "(Path_to_crate)/target/debug/deps/"` +One (not very nice way) to do this, is taking any Rust crate that uses `prusti_contracts`, building it, then passing the path to that crate to mdbook: `mdbook test -L "(Path_to_crate)/target/debug/deps/"` + +The crate `docs/dummy` is provided just to provide the dependencies. +To run the doctests (in this case for the user guide): +- `cd docs/dummy/` +- `cargo build` +- `cd ../user-guide/` +- `mdbook test -L ../dummy/target/debug/deps/` + diff --git a/docs/dummy/Cargo.toml b/docs/dummy/Cargo.toml new file mode 100644 index 00000000000..99aa54806aa --- /dev/null +++ b/docs/dummy/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dummy" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# This crate is just used to provide the needed dependencies +# for the mdbook documentation tests + +[dependencies] +prusti-contracts = "0.1.4" diff --git a/docs/dummy/src/lib.rs b/docs/dummy/src/lib.rs new file mode 100644 index 00000000000..23d9c0fcff0 --- /dev/null +++ b/docs/dummy/src/lib.rs @@ -0,0 +1,8 @@ +use prusti_contracts::*; + +// This crate is just used to provide the needed dependencies +// for the mdbook documentation tests + +#[requires(true)] +#[ensures(true)] +fn _dummy() {} \ No newline at end of file diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index ac72033b072..f5d169ac0c4 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -32,5 +32,5 @@ - [Closures](verify/closure.md) - [Specification entailments](verify/spec_ent.md) - [Type models](verify/type-models.md) - - [Customizable counterexample](verify/print_counterexample.md) + - [Counterexamples](verify/counterexample.md) - [Specification Syntax](syntax.md) diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index 7eee0239d34..b4da1ea38dd 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -22,8 +22,8 @@ When using Prusti, `result` is used to refer to what a function returns. Here is an example for returning an integer: ```rust,noplaypen,ignore -use prusti_contracts::*; - +# use prusti_contracts::*; +# #[ensures(result == 5)] fn five() -> i32 { 5 @@ -32,8 +32,8 @@ fn five() -> i32 { And an example for returning a tuple and accessing individual fields: ```rust,noplaypen,ignore -use prusti_contracts::*; - +# use prusti_contracts::*; +# #[ensures(result.0 / 2 == result.1 && result.2 == 'a')] fn tuple() -> (i32, i32, char) { (10, 5, 'a') @@ -46,8 +46,8 @@ fn tuple() -> (i32, i32, char) { Old expressions are used to refer to the value that a memory location pointed at by a mutable reference had at the beginning of the function: ```rust,noplaypen,ignore -use prusti_contracts::*; - +# use prusti_contracts::*; +# #[ensures(*x == old(*x) + 1)] pub fn inc(x: &mut u32) { *x += 1; @@ -65,10 +65,23 @@ Implications express a [relationship](https://en.wikipedia.org/wiki/Material_con #[pure] #[ensures(result ==> self.len() == 0)] #[ensures(!result ==> self.len() > 0)] -pub fn is_empty(&self) -> bool; +pub fn is_empty(&self) -> bool { + // ... +} ``` -`a ==> b` is equivalent to `!a || b` and `!(a && !b)`. This also extends to the short-circuiting behaviour: if `a` is not true, `b` is not evaluated. +`a ==> b` is equivalent to `!a || b` and `!(a && !b)`. Here you can see a truth table for the implication operator: + +| `a` | `b` | `a ==> b` | +|-------|-------|-----------| +| `false` | `false` | `true` | +| `false` | `true` | `true` | +| `true` | `false` | `false` | +| `true` | `true` | `true` | + +Note: The expression `b` is only evaluated if `a` is true ([Short-circuit evaluation](https://en.wikipedia.org/wiki/Short-circuit_evaluation)). + +There is also syntax for a right-to-left implication: ```rust,noplaypen,ignore # use prusti_contracts::*; @@ -86,7 +99,9 @@ There is also syntax for biconditionals ("if and only if"): # #[pure] #[ensures(self.len() == 0 <==> result)] -pub fn is_empty(&self) -> bool; +pub fn is_empty(&self) -> bool { + // ... +} ``` Semantically, a biconditional is equivalent to a Boolean `==`. However, it has lower precedence than the `==` operator. @@ -119,14 +134,17 @@ fn main() { There is also the counterpart for `!=` for checking structural inequality: `!==`. -# TODO - ## `snap` Function The function `snap` can be used to take a snapshot of a reference in specifications. -Its functionality is similar to the `clone` function, but `snap` is only intended for use in specifications. It also does not require the type behind the reference to implement the `Clone` trait. +Its functionality is similar to the `clone` function, but `snap` is only intended for use in specifications. It also does not require the type behind the reference to implement the `Clone` trait: +```rust,noplaypen,ignore +fn snap(input: &T) -> T { + // ... +} +``` The `snap` function enables writing specifications that would otherwise break Rusts ownership rules: -```rust,noplaypen,ignore +```rust,noplaypenm,ignore # use prusti_contracts::*; # struct NonCopyInt { @@ -140,7 +158,8 @@ fn do_nothing_1(x: &mut NonCopyInt) {} fn do_nothing_2(x: &mut NonCopyInt) {} ``` -TODO: avoid snap +In the first function, `x` will be borrowed by the `old` function, and can therefore not be used in the snapshot equality `===` at the same time. +Using `snap(x)` will create a snapshot of `x`, almost like using `x.clone()`, but only for specifications and even for `x` that cannot be cloned normally. ## Quantifiers diff --git a/docs/user-guide/src/tour/new.md b/docs/user-guide/src/tour/new.md index 175f021d625..79d9a25f842 100644 --- a/docs/user-guide/src/tour/new.md +++ b/docs/user-guide/src/tour/new.md @@ -102,7 +102,7 @@ error: [Prusti: invalid specification] use of impure function "Link::len" in pur Whenever we add the attribute `#[pure]` to a function, Prusti will check whether that function is indeed deterministic and side-effect free -(notice that [termination](../limitations.md#termination-checks-total-correctness-missing) is *not* checked); otherwise, it complains. +(notice that [termination](../capabilities/limitations.md#termination-checks-total-correctness-missing) is *not* checked); otherwise, it complains. In this case, Prusti complains because we call an impure function, namely `Link::len()`, within the body of the pure function `List::len()`. diff --git a/docs/user-guide/src/tour/option.md b/docs/user-guide/src/tour/option.md index e022c504d3f..b0e3345f438 100644 --- a/docs/user-guide/src/tour/option.md +++ b/docs/user-guide/src/tour/option.md @@ -22,7 +22,7 @@ Changing the `Link` type requires some adjustments of the code and specification {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:rewrite_link_impl}} ``` -Due to current [limitations of Prusti](../limitations.md#loops-in-pure-functions-unsupported), we cannot replace our `link_len` and `link_lookup` functions with loops: +Due to current [limitations of Prusti](../capabilities/limitations.md#loops-in-pure-functions-unsupported), we cannot replace our `link_len` and `link_lookup` functions with loops: ```rust,noplaypen,ignore {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs:code}} diff --git a/docs/user-guide/src/tour/peek.md b/docs/user-guide/src/tour/peek.md index 833b3930ff0..31d3e6332d7 100644 --- a/docs/user-guide/src/tour/peek.md +++ b/docs/user-guide/src/tour/peek.md @@ -3,7 +3,7 @@ > **Recommended reading:** > [3.3: Peek](https://rust-unofficial.github.io/too-many-lists/second-peek.html) -Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. This is currently not possible in Prusti, since structures containing references are not supported at the moment (see chapter [Prusti Limitations](../limitations.md)). +Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. This is currently not possible in Prusti, since structures containing references are not supported at the moment (see chapter [Prusti Limitations](../capabilities/limitations.md)). We can still implement `peek`, but we just cannot do it by using `try_peek` like before in `pop`. Instead, we can reuse th already implemented and verified `lookup` function! Since `lookup` can return a reference to any element of the list, we can just call `self.lookup(0)` inside of `peek`: diff --git a/docs/user-guide/src/tour/pledges.md b/docs/user-guide/src/tour/pledges.md index 53ac65f812b..585f7d1a322 100644 --- a/docs/user-guide/src/tour/pledges.md +++ b/docs/user-guide/src/tour/pledges.md @@ -9,7 +9,7 @@ We will demonstrate it by implementing a function that gives you a mutable refer The `peek_mut` will return a mutable reference of type `T`, so the precondition of the list requires it to be non-empty. As a first postcondition, we want to ensure that the `result` of `peek_mut` points to the first element of the list. -In the code, we need to get a mutable reference to the type inside the `Link = Option>>`. This requires the use of the type `Option<&mut T>`, which is a structure containing a reference, so it is not yet supported by Prusti (see [limitations chapter](../limitations.md)). To still be able to verify `peek_mut`, we mark it as `trusted` for now: +In the code, we need to get a mutable reference to the type inside the `Link = Option>>`. This requires the use of the type `Option<&mut T>`, which is a structure containing a reference, so it is not yet supported by Prusti (see [limitations chapter](../capabilities/limitations.md)). To still be able to verify `peek_mut`, we mark it as `trusted` for now: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs:peek_mut_code}} diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md index 3daaa33751e..483a1fc229c 100644 --- a/docs/user-guide/src/tour/setup.md +++ b/docs/user-guide/src/tour/setup.md @@ -33,7 +33,7 @@ In this file, you can set [configuration flags](https://viperproject.github.io/p check_overflows = false ``` -**Note**: Creating a new project will create a `main.rs` file containing a `Hello World` program. Since Prusti does not yet support Strings (see [Prusti Limitations](../limitations.md#strings-and-string-slices) chapter), verification will fail on `main.rs`. To still verify the code, remove the line `println!("Hello, world!");`. +**Note**: Creating a new project will create a `main.rs` file containing a `Hello World` program. Since Prusti does not yet support Strings (see [Prusti Limitations](../capabilities/limitations.md#strings-and-string-slices) chapter), verification will fail on `main.rs`. To still verify the code, remove the line `println!("Hello, world!");`. ## Standard library annotations diff --git a/docs/user-guide/src/verify/assert_refute_assume.md b/docs/user-guide/src/verify/assert_refute_assume.md index a55fb7d31cc..2786692e389 100644 --- a/docs/user-guide/src/verify/assert_refute_assume.md +++ b/docs/user-guide/src/verify/assert_refute_assume.md @@ -19,7 +19,7 @@ fn go(x: &mut u32) { } ``` -The two macros `prusti_assert_eq` and `prusti_assert_ne` are also slightly different than their standard counterparts, in that they use [snapshot equality](syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. +The two macros `prusti_assert_eq` and `prusti_assert_ne` are also slightly different than their standard counterparts, in that they use [snapshot equality](../syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. ```rust,noplaypen,ignore # use prusti_contracts::*; diff --git a/docs/user-guide/src/verify/counterexample.md b/docs/user-guide/src/verify/counterexample.md new file mode 100644 index 00000000000..39407df37a1 --- /dev/null +++ b/docs/user-guide/src/verify/counterexample.md @@ -0,0 +1,56 @@ +# Printing Counterexamples + +Prusti can print counterexamples for verification failures, i.e., values for variables that violate some assertion or pre-/postcondition. +This can be enabled by setting [`counterexample = true`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#counterexample) in your `Prusti.toml` file, or with the `PRUSTI_COUNTEREXAMPLES=true` environment variable. + +For example: +```rust,noplaypen +fn test_assert(x: i32) { + assert!(x >= 10); +} +``` +This will result in an error like this one: +```plain +[Prusti: verification error] the asserted expression might not hold +assert_counterexample.rs(3, 4): counterexample for "x" +initial value: 9 +final value: 9 +``` + +Note 1: There are no guarantees on which value gets returned for the counterexample. The result will be an arbitrary value that fails the assertion (in this case any value in the range `i32::MIN..=9`). +Note 2: Verification will be slower with `counterexamples = true`. + + +# Customizable counterexamples + +A counterexample for structs and enums can be formatted by annotating the type with `#[print_counterexample()]`. This is only available if the [`unsafe_core_proof`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#unsafe_core_proof) flag is set to `true`. + +## Syntax structs + +If a struct is annotated, the macro must have at least one argument and the first argument must be of type String and can contain an arbitrary number of curly brackets. The number of curly brackets must match the number of the remaining arguments. The remaining arguments must either be a field name, if the fields are named, or an index, if the fields are unnamed. A field can be used multiple times. + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +#[print_counterexample("Custom message: {}, {}", field_1, field_2) ] +struct X { + field_1: i32, + field_2: i32, +} +``` + +## Syntax enums + +If an enum is annotated, the macro must not contain any arguments. Each variant can be annotated in the exact same way as previously described. Only annotating a variant without the enum itself will result in a compile time error. + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +#[print_counterexample()] +enum X { + #[print_counterexample("Custom message: {}, {}", 0, 1)] + Variant1(i32, i32), + #[print_counterexample("Custom message")] + Variant2, +} +``` diff --git a/docs/user-guide/src/verify/print_counterexample.md b/docs/user-guide/src/verify/print_counterexample.md deleted file mode 100644 index f031bf4957f..00000000000 --- a/docs/user-guide/src/verify/print_counterexample.md +++ /dev/null @@ -1,29 +0,0 @@ -# Customizable counterexample - -A counterexample for structs and enums can be formatted by annotating the type with `#[print_counterexample()]`. This is only avaible if the [`unsafe_core_proof`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#unsafe_core_proof) flag is set to `true`. - -## Syntax structs - -If a struct is annotated, the macro must have at least one argument and the first argument must be of type String and can contain an arbitrary number of curly brackets. The number of curly brackets must match the number of the remaining arguments. The remaining arguments must either be a field name, if the fields are named, or an index, if the fields are unnamed. A field can be used multiple times. - -```rust,noplaypen,ignore -#[print_counterexample("Custom message: {}, {}", field_1, field_2) ] -struct X { - field_1: i32, - field_2: i32, -} -``` - -## Syntax enums - -If an enum is annotated, the macro must not contain any arguments. Each variant can be annotated in the exact same way as previously described. Only annotating a variant without the enum itself will result in a compile time error. - -```rust,noplaypen,ignore -#[print_counterexample()] -enum X { - #[print_counterexample("Custom message: {}, {}", 0, 1)] - Variant1(i32, i32), - #[print_counterexample("Custom message")] - Variant2, -} -``` From 9812eaa2321707b3b4ba9e57d35300095b2f8323 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Sun, 12 Mar 2023 11:53:43 +0100 Subject: [PATCH 12/31] Document refine_trait_spec annotation --- docs/user-guide/src/syntax.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index b4da1ea38dd..bcdfae1b3bf 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -200,6 +200,35 @@ and the syntax of existential ones: exists(|: , ...| ) ``` +## Adding specification in trait `impl` blocks + +Adding specifications to trait functions requires the `impl` block to be annotated with `#[refine_trait_spec]`: + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +trait TestTrait { + fn trait_fn(self) -> i64; +} + +#[refine_trait_spec] // <== Add this annotation +impl TestTrait for i64 { + + // Cannot add these 2 specifications without `refine_trait_spec`: + #[requires(true)] + #[ensures(result >= 0)] + fn trait_fn(self) -> i64 { + 5 + } +} +``` + +Note: Currently there is no clear error message when `#[refine_trait_spec]` is missing, you will just get an error message on the `requires` or the `ensures` like this one: +```plain +[E0407] method `prusti_pre_item_trait_fn_d5ce99cd719545e8adb9de778a953ec2` is not a member of trait `TestTrait`. +``` +See [issue #625](https://github.com/viperproject/prusti-dev/issues/625) for more details. + ## Specification entailments From 34385e7812ac1ee75b83e3d9f744b3ca9688f45d Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 14 Mar 2023 18:01:50 +0100 Subject: [PATCH 13/31] Added mdBook test to the CI --- .github/workflows/docs.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dddd2fcc76d..22a7c387a52 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,6 +27,36 @@ jobs: # - name: Spellcheck # uses: rojopolis/spellcheck-github-actions@0.27.0 + mdbook_test: # https://rust-lang.github.io/mdBook/continuous-integration.html + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + path: "repo" + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: "latest" + + # cargo should be installed (TODO: Maybe have to add a rustup toolchain file (for correct version locks)) + + - name: Build dummy crate to get dependencies + run: | + cd repo/docs/dummy/ + cargo build + + - name: Doctest user guide + run: | + cd repo/docs/user-guide + mdbook test -L "../../dummy/target/debug/deps/" + + - name: Doctest dev guide + run: | + cd repo/docs/dev-guide + mdbook test -L "../../dummy/target/debug/deps/" + deploy: # Only deploy on push to master if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} From c5a1e0f7a3c55f29708a7c5b2ce5472c88b848d6 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 14 Mar 2023 19:03:02 +0100 Subject: [PATCH 14/31] Fixed workflow file --- .github/workflows/docs.yml | 54 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 22a7c387a52..a4d3601214c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,33 +29,33 @@ jobs: mdbook_test: # https://rust-lang.github.io/mdBook/continuous-integration.html runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - path: "repo" - - - name: Setup mdBook - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: "latest" - - # cargo should be installed (TODO: Maybe have to add a rustup toolchain file (for correct version locks)) - - - name: Build dummy crate to get dependencies - run: | - cd repo/docs/dummy/ - cargo build - - - name: Doctest user guide - run: | - cd repo/docs/user-guide - mdbook test -L "../../dummy/target/debug/deps/" - - - name: Doctest dev guide - run: | - cd repo/docs/dev-guide - mdbook test -L "../../dummy/target/debug/deps/" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + path: "repo" + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: "latest" + + # cargo should be installed (TODO: Maybe have to add a rustup toolchain file (for correct version locks)) + + - name: Build dummy crate to get dependencies + run: | + cd repo/docs/dummy/ + cargo build + + - name: Doctest user guide + run: | + cd repo/docs/user-guide + mdbook test -L "../../dummy/target/debug/deps/" + + - name: Doctest dev guide + run: | + cd repo/docs/dev-guide + mdbook test -L "../../dummy/target/debug/deps/" deploy: # Only deploy on push to master From 1986a7f8d209bf8336aed04057087385937931b4 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 14 Mar 2023 19:09:40 +0100 Subject: [PATCH 15/31] Fixed dependency path --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a4d3601214c..a985756e893 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,12 +50,12 @@ jobs: - name: Doctest user guide run: | cd repo/docs/user-guide - mdbook test -L "../../dummy/target/debug/deps/" + mdbook test -L "repo/docs/dummy/target/debug/deps/" - name: Doctest dev guide run: | cd repo/docs/dev-guide - mdbook test -L "../../dummy/target/debug/deps/" + mdbook test -L "repo/docs/dummy/target/debug/deps/" deploy: # Only deploy on push to master From 8d8a137dedaa02605d7805f0e7800b0a4ad2a966 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 15 Mar 2023 09:14:19 +0100 Subject: [PATCH 16/31] Debug failing mdBook test action --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a985756e893..70b240a0324 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,12 +40,12 @@ jobs: with: mdbook-version: "latest" - # cargo should be installed (TODO: Maybe have to add a rustup toolchain file (for correct version locks)) - - name: Build dummy crate to get dependencies run: | cd repo/docs/dummy/ cargo build + ls -la repo/docs/dummy/target/ + ls -la repo/docs/dummy/target/deps/ - name: Doctest user guide run: | From 7138ca6df244aaa6b7728bcac8f3d02c6aac59ce Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 15 Mar 2023 09:23:13 +0100 Subject: [PATCH 17/31] Fixed dummy crate target directory path --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 70b240a0324..fc595f6fa8c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -43,7 +43,7 @@ jobs: - name: Build dummy crate to get dependencies run: | cd repo/docs/dummy/ - cargo build + cargo build --target-dir repo/docs/dummy/target/ ls -la repo/docs/dummy/target/ ls -la repo/docs/dummy/target/deps/ From 1afa6b1a99af246a9fdd5a5ca76916960d074ba9 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 15 Mar 2023 09:30:02 +0100 Subject: [PATCH 18/31] Debugging mdBook action failure --- .github/workflows/docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fc595f6fa8c..723bc8c63ab 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -45,7 +45,8 @@ jobs: cd repo/docs/dummy/ cargo build --target-dir repo/docs/dummy/target/ ls -la repo/docs/dummy/target/ - ls -la repo/docs/dummy/target/deps/ + ls -la repo/docs/dummy/target/debug/ + ls -la repo/docs/dummy/target/debug/deps/ - name: Doctest user guide run: | From 05c03886af3737d71ad54c3da1380af7f0bc0528 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 15 Mar 2023 09:31:04 +0100 Subject: [PATCH 19/31] Enable sparse registries protocol for cargo --- docs/dummy/.cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/dummy/.cargo/config.toml diff --git a/docs/dummy/.cargo/config.toml b/docs/dummy/.cargo/config.toml new file mode 100644 index 00000000000..70f9eaeb270 --- /dev/null +++ b/docs/dummy/.cargo/config.toml @@ -0,0 +1,2 @@ +[registries.crates-io] +protocol = "sparse" From 6740ae800fc8d4432f64f6cef7928ee88c920184 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 15 Mar 2023 09:34:47 +0100 Subject: [PATCH 20/31] Fixed path for mdBook test dependecies --- .github/workflows/docs.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 723bc8c63ab..26762de57fe 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -44,19 +44,19 @@ jobs: run: | cd repo/docs/dummy/ cargo build --target-dir repo/docs/dummy/target/ - ls -la repo/docs/dummy/target/ - ls -la repo/docs/dummy/target/debug/ - ls -la repo/docs/dummy/target/debug/deps/ + # ls -la repo/docs/dummy/target/ + # ls -la repo/docs/dummy/target/debug/ + # ls -la repo/docs/dummy/target/debug/deps/ - name: Doctest user guide run: | cd repo/docs/user-guide - mdbook test -L "repo/docs/dummy/target/debug/deps/" + mdbook test -L "../dummy/target/debug/deps/" - name: Doctest dev guide run: | cd repo/docs/dev-guide - mdbook test -L "repo/docs/dummy/target/debug/deps/" + mdbook test -L "../dummy/target/debug/deps/" deploy: # Only deploy on push to master From cbe5987d50378a28d9cf793917960ddf2d4e06e0 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Fri, 17 Mar 2023 13:14:41 +0100 Subject: [PATCH 21/31] Debugging CI failure --- .github/workflows/docs.yml | 11 ++- docs/README.md | 5 +- docs/dev-guide/src/encoding/pure.md | 7 +- docs/dev-guide/src/encoding/types-heap.md | 9 ++- docs/dev-guide/src/encoding/types-snap.md | 31 +++++--- docs/dummy/README.md | 2 + docs/user-guide/src/tour/counterexamples.md | 2 +- docs/user-guide/src/tour/final.md | 2 - docs/user-guide/src/tour/summary.md | 8 +-- docs/user-guide/src/tour/testing.md | 55 ++++++++++---- .../fail/user-guide/peek_mut_pledges.rs | 70 +++++------------- .../user-guide/testing_incorrect_specs.rs | 17 ++--- .../fail/user-guide/testing_restrictive_fn.rs | 11 ++- .../pass/user-guide/assert_on_expiry.rs | 71 +++++-------------- .../tests/verify/pass/user-guide/generic.rs | 61 ++++------------ .../tests/verify/pass/user-guide/option.rs | 45 ++---------- .../tests/verify/pass/user-guide/peek.rs | 54 +++----------- .../pass/user-guide/peek_mut_pledges.rs | 69 +++++------------- .../pass/user-guide/testing_initial_code.rs | 20 ++---- 19 files changed, 186 insertions(+), 364 deletions(-) create mode 100644 docs/dummy/README.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 26762de57fe..9e31b58d4ed 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -43,20 +43,17 @@ jobs: - name: Build dummy crate to get dependencies run: | cd repo/docs/dummy/ - cargo build --target-dir repo/docs/dummy/target/ - # ls -la repo/docs/dummy/target/ - # ls -la repo/docs/dummy/target/debug/ - # ls -la repo/docs/dummy/target/debug/deps/ + cargo build --target-dir ./target/ - name: Doctest user guide run: | - cd repo/docs/user-guide - mdbook test -L "../dummy/target/debug/deps/" + cd repo/docs/user-guide/ + mdbook test -L ../dummy/target/debug/deps/ - name: Doctest dev guide run: | cd repo/docs/dev-guide - mdbook test -L "../dummy/target/debug/deps/" + mdbook test -L ../dummy/target/debug/deps/ deploy: # Only deploy on push to master diff --git a/docs/README.md b/docs/README.md index 67c3747fcc5..01c41470db8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,10 @@ These files will be automatically tested by the Prusti test-suite. Code blocks that are not supposed to be run by a user, please add `noplaypen` to the code block. +To test all code imported into the documentation (in this case the user guide tutorial), you can run this command: +- `./x.py test -p prusti-tests --test compiletest -- user-guide` +This will attempt to verify all the Prusti examples in the `prusti-tests` directory that have the string "user-guide" in their path. + ## Doctests @@ -44,4 +48,3 @@ To run the doctests (in this case for the user guide): - `cargo build` - `cd ../user-guide/` - `mdbook test -L ../dummy/target/debug/deps/` - diff --git a/docs/dev-guide/src/encoding/pure.md b/docs/dev-guide/src/encoding/pure.md index dbad47c1b9b..1bbd5f4100c 100644 --- a/docs/dev-guide/src/encoding/pure.md +++ b/docs/dev-guide/src/encoding/pure.md @@ -4,7 +4,12 @@ To encode specifications and side-effect-free functions (marked with `#[pure]`), For example, the Rust function: -```rust +```rust,noplaypen +# // The next line is only used to enable mdBook doctests for this file to work +# extern crate prusti_contracts; +# +# use prusti_contracts::*; +# #[pure] fn hello(a: i32) -> i32 { let mut b = 10; diff --git a/docs/dev-guide/src/encoding/types-heap.md b/docs/dev-guide/src/encoding/types-heap.md index 53615cd5aef..afd838df7d5 100644 --- a/docs/dev-guide/src/encoding/types-heap.md +++ b/docs/dev-guide/src/encoding/types-heap.md @@ -70,7 +70,10 @@ predicate Tuple_X_Y(self: Ref) { Structures are encoded similarly to tuples, except that fields names correspond to the names defined in the Rust type. -```rust +```rust,noplaypen +# type X = (); +# type Y = (); +# // for a Rust type, assuming types X and Y are defined struct SomeStruct { a: X, @@ -91,7 +94,9 @@ predicate SomeStruct(self:Ref) { Enumerations (ADTs) have values corresponding to one of their variants. Each variant can hold different types of data, so the Viper encoding contains implications of the form "if the variant of the value is X, the value contains the following fields". The variant index is encoded as the `discriminant` field. -```rust +```rust,noplaypen +# type X = (); +# // for a Rust type, assuming type X is defined enum SomeEnum { Foo, diff --git a/docs/dev-guide/src/encoding/types-snap.md b/docs/dev-guide/src/encoding/types-snap.md index d47f32c5f78..9be0b1897f8 100644 --- a/docs/dev-guide/src/encoding/types-snap.md +++ b/docs/dev-guide/src/encoding/types-snap.md @@ -22,7 +22,7 @@ The snapshot-based encoding of a Rust type, say `T`, consists of four components Consider the Rust struct declared below. -```rust +```rust,noplaypen struct SomeStruct { a: i32, b: i32, @@ -104,8 +104,12 @@ Nested Rust structures are encoded as in the previous example - the main differe For instance, assume we extend the previous example with another structure that re-uses `SomeStruct`: -```rust -// assuming SomeStruct as before +```rust,noplaypen +# struct SomeStruct { +# a: i32, +# b: i32, +# } +# struct BiggerStruct { foo: i32, bar: SomeStruct, @@ -142,8 +146,12 @@ While the snapshot-based encoding of enumerations is mostly analogous to the enc For example, consider the enumeration below, which defines a custom Option type. -```rust -// assuming SomeStruct as before +```rust,noplaypen +# struct SomeStruct { +# a: i32, +# b: i32, +# } +# enum MyOption { _Some(SomeStruct), _None, @@ -226,7 +234,7 @@ If a type does not meet these criteria, it either invokes a user-supplied custom Whenever one invokes a [pure function](pure.md) with equal arguments, the function should yield the same return value, i.e., a function `f` with one argument should satisfy the following specification: -``` +```rust,noplaypen,ignore x == y ==> f(x) == f(y) ``` @@ -234,7 +242,12 @@ For non-recursive types, the snapshot function `snap$type(ref)` recursively unfo For example, the following piece of Rust code verifies while internally using snapshots to discharge equality checks: -```rust +```rust,noplaypen +# // The next line is only used to enable mdBook doctests for this file to work +# extern crate prusti_contracts; +# +# use prusti_contracts::*; +# // as before, but derives Eq #[derive(PartialEq, Eq)] struct SomeStruct { @@ -245,7 +258,7 @@ struct SomeStruct { #[pure] #[requires(x == y)] #[ensures(result == y.a)] -fn foo(x: SomeStruct, y: SomeStruct) -> i32 { +fn foo(x: SomeStruct) -> i32 { x.a } @@ -283,7 +296,7 @@ At the moment, only types implementing the [`Copy`](https://doc.rust-lang.org/co As an example, assume both the struct `BiggerStruct` and the struct `SomeStruct` from previous examples derive the traits `Copy` and `Eq`. Moreover, consider the following pure function `get` mapping every instance of `BiggerStruct` to its wrapped instance of `SomeStruct`: -```rust +```rust,noplaypen,ignore #[pure] fn get(x: BiggerStruct) -> SomeStruct { x.bar diff --git a/docs/dummy/README.md b/docs/dummy/README.md new file mode 100644 index 00000000000..dfcdf08ddfc --- /dev/null +++ b/docs/dummy/README.md @@ -0,0 +1,2 @@ +// This crate is just used to provide the needed dependencies +// for the mdbook documentation tests \ No newline at end of file diff --git a/docs/user-guide/src/tour/counterexamples.md b/docs/user-guide/src/tour/counterexamples.md index 56d090a85cf..9749789568b 100644 --- a/docs/user-guide/src/tour/counterexamples.md +++ b/docs/user-guide/src/tour/counterexamples.md @@ -1,6 +1,6 @@ # Counterexamples -Let's take the summation function from the last chapter, which adds up all the numbers from 1 to `x`. Let's suppose we forgot to add the non-negative postcondition for `x`: +Let's take the summation function from the [Loop invariants](loop_invariants.md) chapter, which adds up all the numbers from 1 to `x`. Let's suppose we forgot to add the non-negativity postcondition for `x`: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/counterexample.rs:code}} diff --git a/docs/user-guide/src/tour/final.md b/docs/user-guide/src/tour/final.md index 48b311e76b3..4612a83d141 100644 --- a/docs/user-guide/src/tour/final.md +++ b/docs/user-guide/src/tour/final.md @@ -6,5 +6,3 @@ // Expand to see the full code {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs:nothing}} ``` - -The next 2 chapters will explain loop invariants and counterexamples, but we will not use the linked list from our previous chapters. \ No newline at end of file diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index 4888c2230d2..3ac0f92fd7b 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -36,16 +36,16 @@ devices. As a quick reference, the main steps of this tour and the involved Prusti features are as follows: -1. [Setup](setup.md) How to add Prusti to a Rust project +1. [Setup](setup.md): Adding Prusti to a Rust project 2. [Getting Started](getting-started.md): Simple runtime errors caught by Prusti 3. [New](new.md): Postconditions, pure functions 4. [Push](push.md): Preconditions, external specifications, trusted functions, old expressions, quantifiers, snapshots, structural/snapshot equality 5. [Pop](pop.md): Similar concepts as in [Push](push.md), predicates -6. [Testing](testing.md): Testing specifications, differences to normal tests +6. [Testing](testing.md): Showing guarantees of verification vs running tests, and how to test specifications 7. [Option](option.md): Changing `Link` to use `Option` type 8. [Generics](generics.md): Prusti and generics 9. [Peek](peek.md): Verifying a `peek` function -10. [Pledges (mutable peek)](pledges.md): Demonstrate Prusti's pledges for functions returning mutable references +10. [Pledges (mutable peek)](pledges.md): Demonstrating Prusti's pledges for functions returning mutable references 11. [Final Code](final.md): Final code for the verified linked list 12. [Loop Invariants](loop_invariants.md): Verifying code containing loops by writing loop invariants -13. [Counterexamples](counterexamples.md): Get a counterexample for a failing assertions +13. [Counterexamples](counterexamples.md): Getting counterexamples for failing assertions diff --git a/docs/user-guide/src/tour/testing.md b/docs/user-guide/src/tour/testing.md index 8d736e75971..a7796fa1645 100644 --- a/docs/user-guide/src/tour/testing.md +++ b/docs/user-guide/src/tour/testing.md @@ -3,11 +3,38 @@ > **Recommended reading:** > [2.6: Testing](https://rust-unofficial.github.io/too-many-lists/first-test.html), -The linked chapter in the "Learning Rust With Entirely Too Many Linked Lists" tutorial explains how testing normal Rust code works. In this chapter we will check both the code and the specifications that we added in the previous chapters. +The linked chapter in the "Learning Rust With Entirely Too Many Linked Lists" tutorial explains how testing normal Rust code works. In this chapter we will show some differences between testing and verification and of the guarantees that each provides. -Note: Normal tests (marked with `#[cfg(test)]` or `#[test]`) are currently ***not*** checked by Prusti, but this may be added in the future. Without the `test` markers, Prusti will check any test like a normal function. +Note: Normal tests (marked with `#[cfg(test)]` or `#[test]`) are currently ***not*** checked by Prusti, but this may be added in the future. If you remove the `test` markers, Prusti will check any test like it would a normal function. -We have written some specifications in the previous chapters, but we didn't check if they are actually correct or useful. For example, a function that has a too restrictive precondition may be verified successfully by itself, but cannot be used: +## Differing guarantees of verification and testing + +Static verification has stronger guarantees than testing. +Running tests is only be possible for a small subset of all possible input values. +Take as an example a function taking a single value of type `u64`. The range of potential inputs is `0` to `2^64 - 1`, or `2^64` total values. Assuming each value takes 1 nano-second to test, it would take approximately `584.5` years to exhaustively test just this single function. + + +In contrast, a static verifier like Prusti is able to check the entire input space of a function with the help of the specifications of each function. + +When verification succeeds, you are guaranteed to not have a bug like a crash, overflow, or return value not fitting the specification. +This assumes that you have manually verified any `#[trusted]` functions and have checked for correct termination of all functions. +If the verification fails, you may have a bug, or your specifications are not strong enough. + +In other words: Testing can show the *presence* of bugs, verification can show the *absence* of bugs. + +The guarantees of testing are different. If a test fails, you know that you have a bug (either in the code or the test), but if all your tests pass, you might still have some bugs, just not with the specific inputs used in the tests. + +It might still be worth writing (and running) some unit tests even for verified code, as they can serve as documentation on using a function. If you made some mistake in both the code and the specification, you may notice it if you write a test for it or use that function in another verified function. + +The next section shows some potential issues with specifications. + + + + +## Examples of bugs in specifications + + +The specification for a function could have a precondition that is too restrictive. If you never use the function in another verified function, you may not notice this: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs:code}} @@ -15,35 +42,35 @@ We have written some specifications in the previous chapters, but we didn't chec This function is correct (ignoring potential overflows), but it is not useful, since the input must be `10`. -Another potential problem could be a disconnect in what the specification says, and what the programmer wants a function to do: +Another potential problem could be an incomplete postcondition. The `abs` function should take the absolute value of `x`, but it only works for positive values. The verification will still succeed, because the postcondition does not specify the result for `x < 0`: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs:code}} ``` -There is not error shown on the square function, since it will never panic (ignoring overflows again), and it does exactly what the specification says it does. Checking the specification with a test function shows however, that the function calculates `x` cubed instead of `x` squared. +This bug will be noticed as soon as you try using `abs` with a negative input. +For functions internal to a project, you will likely notice mistakes in the specification when you try to use the function in other code. However, when you have public functions, like for example in a library, you might want to write some test functions for your specification. Specifications errors sometimes only show up when they are actually used. + -For internal functions, you will likely notice mistakes in the specification when you use it in other code in your project. When you have public functions however, like for example in a library, you might want to write some tests for your specification to ensure that they are correct and useful. ## Testing our linked list specifications -To check our specifications and code, we can write a function that relies on the expected behavior. We can create a new namespace for the test, here we call it `prusti_tests`: +To check our specifications and code, we could write a function that relies on the expected behavior. We can create a new namespace for the test, here we call it `prusti_tests`: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs:test_1}} // Prusti: verifies ``` -We can also have tests that take arguments and also have pre- and postconditions: + + +Note, the function `test_list` would still be available when the program is compiled normally. There may be a possibility to have something similar to the `#[cfg(test)]` annotation in Prusti. This would allow you to only have `test_list` during verification, see [this section](../capabilities/limitations.md#conditional-compilation-for-verification-vs-normal-compilation) of the limitations chapter. -If you change any parts of this test, you will get a verification error, e.g., testing for any different lengths will cause the verification to fail. -Since this code can be verified, it appears that our specification matches our expectation for what the code does. -## Note on differences between verification and unit tests +Our test code can be verified, so it appears that our specification is not too restrictive or incomplete. -Static verification is stronger than unit testing, since unit tests will not be able to check the entire space of possible inputs for a function. -It might still be worth writing some unit tests even for verified code, to catch errors in the specifications. As noted above, Prusti will be able to check unit tests in the future. \ No newline at end of file +Let's continue now with allowing different types of values to be stored in the linked list, not just `i32`. diff --git a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs index ed59b7de7ed..a559638f041 100644 --- a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs @@ -193,100 +193,62 @@ mod prusti_tests { use super::*; //// ANCHOR_END: test_peek_mut - fn test_1(){ - let mut list = List::new(); - prusti_assert!(list.is_empty() && list.len() == 0); + fn _test_list(){ + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); - prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` - list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` - } - - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(*list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn test_2(list_0: &mut List, list_1: &mut List) { - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(*list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0.lookup(0)))); - prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0.lookup(2)))); - prusti_assert!(x3 == old(snap(list_0.lookup(3)))); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1.lookup(0)))); - prusti_assert!(y0 == x3); + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` } - fn _test_3() { + fn _test_peek() { let mut list = List::new(); list.push(16); prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); list.push(5); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 5); list.pop(); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); } - //// ANCHOR: test_peek_mut - fn _test_4() { + fn _test_peek_mut() { let mut list = List::new(); list.push(8); list.push(16); - prusti_assert!(*list.lookup(0) == 16); - prusti_assert!(*list.lookup(1) == 8); - prusti_assert!(list.len() == 2); + // Open a new scope using an opening bracket `{` + // `first` will get dropped at the closing bracket `}` { let first = list.peek_mut(); // `first` is a mutable reference to the first element of the list - // for as long as `first` is live, `list` cannot be accessed + // for as long as `first` is exists, `list` cannot be accessed prusti_assert!(*first == 16); - *first = 5; + *first = 5; // Write 5 to the first slot of the list prusti_assert!(*first == 5); // `first` gets dropped here, `list` can be accessed again } prusti_assert!(list.len() == 2); //~ ERROR the asserted expression might not hold - prusti_assert!(*list.lookup(0) == 5); // Fails too - prusti_assert!(*list.lookup(1) == 8); // Fails too + prusti_assert!(*list.lookup(0) == 5); // this fails too + prusti_assert!(*list.lookup(1) == 8); // this fails too } } //// ANCHOR_END: test_peek_mut \ No newline at end of file diff --git a/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs b/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs index 97ead5a2379..2204bd0cab9 100644 --- a/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs +++ b/prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs @@ -5,14 +5,15 @@ use prusti_contracts::*; fn main() {} //// ANCHOR: code -#[ensures(result == x * x * x)] -fn square(x: i32) -> i32 { - x * x * x +#[pure] +// Note that a postcondition is not actually needed here, since `abs` is #[pure] +#[ensures(x >= 0 ==> result == x)] +pub fn abs(x: i32) -> i32 { + x } -fn test() { - let x = 10; - let x_squared = square(x); - prusti_assert!(x_squared == 100); //~ ERROR the asserted expression might not hold +fn test_abs() { + prusti_assert!(abs(8) == 8); // Works + prusti_assert!(abs(-10) == 10); //~ ERROR the asserted expression might not hold } -//// ANCHOR_END: code \ No newline at end of file +//// ANCHOR_END: code diff --git a/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs b/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs index 33120c4c23b..845b45ceb24 100644 --- a/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs +++ b/prusti-tests/tests/verify/fail/user-guide/testing_restrictive_fn.rs @@ -5,17 +5,14 @@ use prusti_contracts::*; fn main() {} //// ANCHOR: code -#[requires(x == 10)] +#[requires(x == 10)] // Restrictive precondition #[ensures(result == x * x)] -fn restrictive_square(x: i32) -> i32 { +pub fn restrictive_square(x: i32) -> i32 { x * x } fn test() { - let x = 10; - let x_squared = restrictive_square(x); - - let y = 5; - let y_squared = restrictive_square(y); //~ ERROR precondition might not hold. + assert!(restrictive_square(10) == 100); // Works + assert!(restrictive_square(5) == 25); //~ ERROR precondition might not hold. } //// ANCHOR_END: code \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs index 04847549665..bd83a9c8867 100644 --- a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs +++ b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs @@ -188,7 +188,7 @@ impl List { } //// ANCHOR_END: assert_on_expiry -fn test_assert_on_expiry() { +fn _test_assert_on_expiry() { let mut list = List::new(); list.push(2); list.push(1); @@ -226,98 +226,61 @@ fn link_len(link: &Link) -> usize { mod prusti_tests { use super::*; - fn _test_1(){ - let mut list = List::new(); - prusti_assert!(list.is_empty() && list.len() == 0); + fn _test_list(){ + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); - prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` - list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` } - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(*list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn _test_2(list_0: &mut List, list_1: &mut List) { - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(*list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0.lookup(0)))); - prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0.lookup(2)))); - prusti_assert!(x3 == old(snap(list_0.lookup(3)))); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1.lookup(0)))); - prusti_assert!(y0 == x3); - } - - fn _test_3() { + fn _test_peek() { let mut list = List::new(); list.push(16); prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); list.push(5); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 5); list.pop(); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); } - fn _test_4() { + fn _test_peek_mut() { let mut list = List::new(); list.push(8); list.push(16); - prusti_assert!(*list.lookup(0) == 16); - prusti_assert!(*list.lookup(1) == 8); - prusti_assert!(list.len() == 2); + // Open a new scope using an opening bracket `{` + // `first` will get dropped at the closing bracket `}` { let first = list.peek_mut(); // `first` is a mutable reference to the first element of the list - // for as long as `first` is live, `list` cannot be accessed + // for as long as `first` is exists, `list` cannot be accessed prusti_assert!(*first == 16); - *first = 5; + *first = 5; // Write 5 to the first slot of the list prusti_assert!(*first == 5); // `first` gets dropped here, `list` can be accessed again } prusti_assert!(list.len() == 2); - prusti_assert!(*list.lookup(0) == 5); - prusti_assert!(*list.lookup(1) == 8); + prusti_assert!(*list.lookup(0) == 5); // slot 0 is now `5` + prusti_assert!(*list.lookup(1) == 8); // slot 1 is unchanged } } \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/user-guide/generic.rs b/prusti-tests/tests/verify/pass/user-guide/generic.rs index 85727ee2c21..e5b719e1009 100644 --- a/prusti-tests/tests/verify/pass/user-guide/generic.rs +++ b/prusti-tests/tests/verify/pass/user-guide/generic.rs @@ -177,70 +177,33 @@ mod prusti_tests { use super::*; //// ANCHOR_END: generic_types - fn test_1(){ - let mut list = List::new(); - prusti_assert!(list.is_empty() && list.len() == 0); - - list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); - // Here we can dereference the lookup, since `i32` is `Copy`: - prusti_assert!(*list.lookup(0) == 5); + fn _test_list(){ // ... //// ANCHOR_END: lookup_reference + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty + list.push(5); list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + + //// ANCHOR: lookup_reference + // Here we can just dereference the lookup with `*`, since `i32` is `Copy`: prusti_assert!(*list.lookup(0) == 10); // head is 10 prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly + //// ANCHOR_END: lookup_reference let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` - } - - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(*list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - //// ANCHOR: generic_types - // For this test we keep `i32`, but we could make it generic too - fn test_2(list_0: &mut List, list_1: &mut List) { - // ... - //// ANCHOR_END: generic_types - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(*list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0.lookup(0)))); - prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0.lookup(2)))); - prusti_assert!(x3 == old(snap(list_0.lookup(3)))); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1.lookup(0)))); - prusti_assert!(y0 == x3); - //// ANCHOR: generic_types + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` //// ANCHOR: lookup_reference } } diff --git a/prusti-tests/tests/verify/pass/user-guide/option.rs b/prusti-tests/tests/verify/pass/user-guide/option.rs index ca44db8e10d..a1192a3ee4e 100644 --- a/prusti-tests/tests/verify/pass/user-guide/option.rs +++ b/prusti-tests/tests/verify/pass/user-guide/option.rs @@ -175,61 +175,26 @@ fn link_len(link: &Link) -> usize { mod prusti_tests { use super::*; - fn test_1(){ + fn _test_list(){ let mut list = List::new(); // create an new, empty list prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); // the list should have a length of 1 - prusti_assert!(list.lookup(0) == 5); // the head of the list should be 5 - list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct prusti_assert!(list.lookup(0) == 10); // head is 10 prusti_assert!(list.lookup(1) == 5); // 5 got pushed back correctly let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` - } - - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn test_2(list_0: &mut List, list_1: &mut List) { - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0)).lookup(0)); - prusti_assert!(x1 == old(snap(list_0)).lookup(1) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0)).lookup(2)); - prusti_assert!(x3 == old(snap(list_0)).lookup(3)); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1)).lookup(0)); - prusti_assert!(y0 == x3); + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` } } \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/user-guide/peek.rs b/prusti-tests/tests/verify/pass/user-guide/peek.rs index f0bd5dd0338..1e02866c03d 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek.rs @@ -159,77 +159,41 @@ mod prusti_tests { use super::*; //// ANCHOR_END: test_peek - fn test_1(){ - let mut list = List::new(); - prusti_assert!(list.is_empty() && list.len() == 0); + fn _test_list(){ + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); - prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` - list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` - } - - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(*list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn test_2(list_0: &mut List, list_1: &mut List) { - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(*list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0.lookup(0)))); - prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0.lookup(2)))); - prusti_assert!(x3 == old(snap(list_0.lookup(3)))); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1.lookup(0)))); - prusti_assert!(y0 == x3); + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` } //// ANCHOR: test_peek - fn _test_3() { + fn _test_peek() { let mut list = List::new(); list.push(16); prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); list.push(5); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 5); list.pop(); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); } } diff --git a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs index 9df1afdc007..f24c6f83fe0 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs @@ -180,98 +180,61 @@ fn link_len(link: &Link) -> usize { mod prusti_tests { use super::*; - fn test_1(){ - let mut list = List::new(); - prusti_assert!(list.is_empty() && list.len() == 0); + fn _test_list(){ + let mut list = List::new(); // create an new, empty list + prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); - prusti_assert!(*list.lookup(0) == 5); // Here we can dereference the lookup, since `i32` is `Copy` - list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct + prusti_assert!(*list.lookup(0) == 10); // head is 10 prusti_assert!(*list.lookup(1) == 5); // 5 got pushed back correctly let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(*list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` } - #[requires(list_0.len() >= 4)] - #[requires(!list_1.is_empty())] - #[requires(*list_0.lookup(1) == 42)] - #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn test_2(list_0: &mut List, list_1: &mut List) { - let x0 = list_0.pop(); - - list_0.push(10); - prusti_assert!(list_0.len() >= 4); - prusti_assert!(*list_0.lookup(1) == 42); - prusti_assert!(list_0.lookup(1) == old(snap(list_0)).lookup(1)); - prusti_assert!(list_0.lookup(2) == old(snap(list_0)).lookup(2)); - prusti_assert!(list_0.lookup(3) == old(snap(list_0)).lookup(3)); - assert!(list_0.pop() == 10); // Cannot be `prusti_assert`, `pop` changes the list - - let x1 = list_0.pop(); - let x2 = list_0.pop(); - let x3 = list_0.pop(); - prusti_assert!(x0 == old(snap(list_0.lookup(0)))); - prusti_assert!(x1 == old(snap(list_0.lookup(1))) && x1 == 42); - prusti_assert!(x2 == old(snap(list_0.lookup(2)))); - prusti_assert!(x3 == old(snap(list_0.lookup(3)))); - - let y0 = list_1.pop(); - prusti_assert!(y0 == old(snap(list_1.lookup(0)))); - prusti_assert!(y0 == x3); - } - - fn test_3() { + fn _test_peek() { let mut list = List::new(); list.push(16); prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); list.push(5); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 5); list.pop(); - prusti_assert!(list.peek() === list.lookup(0)); prusti_assert!(*list.peek() == 16); } - fn test_4() { + fn _test_peek_mut() { let mut list = List::new(); list.push(8); list.push(16); - prusti_assert!(*list.lookup(0) == 16); - prusti_assert!(*list.lookup(1) == 8); - prusti_assert!(list.len() == 2); + // Open a new scope using an opening bracket `{` + // `first` will get dropped at the closing bracket `}` { let first = list.peek_mut(); // `first` is a mutable reference to the first element of the list - // for as long as `first` is live, `list` cannot be accessed + // for as long as `first` is exists, `list` cannot be accessed prusti_assert!(*first == 16); - *first = 5; + *first = 5; // Write 5 to the first slot of the list prusti_assert!(*first == 5); // `first` gets dropped here, `list` can be accessed again } prusti_assert!(list.len() == 2); - prusti_assert!(*list.lookup(0) == 5); - prusti_assert!(*list.lookup(1) == 8); + prusti_assert!(*list.lookup(0) == 5); // slot 0 is now `5` + prusti_assert!(*list.lookup(1) == 8); // slot 1 is unchanged } } \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs index 035b9b2ee34..38734c1be3b 100644 --- a/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs @@ -149,43 +149,37 @@ mod prusti_tests { use super::*; //// ANCHOR_END: test_2 - fn test_1(){ + fn _test_list(){ let mut list = List::new(); // create an new, empty list prusti_assert!(list.is_empty() && list.len() == 0); // list should be empty list.push(5); - prusti_assert!(!list.is_empty() && list.len() == 1); // the list should have a length of 1 - prusti_assert!(list.lookup(0) == 5); // the head of the list should be 5 - list.push(10); prusti_assert!(!list.is_empty() && list.len() == 2); // length correct prusti_assert!(list.lookup(0) == 10); // head is 10 prusti_assert!(list.lookup(1) == 5); // 5 got pushed back correctly let x = list.pop(); - prusti_assert!(!list.is_empty() && list.len() == 1); // length correct - prusti_assert!(list.lookup(0) == 5); // 5 should be at the head again prusti_assert!(x == 10); // pop returns the value that was added last - if let Some(y) = list.try_pop() { - prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(y == 5); // correct value inside the `Some` - } else { - unreachable!() // This should not happen, since `try_pop` never returns `None` + match list.try_pop() { + Some(y) => assert!(y == 5), + None => unreachable!() } let z = list.try_pop(); prusti_assert!(list.is_empty() && list.len() == 0); // length correct - prusti_assert!(z.is_none()); // trying to pop from an empty list should return `None` + prusti_assert!(z.is_none()); // `try_pop` on an empty list should return `None` } //// ANCHOR_END: test_1 //// ANCHOR: test_2 + // This shows a test that takes 2 lists as input parameters: #[requires(list_0.len() >= 4)] #[requires(!list_1.is_empty())] #[requires(list_0.lookup(1) == 42)] #[requires(list_0.lookup(3) == list_1.lookup(0))] - fn test_2(list_0: &mut List, list_1: &mut List) { + fn _test_lists(list_0: &mut List, list_1: &mut List) { let x0 = list_0.pop(); list_0.push(10); From c993eab0b66d9f6cd76126420ca3308dc8da1fe5 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 22 Mar 2023 12:40:01 +0100 Subject: [PATCH 22/31] Fixed non-lowercase message warning --- docs/user-guide/src/tour/testing.md | 2 +- docs/user-guide/src/verify/prusti-feature.md | 17 +++++++++++++++++ docs/user-guide/src/verify/summary.md | 1 + .../verify/fail/user-guide/peek_mut_pledges.rs | 1 + .../verify/pass/user-guide/assert_on_expiry.rs | 1 + .../tests/verify/pass/user-guide/generic.rs | 1 + .../tests/verify/pass/user-guide/option.rs | 1 + .../tests/verify/pass/user-guide/peek.rs | 1 + .../verify/pass/user-guide/peek_mut_pledges.rs | 1 + .../pass/user-guide/testing_initial_code.rs | 1 + .../tests/verify_partial/fail/nested-pure-fn.rs | 2 +- prusti-viper/src/encoder/procedure_encoder.rs | 4 ++-- 12 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 docs/user-guide/src/verify/prusti-feature.md diff --git a/docs/user-guide/src/tour/testing.md b/docs/user-guide/src/tour/testing.md index a7796fa1645..2e60dae934a 100644 --- a/docs/user-guide/src/tour/testing.md +++ b/docs/user-guide/src/tour/testing.md @@ -68,7 +68,7 @@ To check our specifications and code, we could write a function that relies on t // Prusti: verifies ``` --> -Note, the function `test_list` would still be available when the program is compiled normally. There may be a possibility to have something similar to the `#[cfg(test)]` annotation in Prusti. This would allow you to only have `test_list` during verification, see [this section](../capabilities/limitations.md#conditional-compilation-for-verification-vs-normal-compilation) of the limitations chapter. +Note the `#[cfg(prusti)]` on the module `prusti_tests`. This makes the module only available during verification, with no effect during normal compilation, similar to `#[cfg(test)]` for unit tests. Our test code can be verified, so it appears that our specification is not too restrictive or incomplete. diff --git a/docs/user-guide/src/verify/prusti-feature.md b/docs/user-guide/src/verify/prusti-feature.md new file mode 100644 index 00000000000..93507dfc565 --- /dev/null +++ b/docs/user-guide/src/verify/prusti-feature.md @@ -0,0 +1,17 @@ +# Conditional compilation for verification vs normal compilation + +Some functions are intended to only be used during verification, but not be part of normal compilation. +Prusti sets the `prusti` feature during verification, which can be use to conditionally include code: + +```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; +use prusti_contracts::*; + +#[cfg(prusti)] +fn prusti_test() { + // Write some code that should be verified, + // but not compiled + // ... +} +``` \ No newline at end of file diff --git a/docs/user-guide/src/verify/summary.md b/docs/user-guide/src/verify/summary.md index 2465182237b..13325d65ab9 100644 --- a/docs/user-guide/src/verify/summary.md +++ b/docs/user-guide/src/verify/summary.md @@ -21,6 +21,7 @@ The following features are either currently supported or planned to be supported - [Closures](closure.md) - [Specification entailments](spec_ent.md) - [Type models](type-models.md) +- [Conditional compilation](prusti-feature.md) By default, Prusti only checks absence of panics. Moreover, Prusti verifies *partial* correctness. That is, it only verifies that *terminating* program executions meet the supplied specification. diff --git a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs index a559638f041..12d950a706c 100644 --- a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs @@ -189,6 +189,7 @@ fn link_len(link: &Link) -> usize { } //// ANCHOR: test_peek_mut +#[cfg(prusti)] mod prusti_tests { use super::*; //// ANCHOR_END: test_peek_mut diff --git a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs index bd83a9c8867..5c848c2c4d7 100644 --- a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs +++ b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs @@ -223,6 +223,7 @@ fn link_len(link: &Link) -> usize { } } +#[cfg(prusti)] mod prusti_tests { use super::*; diff --git a/prusti-tests/tests/verify/pass/user-guide/generic.rs b/prusti-tests/tests/verify/pass/user-guide/generic.rs index e5b719e1009..8f25ba1da4e 100644 --- a/prusti-tests/tests/verify/pass/user-guide/generic.rs +++ b/prusti-tests/tests/verify/pass/user-guide/generic.rs @@ -173,6 +173,7 @@ fn link_len(link: &Link) -> usize { //// ANCHOR: generic_types //// ANCHOR: lookup_reference +#[cfg(prusti)] mod prusti_tests { use super::*; diff --git a/prusti-tests/tests/verify/pass/user-guide/option.rs b/prusti-tests/tests/verify/pass/user-guide/option.rs index a1192a3ee4e..48d75f37026 100644 --- a/prusti-tests/tests/verify/pass/user-guide/option.rs +++ b/prusti-tests/tests/verify/pass/user-guide/option.rs @@ -172,6 +172,7 @@ fn link_len(link: &Link) -> usize { } //// ANCHOR_END: rewrite_link_impl +#[cfg(prusti)] mod prusti_tests { use super::*; diff --git a/prusti-tests/tests/verify/pass/user-guide/peek.rs b/prusti-tests/tests/verify/pass/user-guide/peek.rs index 1e02866c03d..1cc6a65c012 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek.rs @@ -155,6 +155,7 @@ fn link_len(link: &Link) -> usize { } //// ANCHOR: test_peek +#[cfg(prusti)] mod prusti_tests { use super::*; diff --git a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs index f24c6f83fe0..c8b6b8ad356 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs @@ -177,6 +177,7 @@ fn link_len(link: &Link) -> usize { } } +#[cfg(prusti)] mod prusti_tests { use super::*; diff --git a/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs index 38734c1be3b..4e2d1de1b7a 100644 --- a/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs @@ -145,6 +145,7 @@ impl Link { //// ANCHOR: test_1 //// ANCHOR: test_2 +#[cfg(prusti)] mod prusti_tests { use super::*; diff --git a/prusti-tests/tests/verify_partial/fail/nested-pure-fn.rs b/prusti-tests/tests/verify_partial/fail/nested-pure-fn.rs index 6c0db131c15..f7c3d93c9f8 100644 --- a/prusti-tests/tests/verify_partial/fail/nested-pure-fn.rs +++ b/prusti-tests/tests/verify_partial/fail/nested-pure-fn.rs @@ -19,7 +19,7 @@ fn pred(m: &Struct) -> bool { outer(inner(&m)) //~^ ERROR Prusti encountered an unexpected internal error //~| NOTE: We would appreciate a bug report - //~| NOTE: There is no procedure contract for loan + //~| NOTE: there is no procedure contract for loan } fn main() {} diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index 34c8992a83c..faab64c38a1 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -2109,7 +2109,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Get the borrow information. if !self.procedure_contracts.contains_key(&loan_location) { return Err(SpannedEncodingError::internal( - format!("There is no procedure contract for loan {loan:?}. This could happen if you \ + format!("there is no procedure contract for loan {loan:?}. This could happen if you \ are chaining pure functions, which is not fully supported."), span )); @@ -2125,7 +2125,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { match borrow_infos.len().cmp(&1) { std::cmp::Ordering::Less => (), std::cmp::Ordering::Greater => return Err(SpannedEncodingError::internal( - format!("We require at most one magic wand in the postcondition. But we have {:?}", borrow_infos.len()), + format!("we require at most one magic wand in the postcondition. But we have {:?}", borrow_infos.len()), span, )), std::cmp::Ordering::Equal => { From bb5b1b01e6d60de976af4c6b9d3a81dcc484aa89 Mon Sep 17 00:00:00 2001 From: Patrick-6 <93388547+Patrick-6@users.noreply.github.com> Date: Mon, 3 Apr 2023 14:26:19 +0200 Subject: [PATCH 23/31] Fix formating Co-authored-by: Aurel --- docs/dummy/README.md | 3 +-- docs/user-guide/src/verify/pledge.md | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/dummy/README.md b/docs/dummy/README.md index dfcdf08ddfc..040837a903c 100644 --- a/docs/dummy/README.md +++ b/docs/dummy/README.md @@ -1,2 +1 @@ -// This crate is just used to provide the needed dependencies -// for the mdbook documentation tests \ No newline at end of file +This crate is only used to provide the needed dependencies for the mdbook documentation tests. \ No newline at end of file diff --git a/docs/user-guide/src/verify/pledge.md b/docs/user-guide/src/verify/pledge.md index 6fbcf573183..4a3aa4d887a 100644 --- a/docs/user-guide/src/verify/pledge.md +++ b/docs/user-guide/src/verify/pledge.md @@ -51,3 +51,11 @@ reference fields) and `condition` is a [Prusti specification](../syntax.md) that structure will look once the borrow expires. To refer in the condition to the state that a memory location pointed at by the reference has just before expiring, use `before_expiry(*reference)`. + +## Run assertions when reference expires + +In some cases, a condition must be checked at the point of expiry, like for example a type invariant. +The syntax for this is `#[assert_on_expiry(condition, invariant)]`. +This means that the `invariant` holds, given that `condition` is true when the reference expires. + +Note that for some condition `A`, `after_expiry(A)` is equal to `assert_one_expiry(true, A)`. From 77cf227e4cb8e1dcad575fed95971ef1fd9e4a5a Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Wed, 29 Mar 2023 17:23:32 +0200 Subject: [PATCH 24/31] Update logging documentation --- docs/dev-guide/src/config/flags.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index b3ef1913dc9..016c02a79ca 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -260,7 +260,8 @@ Log level and filters. See [`env_logger` documentation](https://docs.rs/env_logg For example, `PRUSTI_LOG=prusti_viper=trace` enables trace logging for the prusti-viper crate, or `PRUSTI_LOG=debug` enables lighter logging everywhere. When using `trace` it is recommended to disable `jni` messages with e.g. `PRUSTI_LOG=trace,jni=warn`. A useful explanation of this can be found in the [rustc docs](https://rustc-dev-guide.rust-lang.org/tracing.html) (we set `PRUSTI_LOG` rather than `RUSTC_LOG`). -Debug and trace logs are not available in release builds. +When running `prusti-rustc` and `prusti-server`, it is possible to report log messages to stderr, however in release builds all trace and most debug logs are not available. + ## `LOG_DIR` From 665e8bb095604f694ce237f9d4803e4c772dfe53 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 11 Apr 2023 10:47:22 +0200 Subject: [PATCH 25/31] Switch dummy crate to use local dependency --- docs/dummy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dummy/Cargo.toml b/docs/dummy/Cargo.toml index 99aa54806aa..5badb845559 100644 --- a/docs/dummy/Cargo.toml +++ b/docs/dummy/Cargo.toml @@ -9,4 +9,4 @@ edition = "2021" # for the mdbook documentation tests [dependencies] -prusti-contracts = "0.1.4" +prusti-contracts = { path = "../../prusti-contracts/prusti-contracts/" } From 4c82c514b53fdf030d0f44c84e168163bb65a467 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 11 Apr 2023 11:24:15 +0200 Subject: [PATCH 26/31] Add better explanation for running cargo-prusti --- docs/user-guide/src/basic.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/src/basic.md b/docs/user-guide/src/basic.md index f4eaf0ce792..ed0e95ce829 100644 --- a/docs/user-guide/src/basic.md +++ b/docs/user-guide/src/basic.md @@ -19,12 +19,18 @@ To run Prusti on a file using the command-line setup: prusti-rustc --edition=2018 path/to/file.rs ``` -To run Prusti on a Rust crate: +Run this command from a crate or workspace root directory to verify it: ```sh cargo-prusti ``` +If Prusti is in `$PATH`, it can also be run as a [Cargo subcommand](https://doc.rust-lang.org/stable/book/ch14-05-extending-cargo.html): + +```sh +cargo prusti +``` + ## Introductory example Let us verify that the function `max` below, which takes two integers and returns the greater one, is implemented correctly. From 64f76683614c3287489be60cec27b8d58de7ad76 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 11 Apr 2023 11:25:44 +0200 Subject: [PATCH 27/31] Update verification syntax documentation --- docs/user-guide/src/SUMMARY.md | 1 + docs/user-guide/src/syntax.md | 37 +++---------------- .../user-guide/src/verify/impl_block_specs.md | 30 +++++++++++++++ 3 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 docs/user-guide/src/verify/impl_block_specs.md diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index f5d169ac0c4..8caa08d7111 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -33,4 +33,5 @@ - [Specification entailments](verify/spec_ent.md) - [Type models](verify/type-models.md) - [Counterexamples](verify/counterexample.md) + - [Specifications in `impl` blocks](verify/impl_block_specs.md) - [Specification Syntax](syntax.md) diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index bcdfae1b3bf..3693e7cc113 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -4,12 +4,14 @@ Prusti specifications are a superset of Rust boolean expressions. They must be d | Syntax | Meaning | | --- | --- | +| [`result`](#result-variable) | Variable to refer to function return value in postcondition | | [`old(...)`](#old-expressions) | Value of expression in a previous state | -| [`... ==> ...`](#implications) | Implication | -| [`... <== ...`](#implications) | Implication | +| [`... ==> ...`](#implications) | Right implication | +| [`... <== ...`](#implications) | Left implication | | [`... <==> ...`](#implications) | Biconditional | | [`... === ...`](#snapshot-equality) | Snapshot equality | | [`... !== ...`](#snapshot-equality) | Snapshot inequality | +| [`snap(...)`](#snap-function) | Snapshot function | | [`forall(...)`](#quantifiers) | Universal quantifier | | [`exists(...)`](#quantifiers) | Existential quantifier | | [... |= ...](#specification-entailments) | Specification entailment | @@ -18,7 +20,7 @@ Prusti specifications are a superset of Rust boolean expressions. They must be d ## `result` Variable When using Prusti, `result` is used to refer to what a function returns. -`result` can only be used inside a postcondition, meaning that variables called `result` used in a function need to be renamed. +`result` can only be used inside a postcondition, meaning that function arguments called `result` need to be renamed. Here is an example for returning an integer: ```rust,noplaypen,ignore @@ -200,35 +202,6 @@ and the syntax of existential ones: exists(|: , ...| ) ``` -## Adding specification in trait `impl` blocks - -Adding specifications to trait functions requires the `impl` block to be annotated with `#[refine_trait_spec]`: - -```rust,noplaypen,ignore -# use prusti_contracts::*; -# -trait TestTrait { - fn trait_fn(self) -> i64; -} - -#[refine_trait_spec] // <== Add this annotation -impl TestTrait for i64 { - - // Cannot add these 2 specifications without `refine_trait_spec`: - #[requires(true)] - #[ensures(result >= 0)] - fn trait_fn(self) -> i64 { - 5 - } -} -``` - -Note: Currently there is no clear error message when `#[refine_trait_spec]` is missing, you will just get an error message on the `requires` or the `ensures` like this one: -```plain -[E0407] method `prusti_pre_item_trait_fn_d5ce99cd719545e8adb9de778a953ec2` is not a member of trait `TestTrait`. -``` -See [issue #625](https://github.com/viperproject/prusti-dev/issues/625) for more details. - ## Specification entailments diff --git a/docs/user-guide/src/verify/impl_block_specs.md b/docs/user-guide/src/verify/impl_block_specs.md new file mode 100644 index 00000000000..09137034044 --- /dev/null +++ b/docs/user-guide/src/verify/impl_block_specs.md @@ -0,0 +1,30 @@ +## Adding specification `impl` blocks + +Adding specifications to trait functions requires the `impl` block to be annotated with `#[refine_trait_spec]`: + +```rust,noplaypen,ignore +# use prusti_contracts::*; +# +trait TestTrait { + fn trait_fn(self) -> i64; +} + +#[refine_trait_spec] // <== Add this annotation +impl TestTrait for i64 { + + // Cannot add these 2 specifications without `refine_trait_spec`: + #[requires(true)] + #[ensures(result >= 0)] + fn trait_fn(self) -> i64 { + 5 + } +} +``` + +Note: The current error message returned when `#[refine_trait_spec]` is missing does not hint at how to fix the issue. A message like this will be shown on either `requires` or `ensures`: +```plain +[E0407] +method `prusti_pre_item_trait_fn_d5ce99cd719545e8adb9de778a953ec2` +is not a member of trait `TestTrait`. +``` +See [issue #625](https://github.com/viperproject/prusti-dev/issues/625) for more details. From de84d596b857806127e18685ff486e724734b304 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 2 May 2023 10:33:21 +0200 Subject: [PATCH 28/31] Remove unneeded mutable reference --- prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs | 2 +- prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs | 2 +- prusti-tests/tests/verify/pass/user-guide/peek.rs | 2 +- prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs index 12d950a706c..4302e9cd5b5 100644 --- a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs @@ -116,7 +116,7 @@ impl List { } // // Not currently possible in Prusti - // pub fn try_peek(&mut self) -> Option<&T> { + // pub fn try_peek(&self) -> Option<&T> { // todo!() // } diff --git a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs index 5c848c2c4d7..1d01faef70b 100644 --- a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs +++ b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs @@ -117,7 +117,7 @@ impl List { } // // Not currently possible in Prusti - // pub fn try_peek(&mut self) -> Option<&T> { + // pub fn try_peek(&self) -> Option<&T> { // todo!() // } diff --git a/prusti-tests/tests/verify/pass/user-guide/peek.rs b/prusti-tests/tests/verify/pass/user-guide/peek.rs index 1cc6a65c012..a964451fe4e 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek.rs @@ -119,7 +119,7 @@ impl List { //// ANCHOR: implementation // // Not currently possible in Prusti - // pub fn try_peek(&mut self) -> Option<&T> { + // pub fn try_peek(&self) -> Option<&T> { // todo!() // } diff --git a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs index c8b6b8ad356..e083b676514 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs @@ -116,7 +116,7 @@ impl List { } // // Not currently possible in Prusti - // pub fn try_peek(&mut self) -> Option<&T> { + // pub fn try_peek(&self) -> Option<&T> { // todo!() // } From ff42ee47cef230c5f4fb61316e9a9959510d6ea0 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Tue, 2 May 2023 10:37:31 +0200 Subject: [PATCH 29/31] Improve user-guide description --- docs/user-guide/src/syntax.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index 3693e7cc113..d363a2d87a5 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -4,14 +4,14 @@ Prusti specifications are a superset of Rust boolean expressions. They must be d | Syntax | Meaning | | --- | --- | -| [`result`](#result-variable) | Variable to refer to function return value in postcondition | +| [`result`](#result-variable) | Function return value | | [`old(...)`](#old-expressions) | Value of expression in a previous state | | [`... ==> ...`](#implications) | Right implication | | [`... <== ...`](#implications) | Left implication | | [`... <==> ...`](#implications) | Biconditional | | [`... === ...`](#snapshot-equality) | Snapshot equality | | [`... !== ...`](#snapshot-equality) | Snapshot inequality | -| [`snap(...)`](#snap-function) | Snapshot function | +| [`snap(...)`](#snap-function) | Snapshot clone function | | [`forall(...)`](#quantifiers) | Universal quantifier | | [`exists(...)`](#quantifiers) | Existential quantifier | | [... |= ...](#specification-entailments) | Specification entailment | From 9ea1371833a505cd15e8e400e4135ff7ce134bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Fri, 23 Jun 2023 13:45:53 +0200 Subject: [PATCH 30/31] cleanup --- docs/user-guide/src/SUMMARY.md | 4 +-- docs/user-guide/src/basic.md | 2 +- docs/user-guide/src/install.md | 4 +-- docs/user-guide/src/syntax.md | 15 ++++++++- docs/user-guide/src/tour/counterexamples.md | 2 +- docs/user-guide/src/tour/final.md | 4 +-- docs/user-guide/src/tour/generics.md | 4 +-- docs/user-guide/src/tour/getting-started.md | 14 ++++---- docs/user-guide/src/tour/loop_invariants.md | 6 ++-- docs/user-guide/src/tour/new.md | 19 ++++++----- docs/user-guide/src/tour/option.md | 2 +- docs/user-guide/src/tour/peek.md | 4 +-- docs/user-guide/src/tour/pledges.md | 28 ++++++++-------- docs/user-guide/src/tour/pop.md | 32 ++++++++---------- docs/user-guide/src/tour/push.md | 33 +++++++++---------- docs/user-guide/src/tour/setup.md | 10 +++--- docs/user-guide/src/tour/summary.md | 8 ++--- docs/user-guide/src/tour/testing.md | 10 +++--- .../src/verify/assert_refute_assume.md | 8 ++--- docs/user-guide/src/verify/counterexample.md | 4 +-- docs/user-guide/src/verify/external.md | 17 +++------- .../user-guide/src/verify/impl_block_specs.md | 2 +- docs/user-guide/src/verify/loop.md | 2 +- docs/user-guide/src/verify/pledge.md | 2 +- docs/user-guide/src/verify/predicate.md | 2 +- docs/user-guide/src/verify/trusted.md | 2 +- docs/user-guide/src/verify/type-models.md | 23 ------------- 27 files changed, 119 insertions(+), 144 deletions(-) diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index 8caa08d7111..10edf7c0b80 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -16,7 +16,7 @@ - [Pledges (mutable peek)](tour/pledges.md) - [Final Code](tour/final.md) - [Loop Invariants](tour/loop_invariants.md) - - [Counterexample](tour/counterexamples.md) + - [Counterexamples](tour/counterexamples.md) - [Verification Features](verify/summary.md) - [Absence of panics](verify/panic.md) - [Overflow checks](verify/overflow.md) @@ -33,5 +33,5 @@ - [Specification entailments](verify/spec_ent.md) - [Type models](verify/type-models.md) - [Counterexamples](verify/counterexample.md) - - [Specifications in `impl` blocks](verify/impl_block_specs.md) + - [Specifications in trait `impl` blocks](verify/impl_block_specs.md) - [Specification Syntax](syntax.md) diff --git a/docs/user-guide/src/basic.md b/docs/user-guide/src/basic.md index ed0e95ce829..7f60d625395 100644 --- a/docs/user-guide/src/basic.md +++ b/docs/user-guide/src/basic.md @@ -9,7 +9,7 @@ When the Prusti Assistant extension is active, Rust files can be verified in one - By saving a Rust document, if "Verify on save" is enabled. - By opening a Rust document, if "Verify on open" is enabled. -See the [following chapter](verify/summary.md) for a list of verification features available in Prusti. +See the [Verification Features chapter](verify/summary.md) for a list of verification features available in Prusti. ## Command line diff --git a/docs/user-guide/src/install.md b/docs/user-guide/src/install.md index 6ecd4b71ad8..bc6fbfcf186 100644 --- a/docs/user-guide/src/install.md +++ b/docs/user-guide/src/install.md @@ -4,7 +4,7 @@ The easiest way to try out Prusti is by using the ["Prusti Assistant"](https://marketplace.visualstudio.com/items?itemName=viper-admin.prusti-assistant) extension for [Visual Studio Code](https://code.visualstudio.com/). Please confer the extension's webpage for: * Detailed installation and first usage instructions. -* The description of the available commands, among which commands to run and update the verifier. +* The description of the available commands, among which are commands to run and update the verifier. * The description of the configuration flags. * Troubleshooting instructions. @@ -17,6 +17,6 @@ Bugs with Prusti itself can be reported on the [prusti-dev repository](https://g ## Command-line setup -Alternatively, Prusti can be set up by downloading the [precompiled binaries](https://github.com/viperproject/prusti-dev/releases) available from the project page. We currently provide binaries for Windows, macOS x86, and Ubuntu. Releases marked as "Pre-release" may contain unstable or experimental features. +Alternatively, Prusti can be set up by downloading the [precompiled binaries](https://github.com/viperproject/prusti-dev/releases) available from the project page. We currently provide binaries for Windows, macOS (Intel), and Ubuntu. Releases marked as "Pre-release" may contain unstable or experimental features. For a command-line setup with Prusti built from source, please confer the [developer guide](https://viperproject.github.io/prusti-dev/dev-guide/development/setup.html). diff --git a/docs/user-guide/src/syntax.md b/docs/user-guide/src/syntax.md index d363a2d87a5..e80bea7c4a5 100644 --- a/docs/user-guide/src/syntax.md +++ b/docs/user-guide/src/syntax.md @@ -1,6 +1,6 @@ # Specification syntax -Prusti specifications are a superset of Rust boolean expressions. They must be deterministic and side-effect free. Therefore, they can only call only [pure functions](verify/pure.md). The extensions to Rust expressions are summarized below: +Prusti specifications are a superset of Rust Boolean expressions. They must be deterministic and side-effect free. Therefore, they can call only [pure functions](verify/pure.md) and [predicates](verify/predicate.md). The extensions to Rust expressions are summarized below: | Syntax | Meaning | | --- | --- | @@ -202,6 +202,19 @@ and the syntax of existential ones: exists(|: , ...| ) ``` +### Triggers + +To specify triggers for a quantifier, the syntax is `triggers=[..]`: + +```plain +forall(|: , ...| ==> , triggers=[]) +``` + +There may be multiple trigger sets. Each trigger set is a tuple of expressions. For example: + +```plain +forall(|x: usize| foo(x) ==> bar(x), triggers=[(foo(x),), (bar(x),)]) +``` ## Specification entailments diff --git a/docs/user-guide/src/tour/counterexamples.md b/docs/user-guide/src/tour/counterexamples.md index 9749789568b..fe73e9e0eaf 100644 --- a/docs/user-guide/src/tour/counterexamples.md +++ b/docs/user-guide/src/tour/counterexamples.md @@ -11,7 +11,7 @@ Attempting to verify this file will result in an error: [Prusti: verification error] postcondition might not hold. ``` -One way to help with debugging such a verification failure, is to have Prusti print a **counterexample**. This can be enabled by adding the `counterexample = true` flag in your `Prusti.toml` file. +One way to help with debugging such a verification failure, is to have Prusti print a **counterexample**. This can be enabled by adding the `counterexample = true` flag in the `Prusti.toml` file. A counterexample is any combination of values, which will cause some postcondition or assertion to fail (there are no guarantees on which values get chosen). diff --git a/docs/user-guide/src/tour/final.md b/docs/user-guide/src/tour/final.md index 4612a83d141..d7da45ec2ea 100644 --- a/docs/user-guide/src/tour/final.md +++ b/docs/user-guide/src/tour/final.md @@ -1,6 +1,6 @@ -# Final List Code +# Final Code -## Full Code +Here you can see the full implementation we have thus far. ```rust,noplaypen // Expand to see the full code diff --git a/docs/user-guide/src/tour/generics.md b/docs/user-guide/src/tour/generics.md index f56e6ac5f48..522368d4a22 100644 --- a/docs/user-guide/src/tour/generics.md +++ b/docs/user-guide/src/tour/generics.md @@ -10,7 +10,7 @@ If you do this process with Prusti, at some point you will encounter the followi ``` This is because the generic type `T` might not implement [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) and thus not have an equality function `==` that could be called on it like `i32` does. Since we only used `==` inside of specifications, we can fix this problems by using [snapshot equality `===`](../syntax.md#snapshot-equality) instead. -Here you can see where some of the changes where done (expand to see full changes): +Here you can see where some of the changes were done (expand to see the full changes): ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/generic.rs:generic_types}} @@ -30,7 +30,7 @@ In addition to returning a reference, we will have to adjust some of the places {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/generic.rs:lookup_reference}} ``` -After all these changes, Prusti is able to verify the code again, so we now our linked list can be used to store elements of any type, not just `i32`! +After all these changes, Prusti is able to verify the code again, so now our linked list can be used to store elements of any type, not just `i32`! If you want to see the full code after all the changes, expand the following code block. ```rust,noplaypen diff --git a/docs/user-guide/src/tour/getting-started.md b/docs/user-guide/src/tour/getting-started.md index deb2dc234ff..e4f3dc182da 100644 --- a/docs/user-guide/src/tour/getting-started.md +++ b/docs/user-guide/src/tour/getting-started.md @@ -1,7 +1,7 @@ # Getting Started Our first goal is to implement and verify a simple singly-linked stack that stores -32 bit integers *without* relying on existing data structures +32-bit integers *without* relying on existing data structures provided by Rust's standard library. For readers that are unfamiliar with Rust, this is a good time to additionally @@ -38,12 +38,12 @@ and makes sure that all list elements are uniformly allocated on the heap. Prusti automatically checks that no statement or macro that causes an explicit runtime error, such as -[`panic`](https://doc.rust-lang.org/std/macro.panic.html), -[`unreachable`](https://doc.rust-lang.org/std/macro.unreachable.html), -[`unimplemented`](https://doc.rust-lang.org/std/macro.unimplemented.html), -[`todo`](https://doc.rust-lang.org/std/macro.todo.html), or +[`panic!`](https://doc.rust-lang.org/std/macro.panic.html), +[`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html), +[`unimplemented!`](https://doc.rust-lang.org/std/macro.unimplemented.html), +[`todo!`](https://doc.rust-lang.org/std/macro.todo.html), or possibly a failing [assertion](https://doc.rust-lang.org/std/macro.assert.html), -is reachable. [Prusti assertions](../verify/assert_assume.md) are also checked. These are like the normal `assert` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks or code when compiled normally. +is reachable. [Prusti assertions](../verify/assert_assume.md) are also checked. These are like the normal `assert!` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks or code when compiled normally. For example, the following test function creates a node with no successor and panics if the node's payload is greater than 23: @@ -63,7 +63,7 @@ with an arbitrary integer: // Prusti: FAILS ``` -Prusti reports errors in the same fashion as the Rust compiler (although with the prefix +Prusti reports errors in the same fashion as the Rust compiler (with the prefix `Prusti: verification error`). For example, the error produced for the above function is: diff --git a/docs/user-guide/src/tour/loop_invariants.md b/docs/user-guide/src/tour/loop_invariants.md index f3690e267b4..aa1caa259f9 100644 --- a/docs/user-guide/src/tour/loop_invariants.md +++ b/docs/user-guide/src/tour/loop_invariants.md @@ -3,7 +3,7 @@ To show how to verify loops, we will use a different example than our linked list for simplicity. We will write and verify a function that can add some value to every element of an array slice. -Lets write a function that takes an integer `x` and sums up all values from 0 to that value in a loop. +Let's write a function that takes an integer `x` and sums up all values from 0 to that value in a loop. For non-negative inputs, the result will be equal to `x * (x + 1) / 2`: ```rust,noplaypen @@ -11,7 +11,7 @@ For non-negative inputs, the result will be equal to `x * (x + 1) / 2`: // Prusti: fails ``` -We cannot verified this code yet, because Prusti does not know what the `while` loop does to `sum` and `i`. For that, we need to add a [`body_invariant`](../verify/loop.md). Body invariants are expressions that always hold at the beginning and end of the loop body. In our case, the invariant is that `sum` contains the sum of all values between 1 and `i`. Since `i` starts at 1 and not at 0, we have to slightly adjust the formula by using `i - 1` instead of `i`, so we get: `sum == (i - 1) * i / 2`. +We cannot verify this code yet, because Prusti does not know what the `while` loop does to `sum` and `i`. For that, we need to add a [`body_invariant`](../verify/loop.md). Body invariants are expressions that always hold at the beginning and end of the loop body. In our case, the invariant is that `sum` contains the sum of all values between 1 and `i`. Since `i` starts at 1 and not at 0, we have to slightly adjust the formula by using `i - 1` instead of `i`, so we get: `sum == (i - 1) * i / 2`. After adding the `body_invariant`, we get this code: @@ -22,4 +22,4 @@ After adding the `body_invariant`, we get this code: This body invariant is enough to verify the postcondition. After the loop, `i == x + 1` will hold. Plugging this into our `body_invariant!(sum == (i - 1) * i / 2)`, we get `sum == x * (x + 1) / 2`, which is our postcondition. -Note that we did not have to add `body_invariant!(1 <= i && i <= x)`. Prusti can sometimes figure out the correct range for `i` by itself, as long as at least one other `body_invariant` is present in the loop. +Note that we did not have to add `body_invariant!(1 <= i && i <= x)`. In some cases, such as when the loop condition is side-effect free, Prusti adds the loop condition to the body invariant, as long as at least one `body_invariant` is syntactically present in the loop. diff --git a/docs/user-guide/src/tour/new.md b/docs/user-guide/src/tour/new.md index 79d9a25f842..e1ac75296af 100644 --- a/docs/user-guide/src/tour/new.md +++ b/docs/user-guide/src/tour/new.md @@ -42,7 +42,7 @@ That is, we attach the [postcondition](../verify/prepost.md) {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/impl_new_spec_1.rs:first_spec_2}} ``` -Unfortunately, Prusti—or rather: the Rust compiler—will complain about +Unfortunately, Prusti—or rather, the Rust compiler—will complain about the postcondition: ```plain @@ -55,7 +55,7 @@ error: cannot find attribute `ensures` in this scope Prusti's specifications consist of Rust [macros and attributes](https://doc.rust-lang.org/reference/procedural-macros.html) -that are defined in a separate crate called `prusti_contracts`. To see how to add this crate to your project, check out [Setup](setup.md). +that are defined in a separate crate called `prusti-contracts`. To see how to add this crate to your project, see the [Setup chapter](setup.md). Before we can use these specifications, we need to make the path to these macros and attributes visible: @@ -63,7 +63,7 @@ macros and attributes visible: {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/impl_new_spec_2.rs:import_prusti}} ``` -Declaring that we use the `prusti_contracts` crate removes the compiler error but +Declaring that we use the `prusti_contracts` module removes the compiler error but leads to a new error. This time it is an error raised by Prusti: ```markdown @@ -114,7 +114,7 @@ To fix this issue, it suffices to mark `Link::len()` as pure as well. ``` ```plain -$ cargo-prusti +$ cargo prusti // ... Successful verification of 4 items ``` @@ -125,21 +125,22 @@ this is hardly surprising since `len()` ultimately always returns 0.) ## Proper implementation of `len` -We will now properly implement `len()`, and while we're at it, `is_empty()` for `Link`. Both of them are pure functions, so we will add the `#[pure]` annotation. Both functions can be called without any restrictions, so they have the default postcondition `#[requires(true)]`, which we don't have to add manually. We also don't need to add any additional postconditions, since pure functions will be inlined wherever they are used during verification. +We will now properly implement `len()`, and while we're at it, `is_empty()` for `Link`. Both of them are pure functions, so we will add the `#[pure]` annotation. Both functions can be called without any restrictions, so they have the default postcondition `#[requires(true)]`, which we don't have to add manually. We also don't need to add any additional postconditions, since the body of pure functions is considered to be part of their contract. ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs:implementation}} ``` -Here we use the [`matches` macro](https://doc.rust-lang.org/std/macro.matches.html) in `is_empty`, which is true if and only if the first argument matches the pattern in the second argument. +Here we use the [`matches!` macro](https://doc.rust-lang.org/std/macro.matches.html) in `is_empty`, which is true if and only if the first argument matches the pattern in the second argument. We can now check if the specification is working, by writing a function that panics if the specification is wrong: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/impl_new_full_code.rs:test_len}} ``` -The last line asserts, that the `is_empty` function only returns `true`, if the `len` function returns `0`. +The last line asserts that the `is_empty` function only returns `true` if the `len` function returns `0`. And Prusti can verify it! Now we know that this assert statement holds for any `link` that is passed to the `test_len` function. +Note that we wrote this function only for demonstration purposes—the contract is checked even without the `test_len` function. We will consider the relationship between testing and static verification further in the [Testing chapter](testing.md). ### Overflow checks @@ -153,9 +154,9 @@ This overflow could happen, if you call `len` on a list with more than `usize::M ## Full code listing -Before we continue, we provide the full code implented in this chapter. +Before we continue, we provide the full code implemented in this chapter. It should successfully verify with Prusti and we will further extend it throughout -the next four chapters. +the next chapters. ```rust,noplaypen // Expand to see full code up to this chapter diff --git a/docs/user-guide/src/tour/option.md b/docs/user-guide/src/tour/option.md index b0e3345f438..a1a5f7b784d 100644 --- a/docs/user-guide/src/tour/option.md +++ b/docs/user-guide/src/tour/option.md @@ -16,7 +16,7 @@ In order to use the `Option::take` function, we also have to implement the `exte {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:option_take_extern_spec}} ``` -Changing the `Link` type requires some adjustments of the code and specifications. With the new type alias for `Link`, we cannot have an `impl Link` block anymore, so our `lookup` and `len` functions on `Link` are now normal, non-associated functions: +Changing the `Link` type requires some adjustments of the code and specifications. With the new type alias for `Link`, we cannot have an `impl Link` block anymore, so our `lookup` and `len` functions on `Link` are now normal, free-standing functions: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:rewrite_link_impl}} diff --git a/docs/user-guide/src/tour/peek.md b/docs/user-guide/src/tour/peek.md index 31d3e6332d7..fbc2fef8829 100644 --- a/docs/user-guide/src/tour/peek.md +++ b/docs/user-guide/src/tour/peek.md @@ -3,9 +3,9 @@ > **Recommended reading:** > [3.3: Peek](https://rust-unofficial.github.io/too-many-lists/second-peek.html) -Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. This is currently not possible in Prusti, since structures containing references are not supported at the moment (see chapter [Prusti Limitations](../capabilities/limitations.md)). +Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. The latter is currently not possible in Prusti, since structures containing references are not supported at the moment (see chapter [Prusti Limitations](../capabilities/limitations.md)). -We can still implement `peek`, but we just cannot do it by using `try_peek` like before in `pop`. Instead, we can reuse th already implemented and verified `lookup` function! Since `lookup` can return a reference to any element of the list, we can just call `self.lookup(0)` inside of `peek`: +We can still implement `peek`, but we just cannot do it by using `try_peek` like before in `pop`. Instead, we can reuse the already implemented and verified `lookup` function! Since `lookup` can return a reference to any element of the list, we can just call `self.lookup(0)` inside of `peek`: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek.rs:implementation}} diff --git a/docs/user-guide/src/tour/pledges.md b/docs/user-guide/src/tour/pledges.md index 585f7d1a322..ae1a8c157bb 100644 --- a/docs/user-guide/src/tour/pledges.md +++ b/docs/user-guide/src/tour/pledges.md @@ -2,7 +2,7 @@ Now we will look at [`pledges`](../verify/pledge.md). Pledges are used for functions that return mutable references into some datastructure. With a pledge you can explain to Prusti how the original object gets affected by changes to the returned reference. -We will demonstrate it by implementing a function that gives you a mutable reference to the first element in the list: +We will demonstrate by implementing a function that gives you a mutable reference to the first element in the list. ## Implementing `peek_mut` @@ -19,7 +19,7 @@ Note that `peek_mut` cannot be `#[pure]`, since it returns a mutable reference. ## Writing a test for our specification -Lets write a test to see if our specification works: +Let's write a test to see if our specification works: - Create a list with two elements: [16, 8] - Get a mutable reference to the first element (16) - Change the first element to 5 @@ -32,38 +32,38 @@ Lets write a test to see if our specification works: {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs:test_peek_mut}} ``` -But this fails, Prusti cannot verify any of our last three `prusti_assert` statements. This is where `pledges` come in. We have to tell Prusti how the `result` affects the original list. Without this, Prusti assumes that changes to the reference can change every property of the original list, so nothing can be known about it after the reference gets dropped. +But this fails, Prusti cannot verify any of our last three `prusti_assert` statements. This is where pledges come in. We have to tell Prusti how the `result` affects the original list. Without this, Prusti assumes that changes to the reference can change every property of the original list, so nothing can be known about it after the reference gets dropped. ## Writing the pledge -The pledge gets written with an annotation like for `ensures` and `requires`, but with the keyword `after_expiry`. +The pledge is written using an annotation, like `ensures` and `requires`, but with the keyword `after_expiry`. Inside we have all the conditions that hold after the returned reference gets dropped: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs:pledge}} ``` -We have 3 conditions here: -1. The list will have the same length afterwards -2. Any element of the list with index `1..list.len()` will not be changed -3. The element at the head of the list is the value that was assigned to the returned reference. This is denoted with the `before_expiry` function +We have three properties here: +1. The list will have the same length afterwards. +2. Any element of the list with index `1..list.len()` will not be changed. +3. The element at the head of the list is the value that was assigned to the returned reference. This is denoted with the `before_expiry` function. -With these 3 conditions, our test verifies successfully! +With these three properties specified, our test verifies successfully! ## Assert on expiry -Like `after_expiry`, there is also `assert_on_expiry`. It is used to check for conditions that have to be true when the returned reference expires, in order to uphold some type invariant. +Like `after_expiry`, there is also `assert_on_expiry`. It is used to check for conditions that have to be true when the returned reference expires, usually in order to uphold some type invariant. As an example, we could use this to make sure that our list of `i32` can only contain elements between 0 and 16. -Given that this invariant held before the reference was given out, it will hold again if the changed element is still in the correct range: +Given that this invariant held before the reference was given out, it will hold again only if the element, potentially changed by the caller, is still in the correct range: ```rust,noplaypen,ignore {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs:assert_on_expiry}} ``` -The syntax here is `#[assert_on_expiry(condition, invariant)]`. -This means that the `invariant` holds, given that `condition` is true when the reference expires. +The syntax here is `#[assert_on_expiry(condition, pledge)]`. +This means that `condition` is checked at the caller side *before* (or "when") the reference expires, and `pledge` must hold *after* the reference expires. -Note that for some condition `A`, `after_expiry(A)` is equal to `assert_one_expiry(true, A)`. +Note that for any assertion `A`, `after_expiry(A)` is equivalent to `assert_on_expiry(true, A)`. ## Full Code diff --git a/docs/user-guide/src/tour/pop.md b/docs/user-guide/src/tour/pop.md index 584b91c75e8..e5c494684c6 100644 --- a/docs/user-guide/src/tour/pop.md +++ b/docs/user-guide/src/tour/pop.md @@ -18,26 +18,22 @@ But, since we are verifying that there will never be `None` passed to `unwrap`, ## Properties of `try_pop` -Lets start by (informally) listing the properties we want our `try_pop` method to have. +Let's start by (informally) listing the properties we want our `try_pop` method to have. We do not need a precondition for `try_pop`, since it can be called on any list. This just leaves all the postconditions, which can be put into two categories: - If the input list is empty before the call: - 1. The `result` will be `None` - 2. The list will still be empty afterwards + 1. The `result` will be `None`. + 2. The list will still be empty afterwards. - If the input list is not empty before the call: - 1. The `result` will be `Some(value)` and `value` is the element that was the first element of the list - 2. The length will get reduced by one - 3. All elements will be shifted forwards by one + 1. The `result` will be `Some(value)` and `value` is the element that was the first element of the list. + 2. The length will get reduced by one. + 3. All elements will be shifted forwards by one. ## Properties of `pop` For `pop`, we will add a precondition that the list is not empty. -The postconditions are similar to those of `try_pop`, but we can skip all those that only apply to empty lists: - -1. The `result` will be the first value of the old list -2. The length will get reduced by one -3. All elements will be shifted forwards by one +The postconditions are similar to those of `try_pop`, but we can skip all those that only apply to empty lists. ## Preparations @@ -51,7 +47,7 @@ Since we will need to check if a list is empty, we can implement a `#[pure]` fun ### Writing the external specifications for `Option` -Since we use `Option::unwrap`, we will need an external specification for it. While we're at it, lets also write the `#[extern_spec]` for `Option::is_some` and `Option::is_none`: +Since we use `Option::unwrap`, we will need an external specification for it. While we're at it, let's also write the `#[extern_spec]` for `Option::is_some` and `Option::is_none`: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:extern_spec}} @@ -105,7 +101,7 @@ For `try_pop`, the condition only holds if the list was *not* empty before the c You may have noticed that the last two conditions for `pop` are the same as the last two of `try_pop`. We could just write the same conditions twice, but we can also place them in a Prusti [`predicate`](../verify/predicate.md), and then use that `predicate` in both specifications. -A `predicate` is basically just a [`pure`](../verify/pure.md) function that returns a `bool`, but it can use all the additional syntax available in Prusti specifications. Lets look at an example: +A `predicate` is basically just a [`pure`](../verify/pure.md) function that returns a `bool`, but it can use all the additional syntax available in Prusti specifications. Let's look at an example: ```rust,noplaypen # // The next line is only required for doctests, you can ignore/remove it @@ -124,7 +120,7 @@ fn ten() -> i32 { } ``` -In our specific case, we want to have a predicate to compare the state of the list before the call to the state afterwards. The `old` function cannot be used inside a predicate, so we have to pass the two states as separate arguments. For this we write a `predicate` with 2 parameters, which represent the state before and after the function. Such a predicate is also called a `two-state predicate`. +In our specific case, we want to have a predicate to compare the state of the list before the call to the state afterwards. The `old` function cannot be used inside a predicate, so we have to pass the two states as separate arguments. For this we write a `predicate` with two parameters, which represent the state before and after the function. Such a predicate is also called a "two-state predicate". Note that we take both arguments by (immutable) reference, since we don't need the predicate to take ownership over the arguments: ```rust,noplaypen,ignore @@ -140,12 +136,12 @@ impl List { } ``` -The 2 parameters are called `self` and `prev`, both with the type `&Self`. +The two parameters are called `self` and `prev`, both with the type `&Self`. The goal of this predicate is to check if the head of a list was correctly removed. For this we need check two properties: -1. The new length is the old length minus one -2. Each element is shifted forwards by one +1. The new length is the old length minus one. +2. Each element is shifted forwards by one. We combine these two properties into a single expression using `&&`: @@ -162,7 +158,7 @@ To use this predicate, we call it on the list `self`, and then pass in a snapsho ``` Prusti can now successfully verify the postconditions of both `try_pop` and `pop`, and ensure that they will not panic! -But there might still be a chance that our specifications don't fully match what we expect the code to do, so we will look at how to test your specifications in the next chapter. +But there might still be a chance that our specifications don't fully match what we expect the code to do, so we will look at how to test specifications in the next chapter. ## Full Code Listing diff --git a/docs/user-guide/src/tour/push.md b/docs/user-guide/src/tour/push.md index 8d41e9636eb..250a5cb7c25 100644 --- a/docs/user-guide/src/tour/push.md +++ b/docs/user-guide/src/tour/push.md @@ -18,14 +18,14 @@ impl List { } ``` -Since `push` modifies `self`, it cannot be marked as a `#[pure]` function. This means we will not be able to use `push` inside specifications for other functions later. +Since `push` modifies `self`, it cannot be marked as a `#[pure]` function (it has a side effect on `self`). This means we will not be able to use `push` inside specifications for other functions later. Before we implement `push`, let us briefly think of possible specifications. Ideally, our implementation satisfies at least the following properties: 1. Executing `push` increases the length of the underlying list by one. [(Chapter link)](push.md#first-property) 2. After `push(elem)` the first element of the list stores the value `elem`. [(Chapter link)](push.md#second-property) -3. After executing `push(elem)`, the elements of the original list remain unchanged (just moved back by 1). [(Chapter link)](push.md#third-property) +3. After executing `push(elem)`, the elements of the original list remain unchanged, but are moved back by 1 position. [(Chapter link)](push.md#third-property) ## Initial code @@ -86,13 +86,13 @@ This can be done with another piece of Prusti syntax, the [extern_spec](../verif {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:extern_spec}} ``` -Lets break this code down step by step: -- First we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. This requires `prusti_contracts::*` to be imported first. -- Next we need to figure out where the function is located. In this case it is `std::mem`, which we then put as the parameter in `#[extern_spec(std::mem)]` +Let's break this snippet down step by step: +- First, we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. This requires `prusti_contracts::*` to be imported first. +- Next, we need to declare where the original function is located. In this case it is the module `std::mem`, so we put its path in the parameter: `#[extern_spec(std::mem)]` - After a quick search for *\"rust std mem replace\"* we can find the [documentation for std::mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html). Here we can get the function signature: `pub fn replace(dest: &mut T, src: T) -> T`. We then write down the signature in the inner module, followed by a `;`. - Since there are no preconditions to `replace`, we can use the (implicit) default `#[requires(true)]`. - For writing the postcondition, we use four pieces of Prusti syntax: - - [`===`](../syntax.md#snapshot-equality) is called **snapshot equality** or **logical equality**. Is essentially checks if the left and right sides are structurally equal. `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. + - [`===`](../syntax.md#snapshot-equality) is called **snapshot equality** or **logical equality**. Is means that the left and right operands are structurally equal. `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. - The [`snap()`](../syntax.md#snap-function) function takes a snapshot of a reference. It has a similar functionality to the [`clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) method, but does not require the type of the reference it is called on to implement the `Clone` trait. `snap` should only be used in specifications, since it ignores the borrow checker. - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state of `snap(dest)` from before the function was called. - The identifier [`result`](../syntax.md#result-variable) is used to refer to the return parameter of the function. @@ -165,13 +165,13 @@ Let's get back to our code. After adding the external specification for `std::me // Prusti: Verifies ``` -With this, the first of our three property of `push` is verified, but we still have 2 more to prove. +With this, the first of the three properties of `push` is verified, but we still have two more to prove. ## Second property Recall the second property of our specification: -> 2. After `push(elem)` the first element of the list stores the value `elem`. +> 2. After `push(elem)`, the first element of the list stores the value `elem`. To formally specify the above property, we first introduce another pure function, called `lookup`, that recursively traverses the list and returns its i-th element. @@ -221,16 +221,15 @@ the postcondition: {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_final_code.rs:shifted_back}} ``` -Lets break this expression down like before: +Let's break this expression down like before: - We start with the `ensures` annotation, to denote a postcondition. -- `forall(..)` denotes a [quantifier](../syntax.md#quantifiers), and it takes a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html). -- The closure is denoted by the two vertical bars: `||`, which contain the parameters that the closure takes. Here we only have one parameter `i: usize`. The return type of the closure is `bool`. You can think of the `forall` expression as follows: *Any parameter passed to the closure makes it return `true`*. - - Closures in a `forall` expression can take any number of parameters, separated by a comma: `|i: usize, j: usize|`. -- In this case, the closure uses the [implication operator `==>`](../syntax.md#implications). It takes a left and right argument of type `bool` and is true, if the left side is false, or both sides are true. - - The left side of the implication is `(1 <= i && i < self.len())`, which is the range where the right side must hold. If the index `i` is outside of this range, we don't care about it, so the condition will be false, making the entire implication true. - - The right side is the condition for everything being shifted back by one element: `old(self.lookup(i - 1)) == self.lookup(i)))`. Note that the right side is only evaluated if the left side is true, otherwise you could get an overflow in `i - 1` for `i == 0`, or a panic if `i` is out of bounds. - -This code is verified successfully by Prusti, so we know that the `lookup` function correctly implements the three postconditions! +- `forall(..)` denotes a [quantifier](../syntax.md#quantifiers). The variables and body of a quantifier use a syntax similar to Rust [closures](https://doc.rust-lang.org/book/ch13-01-closures.html). +- The two vertical bars: `||` contain the variables that the quantifier quantifies over. Here we only have one parameter `i: usize`. The type of the quantifier body is `bool`. You can think of the `forall` expression as follows: *Any values chosen for the quantified variables should result in the expression evaluating to `true`*. +- In this case, the quantifier uses the [implication operator `==>`](../syntax.md#implications). It takes a left and right argument of type `bool` and is true if the left-hand side is false, or both sides are true. + - The left-hand side of the implication is `(1 <= i && i < self.len())`, which is the range where the right side must hold. If the index `i` is outside of this range, we don't care about it, so the condition will be false, making the entire implication true. + - The right-hand side is the condition for everything being shifted back by one element: `old(self.lookup(i - 1)) == self.lookup(i)))`. Note that the right side is only evaluated if the left side is true, otherwise there would be an overflow in `i - 1` for `i == 0`, or a panic if `i` is out of bounds. + +This code is verified successfully by Prusti, so we know that the `lookup` function satisfies the three postconditions! ## Full code listing diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md index 483a1fc229c..ce2a9b2d873 100644 --- a/docs/user-guide/src/tour/setup.md +++ b/docs/user-guide/src/tour/setup.md @@ -11,14 +11,14 @@ cd ./prusti_tutorial/ ``` -To use the additional syntax used for verification with Prusti, you need to add the [prusti_contracts](https://docs.rs/prusti-contracts/latest/prusti_contracts/) crate to your project. If you have at least Cargo version 1.62.0, you can use this command to add the dependency: +To use the additional syntax used for verification with Prusti, you need to add the [`prusti-contracts`](https://crates.io/crates/prusti-contracts) crate to your project. If you have at least Cargo version 1.62.0, you can use this command to add the dependency: ```sh cargo add prusti-contracts ``` For older versions of Rust, you can manually add the dependency in your Cargo.toml file: ```toml [dependencies] -prusti-contracts = "0.1.4" +prusti-contracts = "0.1.6" ``` To use prusti-contracts in a Rust code file, just add the following line: @@ -27,7 +27,7 @@ use prusti_contracts::*; ``` -To simplify the tutorial, overflow checks by Prusti will be disabled. To do that, create a file called `Prusti.toml` in your projects root directory (where `Cargo.toml` is located). +To simplify the tutorial, overflow checks by Prusti will be disabled. To do that, create a file called `Prusti.toml` in your project's root directory (where `Cargo.toml` is located). In this file, you can set [configuration flags](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html) used by Prusti. To disable overflow checking, add the following line: ```toml check_overflows = false @@ -38,7 +38,7 @@ check_overflows = false ## Standard library annotations -Annotations for functions and types in the Rust standard library will be available in the [prusti-std crate](https://crates.io/crates/prusti-std) after [this PR](https://github.com/viperproject/prusti-dev/pull/1249) is merged. +Annotations for functions and types in the Rust standard library will be available in the [`prusti-std` crate](https://crates.io/crates/prusti-std) after [this PR](https://github.com/viperproject/prusti-dev/pull/1249) is merged. Adding this crate works the same as for the `prusti-contracts` crate: ```sh @@ -47,6 +47,6 @@ cargo add prusti-std or: ```toml [dependencies] -prusti-std = "0.1.4" +prusti-std = "0.1.6" ``` You do not need to import anything to use the annotations in this crate in a file. diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index 3ac0f92fd7b..2f3c4bd7534 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -44,8 +44,8 @@ are as follows: 6. [Testing](testing.md): Showing guarantees of verification vs running tests, and how to test specifications 7. [Option](option.md): Changing `Link` to use `Option` type 8. [Generics](generics.md): Prusti and generics -9. [Peek](peek.md): Verifying a `peek` function +9. [Peek](peek.md): Verifying a `peek` function 10. [Pledges (mutable peek)](pledges.md): Demonstrating Prusti's pledges for functions returning mutable references -11. [Final Code](final.md): Final code for the verified linked list -12. [Loop Invariants](loop_invariants.md): Verifying code containing loops by writing loop invariants -13. [Counterexamples](counterexamples.md): Getting counterexamples for failing assertions +11. [Final Code](final.md): Final code for the verified linked list +12. [Loop Invariants](loop_invariants.md): Verifying code containing loops by writing loop invariants +13. [Counterexamples](counterexamples.md): Getting counterexamples for failing assertions diff --git a/docs/user-guide/src/tour/testing.md b/docs/user-guide/src/tour/testing.md index 2e60dae934a..69a62d2a20a 100644 --- a/docs/user-guide/src/tour/testing.md +++ b/docs/user-guide/src/tour/testing.md @@ -20,10 +20,10 @@ When verification succeeds, you are guaranteed to not have a bug like a crash, o This assumes that you have manually verified any `#[trusted]` functions and have checked for correct termination of all functions. If the verification fails, you may have a bug, or your specifications are not strong enough. -In other words: Testing can show the *presence* of bugs, verification can show the *absence* of bugs. - The guarantees of testing are different. If a test fails, you know that you have a bug (either in the code or the test), but if all your tests pass, you might still have some bugs, just not with the specific inputs used in the tests. +In other words: Testing can show the *presence* of bugs, verification can show the *absence* of bugs. + It might still be worth writing (and running) some unit tests even for verified code, as they can serve as documentation on using a function. If you made some mistake in both the code and the specification, you may notice it if you write a test for it or use that function in another verified function. The next section shows some potential issues with specifications. @@ -42,14 +42,14 @@ The specification for a function could have a precondition that is too restricti This function is correct (ignoring potential overflows), but it is not useful, since the input must be `10`. -Another potential problem could be an incomplete postcondition. The `abs` function should take the absolute value of `x`, but it only works for positive values. The verification will still succeed, because the postcondition does not specify the result for `x < 0`: +Another potential problem could be an incomplete postcondition. The `abs` function below should return the absolute value of `x`, but it only works for positive values. The verification will still succeed, because the postcondition does not specify the result for `x < 0`: ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/testing_incorrect_specs.rs:code}} ``` This bug will be noticed as soon as you try using `abs` with a negative input. -For functions internal to a project, you will likely notice mistakes in the specification when you try to use the function in other code. However, when you have public functions, like for example in a library, you might want to write some test functions for your specification. Specifications errors sometimes only show up when they are actually used. +For functions internal to a project, you will likely notice mistakes in the specification when you try to use the function in other code. However, when you have public functions, like for example in a library, you might want to write some test functions for your specification. Specification errors sometimes only show up when they are actually used. @@ -72,5 +72,3 @@ Note the `#[cfg(prusti)]` on the module `prusti_tests`. This makes the module on Our test code can be verified, so it appears that our specification is not too restrictive or incomplete. - -Let's continue now with allowing different types of values to be stored in the linked list, not just `i32`. diff --git a/docs/user-guide/src/verify/assert_refute_assume.md b/docs/user-guide/src/verify/assert_refute_assume.md index 2786692e389..cea66713733 100644 --- a/docs/user-guide/src/verify/assert_refute_assume.md +++ b/docs/user-guide/src/verify/assert_refute_assume.md @@ -7,7 +7,7 @@ function (via an assumption). ## Assertions -The macros `prusti_assert!`, `prusti_assert_eq` and `prusti_assert_ne` instruct Prusti to verify that a certain property holds at a specific point within the body of a function. In contrast to the `assert!`, `assert_eq` and `assert_ne` macros, which only accept Rust expressions, the Prusti variants accept [specification](../syntax.md) expressions as arguments. Therefore, [quantifiers](../syntax.md#quantifiers), [`old`](../syntax.md#old-expressions) expressions and other Prusti specification syntax is allowed within a call to `prusti_assert!`, as in the following example: +The macros `prusti_assert!`, `prusti_assert_eq!` and `prusti_assert_ne!` instruct Prusti to verify that a certain property holds at a specific point within the body of a function. In contrast to the `assert!`, `assert_eq!` and `assert_ne!` macros, which only accept Rust expressions, the Prusti variants accept [specification](../syntax.md) expressions as arguments. Therefore, [quantifiers](../syntax.md#quantifiers), [`old`](../syntax.md#old-expressions) expressions and other Prusti specification syntax is allowed within a call to `prusti_assert!`, as in the following example: ```rust,noplaypen,ignore # use prusti_contracts::*; @@ -19,7 +19,7 @@ fn go(x: &mut u32) { } ``` -The two macros `prusti_assert_eq` and `prusti_assert_ne` are also slightly different than their standard counterparts, in that they use [snapshot equality](../syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. +The two macros `prusti_assert_eq!` and `prusti_assert_ne!` are also slightly different than their standard counterparts, in that they use [snapshot equality](../syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. ```rust,noplaypen,ignore # use prusti_contracts::*; @@ -50,11 +50,11 @@ prusti_assert!(map.insert(5)); // error # } ``` -Using Prusti assertions instead of normal assertions can speed up verification, because every `assert` results in a branch in the code, while `prusti_assert` does not. +Using Prusti assertions instead of normal assertions can speed up verification, because every `assert!` results in a branch in the code, while `prusti_assert!` does not. ## Refutations -> Refutation **should not be relied upon for soundness** as it may succeed even when it should fail; Prusti may not be able to prove the property being refuted and thus won't complain even though the property actually holds (e.g. if the property is difficult to prove). +> Refutation **should not be relied upon for soundness** as they may succeed even when expected to fail; Prusti may not be able to prove the property being refuted and thus won't complain even though the property actually holds (e.g. if the property is difficult to prove). The `prusti_refute!` macro is similar to `prusti_assert!` in its format, conditions of use and what expressions it accepts. It instructs Prusti to verify that a certain property at a specific point within the body of a function might hold in some, but not all cases. For example the following code will verify: diff --git a/docs/user-guide/src/verify/counterexample.md b/docs/user-guide/src/verify/counterexample.md index 39407df37a1..0bb75e49326 100644 --- a/docs/user-guide/src/verify/counterexample.md +++ b/docs/user-guide/src/verify/counterexample.md @@ -1,7 +1,7 @@ # Printing Counterexamples Prusti can print counterexamples for verification failures, i.e., values for variables that violate some assertion or pre-/postcondition. -This can be enabled by setting [`counterexample = true`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#counterexample) in your `Prusti.toml` file, or with the `PRUSTI_COUNTEREXAMPLES=true` environment variable. +This can be enabled by setting [`counterexample = true`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#counterexample) in the `Prusti.toml` file, or with the `PRUSTI_COUNTEREXAMPLES=true` environment variable. For example: ```rust,noplaypen @@ -23,7 +23,7 @@ Note 2: Verification will be slower with `counterexamples = true`. # Customizable counterexamples -A counterexample for structs and enums can be formatted by annotating the type with `#[print_counterexample()]`. This is only available if the [`unsafe_core_proof`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#unsafe_core_proof) flag is set to `true`. +A counterexample for structs and enums can be formatted by annotating the type with `#[print_counterexample(..)]`. This is only available if the [`unsafe_core_proof`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#unsafe_core_proof) flag is set to `true`. ## Syntax structs diff --git a/docs/user-guide/src/verify/external.md b/docs/user-guide/src/verify/external.md index e0dd156cfdc..042c1bf754e 100644 --- a/docs/user-guide/src/verify/external.md +++ b/docs/user-guide/src/verify/external.md @@ -26,21 +26,12 @@ impl std::option::Option { Any function in an external specification is implicitly [trusted](trusted.md) (as if marked with `#[trusted]`). It is possible to specify multiple `#[extern_spec]` implementations for the same type, but it is an error to externally specify the same function multiple times. -Module functions can be specified using a nested `mod` syntax: +The `extern_spec` attribute accepts an optional argument to provide the module path to the function being specified. For example, to specify `std::mem::swap`, the argument is `std::mem`: ```rust,noplaypen,ignore use prusti_contracts::*; -#[extern_spec] -mod std { - mod mem { - use prusti_contracts::*; - - #[ensures(*a == old(*b) && *b == old(*a))] - pub fn swap(a: &mut i32, b: &mut i32); - // pub fn swap(a: &mut T, b: &mut T); - } -} +#[extern_spec(std::mem)] +#[ensures(*a === old(snap(b)) && *b === old(snap(a)))] +fn swap(a: &mut T, b: &mut T); ``` - -There are currently issues with external specifications combined with generics, so the function `swap` above is specified for `i32` arguments only. diff --git a/docs/user-guide/src/verify/impl_block_specs.md b/docs/user-guide/src/verify/impl_block_specs.md index 09137034044..781f5101e2f 100644 --- a/docs/user-guide/src/verify/impl_block_specs.md +++ b/docs/user-guide/src/verify/impl_block_specs.md @@ -1,4 +1,4 @@ -## Adding specification `impl` blocks +## Specifications in trait `impl` blocks Adding specifications to trait functions requires the `impl` block to be annotated with `#[refine_trait_spec]`: diff --git a/docs/user-guide/src/verify/loop.md b/docs/user-guide/src/verify/loop.md index 61e1ef27b9d..1322dc0ee5c 100644 --- a/docs/user-guide/src/verify/loop.md +++ b/docs/user-guide/src/verify/loop.md @@ -1,6 +1,6 @@ # Loop body invariants -To verify loops, including loops in which the loop condition has side effects, Prusti allows specifying the *invariant of the loop body* using the `body_invariant!(...);` statement. The expression inside the parentheses should be a [Prusti specification](../syntax.md). There may be any number of body invariants in any given loop, but they must all be written at the beginning of the loop body. +To verify loops, including loops in which the loop condition has side effects, Prusti allows specifying the *invariant of the loop body* using the `body_invariant!(...);` statement. The expression inside the parentheses should be a [Prusti specification](../syntax.md). There may be any number of body invariants in any given loop, but they must all be written next to each other. | Feature | Status | | --- | --- | diff --git a/docs/user-guide/src/verify/pledge.md b/docs/user-guide/src/verify/pledge.md index 4a3aa4d887a..2cd52a9bfbf 100644 --- a/docs/user-guide/src/verify/pledge.md +++ b/docs/user-guide/src/verify/pledge.md @@ -58,4 +58,4 @@ In some cases, a condition must be checked at the point of expiry, like for exam The syntax for this is `#[assert_on_expiry(condition, invariant)]`. This means that the `invariant` holds, given that `condition` is true when the reference expires. -Note that for some condition `A`, `after_expiry(A)` is equal to `assert_one_expiry(true, A)`. +Note that for any assertion `A`, `after_expiry(A)` is equivalent to `assert_on_expiry(true, A)`. diff --git a/docs/user-guide/src/verify/predicate.md b/docs/user-guide/src/verify/predicate.md index 162b8344de8..3e300887c0d 100644 --- a/docs/user-guide/src/verify/predicate.md +++ b/docs/user-guide/src/verify/predicate.md @@ -2,7 +2,7 @@ Predicates are similar to [pure functions](pure.md) in that they are deterministic and side-effect free and used in specifications. -They are more powerful than pure functions: inside predicate bodies the full [Prusti specification syntax](../syntax.md) is allowed. However, they are not usable in regular Rust code, as there are no direct Rust equivalents for specification constructs like [quantifiers](../syntax.md#quantifiers) or [implications](../syntax.md#implications). Instead, predicates can only be called from within specifications. +They are more powerful than pure functions: inside predicate bodies the full [Prusti specification syntax](../syntax.md) is allowed. However, they are not usable in regular Rust code, as there are no direct Rust equivalents for specification constructs like [quantifiers](../syntax.md#quantifiers) or [implications](../syntax.md#implications). Instead, predicates can only be called from within specifications and other predicates. Predicates are declared using the `predicate!` macro on a function: diff --git a/docs/user-guide/src/verify/trusted.md b/docs/user-guide/src/verify/trusted.md index b66955f6eca..4de08b1b7b8 100644 --- a/docs/user-guide/src/verify/trusted.md +++ b/docs/user-guide/src/verify/trusted.md @@ -16,7 +16,7 @@ fn xor_swap(a: &mut i32, b: &mut i32) { In the above example, the contract for `xor_swap` is correct, but Prusti would not be able to verify it because it uses currently unsupported XOR operations. -While a common application of `#[trusted]` is to wrap functions from the standard library or external libraries, notice that [external specifications](external.md) provide a more robust solution for this use case. +While a common application of `#[trusted]` is to wrap functions from the standard library or external libraries, note that [external specifications](external.md) provide a more robust solution for this use case. ## Why trusted functions are dangerous diff --git a/docs/user-guide/src/verify/type-models.md b/docs/user-guide/src/verify/type-models.md index b4a31b4ea5b..dd0d4dc5856 100644 --- a/docs/user-guide/src/verify/type-models.md +++ b/docs/user-guide/src/verify/type-models.md @@ -34,29 +34,6 @@ fn some_client(some_struct: &mut SomeStruct) { This means that a model cannot be instantiated or directly manipulated with runtime code. Instead, the _source_ of a model is always a [trusted function](trusted.md) or an [external specification](external.md). -## Generic models - -Models can be generic over type parameters or concrete type arguments. That is, given a struct `SomeGenericStruct` -, you can define models for: - -* Concrete type arguments, e.g. `SomeGenericStruct` and `SomeGenericStruct` -* Generic type parameters, e.g. `SomeGenericStruct` -* Mix the two concepts, e.g. `SomeGenericStruct` - -Different (generic) models for the same type can have different fields. In order to correctly parse the model, Prusti -needs attributes attached to the generics. A type argument needs to be attributed with `#[concrete]`, a type parameter -with `#[generic]`. In the last example above, we would create a model with: - -```rust,noplaypen,ignore -#[model] -struct SomeGenericStruct<#[generic] A: Copy, #[concrete] i32> { - field_a: A - // ... fields -} -``` - -Note: If you create ambiguous models, you can get a compile error when accessing the model via the `.model()` method. - ## Further remarks * A model needs to be copyable, i.e. all fields need to be `Copy`. That also applies to type parameters where you need From 1ac076e8dcef83198eb6460f74725f02cbb5461a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Mon, 26 Jun 2023 13:32:37 +0200 Subject: [PATCH 31/31] fixes --- .github/workflows/pages.yml | 2 -- docs/user-guide/src/tour/getting-started.md | 2 +- docs/user-guide/src/tour/new.md | 4 +++- docs/user-guide/src/tour/option.md | 4 +++- docs/user-guide/src/tour/peek.md | 4 +++- docs/user-guide/src/tour/pledges.md | 4 +++- docs/user-guide/src/tour/setup.md | 3 ++- .../src/verify/assert_refute_assume.md | 20 ++++++++++++++++++- .../prusti-contracts-proc-macros/Cargo.toml | 4 ++-- prusti-contracts/prusti-contracts/Cargo.toml | 4 ++-- prusti-contracts/prusti-specs/Cargo.toml | 2 +- prusti-contracts/prusti-std/Cargo.toml | 4 ++-- 12 files changed, 41 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 286f9cdcd08..243d0fb47e9 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -72,8 +72,6 @@ jobs: python x.py doc --all --no-deps cp -r ./target/doc ../output/doc - - name: - # Only deploy on push to master - name: Publish to GitHub pages uses: peaceiris/actions-gh-pages@v3 diff --git a/docs/user-guide/src/tour/getting-started.md b/docs/user-guide/src/tour/getting-started.md index e4f3dc182da..d2244250ec3 100644 --- a/docs/user-guide/src/tour/getting-started.md +++ b/docs/user-guide/src/tour/getting-started.md @@ -43,7 +43,7 @@ an explicit runtime error, such as [`unimplemented!`](https://doc.rust-lang.org/std/macro.unimplemented.html), [`todo!`](https://doc.rust-lang.org/std/macro.todo.html), or possibly a failing [assertion](https://doc.rust-lang.org/std/macro.assert.html), -is reachable. [Prusti assertions](../verify/assert_assume.md) are also checked. These are like the normal `assert!` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks or code when compiled normally. +is reachable. [Prusti assertions](../verify/assert_refute_assume.md) are also checked. These are like the normal `assert!` statements, but they can use the full Prusti specification syntax and will not result in any runtime checks or code when compiled normally. For example, the following test function creates a node with no successor and panics if the node's payload is greater than 23: diff --git a/docs/user-guide/src/tour/new.md b/docs/user-guide/src/tour/new.md index e1ac75296af..8f4efc17fdf 100644 --- a/docs/user-guide/src/tour/new.md +++ b/docs/user-guide/src/tour/new.md @@ -102,10 +102,12 @@ error: [Prusti: invalid specification] use of impure function "Link::len" in pur Whenever we add the attribute `#[pure]` to a function, Prusti will check whether that function is indeed deterministic and side-effect free -(notice that [termination](../capabilities/limitations.md#termination-checks-total-correctness-missing) is *not* checked); otherwise, it complains. +(notice that termination is *not* checked); otherwise, it complains. In this case, Prusti complains because we call an impure function, namely `Link::len()`, within the body of the pure function `List::len()`. + + To fix this issue, it suffices to mark `Link::len()` as pure as well. ```rust,noplaypen diff --git a/docs/user-guide/src/tour/option.md b/docs/user-guide/src/tour/option.md index a1a5f7b784d..77281396469 100644 --- a/docs/user-guide/src/tour/option.md +++ b/docs/user-guide/src/tour/option.md @@ -22,7 +22,9 @@ Changing the `Link` type requires some adjustments of the code and specification {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:rewrite_link_impl}} ``` -Due to current [limitations of Prusti](../capabilities/limitations.md#loops-in-pure-functions-unsupported), we cannot replace our `link_len` and `link_lookup` functions with loops: +Due to current limitations of Prusti, we cannot replace our `link_len` and `link_lookup` functions with loops: + + ```rust,noplaypen,ignore {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/option_loops_in_pure_fn.rs:code}} diff --git a/docs/user-guide/src/tour/peek.md b/docs/user-guide/src/tour/peek.md index fbc2fef8829..19bccb4e764 100644 --- a/docs/user-guide/src/tour/peek.md +++ b/docs/user-guide/src/tour/peek.md @@ -3,7 +3,9 @@ > **Recommended reading:** > [3.3: Peek](https://rust-unofficial.github.io/too-many-lists/second-peek.html) -Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. The latter is currently not possible in Prusti, since structures containing references are not supported at the moment (see chapter [Prusti Limitations](../capabilities/limitations.md)). +Ideally, we could implement `peek` and `try_peek` like we implemented `pop` and `try_pop` before. Like `pop`, `push` can only be called if the list is non-empty, and it then always returns a reference to the element at the head of the list (type `&T`). Similarly, `try_peek` can be called on any list, but returns an `Option<&T>`. The latter is currently not possible in Prusti, since structures containing references are not supported at the moment. + + We can still implement `peek`, but we just cannot do it by using `try_peek` like before in `pop`. Instead, we can reuse the already implemented and verified `lookup` function! Since `lookup` can return a reference to any element of the list, we can just call `self.lookup(0)` inside of `peek`: diff --git a/docs/user-guide/src/tour/pledges.md b/docs/user-guide/src/tour/pledges.md index ae1a8c157bb..ffa5fe87ad0 100644 --- a/docs/user-guide/src/tour/pledges.md +++ b/docs/user-guide/src/tour/pledges.md @@ -9,7 +9,9 @@ We will demonstrate by implementing a function that gives you a mutable referenc The `peek_mut` will return a mutable reference of type `T`, so the precondition of the list requires it to be non-empty. As a first postcondition, we want to ensure that the `result` of `peek_mut` points to the first element of the list. -In the code, we need to get a mutable reference to the type inside the `Link = Option>>`. This requires the use of the type `Option<&mut T>`, which is a structure containing a reference, so it is not yet supported by Prusti (see [limitations chapter](../capabilities/limitations.md)). To still be able to verify `peek_mut`, we mark it as `trusted` for now: +In the code, we need to get a mutable reference to the type inside the `Link = Option>>`. This requires the use of the type `Option<&mut T>`, which is a structure containing a reference, so it is not yet supported by Prusti. To still be able to verify `peek_mut`, we mark it as `trusted` for now: + + ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs:peek_mut_code}} diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md index ce2a9b2d873..ea0e2171e91 100644 --- a/docs/user-guide/src/tour/setup.md +++ b/docs/user-guide/src/tour/setup.md @@ -33,8 +33,9 @@ In this file, you can set [configuration flags](https://viperproject.github.io/p check_overflows = false ``` -**Note**: Creating a new project will create a `main.rs` file containing a `Hello World` program. Since Prusti does not yet support Strings (see [Prusti Limitations](../capabilities/limitations.md#strings-and-string-slices) chapter), verification will fail on `main.rs`. To still verify the code, remove the line `println!("Hello, world!");`. +**Note**: Creating a new project will create a `main.rs` file containing a `Hello World` program. Since Prusti does not yet support Strings, verification will fail on `main.rs`. To still verify the code, remove the line `println!("Hello, world!");`. + ## Standard library annotations diff --git a/docs/user-guide/src/verify/assert_refute_assume.md b/docs/user-guide/src/verify/assert_refute_assume.md index cea66713733..b114e690263 100644 --- a/docs/user-guide/src/verify/assert_refute_assume.md +++ b/docs/user-guide/src/verify/assert_refute_assume.md @@ -10,6 +10,8 @@ function (via an assumption). The macros `prusti_assert!`, `prusti_assert_eq!` and `prusti_assert_ne!` instruct Prusti to verify that a certain property holds at a specific point within the body of a function. In contrast to the `assert!`, `assert_eq!` and `assert_ne!` macros, which only accept Rust expressions, the Prusti variants accept [specification](../syntax.md) expressions as arguments. Therefore, [quantifiers](../syntax.md#quantifiers), [`old`](../syntax.md#old-expressions) expressions and other Prusti specification syntax is allowed within a call to `prusti_assert!`, as in the following example: ```rust,noplaypen,ignore +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; # use prusti_contracts::*; # #[requires(*x != 2)] @@ -22,6 +24,8 @@ fn go(x: &mut u32) { The two macros `prusti_assert_eq!` and `prusti_assert_ne!` are also slightly different than their standard counterparts, in that they use [snapshot equality](../syntax.md#snapshot-equality) `===` instead of [Partial Equality](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) `==`. ```rust,noplaypen,ignore +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; # use prusti_contracts::*; # #[requires(a === b)] @@ -42,6 +46,8 @@ fn different(a: u64, b: u64) { Note that the expression given to `prusti_assert!` must be side-effect free, since they will not result in any runtime code. Therefore, using code containing [impure](../verify/pure.md) functions will work in an `assert!`, but not within a `prusti_assert!`. For example: ```rust,noplaypen,ignore +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; # use prusti_contracts::*; # # fn test(map: std::collections::HashMap) { @@ -59,6 +65,10 @@ Using Prusti assertions instead of normal assertions can speed up verification, The `prusti_refute!` macro is similar to `prusti_assert!` in its format, conditions of use and what expressions it accepts. It instructs Prusti to verify that a certain property at a specific point within the body of a function might hold in some, but not all cases. For example the following code will verify: ```rust,noplaypen +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[ensures(val < 0 ==> result == -1)] #[ensures(val == 0 ==> result == 0)] #[ensures(val > 0 ==> result == 1)] @@ -78,7 +88,11 @@ fn sign(val: i32) -> i32 { But the following function would not: -```rust,noplaypen +```rust,noplaypen,ignore +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[requires(val < 0 && val > 0)] #[ensures(result == val/2)] fn half(val: i32) -> i32 { @@ -95,6 +109,10 @@ this can be used to introduce unsoundness. For example, Prusti would verify the following function: ```rust,noplaypen,ignore +# // The next line is only required for doctests, you can ignore/remove it +# extern crate prusti_contracts; +# use prusti_contracts::*; +# #[ensures(p == np)] fn proof(p: u32, np: u32) { prusti_assume!(false); diff --git a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml index 5823cf59d92..90a8e02eae8 100644 --- a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml +++ b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-contracts-proc-macros" -version = "0.1.6" +version = "0.1.8" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" @@ -15,7 +15,7 @@ categories = ["development-tools", "development-tools::testing"] proc-macro = true [dependencies] -prusti-specs = { path = "../prusti-specs", version = "0.1.6", optional = true } +prusti-specs = { path = "../prusti-specs", version = "0.1.8", optional = true } proc-macro2 = { version = "1.0", optional = true } [features] diff --git a/prusti-contracts/prusti-contracts/Cargo.toml b/prusti-contracts/prusti-contracts/Cargo.toml index 93bd3f6dff7..afbd45a10f9 100644 --- a/prusti-contracts/prusti-contracts/Cargo.toml +++ b/prusti-contracts/prusti-contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-contracts" -version = "0.1.6" +version = "0.1.8" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" @@ -12,7 +12,7 @@ keywords = ["prusti", "contracts", "verification", "formal", "specifications"] categories = ["development-tools", "development-tools::testing"] [dependencies] -prusti-contracts-proc-macros = { path = "../prusti-contracts-proc-macros", version = "0.1.6" } +prusti-contracts-proc-macros = { path = "../prusti-contracts-proc-macros", version = "0.1.8" } [dev-dependencies] trybuild = "1.0" diff --git a/prusti-contracts/prusti-specs/Cargo.toml b/prusti-contracts/prusti-specs/Cargo.toml index 0a8f68b301d..66c3f75c5fd 100644 --- a/prusti-contracts/prusti-specs/Cargo.toml +++ b/prusti-contracts/prusti-specs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-specs" -version = "0.1.7" +version = "0.1.8" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" diff --git a/prusti-contracts/prusti-std/Cargo.toml b/prusti-contracts/prusti-std/Cargo.toml index 1b12f2a0235..01eba50e347 100644 --- a/prusti-contracts/prusti-std/Cargo.toml +++ b/prusti-contracts/prusti-std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-std" -version = "0.1.6" +version = "0.1.8" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" @@ -12,7 +12,7 @@ keywords = ["prusti", "contracts", "verification", "formal", "specifications"] categories = ["development-tools", "development-tools::testing"] [dependencies] -prusti-contracts = { path = "../prusti-contracts", version = "0.1.6" } +prusti-contracts = { path = "../prusti-contracts", version = "0.1.8" } # Forward "prusti" flag [features]