Skip to content

Commit a7d917d

Browse files
thanhnguyen-awsCarolyn Zechcarolynzechtautschnigzhassan-aws
authored
Add loop-contracts support for for loop (#4143)
This PR adds loop-contracts support for `for` loop for 12 types: array, slice, Iter, Vec, Range<Integer>, StepBy, Map, Chain, Zip, Enumerate, Take, Rev. Main ideas: `for` loop implementation: A `for` loop of the form ``` rust for pat in expr { body } ``` is transformed into a `while` loop: ``` rust let kani_iter = kani::KaniIntoIter::kani_into_iter(expr); \\ init_iter_stmt let kani_iter_len = kani::KaniIter::len(&kani_iter); \\ init_len_stmt if kaniiterlen > 0 { let mut kani_index = 0; \\ init_index_stmt kani::assume (kani::KaniIter::assumption(&kani_iter)); \\ iter_assume_stmt let pat = kani::KaniIter::first(&kani_iter); \\ init_pat_stmt while kani_index < kani_iter_len { kani::assume (kani::KaniIter::assumption(&kani_iter)); \\ iter_assume_stmt let pat = kani::KaniIter::nth(&kani_iter, kani_index); \\ pat_assign_stmt kani_index = kani_index + 1; \\ increase_iter_stmt body } } ``` Note that 1. expr's type is required to `impl KaniIntoIter` trait, which is the override `impl` of Rust's `IntoIter` (see library/kani_core/src/iter.rs). Reason: a) Reduce stack-call b) Avoid types that cannot be havoc effectively (user cannot state the type invariant in the loop invariant due to private fields) c) There is no generic way to handle Rust's `into_iter()`. 2. The `init_index_stmt` and `init_pat_stmt` support writing the loop-invariant properties that involve pat and kaniindex 3. The `iter_assume_stmt` assumes some truths about allocation, so that the user does not need to specify them in the loop invariant 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: Carolyn Zech <[email protected]> Co-authored-by: Carolyn Zech <[email protected]> Co-authored-by: Michael Tautschnig <[email protected]> Co-authored-by: Zyad Hassan <[email protected]>
1 parent c29b906 commit a7d917d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2038
-99
lines changed

docs/src/reference/experimental/loop-contracts.md

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Loop Contracts
22

3-
Loop contract are used to specify invariants for loops for the sake of extending Kani's *bounded proofs* to *unbounded proofs*.
3+
## Introduction
4+
5+
Loop contracts are used to specify invariants for loops for the sake of extending Kani's *bounded proofs* to *unbounded proofs*.
46
A [loop invariant](https://en.wikipedia.org/wiki/Loop_invariant) is an expression that holds upon entering a loop and after every execution of the loop body.
7+
Loop contracts are composed of one or more loop invariants as well as optional `loop_modifies` attributes.
58
It captures something that does not change about every step of the loop.
69

710
It is worth revisiting the discussion about [bounded proof](../../tutorial-loop-unwinding.md#bounded-proof) and
@@ -24,7 +27,7 @@ fn simple_loop() {
2427
```
2528

2629
In this program, the loop repeatedly decrements `x` until it equals `1`. Because we haven't specified an upper bound for `x`, to verify this function,
27-
Kani needs to unwind the loop for `u64::MAX` iterations, which is computationally expensive. Loop contracts allow us to abstract the loop behavior, significantly reducing the verification cost.
30+
Kani needs to unwind the loop for `u64::MAX` iterations, which is intractable. Loop contracts allow us to abstract the loop behavior, significantly reducing the verification cost.
2831

2932
With loop contracts, we can specify the loop’s behavior using invariants. For example:
3033

@@ -61,7 +64,7 @@ Check 10: simple_loop_with_loop_contracts.loop_invariant_base.1
6164
- Description: "Check invariant before entry for loop simple_loop_with_loop_contracts.0"
6265
- Location: simple_while_loop.rs:15:5 in function simple_loop_with_loop_contracts
6366
64-
Check 11: simple_loop_with_loop_contracts.loop_modifies.1
67+
Check 11: simple_loop_with_loop_contracts.loop_assigns.1
6568
- Status: SUCCESS
6669
- Description: "Check assigns clause inclusion for loop simple_loop_with_loop_contracts.0"
6770
- Location: simple_while_loop.rs:15:5 in function simple_loop_with_loop_contracts
@@ -93,19 +96,19 @@ Complete - 1 successfully verified harnesses, 0 failures, 1 total.
9396
```
9497

9598

96-
## Loop contracts for `while` loops
99+
## Syntax and Semantics
97100

98101
### Syntax
99102
>
100103
> \#\[kani::loop_invariant\( [_Expression_](https://doc.rust-lang.org/reference/expressions.html) \)\]
101104
>
102-
> `while` [_Expression_](https://doc.rust-lang.org/reference/expressions.html)<sub>_except struct expression_</sub> [_BlockExpression_](https://doc.rust-lang.org/reference/expressions/block-expr.html)
105+
> [_LoopExpression_](https://doc.rust-lang.org/reference/expressions/loop-expr.html#grammar-LoopExpression)
103106
104107

105108
An invariant contract `#[kani::loop_invariant(cond)]` accepts a valid Boolean expression `cond` over the variables visible at the same scope as the loop.
106109

107110
### Semantics
108-
A loop invariant contract expands to several assumptions and assertions:
111+
A loop contract expands to several assumptions and assertions:
109112
1. The invariant is asserted just before the first iteration.
110113
2. The invariant is assumed on a non-deterministic state to model a non-deterministic iteration.
111114
3. The invariant is finally asserted again to establish its inductiveness.
@@ -134,11 +137,57 @@ That is, we assume that we are in an arbitrary iteration after checking that the
134137
we will either enter the loop (proof path 1) or leave the loop (proof path 2). We prove the two paths separately by killing path 1 with `kani::assume(false);`.
135138
Note that all assertions after `kani::assume(false)` will be ignored as `false => p` can be deduced as `true` for any `p`.
136139

137-
In proof path 1, we prove properties inside the loop and at last check that the loop invariant is inductive.
140+
In proof path 1, we prove properties inside the loop and at last check that the loop contract is inductive.
138141

139142
In proof path 2, we prove properties after leaving the loop. As we leave the loop only when the loop guard is violated, the post condition of the loop can be expressed as
140143
`!guard && inv`, which is `x <= 1 && x >= 1` in the example. The postcondition implies `x == 1`—the property we want to prove at the end of `simple_loop_with_loop_contracts`.
141144

145+
## Historic values and extra variables
146+
147+
### Historic values
148+
149+
We support two notations for historic values in loop contracts:
150+
1. `on_entry(expr)` : The value of the `expr` before entering the loop.
151+
2. `prev(expr)` : the value of `expr` in the previous iteration. Note that Kani will assert that the loop has at least one iteration if `prev` is used in loop contracts.
152+
153+
Example:
154+
```Rust
155+
#[kani::proof]
156+
pub fn loop_with_old_and_prev() {
157+
let mut i = 100;
158+
#[kani::loop_invariant((i >= 2) && (i <= 100) && (i % 2 == 0) && (on_entry(i) == 100) && (prev(i) == i + 2))]
159+
while i > 2 {
160+
if i == 1 {
161+
break;
162+
}
163+
i = i - 2;
164+
}
165+
assert!(i == 2);
166+
}
167+
```
168+
169+
### `kani::index` variable in `for` loop
170+
171+
Kani provides an extra variable: `kani::index` that can be used in loop contracts of `for` loops.
172+
`kani::index` presents the position (index) of the current iteration in the iterator
173+
and is only associated with the `for` loop that immediately follows the loop contract.
174+
175+
Example:
176+
177+
```Rust
178+
#[kani::proof]
179+
fn forloop() {
180+
let mut sum: u32 = 0;
181+
let a: [u8; 10] = kani::any();
182+
kani::assume(kani::forall!(|i in (0,10)| a[i] <= 20));
183+
#[kani::loop_invariant(sum <= (kani::index as u32 * 20) )]
184+
for (i, j) in a.iter().enumerate() {
185+
sum = sum + (i as u32) ;
186+
}
187+
assert!(sum <= 200);
188+
}
189+
```
190+
142191
## Loop contracts inside functions with contracts
143192
Kani supports using loop contracts together with function contracts, as demonstrated in the following example:
144193
``` Rust
@@ -231,10 +280,10 @@ fn main() {
231280

232281
Loop contracts comes with the following limitations.
233282

234-
1. `while` loops and `loop` loops are supported. The other kinds of loops are not supported: [`while let` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops), and [`for` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops).
283+
1. `while` loops, `loop` loops are supported. `for` loops are supported for array, slice, Iter, Vec, Range, StepBy, Chain, Zip, Map, and Enumerate. The other kinds of loops are not supported: [`while let` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops).
235284
2. Kani infers *loop modifies* with alias analysis. Loop modifies are those variables we assume to be arbitrary in the inductive hypothesis, and should cover all memory locations that are written to during
236285
the execution of the loops. A proof will fail if the inferred loop modifies misses some targets written in the loops.
237286
We observed this happens when some fields of structs are modified by some other functions called in the loops.
238287
3. Kani doesn't check if a loop will always terminate in proofs with loop contracts. So it could be that some properties are proved successfully with Kani but actually are unreachable due to the
239288
non-termination of some loops.
240-
4. We don't check if loop invariants are side-effect free. A loop invariant with a side effect could lead to an unsound proof result. Make sure that the specified loop contracts are side-effect free.
289+
4. We don't check if loop contracts are side-effect free. A loop contract with a side effect could lead to an unsound proof result. Make sure that the specified loop contracts are side-effect free.

0 commit comments

Comments
 (0)