Skip to content

Commit 89c1269

Browse files
authored
Revise first steps tutorial (rust-lang#1330)
1 parent 1d7f717 commit 89c1269

File tree

6 files changed

+66
-38
lines changed

6 files changed

+66
-38
lines changed

Diff for: docs/src/tutorial-first-steps.md

+62-34
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
Kani is unlike the testing tools you may already be familiar with.
44
Much of testing is concerned with thinking of new corner cases that need to be covered.
5-
With Kani, all the corner cases are covered from the start, and the new concern is narrowing down the scope to something manageable for the checker.
5+
With Kani, all the corner cases are covered from the start, and the new concern is narrowing down the scope to something manageable for the verifier.
66

77
Consider this first program (which can be found under [`first-steps-v1`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/first-steps-v1/)):
88

99
```rust
10-
{{#include tutorial/first-steps-v1/src/main.rs:code}}
10+
{{#include tutorial/first-steps-v1/src/lib.rs:code}}
1111
```
1212

1313
Think about the test harness you would need to write to test this function.
@@ -18,7 +18,7 @@ And if this function was more complicated—for example, if some of the branches
1818
We can try to property test a function like this, but if we're naive about it (and consider all possible `u32` inputs), then it's unlikely we'll ever find the bug.
1919

2020
```rust
21-
{{#include tutorial/first-steps-v1/src/main.rs:proptest}}
21+
{{#include tutorial/first-steps-v1/src/lib.rs:proptest}}
2222
```
2323

2424
```
@@ -29,10 +29,11 @@ test tests::doesnt_crash ... ok
2929

3030
There's only 1 in 4 billion inputs that fail, so it's vanishingly unlikely the property test will find it, even with a million samples.
3131

32-
With Kani, however, we can use `kani::any()` to represent all possible `u32` values:
32+
Let's write a Kani _proof harness_ for `estimate_size`.
33+
This is a lot like a test harness, but now we can use `kani::any()` to represent all possible `u32` values:
3334

3435
```rust
35-
{{#include tutorial/first-steps-v1/src/main.rs:kani}}
36+
{{#include tutorial/first-steps-v1/src/lib.rs:kani}}
3637
```
3738

3839
```
@@ -50,31 +51,42 @@ VERIFICATION:- FAILED
5051

5152
Kani has immediately found a failure.
5253
Notably, we haven't had to write explicit assertions in our proof harness: by default, Kani will find a host of erroneous conditions which include a reachable call to `panic` or a failing `assert`.
54+
If Kani had run successfully on this harness, this amounts to a mathematical proof that there is no input that could cause a panic in `estimate_size`.
5355

5456
### Getting a trace
5557

5658
By default, Kani only reports failures, not how the failure happened.
57-
This is because, in its full generality, understanding how a failure happened requires exploring a full (potentially large) execution trace.
58-
Here, we've just got some nondeterministic inputs up front, but that's something of a special case that has a "simpler" explanation (just the choice of nondeterministic input).
59+
In this running example, it seems obvious what we're interested in (the value of `x` that caused the failure) because we just have one unknown input at the start (similar to the property test), but that's kind of a special case.
60+
In general, understanding how a failure happened requires exploring a full (potentially large) _execution trace_.
5961

60-
To see traces, run:
62+
An execution trace is a record of exactly how a failure can occur.
63+
Nondeterminism (like a call to `kani::any()`, which could return any value) can appear in the middle of its execution.
64+
A trace is a record of exactly how execution proceeded, including concrete choices (like `1023`) for all of these nondeterministic values.
65+
66+
To get a trace for a failing check in Kani, run:
6167

6268
```
63-
kani --visualize src/main.rs
69+
cargo kani --visualize
6470
```
6571

66-
This command runs Kani and generates the HTML report in `report-main/html/index.html`.
72+
This command runs Kani and generates an HTML report that includes a trace.
6773
Open the report with your preferred browser.
68-
From this report, we can find the trace of the failure and filter through it to find the relevant line (at present time, an unfortunate amount of generated code is present in the trace):
74+
Under the "Errors" heading, click on the "trace" link to find the trace for this failure.
75+
76+
From this trace report, we can filter through it to find relevant lines.
77+
A good rule of thumb is to search for either `kani::any()` or assignments to variables you're interested in.
78+
At present time, an unfortunate amount of generated code is present in the trace.
79+
This code isn't a part of the Rust code you wrote, but is an internal implementation detail of how Kani runs proof harnesses.
80+
Still, searching for `kani::any()` quickly finds us these lines:
6981

7082
```
7183
let x: u32 = kani::any();
7284
x = 1023u
7385
```
7486

7587
Here we're seeing the line of code and the value assigned in this particular trace.
76-
Like property testing, this is just one example of a failure.
77-
To find more, we'd presumably fix this issue and then re-run Kani.
88+
Like property testing, this is just one **example** of a failure.
89+
To proceed, we recommend fixing the code to avoid this particular issue and then re-running Kani to see if you find more issues.
7890

7991
### Exercise: Try other failures
8092

@@ -106,14 +118,14 @@ But Kani still catches the issue:
106118
[...]
107119
RESULTS:
108120
[...]
109-
Check 2: foo.pointer_dereference.1
121+
Check 2: estimate_size.pointer_dereference.1
110122
- Status: FAILURE
111123
- Description: "dereference failure: pointer NULL"
112124
[...]
113125
VERIFICATION:- FAILED
114126
```
115127

116-
**Can you find an example where the Rust compiler will not complain, and Kani will?**
128+
**Exercise: Can you find an example where the Rust compiler will not complain, and Kani will?**
117129

118130
<details>
119131
<summary>Click to show one possible answer</summary>
@@ -127,11 +139,11 @@ Overflow (in addition, multiplication or, in this case, [bit-shifting by too muc
127139
```
128140
RESULTS:
129141
[...]
130-
Check 3: foo.assertion.1
142+
Check 1: estimate_size.assertion.1
131143
- Status: FAILURE
132144
- Description: "attempt to shift left with overflow"
133145
134-
Check 4: foo.undefined-shift.1
146+
Check 3: estimate_size.undefined-shift.1
135147
- Status: FAILURE
136148
- Description: "shift distance too large"
137149
[...]
@@ -142,38 +154,43 @@ VERIFICATION:- FAILED
142154

143155
## Assertions, Assumptions, and Harnesses
144156

145-
It seems a bit odd that we can take billions of inputs when our function only handles up to a few thousand.
146-
Let's encode this fact about our function by asserting some reasonable bound on our input, after we've fixed our bug (code available under
147-
[`first-steps-v2`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/first-steps-v2/)):
157+
It seems a bit odd that our example function is tested against billions of possible inputs, when it really only seems to be designed to handle a few thousand.
158+
Let's encode this fact about our function by asserting some reasonable upper bound on our input, after we've fixed our bug.
159+
(New code available under [`first-steps-v2`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/first-steps-v2/)):
148160

149161
```rust
150-
{{#include tutorial/first-steps-v2/src/main.rs:code}}
162+
{{#include tutorial/first-steps-v2/src/lib.rs:code}}
151163
```
152164

153-
Now we've stated our previously implicit expectation: this function should never be called with inputs that are too big.
154-
But if we attempt to verify this, we get a problem:
165+
Now we've explicitly stated our previously implicit expectation: this function should never be called with inputs that are too big.
166+
But if we attempt to verify this modified function, we run into a problem:
155167

156168
```
157169
[...]
158170
RESULTS:
159171
[...]
160-
Check 3: final_form::estimate_size.assertion.1
172+
Check 3: estimate_size.assertion.1
161173
- Status: FAILURE
162174
- Description: "assertion failed: x < 4096"
163175
[...]
164176
VERIFICATION:- FAILED
165177
```
166178

167-
We intended this to be a precondition of calling the function, but Kani is treating it like a failure.
168-
If we call this function with too large of a value, it will crash with an assertion failure.
169-
But we know that, that was our intention.
179+
What we want is a _precondition_ for `estimate_size`.
180+
That is, something that should always be true every time we call the function.
181+
By putting the assertion at the beginning, we ensure the function immediately fails if that expectation is not met.
182+
183+
But our proof harness will still call this function with any integer, even ones that just don't meet the function's preconditions.
184+
That's... not a useful or interesting result.
185+
We know that won't work already.
186+
How do we go back to successfully verifying this function?
170187

171-
This is the purpose of _proof harnesses_.
188+
This is the purpose of writing a proof harness.
172189
Much like property testing (which would also fail in this assertion), we need to set up our preconditions, call the function in question, then assert our postconditions.
173190
Here's a revised example of the proof harness, one that now succeeds:
174191

175192
```rust
176-
{{#include tutorial/first-steps-v2/src/main.rs:kani}}
193+
{{#include tutorial/first-steps-v2/src/lib.rs:kani}}
177194
```
178195

179196
But now we must wonder if we've really fully tested our function.
@@ -183,14 +200,25 @@ Fortunately, Kani is able to report a coverage metric for each proof harness.
183200
Try running:
184201

185202
```
186-
kani --visualize src/main.rs --harness verify_success
187-
open report-verify_success/html/index.html
203+
cargo kani --visualize --harness verify_success
188204
```
189205

190206
The beginning of the report includes coverage information.
191207
Clicking through to the file will show fully-covered lines in green.
192208
Lines not covered by our proof harness will show in red.
193209

194-
1. Try changing the assumption in the proof harness to `x < 2048`. Now the harness won't be testing all possible cases.
195-
2. Rerun `kani --visualize` on the file
196-
3. Look at the report: you'll see we no longer have 100% coverage of the function.
210+
Try changing the assumption in the proof harness to `x < 2048`.
211+
Now the harness won't be testing all possible cases.
212+
Rerun `cargo kani --visualize`.
213+
Look at the report: you'll see we no longer have 100% coverage of the function.
214+
215+
## Summary
216+
217+
In this section:
218+
219+
1. We saw Kani find panics, assertion failures, and even some other failures like unsafe dereferencing of null pointers.
220+
2. We saw Kani find failures that testing could not easily find.
221+
3. We saw how to write a proof harness and use `kani::any()`.
222+
4. We saw how to get a failing **trace** using `kani --visualize`
223+
5. We saw how proof harnesses are used to set up preconditions with `kani::assume()`.
224+
6. We saw how to obtain **coverage** metrics and use them to ensure our proofs are covering as much as they should be.

Diff for: docs/src/tutorial-kinds-of-failure.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ In this case, where we just have a couple of `kani::any` values in our proof har
126126
In this trace we find (and the values you get may be different):
127127

128128
```
129-
Step 36: Function bound_check, File src/bounds_check.rs, Line 43
129+
Step 36: Function bound_check, File src/bounds_check.rs, Line 37
130130
let size: usize = kani::any();
131131
size = 2464ul
132132
133-
Step 39: Function main, File src/bounds_check.rs, Line 45
133+
Step 39: Function bound_check, File src/bounds_check.rs, Line 39
134134
let index: usize = kani::any();
135135
index = 2463ul
136136
```

Diff for: docs/src/tutorial/first-steps-v1/src/main.rs renamed to docs/src/tutorial/first-steps-v1/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ mod tests {
4949
// ANCHOR: kani
5050
#[cfg(kani)]
5151
#[kani::proof]
52-
fn main() {
52+
fn check_estimate_size() {
5353
let x: u32 = kani::any();
5454
estimate_size(x);
5555
}

Diff for: docs/src/tutorial/first-steps-v2/src/main.rs renamed to docs/src/tutorial/first-steps-v2/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn verify_success() {
6161

6262
#[cfg(kani)]
6363
#[kani::proof]
64-
fn main() {
64+
fn will_fail() {
6565
let x: u32 = kani::any();
6666
let y = estimate_size(x);
6767
}

0 commit comments

Comments
 (0)