Commit 78eb465
authored
Add
Add `#[kani::loop_decreases]` for proving loop termination via Floyd's
method. Loop invariants alone prove partial correctness, they don't
guarantee the loop terminates. A decreases clause specifies a measure
that must strictly decrease on each iteration, proving total
correctness.
Maps to CBMC's `#spec_decreases` annotation via `goto-instrument`.
## Problem
Without a decreases clause, a loop invariant can verify properties on
code after the loop, but if the loop never terminates, that code is
unreachable and the property is vacuously true. This is a soundness gap
for total correctness proofs.
## Solution
### Proc macro (`kani_macros`)
- `#[kani::loop_decreases(expr)]` attribute generates a `let
kani_loop_decreases = (expr,);` binding before the loop
- Validates the attribute is applied to a loop statement (`while`,
`loop`, `for`)
- Rejects multi-dimensional measures (`loop_decreases(a, b)`) with a
compile error — lexicographic ordering is not yet supported (#4585)
### CBMC bindings (`cprover_bindings`)
- `loop_decreases: Option<Expr>` field in `StmtBody::Goto`
- `with_loop_decreases()` builder, mirroring `with_loop_invariants()`
and `with_loop_modifies()`
- `CSpecDecreases` irep serialization
### Compiler codegen (`kani-compiler`)
- Detects `kani_loop_decreases` assignments in MIR and stores the
expression
- Attaches the decreases expression to the loop's goto statement
- Emits a warning when `#[kani::loop_decreases]` is used without `-Z
loop-contracts`
## Example
```rust
#[kani::proof]
fn binary_search_terminates() {
let arr: [i32; 8] = kani::any();
let target: i32 = kani::any();
let mut lo: usize = 0;
let mut hi: usize = arr.len();
#[kani::loop_invariant(lo <= hi && hi <= arr.len())]
#[kani::loop_decreases(hi - lo)]
while lo < hi {
let mid = lo + (hi - lo) / 2;
if arr[mid] == target { break; }
else if arr[mid] < target { lo = mid + 1; }
else { hi = mid; }
}
}
```
## Testing
**8 passing expected tests:**
- `simple_while_loop_decreases` — basic countdown
- `loop_loop_decreases` — `loop { ... break }` with decreases
- `decreases_expr` — expression-based measure
- `decreases_binary_search` — binary search termination
- `decreases_loop_max` — function with loop + decreases
- `decreases_with_function_contract` — combined with
`#[kani::requires]`/`#[kani::ensures]`
- `decreases_with_old` — using `on_entry()` in decreases
- `decreases_no_flag` — warning emitted without `-Z loop-contracts`
**2 expected-fail tests:**
- `decreases_fail_wrong_measure` — incorrect measure (increasing instead
of decreasing)
- `decreases_fail_stale_measure` — stale measure (variable not modified
by loop)
**4 fixme tests** (known limitations, tracked as issues):
- `fixme_decreases_struct_field` — struct field projections (#4584)
- `fixme_multi_dim_decreases` — multi-dimensional measures (#4585)
- `fixme_decreases_with_modifies` — combined with `loop_modifies`
(#4586)
- `fixme_nested_loops_decreases` — nested loops with assigns conflict
(#4586)
## Known Limitations
The following limitations are tracked as separate issues:
- **Arithmetic expressions in decreases clauses** (e.g., `19 - p`) fail
due to Rust's checked arithmetic producing `StatementExpression` nodes.
Workaround: use `wrapping_sub`. (#4584)
- **Multi-dimensional decreases** (`#[kani::loop_decreases(a, b)]`) is
rejected at compile time; lexicographic ordering is not yet supported.
(#4585)
- **Combining `loop_decreases` with `loop_modifies`** causes spurious
verification failures. Workaround: use `loop_decreases` +
`loop_invariant` without `loop_modifies`. (#4586)
---
By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.
---------
Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>#[kani::loop_decreases] for proving loop termination (#4564)1 parent 78a0630 commit 78eb465
38 files changed
Lines changed: 842 additions & 11 deletions
File tree
- cprover_bindings/src
- goto_program
- irep
- docs/src/reference
- experimental
- kani-compiler/src/codegen_cprover_gotoc
- codegen
- context
- overrides
- library/kani_macros/src
- sysroot/loop_contracts
- tests/expected/loop-contract
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| |||
282 | 283 | | |
283 | 284 | | |
284 | 285 | | |
285 | | - | |
| 286 | + | |
286 | 287 | | |
287 | 288 | | |
288 | 289 | | |
| |||
327 | 328 | | |
328 | 329 | | |
329 | 330 | | |
330 | | - | |
| 331 | + | |
331 | 332 | | |
332 | 333 | | |
333 | 334 | | |
334 | 335 | | |
335 | 336 | | |
336 | | - | |
| 337 | + | |
| 338 | + | |
337 | 339 | | |
338 | 340 | | |
339 | 341 | | |
| |||
343 | 345 | | |
344 | 346 | | |
345 | 347 | | |
346 | | - | |
| 348 | + | |
347 | 349 | | |
348 | 350 | | |
349 | 351 | | |
350 | 352 | | |
351 | 353 | | |
352 | | - | |
| 354 | + | |
| 355 | + | |
353 | 356 | | |
354 | 357 | | |
355 | 358 | | |
356 | 359 | | |
357 | 360 | | |
358 | 361 | | |
359 | 362 | | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
360 | 380 | | |
361 | 381 | | |
362 | 382 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
595 | 595 | | |
596 | 596 | | |
597 | 597 | | |
| 598 | + | |
598 | 599 | | |
599 | 600 | | |
600 | 601 | | |
| |||
1485 | 1486 | | |
1486 | 1487 | | |
1487 | 1488 | | |
| 1489 | + | |
1488 | 1490 | | |
1489 | 1491 | | |
1490 | 1492 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
556 | 556 | | |
557 | 557 | | |
558 | 558 | | |
559 | | - | |
| 559 | + | |
560 | 560 | | |
561 | 561 | | |
562 | 562 | | |
| |||
565 | 565 | | |
566 | 566 | | |
567 | 567 | | |
| 568 | + | |
568 | 569 | | |
569 | 570 | | |
570 | 571 | | |
571 | 572 | | |
| 573 | + | |
572 | 574 | | |
573 | 575 | | |
574 | 576 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
248 | 248 | | |
249 | 249 | | |
250 | 250 | | |
251 | | - | |
| 251 | + | |
252 | 252 | | |
253 | 253 | | |
0 commit comments