From 204d26797c18c216c352be818a429299eb771a74 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 07:37:33 +0100 Subject: [PATCH 01/43] initial specs --- .../prusti-contracts/src/core_spec.rs | 18 -- .../prusti-contracts/src/core_spec/clone.rs | 12 + .../prusti-contracts/src/core_spec/default.rs | 47 ++++ .../prusti-contracts/src/core_spec/mod.rs | 10 + .../prusti-contracts/src/core_spec/option.rs | 210 ++++++++++++++++++ .../prusti-contracts/src/core_spec/result.rs | 181 +++++++++++++++ prusti-contracts/prusti-contracts/src/lib.rs | 8 +- 7 files changed, 465 insertions(+), 21 deletions(-) delete mode 100644 prusti-contracts/prusti-contracts/src/core_spec.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/clone.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/default.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/mod.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/option.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/result.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec.rs b/prusti-contracts/prusti-contracts/src/core_spec.rs deleted file mode 100644 index 61fa73e42b8..00000000000 --- a/prusti-contracts/prusti-contracts/src/core_spec.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::*; - -#[extern_spec] -impl ::core::result::Result { - #[pure] - #[ensures(result == matches!(self, Ok(_)))] - fn is_ok(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Err(_)))] - fn is_err(&self) -> bool; -} - -#[extern_spec] -impl ::core::result::Result { - #[requires(matches!(self, Ok(_)))] - fn unwrap(self) -> T; -} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs new file mode 100644 index 00000000000..927d1ddc1da --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs @@ -0,0 +1,12 @@ +use crate::*; + +#[extern_spec] +trait Clone { + #[ghost_constraint(Self: SnapshotEqualClone, [ + ensures(result === self), + ])] + fn clone(&self) -> Self; +} + +/// Specifies that `Clone::clone`, if implemented, preserves snapshot equality (`===`). +pub auto trait SnapshotEqualClone {} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs new file mode 100644 index 00000000000..ab2e96f3938 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -0,0 +1,47 @@ +use crate::*; + +#[extern_spec] +trait Default { + #[ghost_constraint(Self: Copy + PureDefault, [pure])] + fn default() -> Self; +} + +/// Specifies that `Default::default`, if implemented, is a pure method, allowing its usage in specs. +/// +/// Does not apply to types that do not implement `Copy`, since pure methods can only involve `Copy` types. +pub auto trait PureDefault {} + +// analogous to https://github.com/rust-lang/rust/blob/872631d0f0fadffe3220ab1bd9c8f1f2342341e2/library/core/src/default.rs#L190-L202 +macro_rules! default_spec { + ($t:ty, $v:expr) => { + #[extern_spec] + impl Default for $t { + #[pure] + #[ensures(result == $v)] + fn default() -> Self { + $v + } + } + }; +} + +default_spec! { (), () } +default_spec! { bool, false } +default_spec! { char, '\x00' } + +default_spec! { usize, 0 } +default_spec! { u8, 0 } +default_spec! { u16, 0 } +default_spec! { u32, 0 } +default_spec! { u64, 0 } +default_spec! { u128, 0 } + +default_spec! { isize, 0 } +default_spec! { i8, 0 } +default_spec! { i16, 0 } +default_spec! { i32, 0 } +default_spec! { i64, 0 } +default_spec! { i128, 0 } + +default_spec! { f32, 0.0f32 } +default_spec! { f64, 0.0f64 } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs new file mode 100644 index 00000000000..ddbe082b4bd --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -0,0 +1,10 @@ +pub mod default; +pub mod option; +pub mod result; +pub mod clone; + +// NOTE: specs marked with FUTURE are not fully expressible yet (in a clean way). +// They are due to be revised later as features are added. + +pub use clone::SnapshotEqualClone; +pub use default::PureDefault; diff --git a/prusti-contracts/prusti-contracts/src/core_spec/option.rs b/prusti-contracts/prusti-contracts/src/core_spec/option.rs new file mode 100644 index 00000000000..5f6a8fa8737 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/option.rs @@ -0,0 +1,210 @@ +use crate::*; + +#[allow(unused_imports)] +use core::pin::Pin; +#[allow(unused_imports)] +use core::ops::{Deref, DerefMut}; +#[allow(unused_imports)] +use core::option::*; + +// NOTE: these are all non-nightly methods without trait bounds as of 2022-09 + +// FUTURE(unsupported_constants): reference None as a constant where it makes sense, rather than .is_none() +#[extern_spec] +impl Option { + #[pure] + #[ensures(result == matches!(self, Some(_)))] + fn is_some(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, None))] + fn is_none(&self) -> bool; + + #[ensures(result.is_some() == self.is_some())] + // FUTURE(refs): ensure identity matches + //#[ensures(match self { + // Some(v) => *result.unwrap() == v, + // None => result.is_none(), + //})] + fn as_ref(&self) -> Option<&T>; + + #[ensures(result.is_some() == self.is_some())] + // FUTURE(refs): ensure identity matches + fn as_mut(&mut self) -> Option<&mut T>; + + #[ensures(result.is_some() == self.is_some())] + // FUTURE(refs): ensure identity matches + fn as_pin_ref(self: Pin<&Option>) -> Option>; + + #[ensures(result.is_some() == self.is_some())] + // FUTURE(refs): ensure identity matches + fn as_pin_mut(self: Pin<&mut Option>) -> Option>; + + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + fn expect(self, msg: &str) -> T; + + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + fn unwrap(self) -> T; + + #[ensures(match old(self) { + Some(v) => result === v, + None => result === default, + })] + fn unwrap_or(self, default: T) -> T; + + #[ensures(match old(self) { + Some(v) => result === v, + None => true, + })] + // FUTURE(calls): describe that the function is called if none + fn unwrap_or_else(self, f: F) -> T + where + F: FnOnce() -> T; + + #[ensures(old(&self).is_none() || old(self) === Some(result))] + #[ghost_constraint(T: super::default::PureDefault, [ + ensures(result === match old(self) { + Some(v) => v, + None => Default::default(), + }) + ])] + // FUTURE(calls): describe as call to Default::default rather than PureDefault + fn unwrap_or_default(self) -> T + where + T: Default; + + #[requires(self.is_some())] + #[ensures(old(self) === Some(result))] + unsafe fn unwrap_unchecked(self) -> T; + + #[ensures(result.is_some() == old(self).is_some())] + // FUTURE(calls): describe that and how the function is called if some, and that its result is returned + fn map(self, f: F) -> Option + where + F: FnOnce(T) -> U; + + #[ensures(old(self).is_none() ==> result === default)] + // FUTURE(calls): describe that and how the function is called if some, and that its result is returned + fn map_or(self, default: U, f: F) -> U + where + F: FnOnce(T) -> U; + + // FUTURE(calls): describe all function calls involved here and how they affect the result + fn map_or_else(self, default: D, f: F) -> U + where + D: FnOnce() -> U, + F: FnOnce(T) -> U; + + #[ensures(result === match old(self) { + Some(v) => Ok(v), + None => Err(err), + })] + fn ok_or(self, err: E) -> Result; + + #[ensures(match old(self) { + Some(v) => result === Ok(v), + None => result.is_err(), + })] + // FUTURE(calls): describe call to error function if none, and that its result is returned + fn ok_or_else(self, err: F) -> Result + where + F: FnOnce() -> E; + + #[ensures(self.is_some() == result.is_some())] + // FUTURE(refs): describe using PureDeref or call description if available + // FUTURE(calls): describe as call to Deref::deref if some + fn as_deref(&self) -> Option<&::Target> + where + T: Deref; + + #[ensures(self.is_some() == result.is_some())] + // FUTURE(calls): describe as call to DerefMut::deref_mut if some + fn as_deref_mut(&mut self) -> Option<&mut ::Target> + where + T: DerefMut; + + // FUTURE(iterators): describe what the iterator will yield + fn iter(&self) -> Iter<'_, T>; + + // FUTURE(iterators): describe what the iterator will yield + fn iter_mut(&mut self) -> IterMut<'_, T>; + + #[ensures(match old(self) { + Some(_) => result === old(optb), + None => result.is_none(), + })] + fn and(self, optb: Option) -> Option; + + #[ensures(old(self).is_none() ==> result.is_none())] + // FUTURE(calls): describe call to function if some, and that its result is returned + fn and_then(self, f: F) -> Option + where + F: FnOnce(T) -> Option; + + #[ensures(match old(self) { + Some(v) => result.is_none() || result === Some(v), + None => result.is_none(), + })] + // FUTURE(calls): describe call to function if some, and how it affects result + fn filter

(self, predicate: P) -> Option + where + P: FnOnce(&T) -> bool; + + #[ensures(result === match old(self) { + Some(v) => Some(v), + None => old(optb), + })] + fn or(self, optb: Option) -> Option; + + #[ensures(old(self).is_some() ==> result.is_some())] + // FUTURE(calls): describe call to function if none, and that its result is returned + fn or_else(self, f: F) -> Option + where + F: FnOnce() -> Option; + + #[ensures(result === match (old(self), old(optb)) { + (Some(v0), None) => Some(v0), + (None, Some(v0)) => Some(v0), + _ => None, + })] + fn xor(self, optb: Option) -> Option; + + #[ensures(*result === value)] + #[after_expiry(snap(self) === Some(before_expiry(snap(result))))] + fn insert(&mut self, value: T) -> &mut T; + + #[ensures(match old(self) { + Some(v) => *result === v, + None => *result === value, + })] + #[after_expiry(snap(self) === Some(before_expiry(snap(result))))] + fn get_or_insert(&mut self, value: T) -> &mut T; + + #[ensures(match old(self) { + Some(v) => *result === v, + None => true, + })] + #[after_expiry(snap(self) === Some(before_expiry(snap(result))))] + // FUTURE(calls): describe call to function if none, and that its result is returned and placed in the option + fn get_or_insert_with(&mut self, f: F) -> &mut T + where + F: FnOnce() -> T; + + #[ensures(result === old(snap(self)))] + #[ensures(self.is_none())] + fn take(&mut self) -> Option; + + #[ensures(result === old(snap(self)))] + #[ensures(*self === Some(value))] + fn replace(&mut self, value: T) -> Option; + + #[ensures(match (old(self), old(other)) { + (Some(v0), Some(v1)) => result === Some((v0, v1)), + _ => result.is_none(), + })] + fn zip(self, other: Option) -> Option<(T, U)>; + + // TODO: specific methods depending on trait bounds? flatten especially might be useful +} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs new file mode 100644 index 00000000000..2eca56bd3b3 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -0,0 +1,181 @@ +use crate::*; + +#[allow(unused_imports)] +use core::fmt::Debug; +#[allow(unused_imports)] +use core::ops::{Deref, DerefMut}; +#[allow(unused_imports)] +use core::result::*; + +#[extern_spec] +impl Result { + // TODO: already in prusti-contracts + #[pure] + #[ensures(result == matches!(self, Ok(_)))] + fn is_ok(&self) -> bool; + + #[pure] + #[ensures(result == matches!(self, Err(_)))] + fn is_err(&self) -> bool; + + #[ensures(result === match old(self) { + Ok(v) => Some(v), + Err(_) => None, + })] + fn ok(self) -> Option; + + #[ensures(result === match old(self) { + Ok(_) => None, + Err(e) => Some(e), + })] + fn err(self) -> Option; + + #[ensures(result.is_ok() == self.is_ok())] + // FUTURE(refs): ensure identity matches + fn as_ref(&self) -> Result<&T, &E>; + + #[ensures(result.is_ok() == old(self).is_ok())] + // FUTURE(refs): ensure identity matches & result affects original + fn as_mut(&mut self) -> Result<&mut T, &mut E>; + + #[ensures(result.is_ok() == old(self).is_ok())] + // FUTURE(calls): describe that and how the function is called if ok, and that its result is returned + fn map(self, op: F) -> Result + where + F: FnOnce(T) -> U; + + #[ensures(old(self).is_err() ==> result === default)] + // FUTURE(calls): describe that and how the function is called if ok, and that its result is returned + fn map_or(self, default: U, op: F) -> U + where + F: FnOnce(T) -> U; + + // FUTURE(calls): describe all function calls involved here and how they affect the result + fn map_or_else(self, default: D, f: F) -> U + where + D: FnOnce(E) -> U, + F: FnOnce(T) -> U; + + #[ensures(match old(self) { + Ok(v) => result === Ok(v), + Err(_) => result.is_err(), + })] + // FUTURE(calls): describe that and how the function is called if err, and that its result is returned + fn map_err(self, op: O) -> Result + where + O: FnOnce(E) -> F; + + #[ensures(match self { + Ok(v) => result.is_ok(), + Err(e) => result === Err(e), + })] + // FUTURE(calls): describe as call to Deref::deref if some + // FUTURE(refs): describe transformation of ok value and error not changing + fn as_deref(&self) -> Result<&::Target, &E> + where + T: Deref; + + #[ensures(result.is_ok() == old(self).is_ok())] + // FUTURE(calls): describe as call to Deref::deref if some + // FUTURE(refs): describe transformation of ok value and error not changing + fn as_deref_mut(&mut self) -> Result<&mut ::Target, &mut E> + where + T: DerefMut; + + // FUTURE(iterators): describe what the iterator will yield + fn iter(&self) -> Iter<'_, T>; + + // FUTURE(iterators): describe what the iterator will yield + fn iter_mut(&mut self) -> IterMut<'_, T>; + + #[requires(self.is_ok())] + #[ensures(old(self) === Ok(result))] + fn expect(self, msg: &str) -> T + where + E: Debug; + + #[requires(self.is_ok())] + #[ensures(old(self) === Ok(result))] + fn unwrap(self) -> T + where + E: Debug; + + #[ensures(old(&self).is_err() || old(self) === Ok(result))] + #[ghost_constraint(T: super::default::PureDefault, [ + ensures(result === match old(self) { + Ok(v) => v, + Err(_) => T::default(), + }) + ])] + // FUTURE(calls): describe as call to Default::default if err + fn unwrap_or_default(self) -> T + where + T: Default; + + #[requires(self.is_err())] + #[ensures(old(self) === Err(result))] + fn expect_err(self, msg: &str) -> E + where + T: Debug; + + #[requires(self.is_err())] + #[ensures(old(self) === Err(result))] + fn unwrap_err(self) -> E + where + T: Debug; + + #[ensures(result === match old(self) { + Ok(v) => old(res), + Err(e) => Err(e), + })] + fn and(self, res: Result) -> Result; + + #[ensures(match old(self) { + Ok(v) => true, + Err(e) => result === Err(e), + })] + // FUTURE(calls): describe call to function if ok, and that its result is returned + fn and_then(self, f: F) -> Result + where + F: FnOnce(T) -> Result; + + #[ensures(result === match old(self) { + Ok(v) => Ok(v), + Err(_) => old(res), + })] + fn or(self, res: Result) -> Result; + + #[ensures(match old(self) { + Ok(v) => result === Ok(v), + Err(_) => true, + })] + // FUTURE(calls): describe call to function if err, and that its result is returned + fn or_else(self, op: O) -> Result + where + O: FnOnce(E) -> Result; + + #[ensures(result === match old(self) { + Ok(v) => v, + Err(_) => default, + })] + fn unwrap_or(self, default: T) -> T; + + #[ensures(match old(self) { + Ok(v) => result === v, + Err(_) => true, + })] + // FUTURE(calls): describe that the function is called if err + fn unwrap_or_else(self, op: F) -> T + where + F: FnOnce(E) -> T; + + #[requires(self.is_ok())] + #[ensures(old(self) === Ok(result))] + unsafe fn unwrap_unchecked(self) -> T; + + #[requires(self.is_err())] + #[ensures(old(self) === Err(result))] + unsafe fn unwrap_err_unchecked(self) -> E; + + // TODO: specific methods depending on trait bounds +} diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index 6fe66caae8f..0a9333f4dca 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -1,4 +1,9 @@ #![no_std] +#![feature(auto_traits)] +#![feature(negative_impls)] + +// this is present even when compiling outside prusti to enable (negatively) implementing traits used for better specs +pub mod core_spec; /// A macro for writing a precondition on a function. pub use prusti_contracts_proc_macros::requires; @@ -105,9 +110,6 @@ mod private { } } -#[cfg(feature = "prusti")] -pub mod core_spec; - #[cfg(feature = "prusti")] mod private { use core::{marker::PhantomData, ops::*}; From 33cb190791b5c98370728ec7b7a08d2ccde15b97 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 07:50:58 +0100 Subject: [PATCH 02/43] add specs for try trait --- .../prusti-contracts/src/core_spec/ops/try.rs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs new file mode 100644 index 00000000000..7f6b06f6cce --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs @@ -0,0 +1,34 @@ +use crate::*; + +#[allow(unused_imports)] +use core::ops::*; +#[allow(unused_imports)] +use std::convert::Infallible; + +#[extern_spec] +impl Try for Result { + #[ensures(result === Ok(output))] + fn from_output(output: T) -> Self; + + #[ensures(match old(self) { + Ok(output) => result === ControlFlow::Continue(output), + Err(error) => result === ControlFlow::Break(Err(error)), + })] + fn branch(self) -> ControlFlow, T>; +} + +#[extern_spec] +impl Try for Option { + #[ensures(result === Some(output))] + fn from_output(output: T) -> Self; + + #[ensures(match old(self) { + Some(output) => result === ControlFlow::Continue(output), + //None => result === ControlFlow::Break(None), + None => match result { + ControlFlow::Break(residual) => residual.is_none(), + _ => false, + }, + })] + fn branch(self) -> ControlFlow, T>; +} From b9700a8c64b15d1010308e8b3492c20012987407 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 17:10:39 +0100 Subject: [PATCH 03/43] specify some known sizes & alignments --- .../prusti-contracts/src/core_spec/mem.rs | 62 +++++++++++++++++++ .../prusti-contracts/src/core_spec/mod.rs | 1 + 2 files changed, 63 insertions(+) create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/mem.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs new file mode 100644 index 00000000000..53d83f45b3a --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -0,0 +1,62 @@ +use crate::*; + +#[extern_spec] +mod core { + mod mem { + use crate::*; + + #[pure] + #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::size())])] + fn size_of() -> usize; + + #[pure] + #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::align())])] + fn align_of() -> usize; + } +} + +pub trait KnownSize { + #[pure] + fn size() -> usize; + + #[pure] + fn align() -> usize; +} + +macro_rules! known_size_spec { + ($t:ty, $size:expr, $align:expr) => { + #[refine_trait_spec] + impl KnownSize for $t { + #[pure] + #[ensures(result == $size)] + fn size() -> usize { + $size + } + + #[pure] + #[ensures(result == $align)] + fn align() -> usize { + $align + } + } + }; +} + +known_size_spec!(bool, 1, 1); + +known_size_spec!(i8, 1, 1); +known_size_spec!(i16, 2, 2); +known_size_spec!(i32, 4, 4); +known_size_spec!(i64, 8, 8); +known_size_spec!(i128, 16, 16); + +known_size_spec!(u8, 1, 1); +known_size_spec!(u16, 2, 2); +known_size_spec!(u32, 4, 4); +known_size_spec!(u64, 8, 8); +known_size_spec!(u128, 16, 16); + +known_size_spec!(f32, 4, 4); +known_size_spec!(f64, 8, 8); + +// usize/isize are not specified exactly because they are platform-dependent and programmers should not depend on their specific values anyway. diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index ddbe082b4bd..35c186de442 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -2,6 +2,7 @@ pub mod default; pub mod option; pub mod result; pub mod clone; +pub mod mem; // NOTE: specs marked with FUTURE are not fully expressible yet (in a clean way). // They are due to be revised later as features are added. From 1a0a6db6fb999184cf8a5267439668305b5fdeed Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 17:12:39 +0100 Subject: [PATCH 04/43] specify swap --- prusti-contracts/prusti-contracts/src/core_spec/mem.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 53d83f45b3a..581e3ebddb3 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -12,6 +12,9 @@ mod core { #[pure] #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::align())])] fn align_of() -> usize; + + #[ensures(*x === old(snap(y)) && *y === old(snap(x)))] + fn swap(x: &mut T, y: &mut T); } } From 453ebc0a05c26f011527fdf3c2fb12f29582c0ab Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 17:17:12 +0100 Subject: [PATCH 05/43] formatting --- .../prusti-contracts/src/core_spec/clone.rs | 4 ++-- .../prusti-contracts/src/core_spec/default.rs | 2 +- .../prusti-contracts/src/core_spec/mem.rs | 12 +++++------ .../prusti-contracts/src/core_spec/ops/try.rs | 20 +++++++++---------- .../prusti-contracts/src/core_spec/option.rs | 10 +++++----- .../prusti-contracts/src/core_spec/result.rs | 6 +++--- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs index 927d1ddc1da..6ec852d986d 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs @@ -2,10 +2,10 @@ use crate::*; #[extern_spec] trait Clone { - #[ghost_constraint(Self: SnapshotEqualClone, [ + #[ghost_constraint(Self: SnapshotEqualClone, [ ensures(result === self), ])] - fn clone(&self) -> Self; + fn clone(&self) -> Self; } /// Specifies that `Clone::clone`, if implemented, preserves snapshot equality (`===`). diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index ab2e96f3938..b4d1063b4c2 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -7,7 +7,7 @@ trait Default { } /// Specifies that `Default::default`, if implemented, is a pure method, allowing its usage in specs. -/// +/// /// Does not apply to types that do not implement `Copy`, since pure methods can only involve `Copy` types. pub auto trait PureDefault {} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 581e3ebddb3..6a8573191f2 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -4,17 +4,17 @@ use crate::*; mod core { mod mem { use crate::*; - + #[pure] #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::size())])] fn size_of() -> usize; - + #[pure] #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::align())])] fn align_of() -> usize; - - #[ensures(*x === old(snap(y)) && *y === old(snap(x)))] - fn swap(x: &mut T, y: &mut T); + + #[ensures(*x === old(snap(y)) && *y === old(snap(x)))] + fn swap(x: &mut T, y: &mut T); } } @@ -35,7 +35,7 @@ macro_rules! known_size_spec { fn size() -> usize { $size } - + #[pure] #[ensures(result == $align)] fn align() -> usize { diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs index 7f6b06f6cce..633611d626a 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs @@ -7,22 +7,22 @@ use std::convert::Infallible; #[extern_spec] impl Try for Result { - #[ensures(result === Ok(output))] - fn from_output(output: T) -> Self; - - #[ensures(match old(self) { + #[ensures(result === Ok(output))] + fn from_output(output: T) -> Self; + + #[ensures(match old(self) { Ok(output) => result === ControlFlow::Continue(output), Err(error) => result === ControlFlow::Break(Err(error)), })] - fn branch(self) -> ControlFlow, T>; + fn branch(self) -> ControlFlow, T>; } #[extern_spec] impl Try for Option { - #[ensures(result === Some(output))] - fn from_output(output: T) -> Self; - - #[ensures(match old(self) { + #[ensures(result === Some(output))] + fn from_output(output: T) -> Self; + + #[ensures(match old(self) { Some(output) => result === ControlFlow::Continue(output), //None => result === ControlFlow::Break(None), None => match result { @@ -30,5 +30,5 @@ impl Try for Option { _ => false, }, })] - fn branch(self) -> ControlFlow, T>; + fn branch(self) -> ControlFlow, T>; } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/option.rs b/prusti-contracts/prusti-contracts/src/core_spec/option.rs index 5f6a8fa8737..944f9449a87 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/option.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/option.rs @@ -1,11 +1,11 @@ use crate::*; -#[allow(unused_imports)] -use core::pin::Pin; #[allow(unused_imports)] use core::ops::{Deref, DerefMut}; #[allow(unused_imports)] use core::option::*; +#[allow(unused_imports)] +use core::pin::Pin; // NOTE: these are all non-nightly methods without trait bounds as of 2022-09 @@ -195,16 +195,16 @@ impl Option { #[ensures(result === old(snap(self)))] #[ensures(self.is_none())] fn take(&mut self) -> Option; - + #[ensures(result === old(snap(self)))] #[ensures(*self === Some(value))] fn replace(&mut self, value: T) -> Option; - + #[ensures(match (old(self), old(other)) { (Some(v0), Some(v1)) => result === Some((v0, v1)), _ => result.is_none(), })] fn zip(self, other: Option) -> Option<(T, U)>; - + // TODO: specific methods depending on trait bounds? flatten especially might be useful } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index 2eca56bd3b3..778d4b01c2b 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -101,7 +101,7 @@ impl Result { E: Debug; #[ensures(old(&self).is_err() || old(self) === Ok(result))] - #[ghost_constraint(T: super::default::PureDefault, [ + #[ghost_constraint(T: super::default::PureDefault, [ ensures(result === match old(self) { Ok(v) => v, Err(_) => T::default(), @@ -172,10 +172,10 @@ impl Result { #[requires(self.is_ok())] #[ensures(old(self) === Ok(result))] unsafe fn unwrap_unchecked(self) -> T; - + #[requires(self.is_err())] #[ensures(old(self) === Err(result))] unsafe fn unwrap_err_unchecked(self) -> E; - + // TODO: specific methods depending on trait bounds } From 7499fac455a8818bf5fa652b7ca8727409aecf38 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 17:19:43 +0100 Subject: [PATCH 06/43] remove invalid extern spec body --- prusti-contracts/prusti-contracts/src/core_spec/default.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index b4d1063b4c2..55bcb40e7c2 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -18,9 +18,7 @@ macro_rules! default_spec { impl Default for $t { #[pure] #[ensures(result == $v)] - fn default() -> Self { - $v - } + fn default() -> Self; } }; } From daa381f5dff2f4993577926006b90c5b21667474 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 17:25:28 +0100 Subject: [PATCH 07/43] remove unnecessary specs on pure functions --- prusti-contracts/prusti-contracts/src/core_spec/mem.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 6a8573191f2..3409315e954 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -31,13 +31,11 @@ macro_rules! known_size_spec { #[refine_trait_spec] impl KnownSize for $t { #[pure] - #[ensures(result == $size)] fn size() -> usize { $size } #[pure] - #[ensures(result == $align)] fn align() -> usize { $align } From e15681b6e0e07a5eee26afa1976451622b1e1443 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 24 Nov 2022 17:30:08 +0100 Subject: [PATCH 08/43] remove resolved TODO --- prusti-contracts/prusti-contracts/src/core_spec/result.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index 778d4b01c2b..9a5b4b94563 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -9,7 +9,6 @@ use core::result::*; #[extern_spec] impl Result { - // TODO: already in prusti-contracts #[pure] #[ensures(result == matches!(self, Ok(_)))] fn is_ok(&self) -> bool; From 4f34396eaa48022b0c6c912a52f0aeb782d75206 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Fri, 25 Nov 2022 07:08:31 +0100 Subject: [PATCH 09/43] attribute formatting rustfmt can't handle this --- .../prusti-contracts/src/core_spec/clone.rs | 4 +- .../prusti-contracts/src/core_spec/ops/try.rs | 20 +++--- .../prusti-contracts/src/core_spec/result.rs | 64 +++++++++---------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs index 6ec852d986d..5faa37179d3 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs @@ -3,8 +3,8 @@ use crate::*; #[extern_spec] trait Clone { #[ghost_constraint(Self: SnapshotEqualClone, [ - ensures(result === self), - ])] + ensures(result === self), + ])] fn clone(&self) -> Self; } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs index 633611d626a..346c020c790 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs @@ -11,9 +11,9 @@ impl Try for Result { fn from_output(output: T) -> Self; #[ensures(match old(self) { - Ok(output) => result === ControlFlow::Continue(output), - Err(error) => result === ControlFlow::Break(Err(error)), - })] + Ok(output) => result === ControlFlow::Continue(output), + Err(error) => result === ControlFlow::Break(Err(error)), + })] fn branch(self) -> ControlFlow, T>; } @@ -23,12 +23,12 @@ impl Try for Option { fn from_output(output: T) -> Self; #[ensures(match old(self) { - Some(output) => result === ControlFlow::Continue(output), - //None => result === ControlFlow::Break(None), - None => match result { - ControlFlow::Break(residual) => residual.is_none(), - _ => false, - }, - })] + Some(output) => result === ControlFlow::Continue(output), + //None => result === ControlFlow::Break(None), + None => match result { + ControlFlow::Break(residual) => residual.is_none(), + _ => false, + }, + })] fn branch(self) -> ControlFlow, T>; } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index 9a5b4b94563..c129e534e04 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -18,15 +18,15 @@ impl Result { fn is_err(&self) -> bool; #[ensures(result === match old(self) { - Ok(v) => Some(v), - Err(_) => None, - })] + Ok(v) => Some(v), + Err(_) => None, + })] fn ok(self) -> Option; #[ensures(result === match old(self) { - Ok(_) => None, - Err(e) => Some(e), - })] + Ok(_) => None, + Err(e) => Some(e), + })] fn err(self) -> Option; #[ensures(result.is_ok() == self.is_ok())] @@ -56,18 +56,18 @@ impl Result { F: FnOnce(T) -> U; #[ensures(match old(self) { - Ok(v) => result === Ok(v), - Err(_) => result.is_err(), - })] + Ok(v) => result === Ok(v), + Err(_) => result.is_err(), + })] // FUTURE(calls): describe that and how the function is called if err, and that its result is returned fn map_err(self, op: O) -> Result where O: FnOnce(E) -> F; #[ensures(match self { - Ok(v) => result.is_ok(), - Err(e) => result === Err(e), - })] + Ok(v) => result.is_ok(), + Err(e) => result === Err(e), + })] // FUTURE(calls): describe as call to Deref::deref if some // FUTURE(refs): describe transformation of ok value and error not changing fn as_deref(&self) -> Result<&::Target, &E> @@ -101,11 +101,11 @@ impl Result { #[ensures(old(&self).is_err() || old(self) === Ok(result))] #[ghost_constraint(T: super::default::PureDefault, [ - ensures(result === match old(self) { - Ok(v) => v, - Err(_) => T::default(), - }) - ])] + ensures(result === match old(self) { + Ok(v) => v, + Err(_) => T::default(), + }) + ])] // FUTURE(calls): describe as call to Default::default if err fn unwrap_or_default(self) -> T where @@ -124,39 +124,39 @@ impl Result { T: Debug; #[ensures(result === match old(self) { - Ok(v) => old(res), - Err(e) => Err(e), - })] + Ok(v) => old(res), + Err(e) => Err(e), + })] fn and(self, res: Result) -> Result; #[ensures(match old(self) { - Ok(v) => true, - Err(e) => result === Err(e), - })] + Ok(v) => true, + Err(e) => result === Err(e), + })] // FUTURE(calls): describe call to function if ok, and that its result is returned fn and_then(self, f: F) -> Result where F: FnOnce(T) -> Result; #[ensures(result === match old(self) { - Ok(v) => Ok(v), - Err(_) => old(res), - })] + Ok(v) => Ok(v), + Err(_) => old(res), + })] fn or(self, res: Result) -> Result; #[ensures(match old(self) { - Ok(v) => result === Ok(v), - Err(_) => true, - })] + Ok(v) => result === Ok(v), + Err(_) => true, + })] // FUTURE(calls): describe call to function if err, and that its result is returned fn or_else(self, op: O) -> Result where O: FnOnce(E) -> Result; #[ensures(result === match old(self) { - Ok(v) => v, - Err(_) => default, - })] + Ok(v) => v, + Err(_) => default, + })] fn unwrap_or(self, default: T) -> T; #[ensures(match old(self) { From 81ae3aeeb4618ee25fa1dad2e16325f114d28894 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Fri, 25 Nov 2022 07:12:21 +0100 Subject: [PATCH 10/43] add shorthand for when size = alignment --- .../prusti-contracts/src/core_spec/mem.rs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 3409315e954..c07eda61fd9 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -41,23 +41,26 @@ macro_rules! known_size_spec { } } }; + ($t:ty, $size:expr) => { + known_size_spec!($t, $size, $size); + }; } -known_size_spec!(bool, 1, 1); +known_size_spec!(bool, 1); -known_size_spec!(i8, 1, 1); -known_size_spec!(i16, 2, 2); -known_size_spec!(i32, 4, 4); -known_size_spec!(i64, 8, 8); -known_size_spec!(i128, 16, 16); +known_size_spec!(i8, 1); +known_size_spec!(i16, 2); +known_size_spec!(i32, 4); +known_size_spec!(i64, 8); +known_size_spec!(i128, 16); -known_size_spec!(u8, 1, 1); -known_size_spec!(u16, 2, 2); -known_size_spec!(u32, 4, 4); -known_size_spec!(u64, 8, 8); -known_size_spec!(u128, 16, 16); +known_size_spec!(u8, 1); +known_size_spec!(u16, 2); +known_size_spec!(u32, 4); +known_size_spec!(u64, 8); +known_size_spec!(u128, 16); -known_size_spec!(f32, 4, 4); -known_size_spec!(f64, 8, 8); +known_size_spec!(f32, 4); +known_size_spec!(f64, 8); // usize/isize are not specified exactly because they are platform-dependent and programmers should not depend on their specific values anyway. From 99afaad6924e581765fef96cf1212311272119d1 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Fri, 25 Nov 2022 07:12:46 +0100 Subject: [PATCH 11/43] specify size of usize/isize --- .../prusti-contracts/src/core_spec/mem.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index c07eda61fd9..46df87f2c54 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -63,4 +63,17 @@ known_size_spec!(u128, 16); known_size_spec!(f32, 4); known_size_spec!(f64, 8); -// usize/isize are not specified exactly because they are platform-dependent and programmers should not depend on their specific values anyway. +#[cfg(target_pointer_width = "16")] +known_size_spec!(usize, 2); +#[cfg(target_pointer_width = "16")] +known_size_spec!(isize, 2); + +#[cfg(target_pointer_width = "32")] +known_size_spec!(usize, 4); +#[cfg(target_pointer_width = "32")] +known_size_spec!(isize, 4); + +#[cfg(target_pointer_width = "64")] +known_size_spec!(usize, 8); +#[cfg(target_pointer_width = "64")] +known_size_spec!(isize, 8); From 40f5c38a9cb4f7c8d9c0539731dbe47a00a2c1c8 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Fri, 25 Nov 2022 07:46:20 +0100 Subject: [PATCH 12/43] ever more formatting --- prusti-contracts/prusti-contracts/src/core_spec/mem.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 46df87f2c54..37e9073eae9 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -42,8 +42,8 @@ macro_rules! known_size_spec { } }; ($t:ty, $size:expr) => { - known_size_spec!($t, $size, $size); - }; + known_size_spec!($t, $size, $size); + }; } known_size_spec!(bool, 1); From 0f86c8769f1cc20917dab1157e2b192aa90022aa Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 16 Jan 2023 13:12:12 +0100 Subject: [PATCH 13/43] update specs to use merged improvements --- .../prusti-contracts/src/core_spec/clone.rs | 2 +- .../prusti-contracts/src/core_spec/default.rs | 2 +- .../prusti-contracts/src/core_spec/mem.rs | 24 ++++++++----------- .../prusti-contracts/src/core_spec/option.rs | 2 +- .../prusti-contracts/src/core_spec/result.rs | 2 +- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs index 5faa37179d3..c8cb089e499 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/clone.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/clone.rs @@ -2,7 +2,7 @@ use crate::*; #[extern_spec] trait Clone { - #[ghost_constraint(Self: SnapshotEqualClone, [ + #[refine_spec(where Self: SnapshotEqualClone, [ ensures(result === self), ])] fn clone(&self) -> Self; diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index 55bcb40e7c2..ef0ce3ba25d 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -2,7 +2,7 @@ use crate::*; #[extern_spec] trait Default { - #[ghost_constraint(Self: Copy + PureDefault, [pure])] + #[refine_spec(where Self: Copy + PureDefault, [pure])] fn default() -> Self; } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 37e9073eae9..b26569d92de 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -1,21 +1,17 @@ use crate::*; -#[extern_spec] -mod core { - mod mem { - use crate::*; - - #[pure] - #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::size())])] - fn size_of() -> usize; +#[extern_spec(core)] +mod mem { + #[pure] + #[refine_spec(where T: KnownSize, [ensures(result == T::size())])] + fn size_of() -> usize; - #[pure] - #[ghost_constraint(T: core_spec::mem::KnownSize, [ensures(result == T::align())])] - fn align_of() -> usize; + #[pure] + #[refine_spec(where T: KnownSize, [ensures(result == T::align())])] + fn align_of() -> usize; - #[ensures(*x === old(snap(y)) && *y === old(snap(x)))] - fn swap(x: &mut T, y: &mut T); - } + #[ensures(*x === old(snap(y)) && *y === old(snap(x)))] + fn swap(x: &mut T, y: &mut T); } pub trait KnownSize { diff --git a/prusti-contracts/prusti-contracts/src/core_spec/option.rs b/prusti-contracts/prusti-contracts/src/core_spec/option.rs index 944f9449a87..12e48fecca6 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/option.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/option.rs @@ -64,7 +64,7 @@ impl Option { F: FnOnce() -> T; #[ensures(old(&self).is_none() || old(self) === Some(result))] - #[ghost_constraint(T: super::default::PureDefault, [ + #[refine_spec(where T: super::default::PureDefault, [ ensures(result === match old(self) { Some(v) => v, None => Default::default(), diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index c129e534e04..026926acb44 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -100,7 +100,7 @@ impl Result { E: Debug; #[ensures(old(&self).is_err() || old(self) === Ok(result))] - #[ghost_constraint(T: super::default::PureDefault, [ + #[refine_spec(where T: super::default::PureDefault, [ ensures(result === match old(self) { Ok(v) => v, Err(_) => T::default(), From fe7467de993fa38ebffec35a6388c92aed716f86 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 16 Jan 2023 15:24:14 +0100 Subject: [PATCH 14/43] include try trait specs --- prusti-contracts/prusti-contracts/src/core_spec/mod.rs | 5 +++-- prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs | 1 + prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs | 4 ++-- prusti-contracts/prusti-contracts/src/lib.rs | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index 35c186de442..850818cd0c2 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -1,8 +1,9 @@ +pub mod clone; pub mod default; +pub mod mem; +pub mod ops; pub mod option; pub mod result; -pub mod clone; -pub mod mem; // NOTE: specs marked with FUTURE are not fully expressible yet (in a clean way). // They are due to be revised later as features are added. diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs new file mode 100644 index 00000000000..9a1cf6ece15 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs @@ -0,0 +1 @@ +pub mod r#try; diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs index 346c020c790..b72e2b3cd35 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/try.rs @@ -1,9 +1,9 @@ use crate::*; #[allow(unused_imports)] -use core::ops::*; +use core::convert::Infallible; #[allow(unused_imports)] -use std::convert::Infallible; +use core::ops::*; #[extern_spec] impl Try for Result { diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index bf9a8f432c2..5befbc6c0f1 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![feature(auto_traits)] #![feature(negative_impls)] +#![feature(try_trait_v2)] // this is present even when compiling outside prusti to enable (negatively) implementing traits used for better specs pub mod core_spec; From 875cff75342a90ec01c192e9c096d2cdde1a10ac Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 16 Jan 2023 15:24:41 +0100 Subject: [PATCH 15/43] specify binop reference forwarding --- .../prusti-contracts/src/core_spec/ops/mod.rs | 1 + .../src/core_spec/ops/ref_forwarding.rs | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/ops/ref_forwarding.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs index 9a1cf6ece15..5118fe75961 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs @@ -1 +1,2 @@ +pub mod ref_forwarding; pub mod r#try; diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/ref_forwarding.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/ref_forwarding.rs new file mode 100644 index 00000000000..93ef0fe2c2f --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/ref_forwarding.rs @@ -0,0 +1,65 @@ +use crate::*; + +#[allow(unused_imports)] +use core::ops::*; + +macro_rules! specify_ref_op { + (impl $trait:tt fn $name:ident as $op:tt for $($t:ty)*) => ($( + #[extern_spec] + impl $trait<$t> for &$t { + #[pure] + #[ensures(result == *self $op other)] + fn $name(self, other: $t) -> $t; + } + + #[extern_spec] + impl $trait<&$t> for $t { + #[pure] + #[ensures(result == self $op *other)] + fn $name(self, other: &$t) -> $t; + } + + #[extern_spec] + impl $trait<&$t> for &$t { + #[pure] + #[ensures(result == *self $op *other)] + fn $name(self, other: &$t) -> $t; + } + )*) +} + +specify_ref_op! { impl Add fn add as + for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op! { impl Sub fn sub as - for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op! { impl Mul fn mul as * for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op! { impl Div fn div as / for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op! { impl Rem fn rem as % for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } + +// FUTURE(bitvectors): bitwise operations on non-boolean types are experimental (`encode_bitvectors` to enable). could specify: usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 +specify_ref_op! { impl BitAnd fn bitand as & for bool } +specify_ref_op! { impl BitOr fn bitor as | for bool } +specify_ref_op! { impl BitXor fn bitxor as ^ for bool } + +// FUTURE(bitvectors): left/right shifts. these will need special care since LHS and RHS may have different types + +macro_rules! specify_ref_op_assign { + (impl $trait:tt fn $name:ident as $op:tt for $($t:ty)*) => ($( + #[extern_spec] + impl $trait<&$t> for $t { + #[ensures(*self == old(*self) $op *other)] + fn $name(&mut self, other: &$t); + } + )*) +} + +specify_ref_op_assign! { impl AddAssign fn add_assign as + for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op_assign! { impl SubAssign fn sub_assign as - for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op_assign! { impl MulAssign fn mul_assign as * for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op_assign! { impl DivAssign fn div_assign as / for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } +specify_ref_op_assign! { impl RemAssign fn rem_assign as % for usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } + +// FUTURE(bitvectors): bitwise operations on non-boolean types are experimental (`encode_bitvectors` to enable). could specify: usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 +specify_ref_op_assign! { impl BitAndAssign fn bitand_assign as & for bool } +specify_ref_op_assign! { impl BitOrAssign fn bitor_assign as | for bool } +specify_ref_op_assign! { impl BitXorAssign fn bitxor_assign as ^ for bool } + +// FUTURE(bitvectors): left/right shifts. these will need special care since LHS and RHS may have different types From a39354fd459da3993f95704a3baf4dbdc450484b Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 16 Jan 2023 15:49:46 +0100 Subject: [PATCH 16/43] add compiletests for built-in specs more coming soon --- prusti-tests/tests/compiletest.rs | 5 + .../prusti_std/pass/ops_ref_forwarding.rs | 46 ++++++ prusti-tests/tests/prusti_std/pass/option.rs | 154 ++++++++++++++++++ prusti-tests/tests/prusti_std/pass/result.rs | 93 +++++++++++ 4 files changed, 298 insertions(+) create mode 100644 prusti-tests/tests/prusti_std/pass/ops_ref_forwarding.rs create mode 100644 prusti-tests/tests/prusti_std/pass/option.rs create mode 100644 prusti-tests/tests/prusti_std/pass/result.rs diff --git a/prusti-tests/tests/compiletest.rs b/prusti-tests/tests/compiletest.rs index 301f409b4bf..367470eb988 100644 --- a/prusti-tests/tests/compiletest.rs +++ b/prusti-tests/tests/compiletest.rs @@ -198,6 +198,11 @@ fn test_runner(_tests: &[&()]) { run_verification_overflow("verify_overflow", &filter); save_verification_cache(); + // Test the functionality of built-in specs. + println!("[prusti_std]"); + run_verification_no_overflow("prusti_std", &filter); + save_verification_cache(); + // Test the verifier with test cases that only partially verify due to known open issues. // The purpose of these tests is two-fold: 1. these tests help prevent potential further // regressions, because the tests also test code paths not covered by other tests; and diff --git a/prusti-tests/tests/prusti_std/pass/ops_ref_forwarding.rs b/prusti-tests/tests/prusti_std/pass/ops_ref_forwarding.rs new file mode 100644 index 00000000000..c86430c22e7 --- /dev/null +++ b/prusti-tests/tests/prusti_std/pass/ops_ref_forwarding.rs @@ -0,0 +1,46 @@ +use prusti_contracts::*; + +fn main() { + let x = 5; + let y = 4; + assert!(x + y == 9); + prusti_assert!(x + &y == 9); + assert!(x + &y == 9); + prusti_assert!(&x + y == 9); + assert!(&x + y == 9); + prusti_assert!(&x + &y == 9); + assert!(&x + &y == 9); + + assert!(x - y == 1); + prusti_assert!(x - &y == 1); + assert!(x - &y == 1); + prusti_assert!(&x - y == 1); + assert!(&x - y == 1); + prusti_assert!(&x - &y == 1); + assert!(&x - &y == 1); + + prusti_assert!(&x - &y + &y == x); + assert!(&x - &y + &y == x); +} + +fn mutation() { + let mut x = 5; + let y = 4; + x += &y; + assert!(x == 9); + x += y; + assert!(x == 13); +} + +fn bools() { + let t = true; + let f = false; + + assert!(t | f == true); + prusti_assert!(t | &f == true); + assert!(t | &f == true); + prusti_assert!(&t & f == false); + assert!(&t & f == false); + prusti_assert!(&t & &f == false); + assert!(&t & &f == false); +} diff --git a/prusti-tests/tests/prusti_std/pass/option.rs b/prusti-tests/tests/prusti_std/pass/option.rs new file mode 100644 index 00000000000..2bec58250e9 --- /dev/null +++ b/prusti-tests/tests/prusti_std/pass/option.rs @@ -0,0 +1,154 @@ +#![feature(unboxed_closures)] +#![feature(fn_traits)] + +use prusti_contracts::*; + +fn main() {} + +// split into many functions to avoid exponential branching complexity + +fn test_1() { + let some = Some(42); + let none = Option::::None; + + assert!(some.is_some()); + assert!(!some.is_none()); + assert!(!none.is_some()); + assert!(none.is_none()); + + assert!(some.as_ref().is_some()); + //let x = some.as_ref().unwrap(); + //assert!(*x == 42); + //assert!(*some.as_ref().unwrap() == 42); + assert!(none.as_ref().is_none()); + + assert!(some.unwrap() == 42); + assert!(some.expect("test") == 42); + assert!(some.unwrap_or(69) == 42); + assert!(none.unwrap_or(69) == 69); + assert!(some.unwrap_or_else(|| 69) == 42); + let _ = none.unwrap_or_else(|| 69); + assert!(some.unwrap_or_default() == 42); + assert!(none.unwrap_or_default() == 0); + unsafe { + assert!(some.unwrap_unchecked() == 42); + } +} + +fn test_2() { + let some = Some(42); + let none = Option::::None; + + assert!(some.map(|x| x + 1).is_some()); + let _ = some.map_or(69, |x| x + 1); + assert!(none.map_or(69, |x| x + 1) == 69); + let _ = some.map_or_else(|| 42, |x| x + 1); + + let ok: Result = Ok(42); + let err: Result = Err(false); + assert!(some.ok_or(false) == ok); + assert!(none.ok_or(false) == err); + assert!(some.ok_or_else(|| true) == ok); + assert!(none.ok_or_else(|| true).is_err()); +} + +fn test_3() { + let some = Some(42); + let none = Option::::None; + + assert!(some.and(some).unwrap() == 42); + assert!(some.and(none).is_none()); + assert!(none.and(some).is_none()); + assert!(none.and(none).is_none()); + + let _ = some.and_then(|v| Some(v)); + let _ = some.and_then(|_| Option::::None); + assert!(none.and_then(|v| Some(v)).is_none()); + assert!(none.and_then(|_| Option::::None).is_none()); +} + +fn test_4() { + let some = Some(42); + let none = Option::::None; + + // manual predicate + + struct Equals42; + + impl FnOnce<(&i32,)> for Equals42 { + type Output = bool; + #[trusted] + extern "rust-call" fn call_once(self, arg: (&i32,)) -> bool { + *arg.0 == 42 + } + } + + let filtered = some.filter(Equals42); + assert!(match filtered { + Some(v) => v == 42, + None => true, // can't yet prove that Equals42 succeeds + }); + assert!(none.filter(Equals42) == none); +} + +fn test_5() { + let some = Some(42); + let some_2 = Some(2); + let none = Option::::None; + + assert!(some.or(some_2) == some); + assert!(none.or(some_2) == some_2); + assert!(some.or(none) == some); + assert!(none.or(none) == none); + + assert!(some.xor(some) == none); + assert!(none.xor(some) == some); + assert!(some.xor(none) == some); + assert!(none.xor(none) == none); +} + +fn test_6() { + let some = Some(42); + let none = Option::::None; + + let mut opt = none; + let val = opt.insert(1); + assert!(*val == 1); + *val = 42; + assert!(opt == some); + + let mut opt = some; + assert!(*opt.get_or_insert(0) == 42); + assert!(opt == some); + let mut opt = none; + let val = opt.get_or_insert(0); + assert!(*val == 0); + *val += 1; + assert!(opt.unwrap() == 1); + + let mut opt = some; + assert!(*opt.get_or_insert_with(|| 0) == 42); + assert!(opt == some); + + let mut opt = none; + *opt.get_or_insert_with(|| 0) = 42; + assert!(opt == some); + + let mut opt = some; + assert!(opt.take() == some); + assert!(opt == none); + assert!(opt.take() == none); + assert!(opt == none); + + let mut opt = none; + assert!(opt.replace(42) == none); + assert!(opt == some); + assert!(opt.replace(1) == some); + assert!(opt.unwrap() == 1); + + let pair = (42, 42); + assert!(some.zip(some).unwrap() == pair); + assert!(some.zip(none).is_none()); + assert!(none.zip(some).is_none()); + assert!(none.zip(none).is_none()); +} diff --git a/prusti-tests/tests/prusti_std/pass/result.rs b/prusti-tests/tests/prusti_std/pass/result.rs new file mode 100644 index 00000000000..415c19f8827 --- /dev/null +++ b/prusti-tests/tests/prusti_std/pass/result.rs @@ -0,0 +1,93 @@ +use prusti_contracts::*; + +fn main() {} + +// split into many functions to avoid exponential branching complexity + +fn test_1() { + let ok = Result::::Ok(42); + let err = Result::::Err("test".to_string()); + + assert!(ok.is_ok()); + assert!(!ok.is_err()); + assert!(!err.is_ok()); + assert!(err.is_err()); + + let some = Some(42); + let none = Option::::None; + assert!(ok.clone().ok() == some); + assert!(err.clone().ok() == none); + + assert!(ok.as_ref().is_ok()); + assert!(err.as_ref().is_err()); +} + +fn test_2() { + let ok = Result::::Ok(42); + let err = Result::::Err(false); + + assert!(ok.map(|x| x + 1).is_ok()); + let _ = ok.map_or(69, |x| x + 1); + assert!(err.map_or(69, |x| x + 1) == 69); + let _ = ok.map_or_else(|_| 42, |x| x + 1); + assert!(ok.map_err(|x| !x) == ok); + assert!(err.map_err(|x| !x).is_err()); +} + +fn test_3() { + let ok = Result::::Ok(42); + let err = Result::::Err(false); + + assert!(ok.expect("test") == 42); + assert!(ok.unwrap() == 42); + assert!(ok.unwrap_or_default() == 42); + //assert!(err.unwrap_or_default() == 0); + assert!(err.expect_err("test") == false); + assert!(err.unwrap_err() == false); +} + +fn test_4() { + let ok = Result::::Ok(42); + let ok2 = Result::::Ok(5); + let err = Result::::Err(false); + let err2 = Result::::Err(true); + + assert!(ok.and(err2) == err2); + assert!(ok.and(ok2) == ok2); + assert!(err.and(ok2) == err); + assert!(err.and(err2) == err); + + let _ = ok.and_then(|v| Result::::Ok(v + 1)); + let _ = ok.and_then(|_| Result::::Err(true)); + assert!(err.and_then(|v| Result::::Ok(v + 1)) == err); + assert!(err.and_then(|_| Result::::Err(true)) == err); + + assert!(ok.or(err2) == ok); + assert!(ok.or(ok2) == ok); + assert!(err.or(ok2) == ok2); + assert!(err.or(err2) == err2); + + assert!(ok.or_else(|_| Result::::Ok(42)) == ok); + assert!(ok.or_else(|e| Result::::Err(e)) == ok); + let _ = err.or_else(|_| Result::::Ok(42)); + let _ = err.or_else(|e| Result::::Err(e)); +} + +fn test_5() { + let ok = Result::::Ok(42); + let err = Result::::Err(false); + + assert!(ok.unwrap_or(69) == 42); + assert!(err.unwrap_or(69) == 69); + + let _ = ok.unwrap_or_else(|_| 42); + let _ = err.unwrap_or_else(|_| 42); + + assert!(ok.unwrap_or_default() == 42); + assert!(err.unwrap_or_default() == 0); + + unsafe { + assert!(ok.unwrap_unchecked() == 42); + assert!(err.unwrap_err_unchecked() == false); + } +} From 9202b5d5ffcd36d8f7b104d490c8b7afb4b3a719 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sun, 22 Jan 2023 00:31:48 +0100 Subject: [PATCH 17/43] include generic params in ghost constraint evaluation substs need this for tuple default specs, and probably others --- prusti-viper/src/encoder/mir/specifications/constraints.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prusti-viper/src/encoder/mir/specifications/constraints.rs b/prusti-viper/src/encoder/mir/specifications/constraints.rs index 2f592b6bd9a..fdbfce5c306 100644 --- a/prusti-viper/src/encoder/mir/specifications/constraints.rs +++ b/prusti-viper/src/encoder/mir/specifications/constraints.rs @@ -186,7 +186,11 @@ mod trait_bounds { .find_trait_method_substs(context.proc_def_id, context.substs); let param_env = if let Some((_, trait_substs)) = maybe_trait_method { trace!("Applying trait substs {:?}", trait_substs); - ty::EarlyBinder(param_env).subst(env.tcx(), trait_substs) + let combined = trait_substs + .iter() + .chain(context.substs.iter()) + .collect::>(); + ty::EarlyBinder(param_env).subst(env.tcx(), &combined) } else { param_env }; From 4a2a241378e24bc1a23da0b97eff33fd3741a621 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sun, 22 Jan 2023 11:57:15 +0100 Subject: [PATCH 18/43] update tests to use included specs --- prusti-contracts/prusti-std/src/lib.rs | 13 ++++++-- .../pass/crates/ansi-term-external-call.rs | 1 + .../verify_overflow/pass/binary_search.rs | 15 ++------- .../pass/extern-spec/linked-list.rs | 22 ++----------- .../verify_overflow/pass/extern-spec/map.rs | 19 +++++------ .../pass/extern-spec/map_unchanged.rs | 17 ++++------ .../pass/extern-spec/option.rs | 26 --------------- .../verify_overflow/pass/extern-spec/set.rs | 32 ++++++------------- .../verify_overflow/pass/extern-spec/swap.rs | 8 ----- .../verify_overflow/pass/extern-spec/vec-3.rs | 20 ++---------- 10 files changed, 44 insertions(+), 129 deletions(-) diff --git a/prusti-contracts/prusti-std/src/lib.rs b/prusti-contracts/prusti-std/src/lib.rs index 5b317c1fc8d..4045ab7fe74 100644 --- a/prusti-contracts/prusti-std/src/lib.rs +++ b/prusti-contracts/prusti-std/src/lib.rs @@ -7,8 +7,15 @@ where S: ::std::hash::BuildHasher, { #[pure] - pub fn contains_key(&self, k: &Q) -> bool + #[ensures(result.is_some() == self.contains_key(k))] + pub fn get(&self, k: &Q) -> Option<&V> where - K: ::core::borrow::Borrow, - Q: ::core::hash::Hash + Eq; + K: core::borrow::Borrow + std::cmp::Eq + std::hash::Hash, + Q: core::hash::Hash + Eq; + + #[pure] + fn contains_key(&self, k: &Q) -> bool + where + K: core::borrow::Borrow + Eq + std::hash::Hash, + Q: core::hash::Hash + Eq; } diff --git a/prusti-tests/tests/verify/pass/crates/ansi-term-external-call.rs b/prusti-tests/tests/verify/pass/crates/ansi-term-external-call.rs index 99fc5219402..88127288a19 100644 --- a/prusti-tests/tests/verify/pass/crates/ansi-term-external-call.rs +++ b/prusti-tests/tests/verify/pass/crates/ansi-term-external-call.rs @@ -100,6 +100,7 @@ impl Default for Style { /// assert_eq!(false, Style::default().is_bold); /// assert_eq!("txt", Style::default().paint("txt").to_string()); /// ``` + #[pure] fn default() -> Style { Style { is_bold: false, diff --git a/prusti-tests/tests/verify_overflow/pass/binary_search.rs b/prusti-tests/tests/verify_overflow/pass/binary_search.rs index b1db1525339..02af81e0962 100644 --- a/prusti-tests/tests/verify_overflow/pass/binary_search.rs +++ b/prusti-tests/tests/verify_overflow/pass/binary_search.rs @@ -1,16 +1,5 @@ use prusti_contracts::*; -#[extern_spec] -impl std::option::Option { - #[pure] - #[ensures(matches!(*self, Some(_)) == result)] - pub fn is_some(&self) -> bool; - - #[pure] - #[ensures(self.is_some() == !result)] - pub fn is_none(&self) -> bool; -} - // cloned unwrap #[trusted] #[pure] @@ -19,7 +8,9 @@ impl std::option::Option { Some(value) => *value == result, None => unreachable!(), })] -fn option_peek(opt: &Option) -> usize { unimplemented!() } +fn option_peek(opt: &Option) -> usize { + unimplemented!() +} fn main() {} diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/linked-list.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/linked-list.rs index b42998e2fd0..7689bf60fc0 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/linked-list.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/linked-list.rs @@ -1,24 +1,6 @@ use prusti_contracts::*; use std::collections::LinkedList; -use std::option::Option; - -#[extern_spec] -impl std::option::Option { - #[pure] - #[ensures(matches!(*self, Some(_)) == result)] - pub fn is_some(&self) -> bool; - - #[pure] - #[ensures(self.is_some() == !result)] - pub fn is_none(&self) -> bool; - - #[requires(self.is_some())] - pub fn unwrap(self) -> T; - - #[requires(self.is_some())] - pub fn expect(self, msg: &str) -> T; -} /// Ghost method for LinkedList used to access an index in the LinkedList #[trusted] @@ -35,7 +17,9 @@ fn get(list: &LinkedList, index: usize) -> T { #[extern_spec] impl LinkedList - where T: Copy + PartialEq { +where + T: Copy + PartialEq, +{ #[ensures(result.is_empty())] pub fn new() -> LinkedList; diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/map.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/map.rs index 9cf4cc00adf..2ec5fa8ce0c 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/map.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/map.rs @@ -1,16 +1,14 @@ use prusti_contracts::*; #[extern_spec] -impl std::option::Option { - #[pure] - pub fn is_some(&self) -> bool; -} - -#[extern_spec] -impl std::collections::HashMap { +impl ::std::collections::hash_map::HashMap +where + K: Eq + ::core::hash::Hash, + S: ::std::hash::BuildHasher, +{ #[pure] #[ensures(result.is_some() == self.contains_key(k))] - pub fn get<'a, Q: ?Sized>(&'a self, k: &Q) -> Option<&'a V> + pub fn get(&self, k: &Q) -> Option<&V> where K: core::borrow::Borrow + std::cmp::Eq + std::hash::Hash, Q: core::hash::Hash + Eq; @@ -18,7 +16,7 @@ impl std::collections::HashMap { #[pure] fn contains_key(&self, k: &Q) -> bool where - K: core::borrow::Borrow + std::cmp::Eq + std::hash::Hash, + K: core::borrow::Borrow + Eq + std::hash::Hash, Q: core::hash::Hash + Eq; } @@ -28,5 +26,4 @@ fn go(key: u32, m: &std::collections::HashMap) { assert!(result.is_some()) } -fn main(){ -} +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/map_unchanged.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/map_unchanged.rs index 936f49dbf63..2ec5fa8ce0c 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/map_unchanged.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/map_unchanged.rs @@ -1,13 +1,11 @@ use prusti_contracts::*; #[extern_spec] -impl std::option::Option { - #[pure] - pub fn is_some(&self) -> bool; -} - -#[extern_spec] -impl std::collections::HashMap { +impl ::std::collections::hash_map::HashMap +where + K: Eq + ::core::hash::Hash, + S: ::std::hash::BuildHasher, +{ #[pure] #[ensures(result.is_some() == self.contains_key(k))] pub fn get(&self, k: &Q) -> Option<&V> @@ -18,7 +16,7 @@ impl std::collections::HashMap { #[pure] fn contains_key(&self, k: &Q) -> bool where - K: core::borrow::Borrow + std::cmp::Eq + std::hash::Hash, + K: core::borrow::Borrow + Eq + std::hash::Hash, Q: core::hash::Hash + Eq; } @@ -28,5 +26,4 @@ fn go(key: u32, m: &std::collections::HashMap) { assert!(result.is_some()) } -fn main(){ -} +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/option.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/option.rs index 73d6f556f48..760110b54ec 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/option.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/option.rs @@ -1,31 +1,5 @@ use prusti_contracts::*; -#[extern_spec] -impl std::option::Option { - #[pure] - #[ensures(matches!(*self, Some(_)) == result)] - pub fn is_some(&self) -> bool; - - #[pure] - #[ensures(self.is_some() == !result)] - pub fn is_none(&self) -> bool; - - #[requires(self.is_some())] - pub fn unwrap(self) -> T; - - pub fn unwrap_or(self, default: T) -> T; - - pub fn unwrap_or_else(self, f: F) -> T - where F: FnOnce() -> T; - - #[requires(self.is_some())] - pub fn expect(self, msg: &str) -> T; - - pub fn as_ref(&self) -> Option<&T>; - - pub fn as_mut(&mut self) -> Option<&mut T>; -} - fn main() { let mut x = Some(3); assert!(x.is_some()); diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/set.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/set.rs index 06344a1b133..1f080b9ae3e 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/set.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/set.rs @@ -1,24 +1,12 @@ use prusti_contracts::*; -use std::collections::HashSet; -use std::borrow::Borrow; -use std::hash::{BuildHasher, Hash}; -use std::cmp::Eq; -use std::option::Option; - -#[extern_spec] -impl Option { - #[pure] - #[ensures(matches!(*self, Some(_)) == result)] - pub fn is_some(&self) -> bool; - - #[pure] - #[ensures(result != self.is_some())] - pub fn is_none(&self) -> bool; - - #[requires(self.is_some())] - pub fn unwrap(self) -> T; -} +use std::{ + borrow::Borrow, + cmp::Eq, + collections::HashSet, + hash::{BuildHasher, Hash}, + option::Option, +}; #[extern_spec] impl HashSet { @@ -47,9 +35,9 @@ where { #[pure] pub fn contains(&self, value: &Q) -> bool - where - T: std::borrow::Borrow, - Q: std::hash::Hash + std::cmp::Eq; + where + T: std::borrow::Borrow, + Q: std::hash::Hash + std::cmp::Eq; #[ensures(self.len() == old(self.len()) + 1)] pub fn insert(&mut self, value: T) -> bool; diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/swap.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/swap.rs index 95098299184..ccc71005e47 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/swap.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/swap.rs @@ -4,14 +4,6 @@ use prusti_contracts::*; -#[extern_spec] -mod std { - mod mem { - #[ensures(*a == old(*b) && *b == old(*a))] - pub fn swap(a: &mut T, b: &mut T); - } -} - fn main() { let mut x = 5; let mut y = 42; diff --git a/prusti-tests/tests/verify_overflow/pass/extern-spec/vec-3.rs b/prusti-tests/tests/verify_overflow/pass/extern-spec/vec-3.rs index 42963b298b8..c8a29fcca05 100644 --- a/prusti-tests/tests/verify_overflow/pass/extern-spec/vec-3.rs +++ b/prusti-tests/tests/verify_overflow/pass/extern-spec/vec-3.rs @@ -1,28 +1,12 @@ #![feature(allocator_api)] use prusti_contracts::*; -use std::vec::Vec; -use std::option::Option; - -#[extern_spec] -impl Option { - #[pure] - #[ensures(matches!(*self, Some(_)) == result)] - pub fn is_some(&self) -> bool; - - #[pure] - #[ensures(self.is_some() ==> !result)] - #[ensures(!self.is_some() ==> result)] - pub fn is_none(&self) -> bool; - - #[requires(self.is_some())] - pub fn unwrap(self) -> T; -} +use std::{option::Option, vec::Vec}; #[extern_spec] impl Vec { #[ensures(result.len() == 0)] - fn new() -> Vec::; + fn new() -> Vec; } #[extern_spec] From b2078f243b2280ae2b452ef706036844deaebb7b Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sun, 22 Jan 2023 12:29:44 +0100 Subject: [PATCH 19/43] more spec removal --- .../refine-non-local-type-with-generics.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/prusti-tests/tests/verify_overflow/pass/trait-contracts-refinement/refine-non-local-type-with-generics.rs b/prusti-tests/tests/verify_overflow/pass/trait-contracts-refinement/refine-non-local-type-with-generics.rs index a182a62242d..22ba19eea21 100644 --- a/prusti-tests/tests/verify_overflow/pass/trait-contracts-refinement/refine-non-local-type-with-generics.rs +++ b/prusti-tests/tests/verify_overflow/pass/trait-contracts-refinement/refine-non-local-type-with-generics.rs @@ -1,17 +1,5 @@ use prusti_contracts::*; -#[extern_spec] -impl std::option::Option { - #[pure] - #[ensures(matches!(*self, Some(_)) == result)] - pub fn is_some(&self) -> bool; - - #[pure] - #[ensures(self.is_some() == ! result)] - #[ensures(matches!(*self, None) == result)] - pub fn is_none(&self) -> bool; -} - trait OptionPeeker { #[pure] #[requires(false)] @@ -33,4 +21,4 @@ impl OptionPeeker for Option { fn main() { let o = Some(5); assert!(o.peek() == 5); -} \ No newline at end of file +} From 9b3c7a1b09dab26be8d1cf41bd5189d7a1ed2311 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 23 Jan 2023 02:34:01 +0100 Subject: [PATCH 20/43] add specs for convert::From and convert::Into --- .../prusti-contracts/src/core_spec/convert.rs | 73 +++++++++++++++++++ .../prusti-contracts/src/core_spec/mod.rs | 1 + prusti-contracts/prusti-contracts/src/lib.rs | 1 + prusti-tests/tests/prusti_std/fail/convert.rs | 43 +++++++++++ prusti-tests/tests/prusti_std/pass/convert.rs | 57 +++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/convert.rs create mode 100644 prusti-tests/tests/prusti_std/fail/convert.rs create mode 100644 prusti-tests/tests/prusti_std/pass/convert.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/convert.rs b/prusti-contracts/prusti-contracts/src/core_spec/convert.rs new file mode 100644 index 00000000000..f5842eec5b7 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/convert.rs @@ -0,0 +1,73 @@ +use crate::*; + +pub auto trait PureFrom {} + +#[extern_spec] +trait From { + #[refine_spec(where Self: Copy + PureFrom, T: Copy, [pure])] + fn from(other: T) -> Self; +} + +#[extern_spec] +impl Into for T +where + U: From, +{ + // When From is pure, we can use it to specify the behavior of Into + #[refine_spec(where U: Copy + PureFrom, T: Copy, [ + pure, + ensures(result === U::from(self)) + ])] + // FUTURE(where_equality): we can specify the behavior of Into for identity conversions even if impure, but for that we need equality constraints + // #[refine_spec(where T = U, [ensures(result === self)])] + fn into(self) -> U; +} + +// identity conversion +#[extern_spec] +impl From for T { + #[ensures(result === other)] + #[refine_spec(where T: Copy, [pure])] + fn from(other: T) -> Self; +} + +// numeric conversions + +// specifies From for lossless numeric conversions using as casts +macro_rules! specify_numeric_from { + ($from:ty => $($to:ty)*) => ($( + #[extern_spec] + impl From<$from> for $to { + #[pure] + #[ensures(result == num as Self)] + fn from(num: $from) -> Self; + } + )*) +} + +specify_numeric_from!(bool => u8 i8 u16 i16 u32 i32 u64 i64 usize isize); + +specify_numeric_from!(u8 => u16 i16 u32 i32 u64 i64 usize); +specify_numeric_from!(u16 => u32 i32 u64 i64); +specify_numeric_from!(u32 => u64 i64); +specify_numeric_from!(i8 => i16 i32 i64 isize); +specify_numeric_from!(i16 => i32 i64); +specify_numeric_from!(i32 => i64); +// size is not guaranteed to be at least 32 or at most 64 bits, so not valid for many conversions one might expect + +// int-to-float conversions are exact when the int type has at most as many bits as the float's mantissa +specify_numeric_from!(u8 => f32 f64); +specify_numeric_from!(u16 => f32 f64); +specify_numeric_from!(u32 => f64); +specify_numeric_from!(i8 => f32 f64); +specify_numeric_from!(i16 => f32 f64); +specify_numeric_from!(i32 => f64); + +specify_numeric_from!(f32 => f64); + +#[cfg(version("1.26"))] +specify_numeric_from!(i16 => isize); +#[cfg(version("1.26"))] +specify_numeric_from!(u16 => usize); +#[cfg(version("1.26"))] +specify_numeric_from!(u8 => isize); diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index 850818cd0c2..dd350ca053e 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -1,4 +1,5 @@ pub mod clone; +pub mod convert; pub mod default; pub mod mem; pub mod ops; diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index 5befbc6c0f1..d2758d09bf8 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -2,6 +2,7 @@ #![feature(auto_traits)] #![feature(negative_impls)] #![feature(try_trait_v2)] +#![feature(cfg_version)] // this is present even when compiling outside prusti to enable (negatively) implementing traits used for better specs pub mod core_spec; diff --git a/prusti-tests/tests/prusti_std/fail/convert.rs b/prusti-tests/tests/prusti_std/fail/convert.rs new file mode 100644 index 00000000000..0e65ddd1cba --- /dev/null +++ b/prusti-tests/tests/prusti_std/fail/convert.rs @@ -0,0 +1,43 @@ +#![feature(negative_impls)] + +use prusti_contracts::*; + +fn main() {} + +fn numeric_conversions_1() { + let small: u8 = 41; + let x = 42; + assert!(i32::from(small) == x); //~ ERROR the asserted expression might not hold +} + +fn numeric_conversions_2() { + let small: u8 = 41; + let x = 42; + let converted: i32 = small.into(); + assert!(converted == x); //~ ERROR the asserted expression might not hold +} + +fn impure_conversion() { + let x = 42; + let converted = ImpureFrom::from(x); + assert!(converted.x == 1); + // because our from implementation is not pure, it can't be used to express the behavior of into: + let converted: ImpureFrom = x.into(); + assert!(converted.x == 1); //~ ERROR the asserted expression might not hold +} + +#[derive(Clone, Copy)] +struct ImpureFrom { + x: i32, +} + +impl !core_spec::convert::PureFrom for ImpureFrom {} + +#[refine_trait_spec] +impl From for ImpureFrom { + #[ensures(result.x == 1)] + #[trusted] + fn from(x: i32) -> Self { + Self { x } + } +} diff --git a/prusti-tests/tests/prusti_std/pass/convert.rs b/prusti-tests/tests/prusti_std/pass/convert.rs new file mode 100644 index 00000000000..342d3c89fb2 --- /dev/null +++ b/prusti-tests/tests/prusti_std/pass/convert.rs @@ -0,0 +1,57 @@ +#![feature(negative_impls)] + +use prusti_contracts::*; + +fn main() {} + +fn numeric_conversions() { + let small: u8 = 42; + let x = 42; + assert!(i32::from(small) == x); + let converted: i32 = small.into(); + assert!(converted == x); +} + +fn self_conversion() { + let foo = Foo { x: 42 }; + let converted: Foo = foo.into(); + assert!(converted.x == foo.x); +} + +#[derive(Clone, Copy)] +struct Foo { + x: i32, +} + +fn impure_conversion() { + let x = 42; + let converted = ImpureFrom::from(x); + assert!(converted.x == 1); +} + +#[derive(Clone, Copy)] +struct ImpureFrom { + x: i32, +} + +impl !core_spec::convert::PureFrom for ImpureFrom {} + +#[refine_trait_spec] +impl From for ImpureFrom { + #[ensures(result.x == 1)] + #[trusted] + fn from(x: i32) -> Self { + Self { x } + } +} + +// can't currently specify blanket Into impl for impure types +fn impure_self_conversion() { + let unique = Unique { x: 1 }; + let converted = Unique::from(unique); + assert!(converted.x == 1); +} + +struct Unique { + x: i32, +} From ed66937cf0f5f93c96cb5439d51235c7563acc60 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 23 Jan 2023 02:54:01 +0100 Subject: [PATCH 21/43] specify Default behavior for tuples --- .../prusti-contracts/src/core_spec/default.rs | 29 +++++++++++++++++++ prusti-tests/tests/prusti_std/fail/default.rs | 19 ++++++++++++ prusti-tests/tests/prusti_std/pass/default.rs | 7 +++++ 3 files changed, 55 insertions(+) create mode 100644 prusti-tests/tests/prusti_std/fail/default.rs create mode 100644 prusti-tests/tests/prusti_std/pass/default.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index ef0ce3ba25d..48d1f920468 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -43,3 +43,32 @@ default_spec! { i128, 0 } default_spec! { f32, 0.0f32 } default_spec! { f64, 0.0f64 } + +// specify behavior for tuples (have to rely on PureDefault) + +// recursive like https://github.com/rust-lang/rust/blob/a5fa99eed20a46a88c0c85eed6552a94b6656634/library/core/src/tuple.rs#L10 +macro_rules! specify_tuple_default { + (impl $( $T:ident )*) => { + #[extern_spec] + impl<$($T,)*> Default for ($($T,)*) where + $($T: Default,)* + { + #[refine_spec(where + $($T: Copy + PureDefault,)* + [ + pure, + ensures(snapshot_equality(&result, &($($T::default(),)*))), + ])] + fn default() -> Self; + } + }; + (impls $T:ident $( $U:ident )+) => { + specify_tuple_default!(impl $T $($U)+); + specify_tuple_default!(impls $($U)+); + }; + (impls $T:ident) => { + specify_tuple_default!(impl $T); + }; +} + +specify_tuple_default!(impls E D C B A Z Y X W V U T); diff --git a/prusti-tests/tests/prusti_std/fail/default.rs b/prusti-tests/tests/prusti_std/fail/default.rs new file mode 100644 index 00000000000..fd46cadaa52 --- /dev/null +++ b/prusti-tests/tests/prusti_std/fail/default.rs @@ -0,0 +1,19 @@ +use prusti_contracts::*; + +fn main() {} + +fn element0() { + let default: (Special, i32) = Default::default(); + assert!(default.0.value == 0); //~ ERROR: the asserted expression might not +} + +fn element1() { + let default: (Special, i32) = Default::default(); + assert!(default.1 == 0); //~ ERROR: the asserted expression might not hold +} + +// not Copy => Default not pure +#[derive(Default)] +struct Special { + value: i32, +} diff --git a/prusti-tests/tests/prusti_std/pass/default.rs b/prusti-tests/tests/prusti_std/pass/default.rs new file mode 100644 index 00000000000..dd54b8f51f6 --- /dev/null +++ b/prusti-tests/tests/prusti_std/pass/default.rs @@ -0,0 +1,7 @@ +use prusti_contracts::*; + +fn main() { + let default: (i32, i32) = Default::default(); + assert!(default.0 == 0); + assert!(default.1 == 0); +} From ca67140d48de8c3ad1471b5a2c6a28576c704ce8 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Tue, 24 Jan 2023 00:17:08 +0100 Subject: [PATCH 22/43] rename std tests folder --- prusti-contracts/prusti-contracts/src/core_spec/mod.rs | 1 + prusti-tests/tests/compiletest.rs | 2 +- prusti-tests/tests/{prusti_std => std}/fail/convert.rs | 2 +- prusti-tests/tests/{prusti_std => std}/fail/default.rs | 0 prusti-tests/tests/{prusti_std => std}/pass/convert.rs | 2 +- prusti-tests/tests/{prusti_std => std}/pass/default.rs | 2 +- .../tests/{prusti_std => std}/pass/ops_ref_forwarding.rs | 0 prusti-tests/tests/{prusti_std => std}/pass/option.rs | 0 prusti-tests/tests/{prusti_std => std}/pass/result.rs | 0 9 files changed, 5 insertions(+), 4 deletions(-) rename prusti-tests/tests/{prusti_std => std}/fail/convert.rs (94%) rename prusti-tests/tests/{prusti_std => std}/fail/default.rs (100%) rename prusti-tests/tests/{prusti_std => std}/pass/convert.rs (94%) rename prusti-tests/tests/{prusti_std => std}/pass/default.rs (66%) rename prusti-tests/tests/{prusti_std => std}/pass/ops_ref_forwarding.rs (100%) rename prusti-tests/tests/{prusti_std => std}/pass/option.rs (100%) rename prusti-tests/tests/{prusti_std => std}/pass/result.rs (100%) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index dd350ca053e..72922304c52 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -10,4 +10,5 @@ pub mod result; // They are due to be revised later as features are added. pub use clone::SnapshotEqualClone; +pub use convert::PureFrom; pub use default::PureDefault; diff --git a/prusti-tests/tests/compiletest.rs b/prusti-tests/tests/compiletest.rs index 367470eb988..3afffa0c87a 100644 --- a/prusti-tests/tests/compiletest.rs +++ b/prusti-tests/tests/compiletest.rs @@ -200,7 +200,7 @@ fn test_runner(_tests: &[&()]) { // Test the functionality of built-in specs. println!("[prusti_std]"); - run_verification_no_overflow("prusti_std", &filter); + run_verification_no_overflow("std", &filter); save_verification_cache(); // Test the verifier with test cases that only partially verify due to known open issues. diff --git a/prusti-tests/tests/prusti_std/fail/convert.rs b/prusti-tests/tests/std/fail/convert.rs similarity index 94% rename from prusti-tests/tests/prusti_std/fail/convert.rs rename to prusti-tests/tests/std/fail/convert.rs index 0e65ddd1cba..21a932f527e 100644 --- a/prusti-tests/tests/prusti_std/fail/convert.rs +++ b/prusti-tests/tests/std/fail/convert.rs @@ -31,7 +31,7 @@ struct ImpureFrom { x: i32, } -impl !core_spec::convert::PureFrom for ImpureFrom {} +impl !core_spec::PureFrom for ImpureFrom {} #[refine_trait_spec] impl From for ImpureFrom { diff --git a/prusti-tests/tests/prusti_std/fail/default.rs b/prusti-tests/tests/std/fail/default.rs similarity index 100% rename from prusti-tests/tests/prusti_std/fail/default.rs rename to prusti-tests/tests/std/fail/default.rs diff --git a/prusti-tests/tests/prusti_std/pass/convert.rs b/prusti-tests/tests/std/pass/convert.rs similarity index 94% rename from prusti-tests/tests/prusti_std/pass/convert.rs rename to prusti-tests/tests/std/pass/convert.rs index 342d3c89fb2..6c12fcacbfa 100644 --- a/prusti-tests/tests/prusti_std/pass/convert.rs +++ b/prusti-tests/tests/std/pass/convert.rs @@ -34,7 +34,7 @@ struct ImpureFrom { x: i32, } -impl !core_spec::convert::PureFrom for ImpureFrom {} +impl !core_spec::PureFrom for ImpureFrom {} #[refine_trait_spec] impl From for ImpureFrom { diff --git a/prusti-tests/tests/prusti_std/pass/default.rs b/prusti-tests/tests/std/pass/default.rs similarity index 66% rename from prusti-tests/tests/prusti_std/pass/default.rs rename to prusti-tests/tests/std/pass/default.rs index dd54b8f51f6..fc9582df200 100644 --- a/prusti-tests/tests/prusti_std/pass/default.rs +++ b/prusti-tests/tests/std/pass/default.rs @@ -1,7 +1,7 @@ use prusti_contracts::*; fn main() { - let default: (i32, i32) = Default::default(); + let default: (i32, u64) = Default::default(); assert!(default.0 == 0); assert!(default.1 == 0); } diff --git a/prusti-tests/tests/prusti_std/pass/ops_ref_forwarding.rs b/prusti-tests/tests/std/pass/ops_ref_forwarding.rs similarity index 100% rename from prusti-tests/tests/prusti_std/pass/ops_ref_forwarding.rs rename to prusti-tests/tests/std/pass/ops_ref_forwarding.rs diff --git a/prusti-tests/tests/prusti_std/pass/option.rs b/prusti-tests/tests/std/pass/option.rs similarity index 100% rename from prusti-tests/tests/prusti_std/pass/option.rs rename to prusti-tests/tests/std/pass/option.rs diff --git a/prusti-tests/tests/prusti_std/pass/result.rs b/prusti-tests/tests/std/pass/result.rs similarity index 100% rename from prusti-tests/tests/prusti_std/pass/result.rs rename to prusti-tests/tests/std/pass/result.rs From a5558a61ac3add01c72dc48a7900269c40801a16 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Tue, 24 Jan 2023 00:18:59 +0100 Subject: [PATCH 23/43] minor tweaks --- .../prusti-contracts/src/core_spec/convert.rs | 4 ++++ .../prusti-contracts/src/core_spec/option.rs | 10 +++++----- .../prusti-contracts/src/core_spec/result.rs | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/convert.rs b/prusti-contracts/prusti-contracts/src/core_spec/convert.rs index f5842eec5b7..b55e81856dc 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/convert.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/convert.rs @@ -1,5 +1,9 @@ use crate::*; +/// Specifies that `From::from`, if implemented, is a pure method, allowing its usage in specs. +/// This enables specifying the blanket impl of `Into` as calling `From::from`. +/// +/// Does not apply to types that do not implement `Copy`, since pure methods can only involve `Copy` types. pub auto trait PureFrom {} #[extern_spec] diff --git a/prusti-contracts/prusti-contracts/src/core_spec/option.rs b/prusti-contracts/prusti-contracts/src/core_spec/option.rs index 12e48fecca6..dbbe827ab38 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/option.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/option.rs @@ -63,7 +63,7 @@ impl Option { where F: FnOnce() -> T; - #[ensures(old(&self).is_none() || old(self) === Some(result))] + #[ensures(old(self.is_none()) || old(self) === Some(result))] #[refine_spec(where T: super::default::PureDefault, [ ensures(result === match old(self) { Some(v) => v, @@ -79,13 +79,13 @@ impl Option { #[ensures(old(self) === Some(result))] unsafe fn unwrap_unchecked(self) -> T; - #[ensures(result.is_some() == old(self).is_some())] + #[ensures(result.is_some() == old(self.is_some()))] // FUTURE(calls): describe that and how the function is called if some, and that its result is returned fn map(self, f: F) -> Option where F: FnOnce(T) -> U; - #[ensures(old(self).is_none() ==> result === default)] + #[ensures(old(self.is_none()) ==> result === default)] // FUTURE(calls): describe that and how the function is called if some, and that its result is returned fn map_or(self, default: U, f: F) -> U where @@ -137,7 +137,7 @@ impl Option { })] fn and(self, optb: Option) -> Option; - #[ensures(old(self).is_none() ==> result.is_none())] + #[ensures(old(self.is_none()) ==> result.is_none())] // FUTURE(calls): describe call to function if some, and that its result is returned fn and_then(self, f: F) -> Option where @@ -158,7 +158,7 @@ impl Option { })] fn or(self, optb: Option) -> Option; - #[ensures(old(self).is_some() ==> result.is_some())] + #[ensures(old(self.is_some()) ==> result.is_some())] // FUTURE(calls): describe call to function if none, and that its result is returned fn or_else(self, f: F) -> Option where diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index 026926acb44..b08221b41c9 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -33,17 +33,17 @@ impl Result { // FUTURE(refs): ensure identity matches fn as_ref(&self) -> Result<&T, &E>; - #[ensures(result.is_ok() == old(self).is_ok())] + #[ensures(result.is_ok() == old(self.is_ok()))] // FUTURE(refs): ensure identity matches & result affects original fn as_mut(&mut self) -> Result<&mut T, &mut E>; - #[ensures(result.is_ok() == old(self).is_ok())] + #[ensures(result.is_ok() == old(self.is_ok()))] // FUTURE(calls): describe that and how the function is called if ok, and that its result is returned fn map(self, op: F) -> Result where F: FnOnce(T) -> U; - #[ensures(old(self).is_err() ==> result === default)] + #[ensures(old(self.is_err()) ==> result === default)] // FUTURE(calls): describe that and how the function is called if ok, and that its result is returned fn map_or(self, default: U, op: F) -> U where @@ -74,7 +74,7 @@ impl Result { where T: Deref; - #[ensures(result.is_ok() == old(self).is_ok())] + #[ensures(result.is_ok() == old(self.is_ok()))] // FUTURE(calls): describe as call to Deref::deref if some // FUTURE(refs): describe transformation of ok value and error not changing fn as_deref_mut(&mut self) -> Result<&mut ::Target, &mut E> @@ -99,7 +99,7 @@ impl Result { where E: Debug; - #[ensures(old(&self).is_err() || old(self) === Ok(result))] + #[ensures(old(self.is_err()) || old(self) === Ok(result))] #[refine_spec(where T: super::default::PureDefault, [ ensures(result === match old(self) { Ok(v) => v, From 0d4866d76e5b1e53e8f39fa5cd69fcfc344163d7 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Wed, 25 Jan 2023 17:16:24 +0100 Subject: [PATCH 24/43] basic slice/vec/str/string spec framework very basic due to blockage by #1221 --- .../prusti-contracts/src/core_spec/default.rs | 24 +++++++ .../prusti-contracts/src/core_spec/mod.rs | 3 + .../src/core_spec/ops/index.rs | 10 +++ .../prusti-contracts/src/core_spec/ops/mod.rs | 1 + .../prusti-contracts/src/core_spec/slice.rs | 51 +++++++++++++++ .../prusti-contracts/src/core_spec/string.rs | 64 +++++++++++++++++++ .../prusti-contracts/src/core_spec/vec.rs | 47 ++++++++++++++ prusti-contracts/prusti-contracts/src/lib.rs | 3 + prusti-tests/tests/std/pass/default.rs | 15 +++++ prusti-tests/tests/std/pass/vec.rs | 23 +++++++ 10 files changed, 241 insertions(+) create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/slice.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/string.rs create mode 100644 prusti-contracts/prusti-contracts/src/core_spec/vec.rs create mode 100644 prusti-tests/tests/std/pass/vec.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index 48d1f920468..ff79f422203 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use crate::*; #[extern_spec] @@ -72,3 +74,25 @@ macro_rules! specify_tuple_default { } specify_tuple_default!(impls E D C B A Z Y X W V U T); + +// more specific types + +#[extern_spec] +impl Default for alloc::string::String { + #[ensures(result.is_empty())] + fn default() -> Self; +} + +#[extern_spec] +impl Default for alloc::vec::Vec { + #[refine_spec(where Self: Copy, [pure])] + #[ensures(result.is_empty())] + fn default() -> Self; +} + +#[extern_spec] +impl Default for Option { + #[refine_spec(where Self: Copy, [pure])] + #[ensures(result.is_none())] + fn default() -> Self; +} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index 72922304c52..16be7ba9ebc 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -5,6 +5,9 @@ pub mod mem; pub mod ops; pub mod option; pub mod result; +pub mod slice; +pub mod string; +pub mod vec; // NOTE: specs marked with FUTURE are not fully expressible yet (in a clean way). // They are due to be revised later as features are added. diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs new file mode 100644 index 00000000000..88dcb4b816a --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs @@ -0,0 +1,10 @@ +use crate::*; + +use core::ops::Index; + +// FUTURE(#1221): This spec is currently not usable due to issue #1221. +#[extern_spec] +trait Index { + #[pure] + fn index(&self, idx: Idx) -> &Self::Output; +} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs index 5118fe75961..01daeaeacf8 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/mod.rs @@ -1,2 +1,3 @@ +pub mod index; pub mod ref_forwarding; pub mod r#try; diff --git a/prusti-contracts/prusti-contracts/src/core_spec/slice.rs b/prusti-contracts/prusti-contracts/src/core_spec/slice.rs new file mode 100644 index 00000000000..f16d98c6923 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/slice.rs @@ -0,0 +1,51 @@ +use crate::*; + +use core::{ops::Index, slice::SliceIndex}; + +#[extern_spec] +impl [T] { + #[pure] + #[ensures(result == (self.len() == 0))] + fn is_empty(&self) -> bool; + + #[pure] + fn len(&self) -> usize; + + #[ensures(match result { + Some(x) => *x === self[0], + None => self.len() == 0, + })] + // FUTURE(refs): make statements about the returned reference + fn first(&self) -> Option<&T>; + + #[ensures(match result { + Some(x) => *x === self[0], + None => self.len() == 0, + })] + // FUTURE(refs): make statements about the returned reference + fn first_mut(&mut self) -> Option<&mut T>; + + #[ensures(match result { + Some(x) => *x === self[self.len() - 1], + None => self.len() == 0, + })] + // FUTURE(refs): make statements about the returned reference + fn last(&self) -> Option<&T>; + + #[ensures(match result { + Some(x) => *x === self[self.len() - 1], + None => self.len() == 0, + })] + // FUTURE(refs): make statements about the returned reference + fn last_mut(&mut self) -> Option<&mut T>; +} + +// FUTURE(#1221): This spec is currently not usable due to issue #1221. +#[extern_spec] +impl Index for [T] +where + I: SliceIndex<[T]>, +{ + #[pure] + fn index(&self, index: I) -> &I::Output; +} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/string.rs b/prusti-contracts/prusti-contracts/src/core_spec/string.rs new file mode 100644 index 00000000000..0d03d2d3eda --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/string.rs @@ -0,0 +1,64 @@ +use crate::*; + +use alloc::string::String; + +// FUTURE(#1221): String should forward its specs to as_str where possible, to avoid writing each spec twice. +// That doesn't currently work though, due to issue #1221 +#[extern_spec] +impl String { + #[ensures(result.is_empty())] + fn new() -> Self; + + #[pure] + #[ensures(result == (self.len() == 0))] + //#[ensures(result == self.as_str().is_empty()))] + fn is_empty(&self) -> bool; + + #[pure] + #[ensures(result.len() == self.len())] + fn as_str(&self) -> &str; + + #[pure] + //#[ensures(result == self.as_str().len())] + fn len(&self) -> usize; + + #[ensures(self.len() == 0)] + fn clear(&mut self); + + #[ensures(self.len() == old(self.len()) + 1)] + //#[ensures(self[self.len() - 1] === ch)] + //#[ensures(forall(|i: usize| i < old(self.len()) ==> self[i] === old(self[i])))] + fn push(&mut self, ch: char); +} + +#[extern_spec] +impl str { + // TODO: this should be a prusti intrinsic, like it is for slices. + #[pure] + fn len(&self) -> usize; + + #[pure] + #[ensures(result == (self.len() == 0))] + fn is_empty(&self) -> bool; +} + +// FUTURE(#1221): these specs make their methods crash due to issue #1221, so they are disabled for now. +/* +#[extern_spec] +impl ToOwned for str { + #[ensures(result.as_str() === self)] + fn to_owned(&self) -> String; +} + +#[extern_spec] +impl ToString for str { + #[ensures(result.as_str() === self)] + fn to_string(&self) -> String; +} + +#[extern_spec] +impl From<&str> for String { + #[ensures(result.as_str() === other)] + fn from(other: &str) -> Self; +} +*/ diff --git a/prusti-contracts/prusti-contracts/src/core_spec/vec.rs b/prusti-contracts/prusti-contracts/src/core_spec/vec.rs new file mode 100644 index 00000000000..0da64fa0acb --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/core_spec/vec.rs @@ -0,0 +1,47 @@ +use crate::*; + +use alloc::{alloc::Allocator, vec::Vec}; + +#[extern_spec] +impl Vec { + #[ensures(result.is_empty())] + fn new() -> Self; +} + +// FUTURE(#1221): Vec should forward its specs to as_slice where possible, to avoid writing each spec twice. +// That doesn't currently work though, due to issue #1221 +#[extern_spec] +impl Vec { + #[pure] + #[ensures(result == (self.len() == 0))] + //#[ensures(result == self.as_slice().is_empty())] + fn is_empty(&self) -> bool; + + #[pure] + //#[ensures(result == self.as_slice().len())] + fn len(&self) -> usize; + + #[pure] + #[ensures(result.len() == self.len())] + fn as_slice(&self) -> &[T]; + + #[ensures(self.is_empty())] + fn clear(&mut self); + + #[ensures(self.len() == old(self.len()) + 1)] + //#[ensures(self[self.len() - 1] === value)] + //#[ensures(forall(|i: usize| i < old(self.len()) ==> &self[i] === old(&self[i])))] + fn push(&mut self, value: T); +} + +// FUTURE(#1221): We'd like to specify the behavior of Index for Vec, but issue #1221 is currently blocking that. Once it's fixed, Prusti can be amended to rely on these specs instead of rejecting the index operation because Vec doesn't have hardcoded support. +/* +use core::{ops::Index, slice::SliceIndex}; + +#[extern_spec] +impl> Index for Vec { + #[pure] + #[ensures(*result === self.as_slice()[index])] + fn index(&self, index: I) -> &Self::Output; +} +*/ diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index d2758d09bf8..be916f7db61 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -3,6 +3,9 @@ #![feature(negative_impls)] #![feature(try_trait_v2)] #![feature(cfg_version)] +#![feature(allocator_api)] + +extern crate alloc; // to specify Vec and String without std // this is present even when compiling outside prusti to enable (negatively) implementing traits used for better specs pub mod core_spec; diff --git a/prusti-tests/tests/std/pass/default.rs b/prusti-tests/tests/std/pass/default.rs index fc9582df200..284f8c44236 100644 --- a/prusti-tests/tests/std/pass/default.rs +++ b/prusti-tests/tests/std/pass/default.rs @@ -4,4 +4,19 @@ fn main() { let default: (i32, u64) = Default::default(); assert!(default.0 == 0); assert!(default.1 == 0); + + let default: Option = Default::default(); + assert!(default.is_none()); + + let default: Vec = Default::default(); + assert!(default.is_empty()); + + let default = String::default(); + assert!(default.is_empty()); +} + +// not Copy => Default not pure +#[derive(Default)] +struct Special { + value: i32, } diff --git a/prusti-tests/tests/std/pass/vec.rs b/prusti-tests/tests/std/pass/vec.rs new file mode 100644 index 00000000000..bcb4deea794 --- /dev/null +++ b/prusti-tests/tests/std/pass/vec.rs @@ -0,0 +1,23 @@ +use prusti_contracts::*; + +fn main() { + let mut v = Vec::new(); + assert!(v.is_empty()); + assert!(v.len() == 0); + + v.push(10); + assert!(!v.is_empty()); + assert!(v.len() == 1); + // issue #1221: + //let slice = v.as_slice(); + //assert!(slice[0] == 10); + + v.push(20); + assert!(v.len() == 2); + //let slice = v.as_slice(); + //assert!(slice[0] == 10); + //assert!(slice[1] == 10); + + v.clear(); + assert!(v.is_empty()); +} From ac3c120aedd3fad6e5a2aff08c62670dbe28bd7e Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Wed, 25 Jan 2023 18:50:59 +0100 Subject: [PATCH 25/43] allow declaring alloc as extern crate this is an exception listed here: https://doc.rust-lang.org/edition-guide/rust-2018/path-changes.html --- prusti-contracts/prusti-contracts/src/core_spec/default.rs | 2 -- x.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index ff79f422203..df0c2888297 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -1,5 +1,3 @@ -extern crate alloc; - use crate::*; #[extern_spec] diff --git a/x.py b/x.py index 3939341220a..5d0ea418d72 100755 --- a/x.py +++ b/x.py @@ -247,7 +247,7 @@ def check_smir(): lines = [ line for line in completed.stdout.decode().splitlines() - if '.rs:' in line and not line.startswith('prusti-tests/tests') + if '.rs:' in line and not line.startswith('prusti-tests/tests') and not 'extern crate alloc' in line ] assert not lines, ( 'found `extern crate` outside ' From 2ee4286909ee2e6221cb31766038f2b465ede28c Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 26 Jan 2023 13:57:51 +0100 Subject: [PATCH 26/43] move alloc specs to prusti-std crate --- .../prusti-contracts/src/core_spec/default.rs | 13 ----------- .../prusti-contracts/src/core_spec/mod.rs | 2 -- prusti-contracts/prusti-contracts/src/lib.rs | 3 --- .../prusti-std/src/alloc_spec/mod.rs | 5 ++++ .../src/alloc_spec}/string.rs | 8 ++++++- .../src/alloc_spec}/vec.rs | 9 +++++++- .../prusti-std/src/collections.rs | 21 +++++++++++++++++ prusti-contracts/prusti-std/src/lib.rs | 23 ++++--------------- 8 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 prusti-contracts/prusti-std/src/alloc_spec/mod.rs rename prusti-contracts/{prusti-contracts/src/core_spec => prusti-std/src/alloc_spec}/string.rs (92%) rename prusti-contracts/{prusti-contracts/src/core_spec => prusti-std/src/alloc_spec}/vec.rs (89%) create mode 100644 prusti-contracts/prusti-std/src/collections.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index df0c2888297..0b841edd744 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -75,19 +75,6 @@ specify_tuple_default!(impls E D C B A Z Y X W V U T); // more specific types -#[extern_spec] -impl Default for alloc::string::String { - #[ensures(result.is_empty())] - fn default() -> Self; -} - -#[extern_spec] -impl Default for alloc::vec::Vec { - #[refine_spec(where Self: Copy, [pure])] - #[ensures(result.is_empty())] - fn default() -> Self; -} - #[extern_spec] impl Default for Option { #[refine_spec(where Self: Copy, [pure])] diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index 16be7ba9ebc..c7aa3b030d9 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -6,8 +6,6 @@ pub mod ops; pub mod option; pub mod result; pub mod slice; -pub mod string; -pub mod vec; // NOTE: specs marked with FUTURE are not fully expressible yet (in a clean way). // They are due to be revised later as features are added. diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index be916f7db61..d2758d09bf8 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -3,9 +3,6 @@ #![feature(negative_impls)] #![feature(try_trait_v2)] #![feature(cfg_version)] -#![feature(allocator_api)] - -extern crate alloc; // to specify Vec and String without std // this is present even when compiling outside prusti to enable (negatively) implementing traits used for better specs pub mod core_spec; diff --git a/prusti-contracts/prusti-std/src/alloc_spec/mod.rs b/prusti-contracts/prusti-std/src/alloc_spec/mod.rs new file mode 100644 index 00000000000..50003f71cd4 --- /dev/null +++ b/prusti-contracts/prusti-std/src/alloc_spec/mod.rs @@ -0,0 +1,5 @@ +// In future, it might make sense to move these specs to yet another crate or have a feature to disable the std parts of this crate. +// alloc is usable in some no_std environments, so it would be helpful to support the use case of core + alloc without std. + +pub mod string; +pub mod vec; diff --git a/prusti-contracts/prusti-contracts/src/core_spec/string.rs b/prusti-contracts/prusti-std/src/alloc_spec/string.rs similarity index 92% rename from prusti-contracts/prusti-contracts/src/core_spec/string.rs rename to prusti-contracts/prusti-std/src/alloc_spec/string.rs index 0d03d2d3eda..8a2219b291b 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/string.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/string.rs @@ -1,4 +1,4 @@ -use crate::*; +use prusti_contracts::*; use alloc::string::String; @@ -62,3 +62,9 @@ impl From<&str> for String { fn from(other: &str) -> Self; } */ + +#[extern_spec] +impl Default for String { + #[ensures(result.is_empty())] + fn default() -> Self; +} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/vec.rs b/prusti-contracts/prusti-std/src/alloc_spec/vec.rs similarity index 89% rename from prusti-contracts/prusti-contracts/src/core_spec/vec.rs rename to prusti-contracts/prusti-std/src/alloc_spec/vec.rs index 0da64fa0acb..6dd89cce150 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/vec.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/vec.rs @@ -1,4 +1,4 @@ -use crate::*; +use prusti_contracts::*; use alloc::{alloc::Allocator, vec::Vec}; @@ -45,3 +45,10 @@ impl> Index for Vec { fn index(&self, index: I) -> &Self::Output; } */ + +#[extern_spec] +impl Default for Vec { + #[refine_spec(where Self: Copy, [pure])] + #[ensures(result.is_empty())] + fn default() -> Self; +} diff --git a/prusti-contracts/prusti-std/src/collections.rs b/prusti-contracts/prusti-std/src/collections.rs new file mode 100644 index 00000000000..da953ef5449 --- /dev/null +++ b/prusti-contracts/prusti-std/src/collections.rs @@ -0,0 +1,21 @@ +use prusti_contracts::*; + +#[extern_spec] +impl std::collections::hash_map::HashMap +where + K: Eq + ::core::hash::Hash, + S: ::std::hash::BuildHasher, +{ + #[pure] + #[ensures(result.is_some() == self.contains_key(k))] + pub fn get(&self, k: &Q) -> Option<&V> + where + K: core::borrow::Borrow + std::cmp::Eq + std::hash::Hash, + Q: core::hash::Hash + Eq; + + #[pure] + fn contains_key(&self, k: &Q) -> bool + where + K: core::borrow::Borrow + Eq + std::hash::Hash, + Q: core::hash::Hash + Eq; +} diff --git a/prusti-contracts/prusti-std/src/lib.rs b/prusti-contracts/prusti-std/src/lib.rs index 4045ab7fe74..1e327ce7f61 100644 --- a/prusti-contracts/prusti-std/src/lib.rs +++ b/prusti-contracts/prusti-std/src/lib.rs @@ -1,21 +1,6 @@ -use prusti_contracts::*; +#![feature(allocator_api)] // to specify Vec -#[extern_spec] -impl ::std::collections::hash_map::HashMap -where - K: Eq + ::core::hash::Hash, - S: ::std::hash::BuildHasher, -{ - #[pure] - #[ensures(result.is_some() == self.contains_key(k))] - pub fn get(&self, k: &Q) -> Option<&V> - where - K: core::borrow::Borrow + std::cmp::Eq + std::hash::Hash, - Q: core::hash::Hash + Eq; +extern crate alloc; - #[pure] - fn contains_key(&self, k: &Q) -> bool - where - K: core::borrow::Borrow + Eq + std::hash::Hash, - Q: core::hash::Hash + Eq; -} +pub mod alloc_spec; +pub mod collections; From 7ae6a063dd8433e1c66d54eeccc023a0beca757d Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 26 Jan 2023 14:38:14 +0100 Subject: [PATCH 27/43] minor fixes --- prusti-tests/tests/compiletest.rs | 2 +- prusti-tests/tests/std/fail/default.rs | 6 ++++++ prusti-tests/tests/std/pass/default.rs | 4 ++++ prusti-tests/tests/std/pass/vec.rs | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/prusti-tests/tests/compiletest.rs b/prusti-tests/tests/compiletest.rs index 3afffa0c87a..ca4a642f374 100644 --- a/prusti-tests/tests/compiletest.rs +++ b/prusti-tests/tests/compiletest.rs @@ -199,7 +199,7 @@ fn test_runner(_tests: &[&()]) { save_verification_cache(); // Test the functionality of built-in specs. - println!("[prusti_std]"); + println!("[std]"); run_verification_no_overflow("std", &filter); save_verification_cache(); diff --git a/prusti-tests/tests/std/fail/default.rs b/prusti-tests/tests/std/fail/default.rs index fd46cadaa52..7d6b5196e1b 100644 --- a/prusti-tests/tests/std/fail/default.rs +++ b/prusti-tests/tests/std/fail/default.rs @@ -1,4 +1,5 @@ use prusti_contracts::*; +use prusti_std; fn main() {} @@ -12,6 +13,11 @@ fn element1() { assert!(default.1 == 0); //~ ERROR: the asserted expression might not hold } +fn special_value() { + let default: Special = Default::default(); + assert!(default.value == 0); //~ ERROR: the asserted expression might not hold +} + // not Copy => Default not pure #[derive(Default)] struct Special { diff --git a/prusti-tests/tests/std/pass/default.rs b/prusti-tests/tests/std/pass/default.rs index 284f8c44236..c4ee58cc84c 100644 --- a/prusti-tests/tests/std/pass/default.rs +++ b/prusti-tests/tests/std/pass/default.rs @@ -1,6 +1,10 @@ use prusti_contracts::*; +use prusti_std; fn main() { + let default: bool = Default::default(); + assert!(!default); + let default: (i32, u64) = Default::default(); assert!(default.0 == 0); assert!(default.1 == 0); diff --git a/prusti-tests/tests/std/pass/vec.rs b/prusti-tests/tests/std/pass/vec.rs index bcb4deea794..4f32aa8e003 100644 --- a/prusti-tests/tests/std/pass/vec.rs +++ b/prusti-tests/tests/std/pass/vec.rs @@ -1,4 +1,5 @@ use prusti_contracts::*; +use prusti_std; fn main() { let mut v = Vec::new(); From 4b58fae9ad340cb89ffdbccb6b2da73d8b25f884 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 26 Jan 2023 17:19:48 +0100 Subject: [PATCH 28/43] update test to use core spec --- .../tests/verify_overflow/pass/copy/erase-regions.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/prusti-tests/tests/verify_overflow/pass/copy/erase-regions.rs b/prusti-tests/tests/verify_overflow/pass/copy/erase-regions.rs index 3c5d4759246..69fe24797bf 100644 --- a/prusti-tests/tests/verify_overflow/pass/copy/erase-regions.rs +++ b/prusti-tests/tests/verify_overflow/pass/copy/erase-regions.rs @@ -6,12 +6,8 @@ struct Iter<'a, #[generic] T: Copy> { position: usize, } -type SliceTy = [T]; #[extern_spec] -impl SliceTy { - #[pure] - fn len(&self) -> usize; - +impl [T] { #[ensures( result.model().position == 0 )] fn iter(&self) -> std::slice::Iter<'_, T>; } @@ -20,6 +16,4 @@ fn verify_slice_iter(slice: &[i32]) { let iter = slice.iter(); } -fn main() { - -} \ No newline at end of file +fn main() {} From 921c6a2e9f0a7241b8eefc1fc6fde9e1f0b9f741 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Fri, 27 Jan 2023 00:47:06 +0100 Subject: [PATCH 29/43] add future spec --- prusti-contracts/prusti-std/src/alloc_spec/string.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prusti-contracts/prusti-std/src/alloc_spec/string.rs b/prusti-contracts/prusti-std/src/alloc_spec/string.rs index 8a2219b291b..b3e4757bf14 100644 --- a/prusti-contracts/prusti-std/src/alloc_spec/string.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/string.rs @@ -44,6 +44,12 @@ impl str { // FUTURE(#1221): these specs make their methods crash due to issue #1221, so they are disabled for now. /* +#[extern_spec] +impl Deref for String { + #[ensures(result === self.as_str()] + fn deref(&self) -> &str; +} + #[extern_spec] impl ToOwned for str { #[ensures(result.as_str() === self)] From 7ad493b03038a5e8485ed7818029569bc2a1784b Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 30 Jan 2023 04:07:11 +0100 Subject: [PATCH 30/43] minor tweaks --- .../prusti-contracts/src/core_spec/ops/index.rs | 1 + prusti-contracts/prusti-contracts/src/core_spec/slice.rs | 1 + prusti-contracts/prusti-std/src/alloc_spec/string.rs | 6 ++++++ prusti-contracts/prusti-std/src/alloc_spec/vec.rs | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs index 88dcb4b816a..ad8106944d8 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs @@ -1,5 +1,6 @@ use crate::*; +#[allow(unused_imports)] use core::ops::Index; // FUTURE(#1221): This spec is currently not usable due to issue #1221. diff --git a/prusti-contracts/prusti-contracts/src/core_spec/slice.rs b/prusti-contracts/prusti-contracts/src/core_spec/slice.rs index f16d98c6923..8fa69f6b612 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/slice.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/slice.rs @@ -1,5 +1,6 @@ use crate::*; +#[allow(unused_imports)] use core::{ops::Index, slice::SliceIndex}; #[extern_spec] diff --git a/prusti-contracts/prusti-std/src/alloc_spec/string.rs b/prusti-contracts/prusti-std/src/alloc_spec/string.rs index b3e4757bf14..b55590644aa 100644 --- a/prusti-contracts/prusti-std/src/alloc_spec/string.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/string.rs @@ -69,6 +69,12 @@ impl From<&str> for String { } */ +#[extern_spec] +impl ToString for String { + #[ensures(result === self)] + fn to_string(&self) -> String; +} + #[extern_spec] impl Default for String { #[ensures(result.is_empty())] diff --git a/prusti-contracts/prusti-std/src/alloc_spec/vec.rs b/prusti-contracts/prusti-std/src/alloc_spec/vec.rs index 6dd89cce150..e635967bc31 100644 --- a/prusti-contracts/prusti-std/src/alloc_spec/vec.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/vec.rs @@ -6,6 +6,10 @@ use alloc::{alloc::Allocator, vec::Vec}; impl Vec { #[ensures(result.is_empty())] fn new() -> Self; + + // capacity is not relevant to functional behavior + #[ensures(result.is_empty())] + fn with_capacity(capacity: usize) -> Self; } // FUTURE(#1221): Vec should forward its specs to as_slice where possible, to avoid writing each spec twice. From 252b46a68190b12b5c4e21a8de499479773f6fc5 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 30 Jan 2023 04:07:26 +0100 Subject: [PATCH 31/43] option flatten & option/result transpose --- .../prusti-contracts/src/core_spec/option.rs | 21 +++++++++++++++- .../prusti-contracts/src/core_spec/result.rs | 10 ++++++++ prusti-tests/tests/std/pass/option.rs | 25 +++++++++++++++++++ prusti-tests/tests/std/pass/result.rs | 10 ++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/option.rs b/prusti-contracts/prusti-contracts/src/core_spec/option.rs index dbbe827ab38..91096bfb3a6 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/option.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/option.rs @@ -206,5 +206,24 @@ impl Option { })] fn zip(self, other: Option) -> Option<(T, U)>; - // TODO: specific methods depending on trait bounds? flatten especially might be useful + // TODO: specific methods depending on trait bounds +} + +#[extern_spec] +impl Option> { + #[ensures(match old(self) { + Some(Some(x)) => result === Some(x), + _ => result.is_none(), + })] + pub fn flatten(self) -> Option; +} + +#[extern_spec] +impl Option> { + #[ensures(match old(self) { + Some(Ok(x)) => result === Ok(Some(x)), + Some(Err(e)) => result === Err(e), + None => matches!(result, Ok(None)), + })] + pub fn transpose(self) -> Result, E>; } diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index b08221b41c9..3d115c18373 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -178,3 +178,13 @@ impl Result { // TODO: specific methods depending on trait bounds } + +#[extern_spec] +impl Result, E> { + #[ensures(match old(self) { + Ok(Some(x)) => result === Some(Ok(x)), + Ok(None) => result.is_none(), + Err(e) => result === Some(Err(e)), + })] + pub fn transpose(self) -> Option>; +} diff --git a/prusti-tests/tests/std/pass/option.rs b/prusti-tests/tests/std/pass/option.rs index 2bec58250e9..1e272c18002 100644 --- a/prusti-tests/tests/std/pass/option.rs +++ b/prusti-tests/tests/std/pass/option.rs @@ -152,3 +152,28 @@ fn test_6() { assert!(none.zip(some).is_none()); assert!(none.zip(none).is_none()); } + +fn test_flatten() { + let x: Option> = Some(Some(6)); + assert!(x.flatten().unwrap() == 6); + + let x: Option> = Some(None); + assert!(x.flatten().is_none()); + + let x: Option> = None; + assert!(x.flatten().is_none()); + + let x: Option>> = Some(Some(Some(6))); + assert!(x.flatten().unwrap().unwrap() == 6); + assert!(x.flatten().flatten().unwrap() == 6); +} + +fn test_transpose() { + #[derive(Debug, Eq, PartialEq)] + struct SomeErr; + + let x: Result, SomeErr> = Ok(Some(5)); + let y: Option> = Some(Ok(5)); + let y = y.transpose(); + prusti_assert!(x === y); +} diff --git a/prusti-tests/tests/std/pass/result.rs b/prusti-tests/tests/std/pass/result.rs index 415c19f8827..07092d24334 100644 --- a/prusti-tests/tests/std/pass/result.rs +++ b/prusti-tests/tests/std/pass/result.rs @@ -91,3 +91,13 @@ fn test_5() { assert!(err.unwrap_err_unchecked() == false); } } + +fn test_transpose() { + #[derive(Debug, Eq, PartialEq)] + struct SomeErr; + + let x: Result, SomeErr> = Ok(Some(5)); + let y: Option> = Some(Ok(5)); + let x = x.transpose(); + prusti_assert!(x === y); +} From 19b6023445158c688daa91b5b9835e6118104e2e Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Thu, 2 Feb 2023 04:01:23 +0100 Subject: [PATCH 32/43] specify size of arrays --- .../prusti-contracts/src/core_spec/mem.rs | 13 +++++++++ .../prusti-contracts/src/core_spec/mod.rs | 1 + prusti-tests/tests/std/pass/mem.rs | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 prusti-tests/tests/std/pass/mem.rs diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index b26569d92de..21e202d0488 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -73,3 +73,16 @@ known_size_spec!(isize, 4); known_size_spec!(usize, 8); #[cfg(target_pointer_width = "64")] known_size_spec!(isize, 8); + +#[refine_trait_spec] +impl KnownSize for [T; N] { + #[pure] + fn size() -> usize { + T::size() * N + } + + #[pure] + fn align() -> usize { + T::align() + } +} diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs index c7aa3b030d9..7b3cc744b9f 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mod.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mod.rs @@ -13,3 +13,4 @@ pub mod slice; pub use clone::SnapshotEqualClone; pub use convert::PureFrom; pub use default::PureDefault; +pub use mem::KnownSize; diff --git a/prusti-tests/tests/std/pass/mem.rs b/prusti-tests/tests/std/pass/mem.rs new file mode 100644 index 00000000000..a3dabdfd56d --- /dev/null +++ b/prusti-tests/tests/std/pass/mem.rs @@ -0,0 +1,27 @@ +use prusti_contracts::*; + +use core::mem::{align_of, size_of}; + +fn main() { + assert!(size_of::<[i32; 3]>() == 12); + assert!(align_of::<[i32; 3]>() == 4); + assert!(size_of::() == 8); + assert!(align_of::() == 4); +} + +struct Foo { + a: i32, + b: i32, +} + +impl core_spec::KnownSize for Foo { + #[pure] + fn size() -> usize { + 8 + } + + #[pure] + fn align() -> usize { + 4 + } +} From d7ce8c77fe527a0efcaa6cbfb384f06f22775934 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Fri, 3 Feb 2023 03:33:01 +0100 Subject: [PATCH 33/43] conditionalize std specs --- prusti-contracts/prusti-std/Cargo.toml | 5 ++++- prusti-contracts/prusti-std/src/alloc_spec/mod.rs | 4 ++-- prusti-contracts/prusti-std/src/lib.rs | 12 +++++++++++- .../prusti-std/src/{ => std_spec}/collections.rs | 0 prusti-contracts/prusti-std/src/std_spec/mod.rs | 1 + 5 files changed, 18 insertions(+), 4 deletions(-) rename prusti-contracts/prusti-std/src/{ => std_spec}/collections.rs (100%) create mode 100644 prusti-contracts/prusti-std/src/std_spec/mod.rs diff --git a/prusti-contracts/prusti-std/Cargo.toml b/prusti-contracts/prusti-std/Cargo.toml index dcd32a40031..1793ba19167 100644 --- a/prusti-contracts/prusti-std/Cargo.toml +++ b/prusti-contracts/prusti-std/Cargo.toml @@ -14,6 +14,9 @@ categories = ["development-tools", "development-tools::testing"] [dependencies] prusti-contracts = { path = "../prusti-contracts", version = "0.1.2" } -# Forward "prusti" flag [features] +default = ["alloc", "std"] +alloc = [] +std = [] +# Forward "prusti" flag prusti = ["prusti-contracts/prusti"] diff --git a/prusti-contracts/prusti-std/src/alloc_spec/mod.rs b/prusti-contracts/prusti-std/src/alloc_spec/mod.rs index 50003f71cd4..88b18b9c830 100644 --- a/prusti-contracts/prusti-std/src/alloc_spec/mod.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/mod.rs @@ -1,5 +1,5 @@ -// In future, it might make sense to move these specs to yet another crate or have a feature to disable the std parts of this crate. -// alloc is usable in some no_std environments, so it would be helpful to support the use case of core + alloc without std. +// These specs can't be built-in like the core specs since some no_std crates may not even have an allocator. +// Instead, prusti-std supports no_std through a feature. pub mod string; pub mod vec; diff --git a/prusti-contracts/prusti-std/src/lib.rs b/prusti-contracts/prusti-std/src/lib.rs index 1e327ce7f61..a67c1354035 100644 --- a/prusti-contracts/prusti-std/src/lib.rs +++ b/prusti-contracts/prusti-std/src/lib.rs @@ -1,6 +1,16 @@ #![feature(allocator_api)] // to specify Vec +#![no_std] // disable std by default +// link to std if the feature is set +#[cfg(feature = "std")] +extern crate std; + +// Even alloc can be disabled for consistency with std, and in preparation for future specs for other, possibly no_std, crates. +#[cfg(feature = "alloc")] extern crate alloc; +// modules have a `_spec` suffix to avoid name conflicts with their crates. +#[cfg(feature = "alloc")] pub mod alloc_spec; -pub mod collections; +#[cfg(feature = "std")] +pub mod std_spec; diff --git a/prusti-contracts/prusti-std/src/collections.rs b/prusti-contracts/prusti-std/src/std_spec/collections.rs similarity index 100% rename from prusti-contracts/prusti-std/src/collections.rs rename to prusti-contracts/prusti-std/src/std_spec/collections.rs diff --git a/prusti-contracts/prusti-std/src/std_spec/mod.rs b/prusti-contracts/prusti-std/src/std_spec/mod.rs new file mode 100644 index 00000000000..2e4fe9b79c2 --- /dev/null +++ b/prusti-contracts/prusti-std/src/std_spec/mod.rs @@ -0,0 +1 @@ +pub mod collections; From 9ca4729644e366d7045faec9cf095df5d5d805d1 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sat, 4 Feb 2023 03:34:26 +0100 Subject: [PATCH 34/43] add missing import --- prusti-contracts/prusti-std/src/alloc_spec/string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prusti-contracts/prusti-std/src/alloc_spec/string.rs b/prusti-contracts/prusti-std/src/alloc_spec/string.rs index b55590644aa..9318eeceba2 100644 --- a/prusti-contracts/prusti-std/src/alloc_spec/string.rs +++ b/prusti-contracts/prusti-std/src/alloc_spec/string.rs @@ -1,6 +1,6 @@ use prusti_contracts::*; -use alloc::string::String; +use alloc::string::{String, ToString}; // FUTURE(#1221): String should forward its specs to as_str where possible, to avoid writing each spec twice. // That doesn't currently work though, due to issue #1221 From f6df16a71a467fb03e5e2f9ac3ef1a9f246c00d9 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sat, 4 Feb 2023 03:48:04 +0100 Subject: [PATCH 35/43] add tests for clone specs includes inheritance of postconditions and auto trait "transitivity" --- prusti-tests/tests/std/fail/clone.rs | 46 ++++++++++++++++++++++++++++ prusti-tests/tests/std/pass/clone.rs | 23 ++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 prusti-tests/tests/std/fail/clone.rs create mode 100644 prusti-tests/tests/std/pass/clone.rs diff --git a/prusti-tests/tests/std/fail/clone.rs b/prusti-tests/tests/std/fail/clone.rs new file mode 100644 index 00000000000..50e8e9665ca --- /dev/null +++ b/prusti-tests/tests/std/fail/clone.rs @@ -0,0 +1,46 @@ +#![feature(negative_impls)] +//~^ ERROR: postcondition might not hold +// the above error is currently reported with an incorrect span due to issue #1310. +// it should be down on line 38 at the actual implementation. + +use prusti_contracts::*; + +fn main() {} + +#[derive(Clone)] +struct Foo { + x: i32, +} + +impl !core_spec::SnapshotEqualClone for Foo {} + +fn foo() { + let foo = Foo { x: 1 }; + assert!(foo.clone().x == 1); //~ ERROR: the asserted expression might not hold +} + +#[derive(Clone)] +struct FooHolder { + foo: Foo, +} + +fn foo_holder() { + let foo = Foo { x: 1 }; + let holder = FooHolder { foo }; + assert!(holder.clone().foo.x == 1); //~ ERROR: the asserted expression might not hold +} + +struct Bar { + x: i32, +} + +impl Clone for Bar { + fn clone(&self) -> Self { + Self { x: self.x + 1 } + } +} + +fn bar() { + let bar = Bar { x: 2 }; + assert!(bar.clone().x == 2); +} diff --git a/prusti-tests/tests/std/pass/clone.rs b/prusti-tests/tests/std/pass/clone.rs new file mode 100644 index 00000000000..9e16efda40f --- /dev/null +++ b/prusti-tests/tests/std/pass/clone.rs @@ -0,0 +1,23 @@ +use prusti_contracts::*; + +fn main() { + let foo = Foo { x: 1 }; + let bar = Bar { x: 2 }; + assert!(foo.clone().x == 1); + assert!(bar.clone().x == 2); +} + +#[derive(Clone)] +struct Foo { + x: i32, +} + +struct Bar { + x: i32, +} + +impl Clone for Bar { + fn clone(&self) -> Self { + Self { x: self.x } + } +} From 5ddc36b800655093ad3368ec8883540629c5ca29 Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sat, 4 Feb 2023 06:00:17 +0100 Subject: [PATCH 36/43] use snapshot equality for Default specs have to desugar this manually since prusti syntax breaks rust's macro parsing --- prusti-contracts/prusti-contracts/src/core_spec/default.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/default.rs b/prusti-contracts/prusti-contracts/src/core_spec/default.rs index 0b841edd744..f5dc47ca1ac 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/default.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/default.rs @@ -17,7 +17,7 @@ macro_rules! default_spec { #[extern_spec] impl Default for $t { #[pure] - #[ensures(result == $v)] + #[ensures(snapshot_equality(result, $v))] fn default() -> Self; } }; From d23dbd5fc0326cd0bb79ef49fb94a3a28721f8cd Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Sat, 4 Feb 2023 14:41:15 +0100 Subject: [PATCH 37/43] update smir checks to allow what prusti-std needs --- x.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x.py b/x.py index 5d0ea418d72..76953574012 100755 --- a/x.py +++ b/x.py @@ -236,8 +236,10 @@ def fmt_check_all(): def check_smir(): """Check that `extern crate` is used only in `prusti_rustc_interface` (TODO: `prusti_interface` is also ignored for now).""" + # also ignore prusti-contracts because the prusti-std needs std but is no_std to make it conditional (https://doc.rust-lang.org/edition-guide/rust-2018/path-changes.html) + blacklist = set(['prusti-rustc-interface', 'prusti-interface', 'prusti-contracts']) for folder in os.listdir('.'): - if folder == 'prusti-rustc-interface' or folder == 'prusti-interface': + if folder in blacklist: continue if os.path.exists(os.path.join(folder, 'Cargo.toml')): completed = subprocess.run( @@ -247,7 +249,7 @@ def check_smir(): lines = [ line for line in completed.stdout.decode().splitlines() - if '.rs:' in line and not line.startswith('prusti-tests/tests') and not 'extern crate alloc' in line + if '.rs:' in line and not line.startswith('prusti-tests/tests') ] assert not lines, ( 'found `extern crate` outside ' From 077c0e0dd8b1b0e7e2de849fbc0aca4eeefaf5cb Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Wed, 15 Feb 2023 14:36:18 +0100 Subject: [PATCH 38/43] add missing "supertrait" bounds --- prusti-contracts/prusti-contracts/src/core_spec/option.rs | 2 +- prusti-contracts/prusti-contracts/src/core_spec/result.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/option.rs b/prusti-contracts/prusti-contracts/src/core_spec/option.rs index 91096bfb3a6..35b247142fa 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/option.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/option.rs @@ -64,7 +64,7 @@ impl Option { F: FnOnce() -> T; #[ensures(old(self.is_none()) || old(self) === Some(result))] - #[refine_spec(where T: super::default::PureDefault, [ + #[refine_spec(where T: Copy + super::default::PureDefault, [ ensures(result === match old(self) { Some(v) => v, None => Default::default(), diff --git a/prusti-contracts/prusti-contracts/src/core_spec/result.rs b/prusti-contracts/prusti-contracts/src/core_spec/result.rs index 3d115c18373..5b42c89abc5 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/result.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/result.rs @@ -100,7 +100,7 @@ impl Result { E: Debug; #[ensures(old(self.is_err()) || old(self) === Ok(result))] - #[refine_spec(where T: super::default::PureDefault, [ + #[refine_spec(where T: Copy + super::default::PureDefault, [ ensures(result === match old(self) { Ok(v) => v, Err(_) => T::default(), From 0b2a7d8e9d262833db1ea5a9bbb0401a34ee20ea Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Wed, 15 Feb 2023 14:36:28 +0100 Subject: [PATCH 39/43] comment out all #1221 specs for now --- prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs | 2 ++ prusti-contracts/prusti-contracts/src/core_spec/slice.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs index ad8106944d8..638464ee4b4 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs @@ -4,8 +4,10 @@ use crate::*; use core::ops::Index; // FUTURE(#1221): This spec is currently not usable due to issue #1221. +/* #[extern_spec] trait Index { #[pure] fn index(&self, idx: Idx) -> &Self::Output; } +*/ diff --git a/prusti-contracts/prusti-contracts/src/core_spec/slice.rs b/prusti-contracts/prusti-contracts/src/core_spec/slice.rs index 8fa69f6b612..9e13193bc97 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/slice.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/slice.rs @@ -42,6 +42,7 @@ impl [T] { } // FUTURE(#1221): This spec is currently not usable due to issue #1221. +/* #[extern_spec] impl Index for [T] where @@ -50,3 +51,4 @@ where #[pure] fn index(&self, index: I) -> &I::Output; } +*/ From 67e96eb53f30426ac959e5e8339a38b4939806cb Mon Sep 17 00:00:00 2001 From: Julian Dunskus Date: Mon, 6 Mar 2023 17:11:32 +0100 Subject: [PATCH 40/43] fix warning --- prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs index 638464ee4b4..faa16470593 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/ops/index.rs @@ -1,3 +1,4 @@ +#[allow(unused_imports)] use crate::*; #[allow(unused_imports)] From b9497e357c12727efd0866f4bd45301824e8f041 Mon Sep 17 00:00:00 2001 From: Vytautas Astrauskas Date: Thu, 4 May 2023 14:14:54 +0200 Subject: [PATCH 41/43] Comment out failing test. --- .../tests/verify_overflow/fail/core_proof/loops.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/loops.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/loops.rs index 8cd19f53d21..00365dce4f8 100644 --- a/prusti-tests/tests/verify_overflow/fail/core_proof/loops.rs +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/loops.rs @@ -107,6 +107,7 @@ fn next3() -> Result, E> { Ok(None) } +/* fn test11() -> Result { while let Some(n) = next3()? { } Err(E) @@ -121,21 +122,21 @@ fn test12() -> Result { fn test13() -> Result { while let Some(n) = next3()? { - assert!(false); //~ ERROR: the asserted expression might not hold + assert!(false); ERROR: the asserted expression might not hold } Err(E) } fn test14() -> Result { while let Some(n) = next3()? { } - assert!(false); //~ ERROR: the asserted expression might not hold + assert!(false); ERROR: the asserted expression might not hold Err(E) } fn test15() -> Result { while let Some(n) = next3()? { body_invariant!(true); - assert!(false); //~ ERROR: the asserted expression might not hold + assert!(false); ERROR: the asserted expression might not hold } Err(E) } @@ -144,7 +145,7 @@ fn test16() -> Result { while let Some(n) = next3()? { body_invariant!(true); } - assert!(false); //~ ERROR: the asserted expression might not hold + assert!(false); ERROR: the asserted expression might not hold Err(E) } @@ -167,6 +168,7 @@ fn test18() -> Result { } Err(E) } +*/ fn next4() -> u32 { 4 From e9ba64e409f4c86ce9e9edcb1a424c6aae99772c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Fri, 5 May 2023 11:29:35 +0200 Subject: [PATCH 42/43] bump versions --- prusti-contracts/prusti-contracts-proc-macros/Cargo.toml | 4 ++-- prusti-contracts/prusti-contracts/Cargo.toml | 4 ++-- prusti-contracts/prusti-specs/Cargo.toml | 2 +- prusti-contracts/prusti-std/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml index 9c6c7d22e36..5bcecf2cb74 100644 --- a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml +++ b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-contracts-proc-macros" -version = "0.1.5" +version = "0.1.7" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" @@ -15,7 +15,7 @@ categories = ["development-tools", "development-tools::testing"] proc-macro = true [dependencies] -prusti-specs = { path = "../prusti-specs", version = "0.1.5", optional = true } +prusti-specs = { path = "../prusti-specs", version = "0.1.7", optional = true } proc-macro2 = { version = "1.0", optional = true } [features] diff --git a/prusti-contracts/prusti-contracts/Cargo.toml b/prusti-contracts/prusti-contracts/Cargo.toml index cda2e19aa92..d0539af0f2f 100644 --- a/prusti-contracts/prusti-contracts/Cargo.toml +++ b/prusti-contracts/prusti-contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-contracts" -version = "0.1.5" +version = "0.1.7" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" @@ -12,7 +12,7 @@ keywords = ["prusti", "contracts", "verification", "formal", "specifications"] categories = ["development-tools", "development-tools::testing"] [dependencies] -prusti-contracts-proc-macros = { path = "../prusti-contracts-proc-macros", version = "0.1.5" } +prusti-contracts-proc-macros = { path = "../prusti-contracts-proc-macros", version = "0.1.7" } [dev-dependencies] trybuild = "1.0" diff --git a/prusti-contracts/prusti-specs/Cargo.toml b/prusti-contracts/prusti-specs/Cargo.toml index 94aa835494f..2e4a1f7ea91 100644 --- a/prusti-contracts/prusti-specs/Cargo.toml +++ b/prusti-contracts/prusti-specs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-specs" -version = "0.1.5" +version = "0.1.7" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" diff --git a/prusti-contracts/prusti-std/Cargo.toml b/prusti-contracts/prusti-std/Cargo.toml index 20a5d8d5e92..c24a319f75f 100644 --- a/prusti-contracts/prusti-std/Cargo.toml +++ b/prusti-contracts/prusti-std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti-std" -version = "0.1.5" +version = "0.1.7" authors = ["Prusti Devs "] edition = "2021" license = "MPL-2.0" @@ -12,7 +12,7 @@ keywords = ["prusti", "contracts", "verification", "formal", "specifications"] categories = ["development-tools", "development-tools::testing"] [dependencies] -prusti-contracts = { path = "../prusti-contracts", version = "0.1.5" } +prusti-contracts = { path = "../prusti-contracts", version = "0.1.7" } [features] default = ["alloc", "std"] From c5ba9e6e09410ececba6275c64e9c28ad55eac1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Mon, 4 Sep 2023 19:36:29 +0200 Subject: [PATCH 43/43] account for prusti-std in user guide --- docs/user-guide/src/SUMMARY.md | 1 + docs/user-guide/src/tour/extern_specs.md | 42 +++++++++ docs/user-guide/src/tour/option.md | 11 +-- docs/user-guide/src/tour/pop.md | 12 --- docs/user-guide/src/tour/push.md | 85 ++----------------- docs/user-guide/src/tour/setup.md | 17 ++-- docs/user-guide/src/tour/summary.md | 1 + .../prusti-contracts/src/core_spec/mem.rs | 4 + .../fail/user-guide/peek_mut_pledges.rs | 26 ------ .../tests/verify/fail/user-guide/pop.rs | 12 +-- .../push_property_2_missing_bounds.rs | 5 -- .../pass/user-guide/assert_on_expiry.rs | 26 ------ .../tests/verify/pass/user-guide/generic.rs | 31 +------ .../tests/verify/pass/user-guide/option.rs | 35 +------- .../tests/verify/pass/user-guide/peek.rs | 27 ------ .../pass/user-guide/peek_mut_pledges.rs | 26 ------ .../tests/verify/pass/user-guide/pop.rs | 25 ------ .../verify/pass/user-guide/push_final_code.rs | 5 -- .../verify/pass/user-guide/push_property_1.rs | 7 -- .../user-guide/push_property_2_with_bounds.rs | 5 -- .../pass/user-guide/testing_initial_code.rs | 23 ----- 21 files changed, 77 insertions(+), 349 deletions(-) create mode 100644 docs/user-guide/src/tour/extern_specs.md diff --git a/docs/user-guide/src/SUMMARY.md b/docs/user-guide/src/SUMMARY.md index 10edf7c0b80..a8f6239eab8 100644 --- a/docs/user-guide/src/SUMMARY.md +++ b/docs/user-guide/src/SUMMARY.md @@ -17,6 +17,7 @@ - [Final Code](tour/final.md) - [Loop Invariants](tour/loop_invariants.md) - [Counterexamples](tour/counterexamples.md) + - [`prusti-std` and External Specifications](tour/extern_specs.md) - [Verification Features](verify/summary.md) - [Absence of panics](verify/panic.md) - [Overflow checks](verify/overflow.md) diff --git a/docs/user-guide/src/tour/extern_specs.md b/docs/user-guide/src/tour/extern_specs.md new file mode 100644 index 00000000000..1c0dfa80ef7 --- /dev/null +++ b/docs/user-guide/src/tour/extern_specs.md @@ -0,0 +1,42 @@ +# `prusti-std` and External Specifications + +In the previous chapters, the code we verified included calls to functions provided by the standard library, such as `std::mem::replace`. Prusti verifies code in a *function modular* fashion, so it only considers the specification of a function in order to verify a call to it. By default, external functions are assumed to have the contract `#[requires(true)]`, `#[ensures(true)]`, i.e. it is always possible to call the function, but it does not guarantee anything. + +`prusti-std` is a crate that is part of the Prusti project. It provides specifications for some standard library methods. It does not provide specifications for *all* standard library methods, but the aim is to provide a high coverage of calls, based on data found in real Rust code, evaluated on the top crates of `crates.io`. + +The documentation of `prusti-std` provides an overview of the specified methods, as well as informal descriptions of the contracts. + +## Creating new external specifications + +When the specification for an external method is not provided (for example, because it is not in `prusti-std`), it is likely that verification of code using that method will fail. To provide the contract for an external method, an [*external specification*](../verify/external.md) should be declared. + +For the sake of example, assume the `std::mem::replace` method was *not* specified in `prusti-std`. We could provide an external specification for it like so: + +```rust,ignore +#[extern_spec(std::mem)] +#[ensures(snap(dest) === src)] +#[ensures(result === old(snap(dest)))] +fn replace(dest: &mut T, src: T) -> T; +``` + +Let's break this snippet down step by step: +- First, we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. This requires `prusti_contracts::*` to be imported first. +- Next, we need to declare where the original function is located. In this case it is the module `std::mem`, so we put its path in the parameter: `#[extern_spec(std::mem)]` +- After a quick search for *\"rust std mem replace\"* we can find the [documentation for std::mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html). Here we can get the function signature: `pub fn replace(dest: &mut T, src: T) -> T`. We then write down the signature in the inner module, followed by a `;`. The visibility modifier is omitted for external specifications. +- Since there are no preconditions to `replace`, we can use the (implicit) default `#[requires(true)]`. +- For writing the postcondition, we use four pieces of Prusti syntax: + - [`===`](../syntax.md#snapshot-equality) is called **snapshot equality** or **logical equality**. Is means that the left and right operands are structurally equal. `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. + - The [`snap()`](../syntax.md#snap-function) function takes a snapshot of a reference. It has a similar functionality to the [`clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) method, but does not require the type of the reference it is called on to implement the `Clone` trait. `snap` should only be used in specifications, since it ignores the borrow checker. + - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state of `snap(dest)` from before the function was called. + - The identifier [`result`](../syntax.md#result-variable) is used to refer to the return parameter of the function. +- The postcondition consists of two parts, which can either be written in one condition with `&&`, or in multiple `#[ensures(...)]` annotations like in the example above. + - The first condition `snap(dest) === src` means: *After the function returns, the location referenced by `dest` is structurally equal to the parameter `src`.* + - The second part of the postcondition is `result === old(snap(dest))`. This means: *The `result` returned by the function is structurally equal to the the element that was referenced by `dest` **before** the function was called.* + +Since `result` is structurally equal to `dest` from before the function call, Prusti knows that the pure function `len()` called on `result` returns the same value as it would have for `dest`. + +An important thing to note here is that Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. + +### Future + +There is currently new functionality planned for Prusti-assistant, which should enable the user to automatically generate the `extern_spec` declaration for a given call. diff --git a/docs/user-guide/src/tour/option.md b/docs/user-guide/src/tour/option.md index 77281396469..b22e46eca18 100644 --- a/docs/user-guide/src/tour/option.md +++ b/docs/user-guide/src/tour/option.md @@ -10,12 +10,6 @@ Just like in the "Learning Rust With Entirely Too Many Linked Lists" tutorial, w type Link = Option>; ``` -In order to use the `Option::take` function, we also have to implement the `extern_spec` for it. As you can see, it is quite similar to the `extern_spec` for `mem::replace`, since `take` does the same as `replace(&mut self, None)`: - -```rust,noplaypen -{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:option_take_extern_spec}} -``` - Changing the `Link` type requires some adjustments of the code and specifications. With the new type alias for `Link`, we cannot have an `impl Link` block anymore, so our `lookup` and `len` functions on `Link` are now normal, free-standing functions: ```rust,noplaypen @@ -31,6 +25,9 @@ Due to current limitations of Prusti, we cannot replace our `link_len` and `link ``` Since Prusti doesn't fully support closures yet, we also cannot do the rewrite to use the `Option::map` function: + + + ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:try_pop_rewrite}} ``` @@ -41,4 +38,4 @@ If you want to see the full code after all the changes, expand the following cod ```rust,noplaypen // Expand to see full code up to this chapter {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/option.rs:nothing}} -``` \ No newline at end of file +``` diff --git a/docs/user-guide/src/tour/pop.md b/docs/user-guide/src/tour/pop.md index e5c494684c6..58182a1288d 100644 --- a/docs/user-guide/src/tour/pop.md +++ b/docs/user-guide/src/tour/pop.md @@ -45,18 +45,6 @@ Since we will need to check if a list is empty, we can implement a `#[pure]` fun {{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/pop.rs:is_empty}} ``` -### Writing the external specifications for `Option` - -Since we use `Option::unwrap`, we will need an external specification for it. While we're at it, let's also write the `#[extern_spec]` for `Option::is_some` and `Option::is_none`: - -```rust,noplaypen -{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/pop.rs:extern_spec}} -``` - -The syntax for writing external specifications for functions associated with `Option` is slightly different to that of `std::mem::replace`, which was a standalone function. - -Note: In the future, you should just be able to import these external specifications using the [`prusti-std` crate](https://crates.io/crates/prusti-std). It should be available after [this PR](https://github.com/viperproject/prusti-dev/pull/1249) is merged. Until then, you can find the work in progress specifications in the PR (e.g., for [`Option::unwrap`](https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49)). - ## Implementing the specification ### Writing the precondition diff --git a/docs/user-guide/src/tour/push.md b/docs/user-guide/src/tour/push.md index 250a5cb7c25..20114500e4d 100644 --- a/docs/user-guide/src/tour/push.md +++ b/docs/user-guide/src/tour/push.md @@ -44,79 +44,20 @@ pure method `len` introduced in the [previous chapter](new.md): ```rust,noplaypen {{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:property_1}} +// Prusti: Verifies ``` -Even though the above implementation of `push` is correct, attempting to verify it with Prusti still yields a verification error: - -```plain -[Prusti: verification error] postcondition might not hold. -``` - -This error may look surprising at first: -We create a new list node that stores the the original list in its next field. -Why is Prusti unable to realize that the length of the resulting list -is one plus the length of the original list? - -The explanation is that Prusti performs *function modular* verification, -that is, it only uses a function's specification (instead of also consulting the -function's implementation) whenever it encounters a function call. -The only exception are *pure* functions, such as `len`, where Prusti also takes the -function body into account. - - - -### Adding external specifications to library code - -In our case, the function `std::mem::replace` is neither marked as `pure` nor does it -come with a specification. Hence, Prusti assumes that it is memory safe and nothing else. -That is, Prusti uses `true` as both pre- and postcondition of `replace`, -which is too weak to prove the specification of `push`. According to its specification, -`replace` could arbitrarily change the original list and thus also its length. -Hence, we cannot conclude that the length the list returned by -`replace(&mut self.head, Link::Empty)` coincides with the length of the original -list. - -We can remedy this issue by strengthening the specification of `replace`. -In this tutorial, we will assume that the standard library is correct, that is, we -do not attempt to verify specifications for functions in external crates, -like `replace`. To this end, we have to add the specification to the function. -This can be done with another piece of Prusti syntax, the [extern_spec](../verify/external.md): - -```rust,noplaypen -{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:extern_spec}} -``` - -Let's break this snippet down step by step: -- First, we write the Prusti annotation `#[extern_spec]` to denote that we are writing an external specification. This requires `prusti_contracts::*` to be imported first. -- Next, we need to declare where the original function is located. In this case it is the module `std::mem`, so we put its path in the parameter: `#[extern_spec(std::mem)]` -- After a quick search for *\"rust std mem replace\"* we can find the [documentation for std::mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html). Here we can get the function signature: `pub fn replace(dest: &mut T, src: T) -> T`. We then write down the signature in the inner module, followed by a `;`. -- Since there are no preconditions to `replace`, we can use the (implicit) default `#[requires(true)]`. -- For writing the postcondition, we use four pieces of Prusti syntax: - - [`===`](../syntax.md#snapshot-equality) is called **snapshot equality** or **logical equality**. Is means that the left and right operands are structurally equal. `===` does not require the type of the compared elements to implement [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), which would be required if we used the standard equality operator `==`. - - The [`snap()`](../syntax.md#snap-function) function takes a snapshot of a reference. It has a similar functionality to the [`clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) method, but does not require the type of the reference it is called on to implement the `Clone` trait. `snap` should only be used in specifications, since it ignores the borrow checker. - - Lastly, we have the [`old()` function](../syntax.md#old-expressions), which denotes that we want to refer to the state of `snap(dest)` from before the function was called. - - The identifier [`result`](../syntax.md#result-variable) is used to refer to the return parameter of the function. -- The postcondition consists of two parts, which can either be written in one condition with `&&`, or in multiple `#[ensures(...)]` annotations like in the example above. - - The first condition `snap(dest) === src` means: *After the function returns, the location referenced by `dest` is structurally equal to the parameter `src`* - - The second part of the postcondition is `result === old(snap(dest))`. This means: *The `result` returned by the function is structurally equal to the the element that was referenced by `dest` **before** the function was called.* - -Since `result` is structurally equal to `dest` from before the function call, Prusti knows that the pure function `len()` called on `result` returns the same value as it would have for `dest`. - - -An important thing to note here is that Prusti does ***not*** check if `replace` actually does what the external specification says it does. `#[extern_spec]` implicitly implies the `#[trusted]` annotation, which means that any postconditions are just accepted and used by Prusti. - -### Future +With this, the first of the three properties of `push` is verified, but we still have two more to prove. -There is currently new functionality planned for Prusti-assistant, which should enable the user to automatically generate parts of the `extern_spec` syntax. +## Note about external specifications -There is also work being done for providing external specifications for the Rust standard library. Depending on when you are reading this, the `std::mem::replace` function might be annotated already, in that case this `extern_spec` may not be needed anymore. -You can track the progress and find some already completed specifications [in this Pull Request](https://github.com/viperproject/prusti-dev/pull/1249). +Prusti verifies the above implementation of `push`, but this might come as a surprise: the implementation calls the standard library method `std::mem::replace`. How does Prusti know what this method does? Prusti performs *function modular* verification, so calls to other methods only use that method's specifications, and never its implementation (which may not even be available, or may use unsupported features). -Specifications for the standard library should eventually be available in the [prusti-std crate](https://crates.io/crates/prusti-std). Any specifications in this crate will be available by adding it to your project's dependencies. +The answer is that the `prusti-std` crate provides specifications for *some* of the most common standard library methods, including `std::mem::replace`. In situations where `prusti-std` does not (yet) provide a suitable specification for a method used in the code, an *external specification* must be declared. Creating external specifications is discussed in the [`prusti-std` and External Specifications](extern_specs.md) chapter of this guide. ## Trusted functions -As mentioned above, `extern_specs` are implicitly `#[trusted]` by Prusti. +External specifications, like the one for `std::mem::replace` provided by `prusti-std`, are implicitly `#[trusted]` by Prusti. Trusted functions can be used for verifying projects containing external code that does not have Prusti annotations, or projects using Rust features that are not yet supported by Prusti. An example is printing a string slice (not supported yet): ```rust,noplaypen @@ -156,17 +97,6 @@ This one is even worse, it will enable anything to be verified: fn wrong() {} ``` -### Checking the `extern_spec` - -Let's get back to our code. After adding the external specification for `std::mem::replace`, we can finally verify the first property of our `push` function: - -```rust,noplaypen -{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/push_property_1.rs:property_1}} -// Prusti: Verifies -``` - -With this, the first of the three properties of `push` is verified, but we still have two more to prove. - ## Second property Recall the second property of our specification: @@ -203,9 +133,6 @@ We don't need to add the condition `0 <= index`, since `index` has the type `usi After these changes, Prusti can successfully verify the code, so the first two properties of `push` are correct. - - - ## Third property The third and final property we will verify for `push` is that the original list diff --git a/docs/user-guide/src/tour/setup.md b/docs/user-guide/src/tour/setup.md index ea0e2171e91..74d9fd4d426 100644 --- a/docs/user-guide/src/tour/setup.md +++ b/docs/user-guide/src/tour/setup.md @@ -18,10 +18,10 @@ cargo add prusti-contracts For older versions of Rust, you can manually add the dependency in your Cargo.toml file: ```toml [dependencies] -prusti-contracts = "0.1.6" +prusti-contracts = "0.1" ``` -To use prusti-contracts in a Rust code file, just add the following line: +To use `prusti-contracts` in a Rust code file, just add the following line: ```rust,ignore use prusti_contracts::*; ``` @@ -37,9 +37,9 @@ check_overflows = false -## Standard library annotations +## Standard library specifications -Annotations for functions and types in the Rust standard library will be available in the [`prusti-std` crate](https://crates.io/crates/prusti-std) after [this PR](https://github.com/viperproject/prusti-dev/pull/1249) is merged. +Specifications for functions and types in the Rust standard library are provided in the [`prusti-std` crate](https://crates.io/crates/prusti-std). Adding this crate works the same as for the `prusti-contracts` crate: ```sh @@ -48,6 +48,11 @@ cargo add prusti-std or: ```toml [dependencies] -prusti-std = "0.1.6" +prusti-std = "0.1" +``` + +To make the specifications available when using `cargo prusti`, the following line also needs to be added to the crate: + +```rust,ignore +use prusti_std; ``` -You do not need to import anything to use the annotations in this crate in a file. diff --git a/docs/user-guide/src/tour/summary.md b/docs/user-guide/src/tour/summary.md index 2f3c4bd7534..3eb78053031 100644 --- a/docs/user-guide/src/tour/summary.md +++ b/docs/user-guide/src/tour/summary.md @@ -49,3 +49,4 @@ are as follows: 11. [Final Code](final.md): Final code for the verified linked list 12. [Loop Invariants](loop_invariants.md): Verifying code containing loops by writing loop invariants 13. [Counterexamples](counterexamples.md): Getting counterexamples for failing assertions +14. [`prusti-std` and External Specifications](extern_specs.md): Specifications for the Rust standard library, specifying external methods diff --git a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs index 21e202d0488..c14919f14b8 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec/mem.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec/mem.rs @@ -12,6 +12,10 @@ mod mem { #[ensures(*x === old(snap(y)) && *y === old(snap(x)))] fn swap(x: &mut T, y: &mut T); + + #[ensures(snap(dest) === src)] + #[ensures(result === old(snap(dest)))] + fn replace(dest: &mut T, src: T) -> T; } pub trait KnownSize { diff --git a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs index 4302e9cd5b5..c92daa57cb7 100644 --- a/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/fail/user-guide/peek_mut_pledges.rs @@ -17,32 +17,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} //// ANCHOR: peek_mut_code impl List { //// ANCHOR_END: peek_mut_code diff --git a/prusti-tests/tests/verify/fail/user-guide/pop.rs b/prusti-tests/tests/verify/fail/user-guide/pop.rs index 95c2eec5369..b7426c55ecc 100644 --- a/prusti-tests/tests/verify/fail/user-guide/pop.rs +++ b/prusti-tests/tests/verify/fail/user-guide/pop.rs @@ -29,7 +29,7 @@ impl List { pub fn len(&self) -> usize { self.head.len() } - + //// ANCHOR: is_empty #[pure] fn is_empty(&self) -> bool { @@ -50,7 +50,7 @@ impl List { self.head.lookup(index) } - #[ensures(self.len() == old(self.len()) + 1)] //~ ERROR postcondition might not hold + #[ensures(self.len() == old(self.len()) + 1)] #[ensures(self.lookup(0) == elem)] #[ensures(forall(|i: usize| (i < old(self.len())) ==> old(self.lookup(i)) == self.lookup(i + 1)))] @@ -73,13 +73,13 @@ impl List { }, } } - + //// ANCHOR_END: initial //// ANCHOR: pop_precondition #[requires(!self.is_empty())] //// ANCHOR: initial pub fn pop(&mut self) -> i32 { - self.try_pop().unwrap() + self.try_pop().unwrap() //~ ERROR precondition might not hold } //// ANCHOR: is_empty } @@ -99,10 +99,10 @@ impl Link { node.next.lookup(index - 1) } }, - Link::Empty => unreachable!(), + Link::Empty => unreachable!(), } } - + #[pure] fn len(&self) -> usize { match self { diff --git a/prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs b/prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs index fbbeec1a421..15bb4a4bfd0 100644 --- a/prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs +++ b/prusti-tests/tests/verify/fail/user-guide/push_property_2_missing_bounds.rs @@ -18,11 +18,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - //// ANCHOR: lookup impl List { #[pure] diff --git a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs index 1d01faef70b..6ba6a46639d 100644 --- a/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs +++ b/prusti-tests/tests/verify/pass/user-guide/assert_on_expiry.rs @@ -17,32 +17,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} //// ANCHOR: pledge impl List { //// ANCHOR_END: pledge diff --git a/prusti-tests/tests/verify/pass/user-guide/generic.rs b/prusti-tests/tests/verify/pass/user-guide/generic.rs index 8f25ba1da4e..0cf6d00cb93 100644 --- a/prusti-tests/tests/verify/pass/user-guide/generic.rs +++ b/prusti-tests/tests/verify/pass/user-guide/generic.rs @@ -20,32 +20,6 @@ struct Node { } //// ANCHOR_END: generic_types -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} //// ANCHOR: generic_types //// ANCHOR: lookup_reference @@ -142,8 +116,8 @@ impl List { } } //// ANCHOR_END: lookup_reference - //// ANCHOR_END: generic_types + #[pure] #[requires(index < link_len(link))] //// ANCHOR: lookup_reference @@ -171,13 +145,11 @@ fn link_len(link: &Link) -> usize { } } -//// ANCHOR: generic_types //// ANCHOR: lookup_reference #[cfg(prusti)] mod prusti_tests { use super::*; - //// ANCHOR_END: generic_types fn _test_list(){ // ... //// ANCHOR_END: lookup_reference @@ -208,5 +180,4 @@ mod prusti_tests { //// ANCHOR: lookup_reference } } -//// ANCHOR_END: generic_types //// ANCHOR_END: lookup_reference \ No newline at end of file diff --git a/prusti-tests/tests/verify/pass/user-guide/option.rs b/prusti-tests/tests/verify/pass/user-guide/option.rs index 48d75f37026..b2e8c4fb3cb 100644 --- a/prusti-tests/tests/verify/pass/user-guide/option.rs +++ b/prusti-tests/tests/verify/pass/user-guide/option.rs @@ -17,39 +17,6 @@ struct Node { next: Link, } -//// ANCHOR: option_take_extern_spec -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -//// ANCHOR_END: option_take_extern_spec -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -//// ANCHOR: option_take_extern_spec -#[extern_spec] -impl std::option::Option { - //// ANCHOR_END: option_take_extern_spec - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - //// ANCHOR: option_take_extern_spec - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} -//// ANCHOR_END: option_take_extern_spec - //// ANCHOR: try_pop_rewrite //// ANCHOR: rewrite_link_impl impl List { @@ -121,7 +88,7 @@ impl List { } } - // // This will likely work in the future, but doesn't currently (even if you provide an `extern_spec` for `Option::map`): + // // This will likely work in the future, but doesn't currently: // // Currently you get this error: // // [Prusti: unsupported feature] unsupported creation of unique borrows (implicitly created in closure bindings) // pub fn try_pop(&mut self) -> Option { diff --git a/prusti-tests/tests/verify/pass/user-guide/peek.rs b/prusti-tests/tests/verify/pass/user-guide/peek.rs index a964451fe4e..8cee81dcbfe 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek.rs @@ -17,33 +17,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} - //// ANCHOR: implementation impl List { //// ANCHOR_END: implementation diff --git a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs index e083b676514..19d99b3cbc1 100644 --- a/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs +++ b/prusti-tests/tests/verify/pass/user-guide/peek_mut_pledges.rs @@ -17,32 +17,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; - - #[ensures(result === old(snap(self)))] - #[ensures(self.is_none())] - pub fn take(&mut self) -> Option; -} //// ANCHOR: pledge impl List { //// ANCHOR_END: pledge diff --git a/prusti-tests/tests/verify/pass/user-guide/pop.rs b/prusti-tests/tests/verify/pass/user-guide/pop.rs index b2026b18b3f..f40290d4d85 100644 --- a/prusti-tests/tests/verify/pass/user-guide/pop.rs +++ b/prusti-tests/tests/verify/pass/user-guide/pop.rs @@ -20,31 +20,6 @@ struct Node { next: Link, } -//// ANCHOR: extern_spec -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; -} -//// ANCHOR_END: extern_spec - //// ANCHOR: two_state_predicate //// ANCHOR: predicate_use //// ANCHOR: try_pop_empty diff --git a/prusti-tests/tests/verify/pass/user-guide/push_final_code.rs b/prusti-tests/tests/verify/pass/user-guide/push_final_code.rs index 30f3e9efaaf..72601b7420f 100644 --- a/prusti-tests/tests/verify/pass/user-guide/push_final_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_final_code.rs @@ -20,11 +20,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - //// ANCHOR: shifted_back impl List { //// ANCHOR_END: shifted_back diff --git a/prusti-tests/tests/verify/pass/user-guide/push_property_1.rs b/prusti-tests/tests/verify/pass/user-guide/push_property_1.rs index 4eb11dd92cb..f0be7f169a1 100644 --- a/prusti-tests/tests/verify/pass/user-guide/push_property_1.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_property_1.rs @@ -18,13 +18,6 @@ struct Node { next: Link, } -//// ANCHOR: extern_spec -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; -//// ANCHOR_END: extern_spec - //// ANCHOR: property_1 impl List { #[ensures(self.len() == old(self.len()) + 1)] // 1. Property diff --git a/prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs b/prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs index 47c9baeae60..fc54e6663ef 100644 --- a/prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs +++ b/prusti-tests/tests/verify/pass/user-guide/push_property_2_with_bounds.rs @@ -18,11 +18,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - //// ANCHOR: bounds impl List { #[pure] diff --git a/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs index 4e2d1de1b7a..94f6e7dff16 100644 --- a/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs +++ b/prusti-tests/tests/verify/pass/user-guide/testing_initial_code.rs @@ -18,29 +18,6 @@ struct Node { next: Link, } -#[extern_spec(std::mem)] -#[ensures(snap(dest) === src)] -#[ensures(result === old(snap(dest)))] -fn replace(dest: &mut T, src: T) -> T; - -// Specs for std::option::Option::unwrap(self) (and others) can be found here (work in progress): -// https://github.com/viperproject/prusti-dev/pull/1249/files#diff-bccda07f8a48357687e26408251041072c7470c188092fb58439de39974bdab5R47-R49 - -#[extern_spec] -impl std::option::Option { - #[requires(self.is_some())] - #[ensures(old(self) === Some(result))] - pub fn unwrap(self) -> T; - - #[pure] - #[ensures(result == matches!(self, None))] - pub const fn is_none(&self) -> bool; - - #[pure] - #[ensures(result == matches!(self, Some(_)))] - pub const fn is_some(&self) -> bool; -} - impl List { #[pure] pub fn len(&self) -> usize {