Skip to content

Commit c048971

Browse files
Do not assert dependency contracts for calls made by contract clauses
Contracts of dependencies are asserted by default (#3802) as an aid for detecting API misuse in user code. Calls made while evaluating *contract clauses*, however, are specification-level plumbing: clause expressions compute a predicate over pre-/post-states, and the functions they call are best executed with their exact semantics. Re-asserting dependency contracts inside every clause evaluation multiplies verification cost - clauses of contract-dense code (e.g. the Rust standard library in model-checking/verify-rust-std) routinely call contracted functions like NonNull::as_ptr, paying the assert-closure overhead per clause instance - without checking any user code. Extend the clause-context dispatch introduced for check modes to assert mode: calls to a contracted dependency that occur during clause evaluation now execute the original body (mode 0). The body remains fully inlined and UB-checked; only the requires/ensures assertions of the *callee's* contract are skipped in this context. The new regression test checks both halves: a clause calling a contracted function with precondition-violating (but well-defined) arguments verifies successfully, while the same misuse in user code is still caught. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent fe4eef9 commit c048971

3 files changed

Lines changed: 68 additions & 14 deletions

File tree

kani-compiler/src/kani_middle/transform/contracts.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -453,20 +453,35 @@ impl FunctionWithContractPass {
453453

454454
let span = mode_call.span(new_body.blocks());
455455
let mode_const = new_body.new_uint_operand(mode as _, UintTy::U8, span);
456-
if matches!(mode, ContractMode::SimpleCheck | ContractMode::RecursiveCheck) {
457-
// While the harness is checking the contract of this function,
458-
// the function may also be called from *contract clauses* of
459-
// other functions in the harness's call graph (e.g. a
460-
// postcondition mentioning `NonNull::as_ptr` evaluated while
461-
// `as_ptr` itself is under verification). Such calls must not be
462-
// dispatched to the check closure: they would consume the single
463-
// top-level contract check and run write-set instrumentation in
464-
// the clause's context. Dispatch them to the original body
465-
// instead (exact semantics; unlike dispatching to the contract
466-
// replacement this does not require the return type to implement
467-
// Arbitrary), by computing the mode at runtime as
468-
// `mode * (1 - in_contract_clause())`, which yields
469-
// `ORIGINAL` (0) during clause evaluation and `mode` otherwise.
456+
if matches!(
457+
mode,
458+
ContractMode::SimpleCheck | ContractMode::RecursiveCheck | ContractMode::Assert
459+
) {
460+
// Calls occurring during the evaluation of *contract clauses* of
461+
// other functions are dispatched to the original body (mode 0,
462+
// exact semantics) rather than the mode selected for normal
463+
// calls, by computing the mode at runtime as
464+
// `mode * (1 - in_contract_clause())`:
465+
//
466+
// * For check modes: while the harness is checking the contract
467+
// of this function, the function may also be called from
468+
// contract clauses of other functions in the harness's call
469+
// graph (e.g. a postcondition mentioning `NonNull::as_ptr`
470+
// evaluated while `as_ptr` itself is under verification). Such
471+
// calls must not be dispatched to the check closure: they
472+
// would consume the single top-level contract check and run
473+
// write-set instrumentation in the clause's context. (Unlike
474+
// dispatching to the contract replacement, the original body
475+
// does not require the return type to implement Arbitrary.)
476+
//
477+
// * For assert mode: asserting the contracts of dependencies
478+
// (the default since #3802) is an aid for detecting API misuse
479+
// in user code; re-asserting them for calls made by *contract
480+
// clauses* checks specification-level plumbing at a
481+
// multiplicative cost. Clause evaluation is meant to compute a
482+
// predicate over the pre-/post-states, and the functions it
483+
// calls are best executed with their exact semantics (their
484+
// bodies remain fully inlined and UB-checked either way).
470485
let in_clause_instance =
471486
Instance::resolve(self.in_clause_fn.unwrap(), &GenericArgs(vec![])).unwrap();
472487
let in_clause_local = new_body.new_local(Ty::bool_ty(), span, Mutability::Mut);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Failed Checks: x >= 10
2+
3+
Verification failed for - check_misuse_still_caught
4+
Complete - 1 successfully verified harnesses, 1 failures, 2 total.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zfunction-contracts
4+
5+
//! Contracts of dependencies are asserted by default (#3802) as an aid for
6+
//! detecting API misuse in user code. Calls made while evaluating *contract
7+
//! clauses*, however, execute the original body (exact semantics, still fully
8+
//! UB-checked) without re-asserting the callee's contract: clause expressions
9+
//! are specifications, and asserting specification-level plumbing multiplies
10+
//! verification cost without checking user code.
11+
//!
12+
//! `one`'s postcondition below calls `plus_one(0)`, which violates
13+
//! `plus_one`'s (overly strict) precondition but is well-defined: the clause
14+
//! must evaluate to true without a contract-assertion failure. The same
15+
//! misuse in *user code* (`check_misuse`) must still be caught.
16+
17+
#[kani::requires(x >= 10)]
18+
fn plus_one(x: u8) -> u8 {
19+
x.wrapping_add(1)
20+
}
21+
22+
#[kani::ensures(|result| *result == plus_one(0))]
23+
fn one() -> u8 {
24+
1
25+
}
26+
27+
#[kani::proof]
28+
fn check_clause_call_not_asserted() {
29+
let _ = one();
30+
}
31+
32+
#[kani::proof]
33+
fn check_misuse_still_caught() {
34+
let _ = plus_one(0);
35+
}

0 commit comments

Comments
 (0)