Skip to content

Conversation

thanhnguyen-aws
Copy link
Contributor

This PR fixes the bug of loop contracts are not composable with function contracts.
Previously, using loop-contract inside a function with contract may result in unwinding the loop instead of contracting it.

Resolves #3910

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@thanhnguyen-aws thanhnguyen-aws requested a review from a team as a code owner April 2, 2025 21:34
@github-actions github-actions bot added the Z-EndToEndBenchCI Tag a PR to run benchmark CI label Apr 2, 2025
Copy link
Contributor

@carolynzech carolynzech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this! Can you also update our loop contracts documentation in our book to talk about this change, and more generally, explain when to use #[kani::proof] vs. #[kani::proof_for_contract]? If I just have a loop contract without a function contract, can I use #[kani::proof_for_contract]?

(I haven't reviewed the code, I'm leaving that to @qinheping).

@thanhnguyen-aws
Copy link
Contributor Author

Thanks for fixing this! Can you also update our loop contracts documentation in our book to talk about this change, and more generally, explain when to use #[kani::proof] vs. #[kani::proof_for_contract]? If I just have a loop contract without a function contract, can I use #[kani::proof_for_contract]?

(I haven't reviewed the code, I'm leaving that to @qinheping).

Using #[kani::proof] or #[kani::proof_for_contract] is irrelevant to the loop or its contract, so we just use it as normal. I added the tests just to make sure there is no bug in both cases.

@carolynzech
Copy link
Contributor

Using #[kani::proof] or #[kani::proof_for_contract] is irrelevant to the loop or its contract, so we just use it as normal. I added the tests just to make sure there is no bug in both cases.

Thanks for the test, but please also update the book documentation so that our users know how to use this feature. I find it confusing that the annotation is irrelevant--why would we have both be acceptable if they do the same thing? Can you do some more thinking about the UX here before we merge this? We want to make sure that our users are set up to use this correctly.

@thanhnguyen-aws
Copy link
Contributor Author

thanhnguyen-aws commented Apr 3, 2025

Using #[kani::proof] or #[kani::proof_for_contract] is irrelevant to the loop or its contract, so we just use it as normal. I added the tests just to make sure there is no bug in both cases.

Thanks for the test, but please also update the book documentation so that our users know how to use this feature. I find it confusing that the annotation is irrelevant--why would we have both be acceptable if they do the same thing? Can you do some more thinking about the UX here before we merge this? We want to make sure that our users are set up to use this correctly.

Let me explain it more clearly. This PR does not add any new feature, it just fixed the bug that when a function has contract, then the loop inside it will be unwinded instead of contracted even if there is a loop invariant and kani flag -Z loop-contracts is enabled. There is no change in the definitions of #[kani::proof] or #[kani::proof_for_contract] whether there are loops/loop-invariants inside the function or not, and the two harnesses are different. As you can see the verification results (in two expected files) are different, #[kani::proof_for_contract] succeed because it assumes the requires clause, while #[kani::proof] failed because it asserts (not assumes) the requires clause. Therefore, I think that the users need no additional knowledge in this case.

@carolynzech
Copy link
Contributor

As you can see the verification results (in two expected files) are different, #[kani::proof_for_contract] succeed because it assumes the requires clause, while #[kani::proof] failed because it asserts (not assumes) the requires clause.

That makes sense.

There is no change in the definitions of #[kani::proof] or #[kani::proof_for_contract] whether there are loops/loop-invariants inside the function or not, and the two harnesses are different.

I'm not sure that's true. This PR changes the behavior of Kani such that you can use #[kani::proof_for_contract] on code with loop invariants. This would change the meaning of #[kani::proof_for_contract]--can we use it to prove loop invariants now, whereas before we could just use #[kani::proof]? Can I use #[kani::proof_for_contract] if we just have a loop contract, and not function contracts? If not, then it's confusing that we call it "loop contract" and we can't use a #[kani::proof_for_contract] to prove it, and we should make that clear in our documentation.

Therefore, I think that the users need no additional knowledge in this case.

I think our documentation does need some kind of example about composing loop contracts with function contracts, since there's nothing in our book to suggest that this is even possible (all of our loop contract examples use #[kani::proof]).

What I really want is documentation that tells users which kind of harness they should use for each case:

  • If my function just has a loop contract, should I use #[kani::proof] or #[kani::proof_for_contract]?
  • If my function has both a loop contract and a function contract, should I use #[kani::proof] or #[kani::proof_for_contract]?
  • How do the answers to the above questions change if I pass --no-assert-contracts?

@thanhnguyen-aws
Copy link
Contributor Author

This PR changes the behavior of Kani such that you can use #[kani::proof_for_contract] on code with loop invariants. This would change the meaning of #[kani::proof_for_contract]--can we use it to prove loop invariants now, whereas before we could just use #[kani::proof]? Can I use #[kani::proof_for_contract] if we just have a loop contract, and not function contracts?

This PR does not change the behavior of Kani for #[kani::proof_for_contract(funcname)]: even with loop invariants, Kani will throw an error if the function does not have contract. I think what is confusing here is the name #[kani::proof_for_contract (function name)]: contract here means function contract, not loop contract. We do not have a harness attribute to enable checking loop-contract, the only way is using -Z loop-contract flag.

If not, then it's confusing that we call it "loop contract" and we can't use a #[kani::proof_for_contract] to prove it, and we should make that clear in our documentation.

I strongly agree that the easiest way to get rid of the confusion is removing the name "loop contract" and consistently calling it "loop-invariant". If everyone else agree, we can create a separate PR to change the flag name and all related tests.

@carolynzech
Copy link
Contributor

carolynzech commented Apr 7, 2025

I strongly agree that the easiest way to get rid of the confusion is removing the name "loop contract" and consistently calling it "loop-invariant". If everyone else agree, we can create a separate PR to change the flag name and all related tests.

I strongly agree that this is the right move. Since this is a breaking change, maybe bring it up at staff meeting before making a PR. Also cc @qinheping.

This PR does not change the behavior of Kani for #[kani::proof_for_contract(funcname)]

It does change the behavior--you can now use #[kani::proof_for_contract(funcname)] to prove and use a loop invariant, whereas before you couldn't. Your point is well-taken that you also need a function contract for the proof to go through, but the fact remains that before you couldn't use loop invariants at all with #[kani::proof_for_contract(funcname)], and now you can. I think we should update our function contracts documentation in the book to reflect this change.

Copy link
Contributor

@carolynzech carolynzech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add this test?

// -Z loop-contracts -Z function-contracts --no-assert-contracts

#![feature(proc_macro_hygiene)]
#![feature(stmt_expr_attributes)]

#[kani::requires(i>=2)]
#[kani::ensures(|ret| *ret == 2)]
pub fn has_loop(mut i: u16) -> u16 {
    #[kani::loop_invariant(i>=2)]
    while i > 2 {
        i = i - 1
    }
    i
}

#[kani::proof]
fn contract_proof() {
    let i: u16 = kani::any();
    let j = has_loop(i);
}

This proof should let you just prove the loop invariant while ignoring the contract.

@thanhnguyen-aws
Copy link
Contributor Author

Can you also add this test?

// -Z loop-contracts -Z function-contracts --no-assert-contracts

#![feature(proc_macro_hygiene)]
#![feature(stmt_expr_attributes)]

#[kani::requires(i>=2)]
#[kani::ensures(|ret| *ret == 2)]
pub fn has_loop(mut i: u16) -> u16 {
    #[kani::loop_invariant(i>=2)]
    while i > 2 {
        i = i - 1
    }
    i
}

#[kani::proof]
fn contract_proof() {
    let i: u16 = kani::any();
    let j = has_loop(i);
}

This proof should let you just prove the loop invariant while ignoring the contract.

I added it

Copy link
Contributor

@qinheping qinheping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

Copy link
Contributor

@carolynzech carolynzech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@thanhnguyen-aws thanhnguyen-aws added this pull request to the merge queue May 9, 2025
Merged via the queue into model-checking:main with commit 670d1d1 May 9, 2025
25 of 26 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Jun 9, 2025
These are the automatically-generated release notes:
```
## What's Changed
* Toolchain upgrade to nightly-2025-05-04 by @thanhnguyen-aws in #4059
* Automatic toolchain upgrade to nightly-2025-05-05 by @github-actions in #4060
* Automatic toolchain upgrade to nightly-2025-05-06 by @github-actions in #4061
* Enable target features: x87 and sse2 by @thanhnguyen-aws in #4062
* Fix the bug: Loop contracts are not composable with function contracts  by @thanhnguyen-aws in #3979
* Automatic cargo update to 2025-05-12 by @github-actions in #4066
* Bump tests/perf/s2n-quic from `6aa9975` to `5f323b7` by @dependabot in #4068
* Fix stabilization instructions in RFC intro by @carolynzech in #4067
* Add support for quantifiers by @qinheping in #3993
* Toolchain upgrade to nightly-2025-05-07 by @thanhnguyen-aws in #4070
* Automatic toolchain upgrade to nightly-2025-05-08 by @github-actions in #4071
* Automatic toolchain upgrade to nightly-2025-05-09 by @github-actions in #4072
* Automatic toolchain upgrade to nightly-2025-05-10 by @github-actions in #4073
* Clippy/Stylistic Fixes by @carolynzech in #4074
* Upgrade toolchain to 2025-05-14 by @zhassan-aws in #4076
* Autoharness argument validation: only error on `--quiet` if `--list` was passed by @carolynzech in #4069
* Upgrade Rust toolchain to 2025-05-16 by @zhassan-aws in #4080
* Automatic toolchain upgrade to nightly-2025-05-17 by @github-actions in #4081
* Add setup scripts for Ubuntu 20.04 by @zhassan-aws in #4082
* Automatic toolchain upgrade to nightly-2025-05-18 by @github-actions in #4083
* Automatic cargo update to 2025-05-19 by @github-actions in #4086
* Automatic toolchain upgrade to nightly-2025-05-19 by @github-actions in #4085
* Automatic toolchain upgrade to nightly-2025-05-20 by @github-actions in #4091
* Bump tests/perf/s2n-quic from `5f323b7` to `22434aa` by @dependabot in #4089
* Fix the error that Kani panics when there is no external parameter in quantifier's closure. by @thanhnguyen-aws in #4088
* Update toolchain to 2025-05-22 by @carolynzech in #4098
* Use our toolchain when invoking `cargo metadata` by @carolynzech in #4090
* Automatic toolchain upgrade to nightly-2025-05-23 by @github-actions in #4099
* Automatic toolchain upgrade to nightly-2025-05-24 by @github-actions in #4101
* Automatic toolchain upgrade to nightly-2025-05-25 by @github-actions in #4102
* Fix a bug codegening `SwitchInt`s with only an otherwise branch by @bkirwi in #4095
* Automatic toolchain upgrade to nightly-2025-05-26 by @github-actions in #4104
* Automatic cargo update to 2025-05-26 by @github-actions in #4105
* Bump tests/perf/s2n-quic from `22434aa` to `550afb3` by @dependabot in #4106
* Automatic toolchain upgrade to nightly-2025-05-27 by @github-actions in #4107
* Update `kani::mem` pointer validity documentation by @carolynzech in #4092
* Add support for edition 2018 crates using assert! (Fixes #3717) by @sintemal in #4096
* Automatic toolchain upgrade to nightly-2025-05-28 by @github-actions in #4113
* Automatic toolchain upgrade to nightly-2025-05-29 by @github-actions in #4115
* Automatic toolchain upgrade to nightly-2025-05-30 by @github-actions in #4118
* Handle generic defaults in BoundedArbitrary derives by @zhassan-aws in #4117
* Automatic cargo update to 2025-06-02 by @github-actions in #4121
* Bump tests/perf/s2n-quic from `550afb3` to `8f54b57` by @dependabot in #4122
* Upgrade Rust toolchain to 2025-06-02 by @zhassan-aws in #4123
* Automatic toolchain upgrade to nightly-2025-06-03 by @github-actions in #4125
* Finish deprecating `--enable-unstable`, `--restrict-vtable`, and `--write-json-symtab` by @carolynzech in #4110
* `ty_mangled_name`: only use non-mangled name if `-Zcffi` is enabled. by @carolynzech in #4114
* Improve Help Menu by @carolynzech in #4109
* Start stabilizing `--jobs` and `list`; deprecate default memory checks by @carolynzech in #4108
* Refactor simd_bitmask to reduce the number of iterations by @zhassan-aws in #4129
* Set target features depending on the target architecture by @zhassan-aws in #4127
* Bump some versions suggested by cargo-outdated by @zhassan-aws in #4131
* Improve linking error output for `#[no_std]` crates by @AlexanderPortland in #4126
* Fix the git log command in the toolchain update script by @zhassan-aws in #4139
* Gate quantifiers behind an experimental feature by @thanhnguyen-aws in #4141
* Automatic cargo update to 2025-06-09 by @github-actions in #4145

## New Contributors
* @bkirwi made their first contribution in #4095
* @sintemal made their first contribution in #4096
* @AlexanderPortland made their first contribution in #4126

**Full Changelog**: kani-0.62.0...kani-0.63.0
```


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 <carolynzech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Z-EndToEndBenchCI Tag a PR to run benchmark CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Loop contracts are not composable with function contracts
3 participants