Skip to content

Commit

Permalink
Merge branch 'main' into modifies_fat
Browse files Browse the repository at this point in the history
  • Loading branch information
Matias Scharager committed Jul 1, 2024
2 parents 2e497f1 + 6f0c0b5 commit 11f8a5b
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 20 deletions.
32 changes: 16 additions & 16 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"

[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"

[[package]]
name = "build-kani"
Expand Down Expand Up @@ -140,19 +140,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "clap"
version = "4.5.7"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
dependencies = [
"clap_builder",
"clap_derive",
]

[[package]]
name = "clap_builder"
version = "4.5.7"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
dependencies = [
"anstream",
"anstyle",
Expand All @@ -162,9 +162,9 @@ dependencies = [

[[package]]
name = "clap_derive"
version = "4.5.5"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck",
"proc-macro2",
Expand Down Expand Up @@ -290,9 +290,9 @@ dependencies = [

[[package]]
name = "either"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"

[[package]]
name = "encode_unicode"
Expand Down Expand Up @@ -536,9 +536,9 @@ dependencies = [

[[package]]
name = "log"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"

[[package]]
name = "matchers"
Expand Down Expand Up @@ -587,9 +587,9 @@ dependencies = [

[[package]]
name = "num-bigint"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
Expand Down Expand Up @@ -929,9 +929,9 @@ dependencies = [

[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0"
dependencies = [
"itoa",
"ryu",
Expand Down
2 changes: 1 addition & 1 deletion kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl<'tcx> GotocCtx<'tcx> {
) -> Expr {
debug!(?op, ?left_op, ?right_op, "codegen_comparison_fat_ptr");
let left_typ = self.operand_ty_stable(left_op);
let right_typ = self.operand_ty_stable(left_op);
let right_typ = self.operand_ty_stable(right_op);
assert_eq!(left_typ, right_typ, "Cannot compare pointers of different types");
assert!(self.is_fat_pointer_stable(left_typ));

Expand Down
2 changes: 1 addition & 1 deletion kani-compiler/src/kani_middle/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl<'tcx> ModelIntrinsics<'tcx> {
let Operand::Constant(fn_def) = func else { unreachable!() };
fn_def.const_ = mirConst::from_value(
ConstValue::ZeroSized,
tcx.type_of(stub_id).instantiate(tcx, &new_gen_args),
tcx.type_of(stub_id).instantiate(tcx, &*new_gen_args),
);
} else {
debug!(?arg_ty, "replace_simd_bitmask failed");
Expand Down
1 change: 1 addition & 0 deletions library/kani/kani_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
void free(void *ptr);
void *memcpy(void *dst, const void *src, size_t n);
void *calloc(size_t nmemb, size_t size);
void *malloc(size_t size);

typedef __CPROVER_bool bool;

Expand Down
7 changes: 7 additions & 0 deletions library/kani/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,10 @@ pub fn untracked_deref<T>(_: &T) -> T {
#[doc(hidden)]
#[rustc_diagnostic_item = "KaniInitContracts"]
pub fn init_contracts() {}

/// This should only be used within contracts. The intent is to
/// perform type inference on a closure's argument
#[doc(hidden)]
pub fn apply_closure<T, U: Fn(&T) -> bool>(f: U, x: &T) -> bool {
f(x)
}
2 changes: 1 addition & 1 deletion library/kani_macros/src/sysroot/contracts/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub fn build_ensures(data: &ExprClosure) -> (TokenStream2, Expr) {
.fold(quote!(), |collect, (ident, expr)| quote!(let #ident = #expr; #collect));

let result: Ident = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
(remembers_stmts, Expr::Verbatim(quote!((#expr)(&#result))))
(remembers_stmts, Expr::Verbatim(quote!(kani::internal::apply_closure(#expr, &#result))))
}

trait OldTrigger {
Expand Down
1 change: 1 addition & 0 deletions rfc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
- [0007-global-conditions](rfcs/0007-global-conditions.md)
- [0008-line-coverage](rfcs/0008-line-coverage.md)
- [0009-function-contracts](rfcs/0009-function-contracts.md)
- [0010-quantifiers](rfcs/0010-quantifiers.md)
203 changes: 203 additions & 0 deletions rfc/src/rfcs/0010-quantifiers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
- **Feature Name:** Quantifiers
- **Feature Request Issue:** [#2546](https://github.com/model-checking/kani/issues/2546) and [#836](https://github.com/model-checking/kani/issues/836)
- **RFC PR:** [#](https://github.com/model-checking/kani/pull/)
- **Status:** Unstable
- **Version:** 1.0

-------------------

## Summary

Quantifiers are logical operators that allow users to express that a property or condition applies to some or all objects within a given domain.

## User Impact

There are two primary quantifiers: the existential quantifier (∃) and the universal quantifier (∀).

1. The existential quantifier (∃): represents the statement "there exists." We use to express that there is at least one object in the domain that satisfies a given condition. For example, "∃x P(x)" means "there exists a value x such that P(x) is true."

2. The universal quantifier (∀): represents the statement "for all" or "for every." We use it to express that a given condition is true for every object in the domain. For example, "∀x P(x)" means "for every value x, P(x) is true."

Rather than exhaustively listing all elements in a domain, quantifiers enable users to make statements about the entire domain at once. This compact representation is crucial when dealing with large or unbounded inputs. Quantifiers also facilitate abstraction and generalization of properties. Instead of specifying properties for specific instances, quantified properties can capture general patterns and behaviors that hold across different objects in a domain. Additionally, by replacing loops in the specification with quantifiers, Kani can encode the properties more efficiently within the specified bounds, making the verification process more manageable and computationally feasible.

This new feature doesn't introduce any breaking changes to users. It will only allow them to write properites using the existential (∃) and universal (∀) quantifiers.

## User Experience

We propose a syntax inspired by ["Pattern Types"](https://github.com/rust-lang/rust/pull/120131). The syntax of existential (i.e., `kani::exists`) and universal (i.e., `kani::forall`) quantifiers are:

```rust
kani::exists(|<var>: <type> [is <range-expr>] | <boolean-expression>)
kani::forall(|<var>: <type> [is <range-expr>] | <boolean-expression>)
```

If `<range-expr>` is not provided, we assume `<var>` can range over all possible values of the given `<type>` (i.e., syntactic sugar for full range `|<var>: <type> as .. |`). CBMC's SAT backend only supports bounded quantification under **constant** lower and upper bounds (for more details, see the [documentation for quantifiers in CBMC](https://diffblue.github.io/cbmc/contracts-quantifiers.html)). The SMT backend, on the other hand, supports arbitrary Boolean expressions. In any case, `<boolean-expression>` should not have side effects, as the purpose of quantifiers is to assert a condition over a domain of objects without altering the state.

Consider the following example adapted from the documentation for the [from_raw_parts](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.from_raw_parts) function:

```rust
use std::ptr;
use std::mem;

#[kani::proof]
fn main() {
let v = vec![kani::any::<usize>(); 100];

// Prevent running `v`'s destructor so we are in complete control
// of the allocation.
let mut v = mem::ManuallyDrop::new(v);

// Pull out the various important pieces of information about `v`
let p = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();

unsafe {
// Overwrite memory
for i in 0..len {
*p.add(i) += 1;
}

// Put everything back together into a Vec
let rebuilt = Vec::from_raw_parts(p, len, cap);
}
}
```

Given the `v` vector has non-deterministic values, there are potential arithmetic overflows that might happen in the for loop. So we need to constrain all values of the array. We may also want to check all values of `rebuilt` after the operation. Without quantifiers, we might be tempted to use loops as follows:

```rust
use std::ptr;
use std::mem;

#[kani::proof]
fn main() {
let original_v = vec![kani::any::<usize>(); 100];
let v = original_v.clone();
for i in 0..v.len() {
kani::assume(v[i] < 5);
}

// Prevent running `v`'s destructor so we are in complete control
// of the allocation.
let mut v = mem::ManuallyDrop::new(v);

// Pull out the various important pieces of information about `v`
let p = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();

unsafe {
// Overwrite memory
for i in 0..len {
*p.add(i) += 1;
}

// Put everything back together into a Vec
let rebuilt = Vec::from_raw_parts(p, len, cap);
for i in 0..len {
assert_eq!(rebuilt[i], original_v[i]+1);
}
}
}
```

This, however, might unnecessary increase the complexity of the verication process. We can achieve the same effect using quantifiers as shown below.

```rust
use std::ptr;
use std::mem;

#[kani::proof]
fn main() {
let original_v = vec![kani::any::<usize>(); 3];
let v = original_v.clone();
kani::assume(kani::forall(|i: usize is ..v.len() | v[i] < 5));

// Prevent running `v`'s destructor so we are in complete control
// of the allocation.
let mut v = mem::ManuallyDrop::new(v);

// Pull out the various important pieces of information about `v`
let p = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();

unsafe {
// Overwrite memory
for i in 0..len {
*p.add(i) += 1;
}

// Put everything back together into a Vec
let rebuilt = Vec::from_raw_parts(p, len, cap);
assert!(kani::forall(|i: usize is ..len | rebuilt[i] == original_v[i]+1));
}
}
```

The same principle applies if we want to use the existential quantifier.

```rust
use std::ptr;
use std::mem;

#[kani::proof]
fn main() {
let original_v = vec![kani::any::<usize>(); 3];
let v = original_v.clone();
kani::assume(kani::forall(|i: usize is ..v.len() | v[i] < 5));

// Prevent running `v`'s destructor so we are in complete control
// of the allocation.
let mut v = mem::ManuallyDrop::new(v);

// Pull out the various important pieces of information about `v`
let p = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();

unsafe {
// Overwrite memory
for i in 0..len {
*p.add(i) += 1;
if i == 1 {
*p.add(i) = 0;
}
}

// Put everything back together into a Vec
let rebuilt = Vec::from_raw_parts(p, len, cap);
assert!(kani::exists(|i: usize is ..len | rebuilt[i] == 0));
}
}
```

The usage of quantifiers should be valid in any part of the Rust code analysed by Kani.

## Detailed Design

<!-- For the implementors or the hackers -->

Kani should have the same support that CBMC has for quantifiers. For more details, see [Quantifiers](https://github.com/diffblue/cbmc/blob/0a69a64e4481473d62496f9975730d24f194884a/doc/cprover-manual/contracts-quantifiers.md).


## Open questions

<!-- For Developers -->
- **Function Contracts RFC** - CBMC has support for both `exists` and `forall`, but the
code generation is difficult. The most ergonomic and easy way to implement
quantifiers on the Rust side is as higher-order functions taking `Fn(T) ->
bool`, where `T` is some arbitrary type that can be quantified over. This
interface is familiar to developers, but the code generation is tricky, as
CBMC level quantifiers only allow certain kinds of expressions. This
necessitates a rewrite of the `Fn` closure to a compliant expression.
- Which kind of expressions should be accepted as a "compliant expression"?


## Future possibilities

<!-- For Developers -->
- CBMC has an SMT backend which allows the use of quantifiers with arbitrary Boolean expressions. Kani must include an option for users to experiment with this backend.

---
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT

[toolchain]
channel = "nightly-2024-06-25"
channel = "nightly-2024-06-27"
components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assertion\
- Status: SUCCESS\
- Description: "|result| (*result == x) | (*result == y)"\
in function max

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

#[kani::ensures(|result| (*result == x) | (*result == y))]
fn max(x: u32, y: u32) -> u32 {
if x > y { x } else { y }
}

#[kani::proof_for_contract(max)]
fn max_harness() {
let _ = Box::new(9_usize);
max(7, 6);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cannot assign to `*_x`, as `Fn` closures cannot mutate their captured variables
12 changes: 12 additions & 0 deletions tests/ui/function-contracts/mutating_ensures_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

#[kani::ensures(|_| {*_x += 1; true})]
fn unit(_x: &mut u32) {}

#[kani::proof_for_contract(id)]
fn harness() {
let mut x = kani::any();
unit(&mut x);
}

0 comments on commit 11f8a5b

Please sign in to comment.