use crate::ElicitResult;
use crate::traits::Elicitation;
use std::fmt::Debug;
#[cfg(all(
feature = "verification",
not(any(
feature = "verify-creusot",
feature = "verify-prusti",
feature = "verify-verus"
))
))]
pub const DEFAULT_STRING_CONTRACT: contracts::StringNonEmpty = contracts::StringNonEmpty;
#[cfg(feature = "verify-creusot")]
pub const DEFAULT_STRING_CONTRACT: contracts::creusot::CreusotStringNonEmpty =
contracts::creusot::CreusotStringNonEmpty;
#[cfg(all(feature = "verify-prusti", not(feature = "verify-creusot")))]
pub const DEFAULT_STRING_CONTRACT: contracts::prusti::PrustiStringNonEmpty =
contracts::prusti::PrustiStringNonEmpty;
#[cfg(all(
feature = "verify-verus",
not(any(feature = "verify-creusot", feature = "verify-prusti"))
))]
pub const DEFAULT_STRING_CONTRACT: contracts::verus::VerusStringNonEmpty =
contracts::verus::VerusStringNonEmpty;
#[cfg(all(
feature = "verification",
not(any(
feature = "verify-creusot",
feature = "verify-prusti",
feature = "verify-verus"
))
))]
pub const DEFAULT_I32_CONTRACT: contracts::I32Positive = contracts::I32Positive;
#[cfg(feature = "verify-creusot")]
pub const DEFAULT_I32_CONTRACT: contracts::creusot::CreusotI32Positive =
contracts::creusot::CreusotI32Positive;
#[cfg(all(feature = "verify-prusti", not(feature = "verify-creusot")))]
pub const DEFAULT_I32_CONTRACT: contracts::prusti::PrustiI32Positive =
contracts::prusti::PrustiI32Positive;
#[cfg(all(
feature = "verify-verus",
not(any(feature = "verify-creusot", feature = "verify-prusti"))
))]
pub const DEFAULT_I32_CONTRACT: contracts::verus::VerusI32Positive =
contracts::verus::VerusI32Positive;
#[cfg(all(
feature = "verification",
not(any(
feature = "verify-creusot",
feature = "verify-prusti",
feature = "verify-verus"
))
))]
pub const DEFAULT_BOOL_CONTRACT: contracts::BoolValid = contracts::BoolValid;
#[cfg(feature = "verify-creusot")]
pub const DEFAULT_BOOL_CONTRACT: contracts::creusot::CreusotBoolValid =
contracts::creusot::CreusotBoolValid;
#[cfg(all(feature = "verify-prusti", not(feature = "verify-creusot")))]
pub const DEFAULT_BOOL_CONTRACT: contracts::prusti::PrustiBoolValid =
contracts::prusti::PrustiBoolValid;
#[cfg(all(
feature = "verify-verus",
not(any(feature = "verify-creusot", feature = "verify-prusti"))
))]
pub const DEFAULT_BOOL_CONTRACT: contracts::verus::VerusBoolValid =
contracts::verus::VerusBoolValid;
pub trait Contract {
type Input: Elicitation + Clone + std::fmt::Debug + Send;
type Output: Elicitation + Clone + std::fmt::Debug + Send;
fn requires(input: &Self::Input) -> bool;
fn ensures(input: &Self::Input, output: &Self::Output) -> bool;
fn invariant(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct AndContract<C1, C2> {
pub first: C1,
pub second: C2,
}
impl<C1, C2> AndContract<C1, C2> {
#[tracing::instrument(level = "trace", skip(first, second))]
pub fn new(first: C1, second: C2) -> Self {
Self { first, second }
}
}
impl<C1, C2, I, O> Contract for AndContract<C1, C2>
where
C1: Contract<Input = I, Output = O>,
C2: Contract<Input = I, Output = O>,
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
type Input = I;
type Output = O;
fn requires(input: &Self::Input) -> bool {
C1::requires(input) && C2::requires(input)
}
fn ensures(input: &Self::Input, output: &Self::Output) -> bool {
C1::ensures(input, output) && C2::ensures(input, output)
}
fn invariant(&self) -> bool {
self.first.invariant() && self.second.invariant()
}
}
#[derive(Debug, Clone)]
pub struct OrContract<C1, C2> {
pub first: C1,
pub second: C2,
}
impl<C1, C2> OrContract<C1, C2> {
#[tracing::instrument(level = "trace", skip(first, second))]
pub fn new(first: C1, second: C2) -> Self {
Self { first, second }
}
}
impl<C1, C2, I, O> Contract for OrContract<C1, C2>
where
C1: Contract<Input = I, Output = O>,
C2: Contract<Input = I, Output = O>,
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
type Input = I;
type Output = O;
fn requires(input: &Self::Input) -> bool {
C1::requires(input) || C2::requires(input)
}
fn ensures(input: &Self::Input, output: &Self::Output) -> bool {
C1::ensures(input, output) || C2::ensures(input, output)
}
fn invariant(&self) -> bool {
self.first.invariant() || self.second.invariant()
}
}
#[derive(Debug, Clone)]
pub struct NotContract<C> {
pub inner: C,
}
impl<C> NotContract<C> {
#[tracing::instrument(level = "trace", skip(inner))]
pub fn new(inner: C) -> Self {
Self { inner }
}
}
impl<C, I, O> Contract for NotContract<C>
where
C: Contract<Input = I, Output = O>,
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
type Input = I;
type Output = O;
fn requires(input: &Self::Input) -> bool {
!C::requires(input)
}
fn ensures(input: &Self::Input, output: &Self::Output) -> bool {
!C::ensures(input, output)
}
fn invariant(&self) -> bool {
!self.inner.invariant()
}
}
pub mod compose {
use super::*;
#[tracing::instrument(level = "trace", skip(first, second))]
pub fn and<C1, C2>(first: C1, second: C2) -> AndContract<C1, C2> {
AndContract::new(first, second)
}
#[tracing::instrument(level = "trace", skip(first, second))]
pub fn or<C1, C2>(first: C1, second: C2) -> OrContract<C1, C2> {
OrContract::new(first, second)
}
#[tracing::instrument(level = "trace", skip(inner))]
pub fn not<C>(inner: C) -> NotContract<C> {
NotContract::new(inner)
}
}
pub trait DynContract<I, O>: Send + Sync
where
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
fn check_requires(&self, input: &I) -> bool;
fn check_ensures(&self, input: &I, output: &O) -> bool;
fn check_invariant(&self) -> bool;
}
impl<T, I, O> DynContract<I, O> for T
where
T: Contract<Input = I, Output = O> + Send + Sync,
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
fn check_requires(&self, input: &I) -> bool {
Self::requires(input)
}
fn check_ensures(&self, input: &I, output: &O) -> bool {
Self::ensures(input, output)
}
fn check_invariant(&self) -> bool {
self.invariant()
}
}
pub enum VerifierBackend<I, O>
where
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
Kani(Box<dyn DynContract<I, O>>),
#[cfg(feature = "verify-creusot")]
Creusot(Box<dyn DynContract<I, O>>),
#[cfg(feature = "verify-prusti")]
Prusti(Box<dyn DynContract<I, O>>),
#[cfg(feature = "verify-verus")]
Verus(Box<dyn DynContract<I, O>>),
}
impl<I, O> VerifierBackend<I, O>
where
I: Elicitation + Clone + Debug + Send,
O: Elicitation + Clone + Debug + Send,
{
pub fn check_precondition(&self, input: &I) -> bool {
match self {
Self::Kani(contract) => contract.check_requires(input),
#[cfg(feature = "verify-creusot")]
Self::Creusot(contract) => contract.check_requires(input),
#[cfg(feature = "verify-prusti")]
Self::Prusti(contract) => contract.check_requires(input),
#[cfg(feature = "verify-verus")]
Self::Verus(contract) => contract.check_requires(input),
}
}
pub fn check_postcondition(&self, input: &I, output: &O) -> bool {
match self {
Self::Kani(contract) => contract.check_ensures(input, output),
#[cfg(feature = "verify-creusot")]
Self::Creusot(contract) => contract.check_ensures(input, output),
#[cfg(feature = "verify-prusti")]
Self::Prusti(contract) => contract.check_ensures(input, output),
#[cfg(feature = "verify-verus")]
Self::Verus(contract) => contract.check_ensures(input, output),
}
}
pub fn check_invariant(&self) -> bool {
match self {
Self::Kani(contract) => contract.check_invariant(),
#[cfg(feature = "verify-creusot")]
Self::Creusot(contract) => contract.check_invariant(),
#[cfg(feature = "verify-prusti")]
Self::Prusti(contract) => contract.check_invariant(),
#[cfg(feature = "verify-verus")]
Self::Verus(contract) => contract.check_invariant(),
}
}
pub fn verify<F>(&self, input: I, f: F) -> Result<O, String>
where
F: FnOnce(I) -> O,
{
if !self.check_precondition(&input) {
return Err("Precondition failed".to_string());
}
if !self.check_invariant() {
return Err("Invariant failed before execution".to_string());
}
let output = f(input.clone());
if !self.check_postcondition(&input, &output) {
return Err("Postcondition failed".to_string());
}
if !self.check_invariant() {
return Err("Invariant failed after execution".to_string());
}
Ok(output)
}
}
pub struct ContractedElicitation<T, C>
where
T: Elicitation,
C: Contract<Input = T, Output = T>,
{
contract: C,
_phantom: std::marker::PhantomData<T>,
}
impl<T, C> ContractedElicitation<T, C>
where
T: Elicitation + Clone + Debug + Send,
C: Contract<Input = T, Output = T> + Send + Sync + 'static,
{
pub fn new(contract: C) -> Self {
Self {
contract,
_phantom: std::marker::PhantomData,
}
}
pub async fn elicit(
self,
peer: std::sync::Arc<crate::rmcp::Peer<crate::rmcp::RoleClient>>,
) -> ElicitResult<T> {
use tracing::{debug, error};
let client = crate::ElicitClient::new(peer);
let value = T::elicit(&client).await?;
debug!(
value = ?value,
"Value elicited, checking contracts"
);
if !C::requires(&value) {
error!("Contract precondition failed");
return Err(crate::ElicitError::new(
crate::ElicitErrorKind::InvalidFormat {
expected: "Value satisfying contract precondition".to_string(),
received: format!("{:?}", value),
},
));
}
if !C::ensures(&value, &value) {
error!("Contract postcondition failed");
return Err(crate::ElicitError::new(
crate::ElicitErrorKind::InvalidFormat {
expected: "Value satisfying contract postcondition".to_string(),
received: format!("{:?}", value),
},
));
}
if !self.contract.invariant() {
error!("Contract invariant failed");
return Err(crate::ElicitError::new(
crate::ElicitErrorKind::InvalidFormat {
expected: "Contract invariant to hold".to_string(),
received: "Invariant violated".to_string(),
},
));
}
debug!("All contracts satisfied");
Ok(value)
}
}
pub trait WithContract: Elicitation + Clone + Debug + Send {
fn with_contract<C>(contract: C) -> ContractedElicitation<Self, C>
where
C: Contract<Input = Self, Output = Self> + Send + Sync + 'static,
{
ContractedElicitation::new(contract)
}
}
impl<T> WithContract for T where T: Elicitation + Clone + Debug + Send {}
pub mod contracts;
pub mod mechanisms;
pub mod types;
#[cfg(feature = "cli")]
pub mod runner;
#[cfg(feature = "verify-kani")]
pub mod kani;
#[cfg(feature = "verify-creusot")]
pub mod creusot;
#[cfg(feature = "cli")]
pub mod prusti_runner;
#[cfg(test)]
mod tests {
use super::*;
use crate::verification::contracts::{BoolValid, I32Positive, StringNonEmpty};
#[test]
fn test_verifier_backend_string_kani() {
let verifier = VerifierBackend::Kani(Box::new(StringNonEmpty));
let input = String::from("hello");
assert!(verifier.check_precondition(&input));
assert!(verifier.check_postcondition(&input, &input));
assert!(verifier.check_invariant());
let result = verifier.verify(input.clone(), |x| x);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "hello");
}
#[test]
fn test_verifier_backend_i32_kani() {
let verifier = VerifierBackend::Kani(Box::new(I32Positive));
let input = 42i32;
assert!(verifier.check_precondition(&input));
assert!(verifier.check_postcondition(&input, &input));
assert!(verifier.check_invariant());
let result = verifier.verify(input, |x| x);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_verifier_backend_bool_kani() {
let verifier = VerifierBackend::Kani(Box::new(BoolValid));
let input = true;
assert!(verifier.check_precondition(&input));
assert!(verifier.check_postcondition(&input, &input));
assert!(verifier.check_invariant());
let result = verifier.verify(input, |x| x);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_verifier_backend_precondition_failure() {
let verifier = VerifierBackend::Kani(Box::new(StringNonEmpty));
let input = String::new();
assert!(!verifier.check_precondition(&input));
let result = verifier.verify(input, |x| x);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Precondition failed");
}
#[test]
fn test_verifier_backend_postcondition_failure() {
let verifier = VerifierBackend::Kani(Box::new(I32Positive));
let input = 42i32;
let result = verifier.verify(input, |_x| -1); assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Postcondition failed");
}
#[test]
fn test_with_contract_creates_contracted_elicitation() {
let contracted = String::with_contract(StringNonEmpty);
let _: ContractedElicitation<String, StringNonEmpty> = contracted;
}
#[test]
fn test_contracted_elicitation_builder_pattern() {
let _contracted = ContractedElicitation {
_phantom: std::marker::PhantomData::<i32>,
contract: I32Positive,
};
assert!(I32Positive::requires(&42));
assert!(!I32Positive::requires(&-1));
}
#[test]
fn test_with_contract_works_for_all_elicitation_types() {
let _string_contracted = String::with_contract(StringNonEmpty);
let _i32_contracted = i32::with_contract(I32Positive);
let _bool_contracted = bool::with_contract(BoolValid);
}
#[test]
#[cfg(feature = "verification")]
fn test_default_contracts_available() {
use super::{DEFAULT_BOOL_CONTRACT, DEFAULT_I32_CONTRACT, DEFAULT_STRING_CONTRACT};
fn check_string_contract<T: Contract<Input = String, Output = String>>(_: T) {}
fn check_i32_contract<T: Contract<Input = i32, Output = i32>>(_: T) {}
fn check_bool_contract<T: Contract<Input = bool, Output = bool>>(_: T) {}
check_string_contract(DEFAULT_STRING_CONTRACT);
check_i32_contract(DEFAULT_I32_CONTRACT);
check_bool_contract(DEFAULT_BOOL_CONTRACT);
}
#[test]
#[cfg(feature = "verification")]
fn test_default_contracts_usable_with_with_contract() {
use super::{DEFAULT_BOOL_CONTRACT, DEFAULT_I32_CONTRACT, DEFAULT_STRING_CONTRACT};
let _string_contracted = String::with_contract(DEFAULT_STRING_CONTRACT);
let _i32_contracted = i32::with_contract(DEFAULT_I32_CONTRACT);
let _bool_contracted = bool::with_contract(DEFAULT_BOOL_CONTRACT);
}
#[test]
fn test_and_contract_both_pass() {
use super::AndContract;
use super::contracts::{I32NonNegative, I32Positive};
AndContract::new(I32Positive, I32NonNegative);
assert!(AndContract::<I32Positive, I32NonNegative>::requires(&42));
assert!(AndContract::<I32Positive, I32NonNegative>::ensures(
&42, &42
));
}
#[test]
fn test_and_contract_one_fails() {
use super::AndContract;
use super::contracts::{I32NonNegative, I32Positive};
assert!(!AndContract::<I32Positive, I32NonNegative>::requires(&0));
assert!(!AndContract::<I32Positive, I32NonNegative>::requires(&-1));
}
#[test]
fn test_or_contract_either_passes() {
use super::OrContract;
use super::contracts::{I32NonNegative, I32Positive};
assert!(OrContract::<I32Positive, I32NonNegative>::requires(&42));
assert!(OrContract::<I32Positive, I32NonNegative>::requires(&0));
assert!(!OrContract::<I32Positive, I32NonNegative>::requires(&-1));
}
#[test]
fn test_not_contract_inverts_logic() {
use super::NotContract;
use super::contracts::I32Positive;
assert!(I32Positive::requires(&42));
assert!(!NotContract::<I32Positive>::requires(&42));
assert!(!I32Positive::requires(&-1));
assert!(NotContract::<I32Positive>::requires(&-1));
}
#[test]
fn test_string_composition_bounded_non_empty() {
use super::AndContract;
use super::contracts::{StringMaxLength, StringNonEmpty};
AndContract::new(StringNonEmpty, StringMaxLength::<10>);
assert!(AndContract::<StringNonEmpty, StringMaxLength<10>>::requires(&"hello".to_string()));
assert!(!AndContract::<StringNonEmpty, StringMaxLength<10>>::requires(&"".to_string()));
assert!(
!AndContract::<StringNonEmpty, StringMaxLength<10>>::requires(
&"12345678901".to_string()
)
);
}
#[test]
fn test_compose_helpers() {
use super::compose;
use super::contracts::{I32NonNegative, I32Positive};
let and_contract = compose::and(I32Positive, I32NonNegative);
assert!(AndContract::<I32Positive, I32NonNegative>::requires(&42));
let or_contract = compose::or(I32Positive, I32NonNegative);
assert!(OrContract::<I32Positive, I32NonNegative>::requires(&0));
let not_contract = compose::not(I32Positive);
assert!(NotContract::<I32Positive>::requires(&-1));
let _: AndContract<I32Positive, I32NonNegative> = and_contract;
let _: OrContract<I32Positive, I32NonNegative> = or_contract;
let _: NotContract<I32Positive> = not_contract;
}
#[test]
fn test_complex_composition() {
use super::contracts::{I32NonNegative, I32Positive};
use super::{AndContract, NotContract, OrContract};
let _pos_and_nonneg = AndContract::new(I32Positive, I32NonNegative);
let _not_pos = NotContract::new(I32Positive);
assert!(OrContract::<
AndContract<I32Positive, I32NonNegative>,
NotContract<I32Positive>,
>::requires(&42));
assert!(OrContract::<
AndContract<I32Positive, I32NonNegative>,
NotContract<I32Positive>,
>::requires(&0));
assert!(OrContract::<
AndContract<I32Positive, I32NonNegative>,
NotContract<I32Positive>,
>::requires(&-1));
}
#[test]
fn test_string_max_length_invariant() {
use super::contracts::StringMaxLength;
let contract = StringMaxLength::<100>;
assert!(contract.invariant());
let invalid = StringMaxLength::<0>;
assert!(!invalid.invariant()); }
}