Skip to content

Commit 3f19734

Browse files
Carolyn Zechtautschnig
andauthored
Kani Book Documentation Improvements (#4296)
Best reviewed commit by commit. Resolves #4026 Resolves #3999 Resolves #3568 Resolves #2539 Resolves #3041 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Michael Tautschnig <[email protected]>
1 parent 5eeb7cf commit 3f19734

File tree

11 files changed

+335
-40
lines changed

11 files changed

+335
-40
lines changed

docs/src/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
- [Nondeterministic variables](./tutorial-nondeterministic-variables.md)
1717

1818
- [Reference](./reference.md)
19+
- [Arbitrary Trait](./reference/arbitrary.md)
1920
- [Attributes](./reference/attributes.md)
2021
- [Bounded Non-deterministic variables](./reference/bounded_arbitrary.md)
22+
- [List Kani Metadata](./reference/list.md)
2123
- [Experimental features](./reference/experimental/experimental-features.md)
2224
- [Automatic Harness Generation](./reference/experimental/autoharness.md)
2325
- [Coverage](./reference/experimental/coverage.md)
@@ -29,6 +31,7 @@
2931
- [Application](./application.md)
3032
- [Comparison with other tools](./tool-comparison.md)
3133
- [Where to start on real code](./tutorial-real-code.md)
34+
- [Debugging slow proofs](./debugging-slow-proofs.md)
3235

3336
- [Developer documentation](dev-documentation.md)
3437
- [Coding conventions](./conventions.md)

docs/src/debugging-slow-proofs.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Debugging Slow Proofs
2+
3+
Kani uses SAT/SMT solvers to verify code, which can sometimes result in slow or non-terminating proofs. This chapter outlines common causes of slowness and strategies to debug and improve proof performance.
4+
5+
## Common Causes of Slow Proofs
6+
7+
### Complex/Large Non-deterministic Types
8+
Some types are inherently more expensive to represent symbolically, e.g. strings, which have complex validation rules for UTF-8 encoding,
9+
or large bounded collections, like a vector with a large size.
10+
11+
### Large Value Operations
12+
Mathematical operations on large values can be expensive, e.g., multiplication/division/modulo, especially with larger types (e.g., `u64`).
13+
14+
### Unbounded Loops
15+
If Kani cannot determine a loop bound, it will unwind forever, c.f. [the loop unwinding tutorial](./tutorial-loop-unwinding.md).
16+
17+
## Debugging Strategies
18+
19+
These are some strategies to debug slow proofs, ordered roughly in terms of in the order you should try them:
20+
21+
### Limit Loop Iterations
22+
23+
First, identify whether (unbounded) loop unwinding may be the root cause. Try the `#[kani::unwind]` attribute or the `--unwind` option to limit [loop unwinding](./tutorial-loop-unwinding.md). If the proof fails because the unwind value is too low, but raising it causing the proof to be too slow, try specifying a [loop contract](./reference/experimental/loop-contracts.md) instead.
24+
25+
### Use Different Solvers
26+
27+
Kani supports multiple SAT/SMT solvers that may perform differently on your specific problem. Try out different solvers with the `#[kani::solver]` [attribute](./reference/attributes.md) or `--solver` option.
28+
29+
### Remove Sources of Nondeterminism
30+
31+
Start by replacing `kani::any()` calls with concrete values to isolate the problem:
32+
33+
```rust
34+
#[kani::proof]
35+
fn slow_proof() {
36+
// Instead of this:
37+
// let x: u64 = kani::any();
38+
// let y: u64 = kani::any();
39+
40+
// Try this:
41+
let x: u64 = 42;
42+
let y: u64 = 100;
43+
44+
let result = complex_function(x, y);
45+
assert!(result > 0);
46+
}
47+
```
48+
49+
If the proof becomes fast with concrete values, the issue is likely with the symbolic representation of your inputs. In that case, see you can [partition the proof](#partition-the-input-space) to cover different ranges of possible values, or restrict the proof to a smaller range of values if that is acceptable for your use case.
50+
51+
### Reduce Collection Sizes
52+
53+
Similarly, if smaller values are acceptable for your proof, use those instead:
54+
55+
```rust
56+
#[kani::proof]
57+
fn test_with_small_collection() {
58+
// Instead of a large Vec
59+
// let vec: Vec<u8> = kani::bounded_any::<_, 100>();
60+
61+
// Start with a small size
62+
let vec: Vec<u8> = kani::bounded_any::<_, 2>();
63+
64+
process_collection(&vec);
65+
}
66+
```
67+
68+
### Partition the Input Space
69+
70+
Break down complex proofs by partitioning the input space:
71+
72+
```rust
73+
// Instead of one slow proof with large inputs
74+
#[kani::proof]
75+
fn test_multiplication_slow() {
76+
let x: u64 = kani::any();
77+
let y: u64 = kani::any();
78+
79+
// This might be too slow for the solver
80+
let result = x.saturating_mul(y);
81+
assert!(result >= x || x == 0);
82+
}
83+
84+
// Split into multiple proofs with bounded inputs
85+
#[kani::proof]
86+
fn test_multiplication_small_values() {
87+
let x: u64 = kani::any_where(|x| *x <= 100);
88+
let y: u64 = kani::any_where(|y| *y <= 100);
89+
90+
let result = x.saturating_mul(y);
91+
assert!(result >= x || x == 0);
92+
}
93+
94+
// Insert harnesses for other ranges of `x` and `y`
95+
```
96+
97+
See this [tracking issue](https://github.com/model-checking/kani/issues/3006) for adding support for such partitioning automatically.
98+
99+
### Use Stubs
100+
101+
If a function has a complex body, consider using a [stub](./reference/experimental/stubbing.md) or a [verified stub](./reference/experimental/contracts.md) to stub the body with a simpler abstraction.
102+
103+
### Disable Unnecessary Checks
104+
105+
If you're focusing on functional correctness rather than safety, you may disable memory safety checks (run `kani --help` for a list of options to do so). Note that disabling these checks may cause Kani to miss undefined behavior, so use it with caution.
106+
107+
Alternatively, to assume that all assertions succeed and only focus on finding safety violations, use the `--prove-safety-only` option.

docs/src/kani-tutorial.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ This tutorial will step you through a progression from simple to moderately comp
66
It's meant to ensure you can get started, and see at least some simple examples of how typical proofs are structured.
77
It will also teach you the basics of "debugging" proof harnesses, which mainly consists of diagnosing and resolving non-termination issues with the solver.
88

9+
For a more in-depth overview of Kani's features, see the [Reference](./reference.md) section.
10+
911
You may also want to read the [Application](./application.md) section to better
1012
understand what Kani is and how it can be applied on real code.

docs/src/reference/arbitrary.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Arbitrary Trait
2+
3+
The `Arbitrary` trait is the foundation for generating non-deterministic values in Kani proof harnesses. It allows you to create symbolic values that represent all possible values of a given type.
4+
5+
For a type to implement `Arbitrary`, Kani must be able to represent every possible value of it, so unbounded types cannot implement it. For nondeterministic representations of unbounded types, e.g., `Vec`, see the [`BoundedArbitrary` trait](./bounded_arbitrary.md).
6+
7+
## Overview
8+
9+
The `Arbitrary` trait defines methods for generating arbitrary (nondeterministic) values:
10+
11+
```rust
12+
pub trait Arbitrary
13+
where
14+
Self: Sized,
15+
{
16+
fn any() -> Self;
17+
fn any_array<const MAX_ARRAY_LENGTH: usize>() -> [Self; MAX_ARRAY_LENGTH] {
18+
[(); MAX_ARRAY_LENGTH].map(|_| Self::any())
19+
}
20+
}
21+
```
22+
23+
## Basic Usage
24+
25+
Use `kani::any()` to generate arbitrary values in proof harnesses:
26+
27+
```rust
28+
#[kani::proof]
29+
fn verify_function() {
30+
let x: u32 = kani::any();
31+
let y: bool = kani::any();
32+
let z: [char; 10] = kani::any();
33+
34+
// x represents all possible u32 values
35+
// y represents both true and false
36+
// z represents an array of length 10 where each element can hold all possible char values
37+
my_function(x, y, z);
38+
}
39+
```
40+
41+
Kani implements `Arbitrary` for primitive types and some standard library types. See the [crate trait documentation](https://model-checking.github.io/kani/crates/doc/kani/trait.Arbitrary.html#foreign-impls) for a full list of implementations.
42+
43+
## Constrained Values
44+
45+
Use `any_where` or `kani::assume()` to add constraints to arbitrary values:
46+
47+
```rust
48+
#[kani::proof]
49+
fn verify_with_constraints() {
50+
let x: u32 = kani::any_where(|t| *t < 1000); // Constrain x to be less than 1000
51+
kani::assume(x % 2 == 0); // Constrain x to be even
52+
53+
// Now x represents all even numbers from 0 to 998
54+
my_function(x);
55+
}
56+
57+
## Derive Implementations
58+
59+
Kani can automatically derive `Arbitrary` implementations for structs and enums when all their fields/variants implement `Arbitrary`:
60+
61+
### Structs
62+
63+
```rust
64+
#[derive(kani::Arbitrary)]
65+
struct Point {
66+
x: i32,
67+
y: i32,
68+
}
69+
70+
#[kani::proof]
71+
fn verify_point() {
72+
let point: Point = kani::any();
73+
// point.x and point.y can be any i32 values
74+
process_point(point);
75+
}
76+
```
77+
78+
### Enums
79+
80+
```rust
81+
#[derive(kani::Arbitrary)]
82+
enum Status {
83+
Ready,
84+
Processing(u32),
85+
Error { code: (char, i32) },
86+
}
87+
88+
#[kani::proof]
89+
fn verify_status() {
90+
let status: Status = kani::any();
91+
// `status` can be any of the variants
92+
match status {
93+
Status::Ready => { /* ... */ }
94+
Status::Processing(id) => { /* id can be any u32 */ }
95+
Status::Error { code } => { /* code can be any (char, i32) tuple */ }
96+
}
97+
}
98+
```
99+
100+
## Manual Implementations
101+
102+
Implement `Arbitrary` manually when you need constraints or custom logic. For example, Kani [manually implements `Arbitrary` for `NonZero` types](https://github.com/model-checking/kani/blob/100857e99d7506992c4589332a0d7d8dae1ee29a/library/kani_core/src/arbitrary.rs#L48-L60) to exclude zero values, e.g:
103+
104+
```rust
105+
impl Arbitrary for NonZeroU8 {
106+
fn any() -> Self {
107+
let val = u8::any();
108+
kani::assume(val != 0);
109+
unsafe { NonZeroU8::new_unchecked(val) }
110+
}
111+
}
112+
```
113+
114+
An alternative means to add value constraints is provided by the [Invariant trait](https://model-checking.github.io/kani/crates/doc/kani/invariant/trait.Invariant.html).
115+
116+
## See Also
117+
118+
- [Nondeterministic Variables Tutorial](../tutorial-nondeterministic-variables.md)
119+
- [Bounded Non-deterministic Variables](./bounded_arbitrary.md)
120+
- [First Steps Tutorial](../tutorial-first-steps.md)

docs/src/reference/attributes.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ At present, the available Kani attributes are the following:
99
- [`#[kani::unwind(<number>)]`](#kaniunwindnumber)
1010
- [`#[kani::solver(<solver>)]`](#kanisolversolver)
1111
- [`#[kani::stub(<original>, <replacement>)]`](#kanistuboriginal-replacement)
12+
- [Contract-related attributes](#contract-attributes)
1213

1314
## `#[kani::proof]`
1415

@@ -239,3 +240,14 @@ default one.
239240
**Replaces the function/method with name <original> with the function/method with name <replacement> during compilation**
240241

241242
Check the [*Stubbing* section](../reference/stubbing.md) for more information about stubbing.
243+
244+
## Contract Attributes
245+
246+
There are numerous attributes for function and loop contracts. At present, these are:
247+
248+
- Proof harness for contracts: `#[kani::proof_for_contract(name-of-function)]`
249+
- Verified stubbing: `#[kani::stub_verified]`
250+
- Function contract specification: `#[kani::requires]`, `#[kani::modifies]`, `#[kani::ensures]`, `#[kani::recursion]`
251+
- Loop contract specification: `#[kani::loop_invariant]`, `#[kani::loop_modifies]`.
252+
253+
See the documentation on [function contracts](./experimental/contracts.md) and [loop contracts](./experimental/loop-contracts.md) for details.

docs/src/reference/experimental/autoharness.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ Note that because Kani prefixes function paths with the crate name, some pattern
7575
For example, given a function `foo_top_level` inside crate `my_crate`, the regex `.*::foo_.*` will match `foo_top_level`, since Kani interprets it as `my_crate::foo_top_level`.
7676
To match only `foo_` functions inside modules, use a more specific pattern, e.g. `.*::[^:]+::foo_.*`.
7777

78+
Autoharness also accepts a `--list` argument, which runs the [list subcommand](../list.md) including automatic harnesses.
79+
80+
For a full list of options, run `kani autoharness --help`.
81+
7882
## Example
7983
Using the `estimate_size` example from [First Steps](../../tutorial-first-steps.md) again:
8084
```rust

docs/src/reference/list.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# List Command
2+
3+
The `list` subcommand provides an overview of harnesses and contracts in the provided project/file. This is useful for understanding which parts of your codebase have verification coverage and tracking verification progress.
4+
5+
## Usage
6+
For basic usage, run `cargo kani list` or `kani list <FILE>`. The current options are:
7+
- `--format [pretty|markdown|json]`: Choose output format
8+
- `--std`: List harnesses and contracts in the standard library (standalone `kani` only)
9+
10+
The default format is `pretty`, which prints a table to the terminal, e.g:
11+
12+
```
13+
Kani Rust Verifier 0.65.0 (standalone)
14+
15+
Contracts:
16+
+-------+----------+-------------------------------+----------------------------------------------------------------+
17+
| | Crate | Function | Contract Harnesses (#[kani::proof_for_contract]) |
18+
+=================================================================================================================+
19+
| | my_crate | example::implementation::bar | example::verify::check_bar |
20+
|-------+----------+-------------------------------+----------------------------------------------------------------|
21+
| | my_crate | example::implementation::foo | example::verify::check_foo_u32, example::verify::check_foo_u64 |
22+
|-------+----------+-------------------------------+----------------------------------------------------------------|
23+
| | my_crate | example::implementation::func | example::verify::check_func |
24+
|-------+----------+-------------------------------+----------------------------------------------------------------|
25+
| | my_crate | example::prep::parse | NONE |
26+
|-------+----------+-------------------------------+----------------------------------------------------------------|
27+
| Total | | 4 | 4 |
28+
+-------+----------+-------------------------------+----------------------------------------------------------------+
29+
30+
Standard Harnesses (#[kani::proof]):
31+
+-------+----------+-------------------------------+
32+
| | Crate | Harness |
33+
+==================================================+
34+
| | my_crate | example::verify::check_modify |
35+
|-------+----------+-------------------------------|
36+
| | my_crate | example::verify::check_new |
37+
|-------+----------+-------------------------------|
38+
| Total | | 2 |
39+
+-------+----------+-------------------------------+
40+
```
41+
42+
The "Contracts" table shows functions that have contract attributes (`#[requires]`, `#[ensures]`, or `modifies`), and which harnesses exist for those functions.
43+
The "Standard Harnesses" table lists all of the `#[kani::proof]` harnesses found.
44+
45+
The `markdown` and `json` options write the same information to Markdown or JSON files, respectively.
46+
47+
For `--std`, ensure that the provided path points to a local copy of the standard library, e.g. `kani list --std rust/library`. (Compiling the standard library [works differently](https://doc.rust-lang.org/cargo/reference/unstable.html#build-std) than compiling a normal Rust project, hence the separate option).
48+
49+
For a full list of options, run `kani list --help`.
50+
51+
## Autoharness
52+
Note that by default, this subcommand only detects manual harnesses. The [experimental autoharness feature](./experimental/autoharness.md) accepts a `--list` argument, which runs this subcommand for both manual harnesses and automatically generated harnesses.

docs/src/regression-testing.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ We've extended
6868
Rust compiler testing framework) to work with these suites. That way, we take
6969
advantage of all `compiletest` features (e.g., parallel execution).
7070

71+
A good rule of thumb is to put a test in the `kani` or `cargo-kani` folders if all we care about is codegen and verification succeeding. This is usually the case if we're fixing a code pattern that previously crashed, so a successful verification result is a sufficient test. Otherwise, if the output refers to UI, e.g., a compiler error with a highlighted span, put it in the `ui` or `cargo-ui` suites. If the output is for a CBMC-based property that we expect Kani to check (e.g., ensuring that a particular assertion passes or fails), use the `expected` folder.
72+
7173
### Testing stages
7274

7375
The process of running single-file tests is split into three stages:
@@ -83,23 +85,15 @@ If a test fails, the error message will include the stage where it failed:
8385
error: test failed: expected check success, got failure
8486
```
8587

86-
When working on a test that's expected to fail, there are two options to
87-
indicate an expected failure. The first one is to add a comment
88+
Some tests have the following, historical syntax:
8889

8990
```rust
9091
// kani-<stage>-fail
9192
```
9293
at the top of the test file, where `<stage>` is the stage where the test is
9394
expected to fail.
9495

95-
The other option is to use the predicate `kani::expect_fail(cond, message)`
96-
included in the Kani library. The `cond` in `kani::expect_fail` is a condition
97-
that you expect not to hold during verification. The testing framework expects
98-
one `EXPECTED FAIL` message in the verification output for each use of the
99-
predicate.
100-
101-
> **NOTE**: `kani::expect_fail` is only useful to indicate failure in the
102-
> `verify` stage, errors in other stages will be considered testing failures.
96+
We retain support for this test format, but prefer `expected` tests, since they more precisely indicate which failure we expect, rather than a general verification or codegen failure.
10397

10498
### Testing options
10599

docs/src/rust-feature-support.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,8 @@ stack unwinding support.
176176

177177
Reading uninitialized memory is
178178
[considered undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#behavior-considered-undefined) in Rust.
179-
At the moment, Kani cannot detect if memory is uninitialized, but in practice
180-
this is mitigated by the fact that all memory is initialized with
181-
nondeterministic values.
182-
Therefore, any code that depends on uninitialized data will exhibit nondeterministic behavior.
183-
See [this issue](https://github.com/model-checking/kani/issues/920) for more details.
179+
Kani has partial, experimental support for detecting access to uninitialized memory with the `-Z uninit-checks` option.
180+
See [this issue](https://github.com/model-checking/kani/issues/3300) for more details.
184181

185182
### Destructors
186183

0 commit comments

Comments
 (0)