Skip to content

Commit

Permalink
make asserting contracts the default
Browse files Browse the repository at this point in the history
  • Loading branch information
carolynzech committed Jan 7, 2025
1 parent d3fdcad commit 8cabda4
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 23 deletions.
3 changes: 3 additions & 0 deletions kani-compiler/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub enum ReachabilityType {
/// with. Usually stored in and accessible via [`crate::kani_queries::QueryDb`].
#[derive(Debug, Default, Clone, clap::Parser)]
pub struct Arguments {
/// Option used to enable asserting function contracts.
#[clap(long)]
pub no_assert_contracts: bool,
/// Option name used to enable assertion reachability checks.
#[clap(long = "assertion-reach-checks")]
pub check_assertion_reachability: bool,
Expand Down
11 changes: 4 additions & 7 deletions kani-compiler/src/kani_middle/transform/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ pub struct FunctionWithContractPass {
check_fn: Option<InternalDefId>,
/// Functions that should be stubbed by their contract.
replace_fns: HashSet<InternalDefId>,
/// Should we interpret contracts as assertions? (true iff the contracts-as-assertions option is passed)
are_contracts_asserted: bool,
/// Should we interpret contracts as assertions? (true iff the no-assert-contracts option is not passed)
assert_contracts: bool,
/// Functions annotated with contract attributes will contain contract closures even if they
/// are not to be used in this harness.
/// In order to avoid bringing unnecessary logic, we clear their body.
Expand Down Expand Up @@ -354,10 +354,7 @@ impl FunctionWithContractPass {
FunctionWithContractPass {
check_fn,
replace_fns,
are_contracts_asserted: queries
.args()
.unstable_features
.contains(&"contracts-as-assertions".to_string()),
assert_contracts: !queries.args().no_assert_contracts,
unused_closures: Default::default(),
run_contract_fn,
}
Expand Down Expand Up @@ -461,7 +458,7 @@ impl FunctionWithContractPass {
}
} else if self.replace_fns.contains(&fn_def_id) {
ContractMode::Replace
} else if self.are_contracts_asserted {
} else if self.assert_contracts {
ContractMode::Assert
} else {
ContractMode::Original
Expand Down
22 changes: 22 additions & 0 deletions kani-driver/src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ pub struct VerificationArgs {
)]
pub synthesize_loop_contracts: bool,

/// Do not assert the function contracts of dependencies. Requires -Z function-contracts.
#[arg(long)]
pub no_assert_contracts: bool,

//Harness Output into individual files
#[arg(long, hide_short_help = true)]
pub output_into_files: bool,
Expand Down Expand Up @@ -702,6 +706,17 @@ impl ValidateArgs for VerificationArgs {
),
));
}

if !self.is_function_contracts_enabled() && self.no_assert_contracts {
return Err(Error::raw(
ErrorKind::MissingRequiredArgument,
format!(
"The `--no-assert-contracts` option requires `-Z {}`.",
UnstableFeature::FunctionContracts
),
));
}

Ok(())
}
}
Expand Down Expand Up @@ -1000,4 +1015,11 @@ mod tests {
let err = StandaloneArgs::try_parse_from(args).unwrap().validate().unwrap_err();
assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
}

#[test]
fn check_no_assert_contracts() {
let args = "kani input.rs --no-assert-contracts".split_whitespace();
let err = StandaloneArgs::try_parse_from(args).unwrap().validate().unwrap_err();
assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
}
}
3 changes: 3 additions & 0 deletions kani-driver/src/call_cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ crate-type = ["lib"]
if let Some(backend_arg) = self.backend_arg() {
pkg_args.push(backend_arg);
}
if self.args.no_assert_contracts {
pkg_args.push("--no-assert-contracts".into());
}

let mut found_target = false;
let packages = self.packages_to_verify(&self.args, &metadata)?;
Expand Down
6 changes: 5 additions & 1 deletion kani-driver/src/call_single_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl KaniSession {
Ok(())
}

/// Create a compiler option that represents the reachability mod.
/// Create a compiler option that represents the reachability mode.
pub fn reachability_arg(&self) -> String {
to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode())])
}
Expand Down Expand Up @@ -152,6 +152,10 @@ impl KaniSession {
flags.push("--print-llbc".into());
}

if self.args.no_assert_contracts {
flags.push("--no-assert-contracts".into());
}

flags.extend(self.args.common_args.unstable_features.as_arguments().map(str::to_string));

flags
Expand Down
2 changes: 0 additions & 2 deletions kani_metadata/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ pub enum UnstableFeature {
FunctionContracts,
/// Enable loop contracts [RFC 12](https://model-checking.github.io/kani/rfc/rfcs/0012-loop-contracts.html)
LoopContracts,
/// Interpret function contracts as assertions. (Requires function-contracts option).
ContractsAsAssertions,
/// Memory predicate APIs.
MemPredicates,
/// Automatically check that no invalid value is produced which is considered UB in Rust.
Expand Down
9 changes: 5 additions & 4 deletions library/kani_macros/src/sysroot/contracts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@
//! We register this closure as `#[kanitool::recursion_check = "__kani_recursion_..."]`.
//!
//! ## Assert closure
//! Kani has an option to interpret contracts as assertions.
//! To support that option, we generate a closure that asserts preconditions and postconditions.
//! This option is especially useful for verifying that a function does not violate the contracts of its dependencies.
//! By default, if we are not checking the function's contract or stubbing it,
//! (i.e., if we are not using the check or replace closures),
//! we use its assert closure, which asserts both preconditions and postconditions.
//! This behavior is useful for verifying that a function does not violate the contracts of its dependencies.
//! For example:
//! ```ignore
//! #[kani::requires(x >= 0)]
Expand All @@ -130,7 +131,7 @@
//! #[kani::requires(x > 0)]
//! fn bar(x: i32) { }
//! ```
//! If we call foo(0), we would satisfy the check closure, since we satisfy foo's precondition.
//! If we call foo(0), we would satisfy foo's check closure, since we satisfy foo's precondition.
//! However, we would violate bar's precondition that x > 0.
//! By using bar's assert closure instead of its original body, we can assert that callers of bar respect its contract
//! and catch this issue.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts -Zcontracts-as-assertions
// kani-flags: -Zfunction-contracts

// Test -Zcontracts-as-assertions for postconditions.
// Test -Zfunction-contracts for asserting postconditions.

#[kani::requires(*add_three_ptr < 100)]
#[kani::modifies(add_three_ptr)]
Expand All @@ -22,7 +22,6 @@ fn add_one(add_one_ptr: &mut u32) {
*add_one_ptr += 1;
}

// -Zcontracts-as-assertions introduces this failure; without it, add_two's and add_one's contracts are ignored.
#[kani::proof_for_contract(add_three)]
fn prove_add_three() {
let mut i = kani::any();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts -Zcontracts-as-assertions
// kani-flags: -Zfunction-contracts

// Test -Zcontracts-as-assertions for preconditions.
// Test -Zfunction-contracts for asserting preconditions.

#[kani::requires(*ptr < 100)]
#[kani::ensures(|result| old(*ptr + 3) == *ptr)]
Expand Down
4 changes: 2 additions & 2 deletions tests/expected/function-contract/as-assertions/loops.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts -Zcontracts-as-assertions
// kani-flags: -Zfunction-contracts

// Check that the -Zcontracts-as-assertions option asserts preconditions and postconditions correctly
// Check that the -Zfunction-contracts option asserts preconditions and postconditions correctly
// when the body of the function has a loop.
// This code is taken from function-contracts/gcd_success.rs

Expand Down
3 changes: 1 addition & 2 deletions tests/expected/function-contract/as-assertions/precedence.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts -Zcontracts-as-assertions
// kani-flags: -Zfunction-contracts

// If a function is the target of a proof_for_contract or stub_verified, we should defer to the contract handling for those modes.
// i.e., test that -Zcontracts-as-assertions does not override the contract handling for proof_for_contract and stub_verified.

#[kani::modifies(add_three_ptr)]
#[kani::requires(*add_three_ptr < 100)]
Expand Down
30 changes: 30 additions & 0 deletions tests/kani/FunctionContracts/no-assert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts --no-assert-contracts

// Check that the -no-assert-contracts option disables the default behavior of asserting contracts of dependencies.

#[kani::requires(*add_three_ptr < 100)]
#[kani::modifies(add_three_ptr)]
fn add_three(add_three_ptr: &mut u32) {
*add_three_ptr += 1;
add_two(add_three_ptr);
}

#[kani::ensures(|_| old(*add_two_ptr + 1) == *add_two_ptr)] // incorrect -- should be old(*add_two_ptr + 2)
fn add_two(add_two_ptr: &mut u32) {
*add_two_ptr += 1;
add_one(add_two_ptr)
}

#[kani::ensures(|_| old(*add_one_ptr + 1) == *add_one_ptr)] // correct -- assertion should always succeed
fn add_one(add_one_ptr: &mut u32) {
*add_one_ptr += 1;
}

// With --no-assert-contracts, add_two's and add_one's contracts are ignored, so verification should succeed.
#[kani::proof_for_contract(add_three)]
fn prove_add_three() {
let mut i = kani::any();
add_three(&mut i);
}

0 comments on commit 8cabda4

Please sign in to comment.