Skip to content

Commit 6efab6c

Browse files
authored
Revise down the nondeterminism tutorial (rust-lang#1353)
1 parent 02ebd42 commit 6efab6c

8 files changed

+141
-102
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,112 @@
11
# Nondeterministic variables
22

3-
Kani is able to reason about programs and their execution paths by allowing users to assign nondeterministic (i.e., symbolic) values to certain variables.
4-
Since Kani is a bit-level model checker, this means that Kani considers that an unconstrained nondeterministic value represents all the possible bit-value combinations assigned to the variable's memory contents.
3+
Kani is able to reason about programs and their execution paths by allowing users to create nondeterministic (also called symbolic) values using `kani::any()`.
4+
Kani is a "bit-precise" model checker, which means that Kani considers all the possible bit-value combinations _that would be valid_ if assigned to a variable's memory contents.
5+
In other words, `kani::any()` should not produce values that are invalid for the type (which would lead to Rust undefined behavior).
56

6-
As a Rust developer, this sounds a lot like the `mem::transmute` operation, which is highly `unsafe`.
7-
And that's correct.
8-
9-
In this tutorial, we will show how to:
10-
1. Safely use nondeterministic assignments to generate valid symbolic variables that respect Rust's type invariants.
11-
2. Unsafely use nondeterministic assignments to generate unconstrained symbolic variables that do not respect Rust's type invariants.
12-
2. Specify invariants for types that you define, enabling the creation of safe nondeterministic variables for those types.
7+
Out of the box, Kani includes `kani::any()` implementations for most primitive and some `std` types.
8+
In this tutorial, we will show how to use `kani::any()` to create symbolic values for other types.
139

1410
## Safe nondeterministic variables
1511

16-
Let's say you're developing an inventory management tool, and you would like to verify that your API to manage items is correct.
17-
Here is a simple implementation of this API:
12+
Let's say you're developing an inventory management tool, and you would like to start verifying properties about your API.
13+
Here is a simple example (available [here](https://github.com/model-checking/kani/blob/main/docs/src/tutorial/arbitrary-variables/src/inventory.rs)):
1814

1915
```rust
2016
{{#include tutorial/arbitrary-variables/src/inventory.rs:inventory_lib}}
2117
```
2218

23-
Now we would like to verify that, no matter which combination of `id` and `quantity`, a call to `Inventory::update()` followed by a call to `Inventory::get()` using the same `id` returns some value that's equal to the one we inserted:
19+
Let's write a fairly simple proof harness, one that just ensures we successfully `get` the value we inserted with `update`:
2420

2521
```rust
2622
{{#include tutorial/arbitrary-variables/src/inventory.rs:safe_update}}
2723
```
2824

29-
In this harness, we use `kani::any()` to generate the new `id` and `quantity`.
30-
`kani::any()` is a **safe** API function, and it represents only valid values.
25+
We use `kani::any()` twice here:
3126

32-
If we run this example, Kani verification will succeed, including the assertion that shows that the underlying `u32` variable used to represent `NonZeroU32` cannot be zero, per its type invariant:
27+
1. `id` has type `ProductId` which was actually just a `u32`, and so any value is fine.
28+
2. `quantity`, however, has type `NonZeroU32`.
29+
In Rust, it would be undefined behavior to have a value of `0` for this type.
3330

34-
You can try it out by running the example under
35-
[`arbitrary-variables`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/arbitrary-variables/):
31+
We included an extra assertion that the value returned by `kani::any()` here was actually non-zero.
32+
If we run this, you'll notice that verification succeeds.
3633

37-
```
34+
```bash
3835
cargo kani --harness safe_update
3936
```
4037

41-
## Unsafe nondeterministic variables
38+
`kani::any()` is safe Rust, and so Kani only implements it for types where type invariants are enforced.
39+
For `NonZeroU32`, this means we never return a `0` value.
40+
The assertion we wrote in this harness was just an extra check we added to demonstrate this fact, not an essential part of the proof.
41+
42+
## Custom nondeterministic types
43+
44+
While `kani::any()` is the only method Kani provides to inject non-determinism into a proof harness, Kani only ships with implementations for a few types where we can guarantee safety.
45+
When you need nondeterministic variables of types that `kani::any()` cannot construct, you have two options:
46+
47+
1. Implement the `kani::Arbitrary` trait for your type, so you can use `kani::any()`.
48+
2. Just write a function.
4249

43-
Kani also includes an **unsafe** method to generate unconstrained nondeterministic variables which do not take type invariants into consideration.
44-
As with any unsafe method in Rust, users have to make sure the right guardrails are put in place to avoid undesirable behavior.
50+
The advantage of the first approach is that it's simple and conventional.
51+
It also means that in addition to being able to use `kani::any()` with your type, you can also use it with `Option<MyType>` (for example).
4552

46-
That said, there may be cases where you want to verify your code taking into consideration that some inputs may contain invalid data.
53+
The advantage of the second approach is that you're able to pass in parameters, like bounds on the size of the data structure.
54+
(Which we'll discuss more in the next section.)
55+
This approach is also necessary when you are unable to implement a trait (like `Arbitrary`) on a type you're importing from another crate.
4756

48-
Let's see what happens if we modify our verification harness to use the unsafe method `kani::any_raw()` to generate the updated value.
57+
Either way, inside this function you would simply return an arbitrary value by generating arbitrary values for its components.
58+
To generate a nondeterministic struct, you would just generate nondeterministic values for each of its fields.
59+
For complex data structures like vectors or other containers, you can start with an empty one and add a (bounded) nondeterministic number of entries.
60+
For an enum, you can make use of a simple trick:
4961

5062
```rust
51-
{{#include tutorial/arbitrary-variables/src/inventory.rs:unsafe_update}}
63+
{{#include tutorial/arbitrary-variables/src/rating.rs:rating_invariant}}
5264
```
5365

54-
We commented out the assertion that the underlying `u32` variable cannot be `0`, since this no longer holds.
55-
The verification will now fail showing that the `inventory.get(&id).unwrap()` method call can panic.
56-
57-
This is an interesting issue that emerges from how `rustc` optimizes the memory layout of `Option<NonZeroU32>`.
58-
The compiler is able to represent `Option<NonZeroU32>` using `32` bits by using the value `0` to represent `None`.
66+
All we're doing here is making use of a nondeterministic integer to decide which variant of `Rating` to return.
5967

60-
You can try it out by running the example under [`arbitrary-variables`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/arbitrary-variables/):
68+
> **NOTE**: If we thought of this code as generating a random value, this function looks heavily biased.
69+
> We'd overwhelmingly generate a `Three` because it's matching "all other integers besides 1 and 2."
70+
> But Kani just see 3 meaningful possibilities, each of which is not treated any differently from each other.
71+
> The "proportion" of integers does not matter.
6172
62-
```
63-
cargo kani --harness unsafe_update
64-
```
73+
## Bounding nondeterministic variables
6574

66-
## Safe nondeterministic variables for custom types
75+
You can use `kani::any()` for `[T; N]` (if implemented for `T`) because this array type has an exact and constant size.
76+
But if you wanted a slice (`[T]`) up to size `N`, you can no longer use `kani::any()` for that.
77+
Likewise, there is no implementation of `kani::any()` for more complex data structures like `Vec`.
6778

68-
Now you would like to add a new structure to your library that allow users to represent a review rating, which can go from 0 to 5 stars.
69-
Let's say you add the following implementation:
79+
The trouble with a nondeterministic vector is that you usually need to _bound_ the size of the vector, for the reasons we investigated in the [last chapter](./tutorial-loop-unwinding.md).
80+
The `kani::any()` function does not have any arguments, and so cannot be given an upper bound.
7081

71-
```rust
72-
{{#include tutorial/arbitrary-variables/src/rating.rs:rating_struct}}
73-
```
82+
This does not mean you cannot have a nondeterministic vector.
83+
It just means you have to construct one.
84+
Our example proof harness above constructs a nondeterministic `Inventory` of size `1`, simply by starting with the empty `Inventory` and inserting a nondeterministic entry.
7485

75-
The easiest way to allow users to create nondeterministic variables of the Rating type which represents values from 0-5 stars is by implementing the `kani::Invariant` trait.
86+
### Exercise
7687

77-
The implementation only requires you to define a check to your structure that returns whether its current value is valid or not.
78-
In our case, we have the following implementation:
88+
Try writing a function to generate a (bounded) nondeterministic inventory (from the first example:)
7989

8090
```rust
81-
{{#include tutorial/arbitrary-variables/src/rating.rs:rating_invariant}}
91+
fn any_inventory(bound: u32) -> Inventory {
92+
// fill in here
93+
}
8294
```
8395

84-
Now you can use `kani::any()` to create valid nondeterministic variables of the Rating type as shown in this harness:
96+
One thing you'll quickly find is that the bounds must be very small.
97+
Kani does not (yet!) scale well to nondeterministic-size data structures involving heap allocations.
98+
A proof harness like `safe_update` above, but starting with `any_inventory(2)` will probably take a couple of minutes to prove.
8599

86-
```rust
87-
{{#include tutorial/arbitrary-variables/src/rating.rs:verify_rating}}
88-
```
100+
A hint for this exercise: you might choose two different behaviors, "size of exactly `bound`" or "size up to `bound`".
101+
Try both!
89102

90-
You can try it out by running the example under
91-
[`arbitrary-variables`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/arbitrary-variables/):
103+
A solution can be found in [`exercise_solution.rs`](https://github.com/model-checking/kani/blob/main/docs/src/tutorial/arbitrary-variables/src/exercise_solution.rs).
92104

93-
```
94-
cargo kani --harness check_rating
95-
```
105+
## Summary
106+
107+
In this section:
108+
109+
1. We saw how `kani::any()` will return "safe" values for each of the types Kani implements it for.
110+
2. We saw how to implement `kani::Arbitrary` or just write a function to create nondeterministic values for other types.
111+
3. We noted that some types cannot implement `kani::any()` as they need a bound on their size.
112+
4. We did an exercise to generate nondeterministic values of bounded size for `Inventory`.

docs/src/tutorial/arbitrary-variables/Cargo.toml

-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,3 @@ edition = "2018"
99
vector-map = "1.0.1"
1010

1111
[workspace]
12-
13-
[workspace.metadata.kani]
14-
flags = { unwind = "3" }
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
SUCCESS\
2-
assertion failed: rating.get() <= 5
3-
SUCCESS\
4-
assertion failed: Rating::from(rating.get()).is_some()
51
VERIFICATION:- SUCCESSFUL
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! If you want to try yourself the exercise in the Kani tutorial, stop reading now!
5+
//!
6+
//! This file is a solution to that exercise.
7+
8+
use crate::inventory::*;
9+
use std::num::NonZeroU32;
10+
use vector_map::VecMap;
11+
12+
#[cfg(kani)]
13+
mod verification {
14+
use super::*;
15+
16+
fn any_inventory(bound: u32) -> Inventory {
17+
let size: u32 = kani::any();
18+
kani::assume(size <= bound);
19+
20+
let mut inner = VecMap::new();
21+
22+
for _ in 0..size {
23+
let id: ProductId = kani::any();
24+
let quantity: NonZeroU32 = kani::any();
25+
26+
inner.insert(id, quantity);
27+
}
28+
29+
Inventory { inner }
30+
}
31+
32+
#[kani::proof]
33+
#[kani::unwind(3)]
34+
pub fn safe_update_with_any() {
35+
let mut inventory = any_inventory(0);
36+
37+
// Create non-deterministic variables for id and quantity.
38+
let id: ProductId = kani::any();
39+
let quantity: NonZeroU32 = kani::any();
40+
assert!(quantity.get() != 0, "NonZeroU32 is internally a u32 but it should never be 0.");
41+
42+
// Update the inventory and check the result.
43+
inventory.update(id, quantity);
44+
assert!(inventory.get(&id).unwrap() == quantity);
45+
}
46+
}

docs/src/tutorial/arbitrary-variables/src/inventory.rs

+6-23
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
use std::num::NonZeroU32;
66
use vector_map::VecMap;
77

8-
type ProductId = u32;
8+
pub type ProductId = u32;
99

1010
pub struct Inventory {
11-
inner: VecMap<ProductId, NonZeroU32>,
11+
/// Every product in inventory must have a non-zero quantity
12+
pub inner: VecMap<ProductId, NonZeroU32>,
1213
}
1314

1415
impl Inventory {
@@ -28,8 +29,9 @@ mod verification {
2829

2930
// ANCHOR: safe_update
3031
#[kani::proof]
32+
#[kani::unwind(3)]
3133
pub fn safe_update() {
32-
// Create inventory variable.
34+
// Empty to start
3335
let mut inventory = Inventory { inner: VecMap::new() };
3436

3537
// Create non-deterministic variables for id and quantity.
@@ -38,27 +40,8 @@ mod verification {
3840
assert!(quantity.get() != 0, "NonZeroU32 is internally a u32 but it should never be 0.");
3941

4042
// Update the inventory and check the result.
41-
inventory.update(id.clone(), quantity);
43+
inventory.update(id, quantity);
4244
assert!(inventory.get(&id).unwrap() == quantity);
4345
}
4446
// ANCHOR_END: safe_update
45-
46-
// ANCHOR: unsafe_update
47-
#[kani::proof]
48-
pub fn unsafe_update() {
49-
// Create inventory variable.
50-
let mut inventory = Inventory { inner: VecMap::new() };
51-
52-
// Create non-deterministic variables for id and quantity with unsafe kani::any_raw().
53-
let id: ProductId = kani::any();
54-
let quantity: NonZeroU32 = unsafe { kani::any_raw() };
55-
56-
// The assert bellow would fail if we comment it out.
57-
// assert!(quantity.get() != 0, "NonZeroU32 is internally a u32 but it should never be 0.");
58-
59-
// Update the inventory and check the result.
60-
inventory.update(id.clone(), quantity);
61-
assert!(inventory.get(&id).unwrap() == quantity); // This unwrap will panic.
62-
}
63-
// ANCHOR_END: unsafe_update
6447
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4+
mod exercise_solution;
45
pub mod inventory;
56
pub mod rating;

docs/src/tutorial/arbitrary-variables/src/rating.rs

+19-15
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,44 @@
33

44
// ANCHOR: rating_struct
55
#[derive(Copy, Clone)]
6-
pub struct Rating {
7-
value: u8,
6+
enum Rating {
7+
One,
8+
Two,
9+
Three,
810
}
911

1012
impl Rating {
11-
pub fn from(value: u8) -> Option<Rating> {
12-
if value <= 5 { Some(Rating { value }) } else { None }
13-
}
14-
15-
pub fn get(&self) -> u8 {
16-
self.value
13+
fn as_int(&self) -> u8 {
14+
match self {
15+
Rating::One => 1,
16+
Rating::Two => 2,
17+
Rating::Three => 3,
18+
}
1719
}
1820
}
19-
2021
// ANCHOR_END: rating_struct
2122

2223
#[cfg(kani)]
2324
mod verification {
2425
use super::*;
2526

2627
// ANCHOR: rating_invariant
27-
unsafe impl kani::Invariant for Rating {
28-
fn is_valid(&self) -> bool {
29-
self.value <= 5
28+
impl kani::Arbitrary for Rating {
29+
fn any() -> Self {
30+
match kani::any::<u8>() {
31+
0 => Rating::One,
32+
1 => Rating::Two,
33+
_ => Rating::Three,
34+
}
3035
}
3136
}
3237
// ANCHOR_END: rating_invariant
3338

3439
// ANCHOR: verify_rating
3540
#[kani::proof]
3641
pub fn check_rating() {
37-
let rating = kani::any::<Rating>();
38-
assert!(rating.get() <= 5);
39-
assert!(Rating::from(rating.get()).is_some());
42+
let rating: Rating = kani::any();
43+
assert!((1..=3).contains(&rating.as_int()));
4044
}
4145
// ANCHOR_END: verify_rating
4246
}

docs/src/tutorial/arbitrary-variables/unsafe_update.expected

-5
This file was deleted.

0 commit comments

Comments
 (0)