Skip to content

Commit

Permalink
Enable doctests to avoid coding mistakes in our API documentation (#3557
Browse files Browse the repository at this point in the history
)

In order to enable the tests, I had to fix a few tests, enhance tests
with hidden code, and mark a few as `no_run` instead of `rust`.

The tests marked as `no_run` will at least compile, but we cannot
execute them otherwise execution would
be stuck in an infinite loop, since we provide dummy implementation
to a lot of our intrinsics with `loop {}`.

Finally, as I was cleaning up the documentation, I noticed that we
accidently published an API that was meant to be internal to Kani.
I marked this API as deprecated for now.

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.
  • Loading branch information
celinval authored Oct 2, 2024
1 parent 14ebca9 commit 68387e4
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 44 deletions.
43 changes: 35 additions & 8 deletions library/kani/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
//! simple division function `my_div`:
//!
//! ```
//! fn my_div(dividend: u32, divisor: u32) -> u32 {
//! fn my_div(dividend: usize, divisor: usize) -> usize {
//! dividend / divisor
//! }
//! ```
Expand All @@ -35,8 +35,12 @@
//! allows us to declare constraints on what constitutes valid inputs to our
//! function. In this case we would want to disallow a divisor that is `0`.
//!
//! ```ignore
//! ```
//! # use kani::requires;
//! #[requires(divisor != 0)]
//! # fn my_div(dividend: usize, divisor: usize) -> usize {
//! # dividend / divisor
//! # }
//! ```
//!
//! This is called a precondition, because it is enforced before (pre-) the
Expand All @@ -51,7 +55,11 @@
//! approximation of the result of division for instance could be this:
//!
//! ```
//! #[ensures(|result : &u32| *result <= dividend)]
//! # use kani::ensures;
//! #[ensures(|result : &usize| *result <= dividend)]
//! # fn my_div(dividend: usize, divisor: usize) -> usize {
//! # dividend / divisor
//! # }
//! ```
//!
//! This is called a postcondition and it also has access to the arguments and
Expand All @@ -66,9 +74,11 @@
//! order does not matter. In our example putting them together looks like this:
//!
//! ```
//! #[kani::requires(divisor != 0)]
//! #[kani::ensures(|result : &u32| *result <= dividend)]
//! fn my_div(dividend: u32, divisor: u32) -> u32 {
//! use kani::{requires, ensures};
//!
//! #[requires(divisor != 0)]
//! #[ensures(|result : &usize| *result <= dividend)]
//! fn my_div(dividend: usize, divisor: usize) -> usize {
//! dividend / divisor
//! }
//! ```
Expand All @@ -84,9 +94,18 @@
//! function we want to check.
//!
//! ```
//! # use kani::{requires, ensures};
//! #
//! # #[requires(divisor != 0)]
//! # #[ensures(|result : &usize| *result <= dividend)]
//! # fn my_div(dividend: usize, divisor: usize) -> usize {
//! # dividend / divisor
//! # }
//! #
//! #[kani::proof_for_contract(my_div)]
//! fn my_div_harness() {
//! my_div(kani::any(), kani::any()) }
//! my_div(kani::any(), kani::any());
//! }
//! ```
//!
//! The harness is checked like any other by running `cargo kani` and can be
Expand All @@ -104,10 +123,18 @@
//! the contract will be used automatically.
//!
//! ```
//! # use kani::{requires, ensures};
//! #
//! # #[requires(divisor != 0)]
//! # #[ensures(|result : &usize| *result <= dividend)]
//! # fn my_div(dividend: usize, divisor: usize) -> usize {
//! # dividend / divisor
//! # }
//! #
//! #[kani::proof]
//! #[kani::stub_verified(my_div)]
//! fn use_div() {
//! let v = vec![...];
//! let v = kani::vec::any_vec::<char, 5>();
//! let some_idx = my_div(v.len() - 1, 3);
//! v[some_idx];
//! }
Expand Down
33 changes: 31 additions & 2 deletions library/kani/src/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
/// ```
/// You can specify its safety invariant as:
/// ```rust
/// # #[derive(kani::Arbitrary)]
/// # pub struct MyDate {
/// # day: u8,
/// # month: u8,
/// # year: i64,
/// # }
/// # fn days_in_month(_: i64, _: u8) -> u8 { 31 }
///
/// impl kani::Invariant for MyDate {
/// fn is_safe(&self) -> bool {
/// self.month > 0
Expand All @@ -49,12 +57,33 @@
/// }
/// ```
/// And use it to check that your APIs are safe:
/// ```rust
/// ```no_run
/// # use kani::Invariant;
/// #
/// # #[derive(kani::Arbitrary)]
/// # pub struct MyDate {
/// # day: u8,
/// # month: u8,
/// # year: i64,
/// # }
/// #
/// # fn days_in_month(_: i64, _: u8) -> u8 { todo!() }
/// # fn increase_date(_: &mut MyDate, _: u8) { todo!() }
/// #
/// # impl Invariant for MyDate {
/// # fn is_safe(&self) -> bool {
/// # self.month > 0
/// # && self.month <= 12
/// # && self.day > 0
/// # && self.day <= days_in_month(self.year, self.month)
/// # }
/// # }
/// #
/// #[kani::proof]
/// fn check_increase_date() {
/// let mut date: MyDate = kani::any();
/// // Increase date by one day
/// increase_date(date, 1);
/// increase_date(&mut date, 1);
/// assert!(date.is_safe());
/// }
/// ```
Expand Down
2 changes: 2 additions & 0 deletions library/kani/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#![feature(ptr_metadata)]
#![feature(f16)]
#![feature(f128)]
// Need to add this since we are deprecating `kani::check`. Remove this when we remove that API.
#![allow(deprecated)]

// Allow us to use `kani::` to access crate features.
extern crate self as kani;
Expand Down
2 changes: 1 addition & 1 deletion library/kani/src/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//!
//! # Example
//!
//! ```
//! ```no_run
//! use kani::shadow::ShadowMem;
//! use std::alloc::{alloc, Layout};
//!
Expand Down
3 changes: 2 additions & 1 deletion library/kani/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::{any, assume};
///
/// # Example:
///
/// ```rust
/// ```no_run
/// # fn foo(_: &[i32]) {}
/// let arr = [1, 2, 3];
/// let slice = kani::slice::any_slice_of_array(&arr);
/// foo(slice); // where foo is a function that takes a slice and verifies a property about it
Expand Down
98 changes: 68 additions & 30 deletions library/kani_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ macro_rules! kani_lib {
pub use kani_core::*;
kani_core::kani_intrinsics!(core);
kani_core::generate_arbitrary!(core);
kani_core::check_intrinsic!();

pub mod mem {
kani_core::kani_mem!(core);
Expand All @@ -58,6 +59,10 @@ macro_rules! kani_lib {
kani_core::kani_intrinsics!(std);
kani_core::generate_arbitrary!(std);

kani_core::check_intrinsic! {
msg="This API will be made private in future releases.", vis=pub
}

pub mod mem {
kani_core::kani_mem!(std);
}
Expand Down Expand Up @@ -85,7 +90,7 @@ macro_rules! kani_intrinsics {
///
/// The code snippet below should never panic.
///
/// ```rust
/// ```no_run
/// let i : i32 = kani::any();
/// kani::assume(i > 10);
/// if i < 0 {
Expand All @@ -95,7 +100,7 @@ macro_rules! kani_intrinsics {
///
/// The following code may panic though:
///
/// ```rust
/// ```no_run
/// let i : i32 = kani::any();
/// assert!(i < 0, "This may panic and verification should fail.");
/// kani::assume(i > 10);
Expand All @@ -118,7 +123,7 @@ macro_rules! kani_intrinsics {
///
/// # Example:
///
/// ```rust
/// ```no_run
/// let x: bool = kani::any();
/// let y = !x;
/// kani::assert(x || y, "ORing a boolean variable with its negation must be true")
Expand All @@ -138,35 +143,15 @@ macro_rules! kani_intrinsics {
assert!(cond, "{}", msg);
}

/// Creates an assertion of the specified condition and message, but does not assume it afterwards.
///
/// # Example:
///
/// ```rust
/// let x: bool = kani::any();
/// let y = !x;
/// kani::check(x || y, "ORing a boolean variable with its negation must be true")
/// ```
#[cfg(not(feature = "concrete_playback"))]
#[inline(never)]
#[rustc_diagnostic_item = "KaniCheck"]
pub const fn check(cond: bool, msg: &'static str) {
let _ = cond;
let _ = msg;
}

#[cfg(feature = "concrete_playback")]
#[inline(never)]
#[rustc_diagnostic_item = "KaniCheck"]
pub const fn check(cond: bool, msg: &'static str) {
assert!(cond, "{}", msg);
}

/// Creates a cover property with the specified condition and message.
///
/// # Example:
///
/// ```rust
/// ```no_run
/// # use crate::kani;
/// #
/// # let array: [u8; 10] = kani::any();
/// # let slice = kani::slice::any_slice_of_array(&array);
/// kani::cover(slice.len() == 0, "The slice may have a length of 0");
/// ```
///
Expand All @@ -193,7 +178,11 @@ macro_rules! kani_intrinsics {
/// In the snippet below, we are verifying the behavior of the function `fn_under_verification`
/// under all possible `NonZeroU8` input values, i.e., all possible `u8` values except zero.
///
/// ```rust
/// ```no_run
/// # use std::num::NonZeroU8;
/// # use crate::kani;
/// #
/// # fn fn_under_verification(_: NonZeroU8) {}
/// let inputA = kani::any::<core::num::NonZeroU8>();
/// fn_under_verification(inputA);
/// ```
Expand Down Expand Up @@ -231,7 +220,11 @@ macro_rules! kani_intrinsics {
/// In the snippet below, we are verifying the behavior of the function `fn_under_verification`
/// under all possible `u8` input values between 0 and 12.
///
/// ```rust
/// ```no_run
/// # use std::num::NonZeroU8;
/// # use crate::kani;
/// #
/// # fn fn_under_verification(_: u8) {}
/// let inputA: u8 = kani::any_where(|x| *x < 12);
/// fn_under_verification(inputA);
/// ```
Expand Down Expand Up @@ -460,3 +453,48 @@ macro_rules! kani_intrinsics {
}
};
}

#[macro_export]
macro_rules! check_intrinsic {
($(msg=$msg:literal, vis=$vis:vis)?) => {
/// Creates a non-fatal property with the specified condition and message.
///
/// This check will not impact the program control flow even when it fails.
///
/// # Example:
///
/// ```no_run
/// let x: bool = kani::any();
/// let y = !x;
/// kani::check(x || y, "ORing a boolean variable with its negation must be true");
/// kani::check(x == y, "A boolean variable is always different than its negation");
/// kani::cover!(true, "This should still be reachable");
/// ```
///
/// # Deprecated
///
/// This function was meant to be internal only, and it was added to Kani's public interface
/// by mistake. Thus, it will be made private in future releases.
/// Instead, we recommend users to either use `assert` or `cover` to create properties they
/// would like to verify.
///
/// See <https://github.com/model-checking/kani/issues/3561> for more details.
#[cfg(not(feature = "concrete_playback"))]
#[inline(never)]
#[rustc_diagnostic_item = "KaniCheck"]
// TODO: Remove the `#![allow(deprecated)]` inside kani's crate once this is made private.
$(#[deprecated(since="0.55.0", note=$msg)])?
$($vis)? const fn check(cond: bool, msg: &'static str) {
let _ = cond;
let _ = msg;
}

#[cfg(feature = "concrete_playback")]
#[inline(never)]
#[rustc_diagnostic_item = "KaniCheck"]
$(#[deprecated(since="0.55.0", note=$msg)])?
$($vis)? const fn check(cond: bool, msg: &'static str) {
assert!(cond, "{}", msg);
}
};
}
4 changes: 2 additions & 2 deletions scripts/kani-regression.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ cargo test -p cprover_bindings
cargo test -p kani-compiler
cargo test -p kani-driver
cargo test -p kani_metadata
# skip doc tests and enable assertions to fail
cargo test -p kani --lib --features concrete_playback
# Use concrete playback to enable assertions failure
cargo test -p kani --features concrete_playback
# Test the actual macros, skipping doc tests and enabling extra traits for "syn"
# so we can debug print AST
RUSTFLAGS=--cfg=kani_sysroot cargo test -p kani_macros --features syn/extra-traits --lib
Expand Down
1 change: 1 addition & 0 deletions tests/ui/check_deprecated/deprecated_warning.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
warning: use of deprecated function `kani::check`: This API will be made private in future releases.
10 changes: 10 additions & 0 deletions tests/ui/check_deprecated/deprecated_warning.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//
// kani-flags: --only-codegen

/// Ensure that Kani prints a deprecation warning if users invoke `kani::check`.
#[kani::proof]
fn internal_api() {
kani::check(kani::any(), "oops");
}

0 comments on commit 68387e4

Please sign in to comment.