|
1 | 1 | # Nondeterministic variables
|
2 | 2 |
|
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). |
5 | 6 |
|
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. |
13 | 9 |
|
14 | 10 | ## Safe nondeterministic variables
|
15 | 11 |
|
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)): |
18 | 14 |
|
19 | 15 | ```rust
|
20 | 16 | {{#include tutorial/arbitrary-variables/src/inventory.rs:inventory_lib}}
|
21 | 17 | ```
|
22 | 18 |
|
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`: |
24 | 20 |
|
25 | 21 | ```rust
|
26 | 22 | {{#include tutorial/arbitrary-variables/src/inventory.rs:safe_update}}
|
27 | 23 | ```
|
28 | 24 |
|
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: |
31 | 26 |
|
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. |
33 | 30 |
|
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. |
36 | 33 |
|
37 |
| -``` |
| 34 | +```bash |
38 | 35 | cargo kani --harness safe_update
|
39 | 36 | ```
|
40 | 37 |
|
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. |
42 | 49 |
|
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). |
45 | 52 |
|
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. |
47 | 56 |
|
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: |
49 | 61 |
|
50 | 62 | ```rust
|
51 |
| -{{#include tutorial/arbitrary-variables/src/inventory.rs:unsafe_update}} |
| 63 | +{{#include tutorial/arbitrary-variables/src/rating.rs:rating_invariant}} |
52 | 64 | ```
|
53 | 65 |
|
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. |
59 | 67 |
|
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. |
61 | 72 |
|
62 |
| -``` |
63 |
| -cargo kani --harness unsafe_update |
64 |
| -``` |
| 73 | +## Bounding nondeterministic variables |
65 | 74 |
|
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`. |
67 | 78 |
|
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. |
70 | 81 |
|
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. |
74 | 85 |
|
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 |
76 | 87 |
|
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:) |
79 | 89 |
|
80 | 90 | ```rust
|
81 |
| -{{#include tutorial/arbitrary-variables/src/rating.rs:rating_invariant}} |
| 91 | +fn any_inventory(bound: u32) -> Inventory { |
| 92 | + // fill in here |
| 93 | +} |
82 | 94 | ```
|
83 | 95 |
|
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. |
85 | 99 |
|
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! |
89 | 102 |
|
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). |
92 | 104 |
|
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`. |
0 commit comments