diff --git a/Cargo.lock b/Cargo.lock index e070c4b73aa..810ead6a36b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,25 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36" +dependencies = [ + "unicode-width", + "yansi-term", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anstream" version = "0.5.0" @@ -381,6 +400,17 @@ dependencies = [ "log", ] +[[package]] +name = "bstr" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +dependencies = [ + "memchr", + "regex-automata 0.3.8", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -399,6 +429,24 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +dependencies = [ + "serde", +] + [[package]] name = "cargo-test-macro" version = "0.1.0" @@ -452,6 +500,20 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.0.83" @@ -536,12 +598,50 @@ dependencies = [ "termcolor", ] +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "combine" version = "4.6.6" @@ -552,6 +652,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "comma" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" + [[package]] name = "compiletest_rs" version = "0.10.2" @@ -602,6 +708,19 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -897,6 +1016,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "distance" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9d8664cf849d7d0f3114a3a387d2f5e4303176d746d5a951aaddc66dfe9240" + [[package]] name = "dlv-list" version = "0.3.0" @@ -959,6 +1084,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1024,6 +1155,16 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "failure" version = "0.1.8" @@ -1532,6 +1673,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "index_vec" version = "0.1.3" @@ -1561,6 +1708,19 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "indicatif" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "instant" version = "0.1.12" @@ -1970,6 +2130,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.1" @@ -2056,6 +2222,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "p384" version = "0.13.0" @@ -2068,6 +2240,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + [[package]] name = "parking" version = "2.1.0" @@ -2225,12 +2406,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "portable-atomic" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettydiff" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff1fec61082821f8236cf6c0c14e8172b62ce8a72a0eedc30d3b247bb68dc11" +dependencies = [ + "ansi_term", + "pad", +] + [[package]] name = "primeorder" version = "0.13.2" @@ -2270,6 +2467,7 @@ dependencies = [ "prusti-interface", "prusti-rustc-interface", "prusti-viper", + "rustc-hash", "tracing 0.1.0", "tracing-chrome", "tracing-subscriber", @@ -2388,6 +2586,7 @@ dependencies = [ "prusti", "prusti-launch", "prusti-server", + "ui_test", "ureq", ] @@ -2894,6 +3093,9 @@ name = "semver" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -3484,6 +3686,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing 0.1.37", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -3550,6 +3762,33 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "ui_test" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7823f926a62629304c4a47c23949925f6faa0d4f14ddbce4405d55dbaeab324" +dependencies = [ + "annotate-snippets", + "anyhow", + "bstr", + "cargo-platform", + "cargo_metadata", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "distance", + "indicatif", + "lazy_static", + "prettydiff", + "regex", + "rustc_version", + "rustfix", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "unicase" version = "2.7.0" @@ -4171,6 +4410,15 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index 016c02a79ca..fe794ceb5b2 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -14,6 +14,7 @@ | [`CHECK_PANICS`](#check_panics) | `bool` | `true` | A | | [`CHECK_TIMEOUT`](#check_timeout) | `Option` | `None` | A | | [`COUNTEREXAMPLE`](#counterexample) | `bool` | `false` | A | +| [`DEBUG_RUNTIME_CHECKS`](#debug_runtime_checks) | `bool` | `false` | A | | [`DELETE_BASIC_BLOCKS`](#delete_basic_blocks) | `Vec` | `vec![]` | A | | [`DISABLE_NAME_MANGLING`](#disable_name_mangling) | `bool` | `false` | A | | [`DUMP_BORROWCK_INFO`](#dump_borrowck_info) | `bool` | `false` | A | @@ -34,6 +35,7 @@ | [`FULL_COMPILATION`](#full_compilation) | `bool` | `false` | A* | | [`HIDE_UUIDS`](#hide_uuids) | `bool` | `false` | A | | [`IGNORE_REGIONS`](#ignore_regions) | `bool` | `false` | A | +| [`INSERT_RUNTIME_CHECKS`](#insert_runtime_checks) | `string` | `"none"` | A | | [`INTERNAL_ERRORS_AS_WARNINGS`](#internal_errors_as_warnings) | `bool` | `false` | A | | [`INTERN_NAMES`](#intern_names) | `bool` | `true` | A | | [`JAVA_HOME`](#java_home) | `Option` | `None` | A | @@ -56,6 +58,7 @@ | [`PRINT_HASH`](#print_hash) | `bool` | `false` | A | | [`PRINT_TYPECKD_SPECS`](#print_typeckd_specs) | `bool` | `false` | A | | [`QUIET`](#quiet) | `bool` | `false` | A* | +| [`REMOVE_DEAD_CODE`](#remove_dead_code) | `bool` | `false` | A | | [`SERVER_ADDRESS`](#server_address) | `Option` | `None` | A | | [`SERVER_MAX_CONCURRENCY`](#server_max_concurrency) | `Option` | `None` | A | | [`SERVER_MAX_STORED_VERIFIERS`](#server_max_stored_verifiers) | `Option` | `None` | A | @@ -81,6 +84,7 @@ | [`VIPER_HOME`](#viper_home) | `Option` | `None` | A | | [`WRITE_SMT_STATISTICS`](#write_smt_statistics) | `bool` | `false` | A | + ## `ALLOW_UNREACHABLE_UNSUPPORTED_CODE` When enabled, unsupported code is encoded as `assert false`. This way error messages are reported only for unsupported code that is actually reachable. @@ -142,6 +146,10 @@ For more information see [here]( https://github.com/viperproject/silicon/blob/4c When enabled, Prusti will try to find and print a counterexample for any failed assertion or specification. +## `DEBUG_RUNTIME_CHECKS` + +When enabled, functions generated for runtime checking will emit a message when they are executed. + ## `DELETE_BASIC_BLOCKS` The given basic blocks will be replaced with `assume false`. @@ -233,6 +241,15 @@ When enabled, UUIDs of expressions and specifications printed with [`PRINT_TYPEC When enabled, debug files dumped by `rustc` will not contain lifetime regions. +## `INSERT_RUNTIME_CHECKS` + +Whether or not to enable runtime checks. Accepts one of the following values: + +- `"none"`: default option, no runtime checks will be inserted. +- `"selective"`: only contracts marked with `#[insert_runtime_check]` will be checked. +- `"all"`: all supported contracts will be runtime checked. + + ## `INTERNAL_ERRORS_AS_WARNINGS` When enabled, internal errors are presented as warnings. @@ -358,6 +375,10 @@ When enabled, user messages are not printed. Otherwise, messages output into `st > **Note:** `cargo prusti` sets this flag with `DEFAULT_PRUSTI_QUIET=true`. +## `REMOVE_DEAD_CODE` + +When enabled, Prusti will perform some optimizations on the generated executable by removing unreachable code and unneeded assertions and their corresponding checked operations. + ## `SERVER_ADDRESS` When set to an address and port (e.g. `"127.0.0.1:2468"`), Prusti will connect to the given server and use it for its verification backend. diff --git a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml index 29ec7cf3746..7a258b794ed 100644 --- a/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml +++ b/prusti-contracts/prusti-contracts-proc-macros/Cargo.toml @@ -22,3 +22,6 @@ proc-macro2 = { version = "1.0", optional = true } # Are we being compiled by Prusti and should include dependency on # prusti-specs and proc-macro2? prusti = ["dep:prusti-specs", "dep:proc-macro2"] +# If std is active, and prusti-specs is imported, forward the std flag +# to prusti-specs. +std = ["prusti-specs?/std"] diff --git a/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs b/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs index 9a50a2f3f8c..2e69a788447 100644 --- a/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs +++ b/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs @@ -130,6 +130,18 @@ pub fn body_variant(_tokens: TokenStream) -> TokenStream { TokenStream::new() } +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn quantifier_runtime_bounds(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn insert_runtime_check(_attr: TokenStream, _tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + // ---------------------- // --- PRUSTI ENABLED --- @@ -273,5 +285,21 @@ pub fn body_variant(tokens: TokenStream) -> TokenStream { prusti_specs::body_variant(tokens.into()).into() } +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn quantifier_runtime_bounds(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn insert_runtime_check(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::InsertRuntimeCheck, + attr.into(), + tokens.into(), + ) + .into() +} // Ensure that you've also crated a transparent `#[cfg(not(feature = "prusti"))]` // version of your new macro above! diff --git a/prusti-contracts/prusti-contracts/Cargo.toml b/prusti-contracts/prusti-contracts/Cargo.toml index be0b96d9a79..1d14bb35866 100644 --- a/prusti-contracts/prusti-contracts/Cargo.toml +++ b/prusti-contracts/prusti-contracts/Cargo.toml @@ -19,4 +19,6 @@ trybuild = "1.0" # Forward "prusti" flag [features] +default = ["std"] prusti = ["prusti-contracts-proc-macros/prusti"] +std = ["prusti-contracts-proc-macros/std"] diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index 28ab1bce27d..78f1256a964 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -1,4 +1,5 @@ -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] +// #![no_std] /// A macro for writing a precondition on a function. pub use prusti_contracts_proc_macros::requires; @@ -66,6 +67,14 @@ pub use prusti_contracts_proc_macros::terminates; /// A macro to annotate body variant of a loop to prove termination pub use prusti_contracts_proc_macros::body_variant; +/// A macro to explicitly annotate a quantifier with ranges for their +/// runtime checks +pub use prusti_contracts_proc_macros::quantifier_runtime_bounds; + +/// A macro to annotate contracts that should be checked +/// at runtime +pub use prusti_contracts_proc_macros::insert_runtime_check; + #[cfg(not(feature = "prusti"))] mod private { use core::marker::PhantomData; @@ -124,6 +133,9 @@ mod private { #[cfg(feature = "prusti")] pub mod core_spec; +#[cfg(feature = "prusti")] +pub mod runtime_check_internals; + #[cfg(feature = "prusti")] mod private { use core::{marker::PhantomData, ops::*}; diff --git a/prusti-contracts/prusti-contracts/src/runtime_check_internals.rs b/prusti-contracts/prusti-contracts/src/runtime_check_internals.rs new file mode 100644 index 00000000000..1b87ddea7e4 --- /dev/null +++ b/prusti-contracts/prusti-contracts/src/runtime_check_internals.rs @@ -0,0 +1,59 @@ +#[cfg(not(feature = "std"))] +use core::fmt::Write; + +#[cfg(not(feature = "std"))] +struct SimpleFormatter<'a>(&'a mut [u8], usize); + +#[cfg(not(feature = "std"))] +impl<'a> core::fmt::Write for SimpleFormatter<'a> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let dest_len = self.0.len(); + let copy_len = core::cmp::min(s.len(), dest_len); + + self.0[..copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); + self.1 = copy_len; + + if s.len() > dest_len { + Err(core::fmt::Error) + } else { + Ok(()) + } + } +} + +#[cfg(not(feature = "std"))] +pub fn num_to_str(n: T, buffer: &mut [u8]) -> &str { + let mut formatter = SimpleFormatter(buffer, 0); + write!(&mut formatter, "{}", n).expect("Failed to write number to buffer"); + let bytes_written = formatter.1; + core::str::from_utf8(&buffer[..bytes_written]).unwrap() +} + +// An internal function used for getting more precise error messages for runtime +// checks. +// Buffer manipulations because we cannot use std and need to operate on a buffer +// that is allocated before this function is called. +#[cfg(not(feature = "std"))] +pub fn check_expr(expr: bool, added_info: &str, buffer: &mut [u8], buffer_len: &mut usize) -> bool { + if !expr { + // buffer already has contents up until buffer_len + let after_len = *buffer_len + added_info.len(); + buffer[*buffer_len..after_len].copy_from_slice(added_info.as_bytes()); + *buffer_len = after_len; + false + } else { + true + } +} + +// If ctd is available, the check function can be a lot simpler, and doesn't +// need to operate on a passed buffer. +#[cfg(feature = "std")] +pub fn check_expr(expr: bool, added_info: &str, message: &mut std::string::String) -> bool { + if !expr { + message.push_str(added_info); + false + } else { + true + } +} diff --git a/prusti-contracts/prusti-specs/Cargo.toml b/prusti-contracts/prusti-specs/Cargo.toml index eed0f890ec9..e43670575ac 100644 --- a/prusti-contracts/prusti-specs/Cargo.toml +++ b/prusti-contracts/prusti-specs/Cargo.toml @@ -21,3 +21,6 @@ proc-macro2 = "1.0" uuid = { version = "1.0", features = ["v4"] } itertools = "0.11" rustc-hash = "1.1.0" + +[features] +std = [] diff --git a/prusti-contracts/prusti-specs/src/attribute_consumer.rs b/prusti-contracts/prusti-specs/src/attribute_consumer.rs new file mode 100644 index 00000000000..2259c5992ea --- /dev/null +++ b/prusti-contracts/prusti-specs/src/attribute_consumer.rs @@ -0,0 +1,60 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; + +// By default, attributes can only be attached to items like functions or closures. +// The point of this is to allow attaching attributes to the contents of a +// prusti_assert!() or similar (predicate). +// Note: this kind of consuming is only necessary when we want to consume an +// attribute at the beginning of a TokenStream without fully parsing it (mainly +// because the rest still has to be parsed by prusti_parse) +pub(crate) struct AttributeConsumer { + pub attributes: Vec, + pub rest: TokenStream, +} + +impl Parse for AttributeConsumer { + fn parse(input: ParseStream) -> syn::Result { + let attributes = input.call(syn::Attribute::parse_outer)?; + let rest: TokenStream = input.parse().unwrap(); + Ok(Self { attributes, rest }) + } +} + +// maybe we can make this more generic so it can be used in other places.. +impl AttributeConsumer { + pub fn get_attribute(&mut self, name: &str) -> Option { + if let Some(pos) = self.attributes.iter().position(|attr| { + if let Some(ident) = attr.path.get_ident() { + if *ident == name { + return true; + } + } + false + }) { + Some(self.attributes.remove(pos)) + } else { + None + } + } + + // /// A function that can be used to check that all attributes have been + // /// consumed. Returns an error with the span of the first remaining attribute + // pub fn check_no_remaining_attrs(&self) -> syn::Result<()> { + // if let Some(attr) = self.attributes.first() { + // Err(syn::Error::new( + // attr.span(), + // "This attribute could not be processed and probably doesn't belong here", + // )) + // } else { + // Ok(()) + // } + // } + + pub fn tokens(self) -> TokenStream { + let Self { attributes, rest } = self; + let mut attrs: TokenStream = attributes.into_iter().map(|attr| quote!(#attr)).collect(); + attrs.extend(quote!(#rest)); + attrs + } +} diff --git a/prusti-contracts/prusti-specs/src/common.rs b/prusti-contracts/prusti-specs/src/common.rs index 787eef8f21b..b0c4f2bdf1a 100644 --- a/prusti-contracts/prusti-specs/src/common.rs +++ b/prusti-contracts/prusti-specs/src/common.rs @@ -709,3 +709,33 @@ mod tests { } } } + +pub enum RuntimeCheckMode { + All, + Selective, + Never, +} + +impl RuntimeCheckMode { + pub fn determine() -> Self { + match std::env::var("PRUSTI_INSERT_RUNTIME_CHECKS") + .unwrap_or("never".to_string()) + .as_str() + { + "all" => Self::All, + "selective" => Self::Selective, + "never" => Self::Never, + _ => Self::Never, + } + } + + /// Depending on whether a contract has a #[insert_runtime_check] attribute + /// decide whether a check should actually be inserted + pub fn decide_insertion(&self, has_attr: bool) -> bool { + match (self, has_attr) { + (Self::All, _) => true, + (Self::Selective, b) => b, + (Self::Never, _) => false, + } + } +} diff --git a/prusti-contracts/prusti-specs/src/lib.rs b/prusti-contracts/prusti-specs/src/lib.rs index aa784c1267a..dbd8ae3585c 100644 --- a/prusti-contracts/prusti-specs/src/lib.rs +++ b/prusti-contracts/prusti-specs/src/lib.rs @@ -4,6 +4,7 @@ #![feature(proc_macro_span)] #![feature(if_let_guard)] #![feature(assert_matches)] +#![feature(proc_macro_diagnostic)] // This Clippy chcek seems to be always wrong. #![allow(clippy::iter_with_drain)] #![warn(clippy::disallowed_types)] @@ -22,17 +23,25 @@ pub mod specifications; mod type_model; mod user_provided_type_params; mod print_counterexample; +mod runtime_checks; +mod attribute_consumer; +use attribute_consumer::AttributeConsumer; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::{quote, quote_spanned, ToTokens}; use rewriter::AstRewriter; +use runtime_checks::check_type::CheckItemType; use std::convert::TryInto; use syn::{spanned::Spanned, visit::Visit}; use crate::{ common::{merge_generics, RewritableReceiver, SelfTypeRewriter}, predicate::{is_predicate_macro, ParsedPredicate}, - specifications::preparser::{parse_prusti, parse_type_cond_spec, NestedSpec}, + runtime_checks::translation::{translate_expression_runtime, translate_runtime_checks}, + specifications::preparser::{ + parse_prusti, parse_prusti_assert_pledge, parse_prusti_pledge, parse_type_cond_spec, + NestedSpec, + }, }; pub use extern_spec_rewriter::ExternSpecKind; use parse_closure_macro::ClosureWithSpec; @@ -89,7 +98,8 @@ fn extract_prusti_attributes( | SpecAttributeKind::Terminates | SpecAttributeKind::Trusted | SpecAttributeKind::Predicate - | SpecAttributeKind::Verified => { + | SpecAttributeKind::Verified + | SpecAttributeKind::InsertRuntimeCheck => { assert!(attr.tokens.is_empty(), "Unexpected shape of an attribute."); attr.tokens } @@ -161,12 +171,25 @@ fn generate_spec_and_assertions( let mut generated_items = vec![]; let mut generated_attributes = vec![]; + let mut check_annotation_for_next = false; + let check_mode = common::RuntimeCheckMode::determine(); + for (attr_kind, attr_tokens) in prusti_attributes.drain(..) { + let runtime_check = check_mode.decide_insertion(check_annotation_for_next); + if check_annotation_for_next { + // warn users if #[insert_runtime_check] attribute is in front + // of an unsupported contract + warn_uncheckable_runtimecheck(&attr_kind, &attr_tokens); + } let rewriting_result = match attr_kind { - SpecAttributeKind::Requires => generate_for_requires(attr_tokens, item), - SpecAttributeKind::Ensures => generate_for_ensures(attr_tokens, item), - SpecAttributeKind::AfterExpiry => generate_for_after_expiry(attr_tokens, item), - SpecAttributeKind::AssertOnExpiry => generate_for_assert_on_expiry(attr_tokens, item), + SpecAttributeKind::Requires => generate_for_requires(attr_tokens, item, runtime_check), + SpecAttributeKind::Ensures => generate_for_ensures(attr_tokens, item, runtime_check), + SpecAttributeKind::AfterExpiry => { + generate_for_after_expiry(attr_tokens, item, runtime_check) + } + SpecAttributeKind::AssertOnExpiry => { + generate_for_assert_on_expiry(attr_tokens, item, runtime_check) + } SpecAttributeKind::Pure => generate_for_pure(attr_tokens, item), SpecAttributeKind::Verified => generate_for_verified(attr_tokens, item), SpecAttributeKind::Terminates => generate_for_terminates(attr_tokens, item), @@ -176,10 +199,18 @@ fn generate_spec_and_assertions( // `check_incompatible_attrs`; so we'll never reach here. SpecAttributeKind::Predicate => unreachable!(), SpecAttributeKind::Invariant => unreachable!(), - SpecAttributeKind::RefineSpec => type_cond_specs::generate(attr_tokens, item), + SpecAttributeKind::RefineSpec => { + type_cond_specs::generate(attr_tokens, item, runtime_check) + } SpecAttributeKind::Model => unreachable!(), SpecAttributeKind::PrintCounterexample => unreachable!(), + SpecAttributeKind::InsertRuntimeCheck => { + // generate runtime check functions for the one following this one + check_annotation_for_next = true; + continue; + } }; + check_annotation_for_next = false; let (new_items, new_attributes) = rewriting_result?; generated_items.extend(new_items); generated_attributes.extend(new_attributes); @@ -188,70 +219,170 @@ fn generate_spec_and_assertions( Ok((generated_items, generated_attributes)) } +fn warn_uncheckable_runtimecheck(attr_kind: &SpecAttributeKind, tokens: &TokenStream) { + if let SpecAttributeKind::Pure + | SpecAttributeKind::Verified + | SpecAttributeKind::Terminates + | SpecAttributeKind::Trusted + | SpecAttributeKind::Predicate + | SpecAttributeKind::Invariant + | SpecAttributeKind::Model + | SpecAttributeKind::PrintCounterexample + | SpecAttributeKind::InsertRuntimeCheck = attr_kind + { + tokens + .span() + .unwrap() + .warning( + "Attribute #[insert_runtime_check] could not be applied to this specification.", + ) + .emit(); + } +} + /// Generate spec items and attributes to typecheck the and later retrieve "requires" annotations. -fn generate_for_requires(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { +fn generate_for_requires( + attr: TokenStream, + item: &untyped::AnyFnItem, + runtime_check: bool, +) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); let spec_id = rewriter.generate_spec_id(); let spec_id_str = spec_id.to_string(); - let spec_item = - rewriter.process_assertion(rewriter::SpecItemType::Precondition, spec_id, attr, item)?; - Ok(( - vec![spec_item], - vec![parse_quote_spanned! {item.span()=> - #[prusti::pre_spec_id_ref = #spec_id_str] - }], - )) + let prusti_tokens = parse_prusti(attr)?; + let spec_item = rewriter.generate_spec_item_fn( + rewriter::SpecItemType::Precondition, + spec_id, + prusti_tokens.clone(), + item, + )?; + let mut items = vec![spec_item]; + let mut attrs = vec![parse_quote_spanned! {item.span() => + #[prusti::pre_spec_id_ref = #spec_id_str] + }]; + + if runtime_check { + let check_id = rewriter.generate_spec_id(); + let check_id_str = check_id.to_string(); + let check_item = + translate_runtime_checks(CheckItemType::Requires, check_id, prusti_tokens, item)?; + items.push(check_item); + attrs.push(parse_quote_spanned! {item.span() => + #[prusti::pre_check_id_ref = #check_id_str] + }); + } + Ok((items, attrs)) } /// Generate spec items and attributes to typecheck the and later retrieve "ensures" annotations. -fn generate_for_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { +fn generate_for_ensures( + attr: TokenStream, + item: &untyped::AnyFnItem, + runtime_check: bool, +) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); let spec_id = rewriter.generate_spec_id(); let spec_id_str = spec_id.to_string(); - let spec_item = - rewriter.process_assertion(rewriter::SpecItemType::Postcondition, spec_id, attr, item)?; - Ok(( - vec![spec_item], - vec![parse_quote_spanned! {item.span()=> - #[prusti::post_spec_id_ref = #spec_id_str] - }], - )) + let prusti_tokens = parse_prusti(attr)?; + + let spec_item = rewriter.generate_spec_item_fn( + rewriter::SpecItemType::Postcondition, + spec_id, + prusti_tokens.clone(), + item, + )?; + let mut items = vec![spec_item]; + let mut attrs = vec![parse_quote_spanned! {item.span()=> + #[prusti::post_spec_id_ref = #spec_id_str] + }]; + if runtime_check { + let check_id = rewriter.generate_spec_id(); + let check_id_str = check_id.to_string(); + let check_item = + translate_runtime_checks(CheckItemType::Ensures, check_id, prusti_tokens, item)?; + items.push(check_item); + attrs.push(parse_quote_spanned! {item.span()=> + #[prusti::post_check_id_ref = #check_id_str] + }); + } + Ok((items, attrs)) } /// Generate spec items and attributes to typecheck and later retrieve "after_expiry" annotations. -fn generate_for_after_expiry(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { +fn generate_for_after_expiry( + attr: TokenStream, + item: &untyped::AnyFnItem, + runtime_check: bool, +) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); let spec_id = rewriter.generate_spec_id(); let spec_id_str = spec_id.to_string(); - let spec_item = rewriter.process_pledge(spec_id, attr, item)?; - Ok(( - vec![spec_item], - vec![parse_quote_spanned! {item.span()=> - #[prusti::pledge_spec_id_ref = #spec_id_str] - }], - )) + let parsed = parse_prusti_pledge(attr)?; + let spec_item = rewriter.generate_spec_item_fn( + rewriter::SpecItemType::Pledge, + spec_id, + parsed.clone(), + item, + )?; + let mut res_items = vec![spec_item]; + let mut res_attrs: Vec = vec![parse_quote_spanned! { item.span() => + #[prusti::pledge_spec_id_ref = #spec_id_str] + }]; + if runtime_check { + let check_id = rewriter.generate_spec_id(); + let check_id_str = check_id.to_string(); + let check_item = translate_runtime_checks( + CheckItemType::PledgeRhs { has_lhs: false }, + check_id, + parsed, + item, + )?; + res_items.push(check_item); + res_attrs.push(parse_quote_spanned! {item.span() => + #[prusti::assert_pledge_check_ref = #check_id_str] + }); + } + Ok((res_items, res_attrs)) } /// Generate spec items and attributes to typecheck and later retrieve "after_expiry" annotations. -fn generate_for_assert_on_expiry(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { +fn generate_for_assert_on_expiry( + attr: TokenStream, + item: &untyped::AnyFnItem, + runtime_check: bool, +) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); let spec_id_lhs = rewriter.generate_spec_id(); let spec_id_lhs_str = spec_id_lhs.to_string(); let spec_id_rhs = rewriter.generate_spec_id(); let spec_id_rhs_str = spec_id_rhs.to_string(); + let parsed = parse_prusti_assert_pledge(attr)?; + let (spec_item_lhs, spec_item_rhs) = - rewriter.process_assert_pledge(spec_id_lhs, spec_id_rhs, attr, item)?; - Ok(( - vec![spec_item_lhs, spec_item_rhs], - vec![ - parse_quote_spanned! {item.span()=> - #[prusti::assert_pledge_spec_id_ref_lhs = #spec_id_lhs_str] - }, - parse_quote_spanned! {item.span()=> - #[prusti::assert_pledge_spec_id_ref_rhs = #spec_id_rhs_str] - }, - ], - )) + rewriter.process_assert_pledge(spec_id_lhs, spec_id_rhs, parsed.clone(), item)?; + + let mut res_items = vec![spec_item_lhs, spec_item_rhs]; + let mut res_attrs: Vec = vec![ + parse_quote_spanned! {item.span()=> + #[prusti::assert_pledge_spec_id_ref_lhs = #spec_id_lhs_str] + }, + parse_quote_spanned! {item.span()=> + #[prusti::assert_pledge_spec_id_ref_rhs = #spec_id_rhs_str] + }, + ]; + + if runtime_check { + // same check_id for both lhs and rhs + let check_id = rewriter.generate_spec_id(); + let check_id_str = check_id.to_string(); + let (lhs_check, rhs_check) = rewriter.create_assert_pledge_check(check_id, parsed, item)?; + res_items.push(lhs_check); + res_items.push(rhs_check); + res_attrs.push(parse_quote_spanned! {item.span()=> + #[prusti::assert_pledge_check_ref = #check_id_str] + }); + } + Ok((res_items, res_attrs)) } /// Generate spec items and attributes to typecheck and later retrieve "terminates" annotations. @@ -417,23 +548,43 @@ fn generate_for_trusted_for_types(attr: TokenStream, item: &syn::DeriveInput) -> } pub fn body_variant(tokens: TokenStream) -> TokenStream { - generate_expression_closure(&AstRewriter::process_loop_variant, tokens) + generate_expression_closure( + &AstRewriter::process_loop_variant, + tokens, + CheckItemType::Unchecked, + ) } pub fn body_invariant(tokens: TokenStream) -> TokenStream { - generate_expression_closure(&AstRewriter::process_loop_invariant, tokens) + generate_expression_closure( + &AstRewriter::process_loop_invariant, + tokens.clone(), + CheckItemType::BodyInvariant, + ) } pub fn prusti_assertion(tokens: TokenStream) -> TokenStream { - generate_expression_closure(&AstRewriter::process_prusti_assertion, tokens) + generate_expression_closure( + &AstRewriter::process_prusti_assertion, + tokens.clone(), + CheckItemType::Assert, + ) } pub fn prusti_assume(tokens: TokenStream) -> TokenStream { - generate_expression_closure(&AstRewriter::process_prusti_assumption, tokens) + generate_expression_closure( + &AstRewriter::process_prusti_assumption, + tokens.clone(), + CheckItemType::Assume, + ) } pub fn prusti_refutation(tokens: TokenStream) -> TokenStream { - generate_expression_closure(&AstRewriter::process_prusti_refutation, tokens) + generate_expression_closure( + &AstRewriter::process_prusti_refutation, + tokens, + CheckItemType::Unchecked, + ) } /// Generates the TokenStream encoding an expression using prusti syntax @@ -441,16 +592,51 @@ pub fn prusti_refutation(tokens: TokenStream) -> TokenStream { fn generate_expression_closure( fun: &dyn Fn(&mut AstRewriter, SpecificationId, TokenStream) -> syn::Result, tokens: TokenStream, + check_type: CheckItemType, ) -> TokenStream { + let mut expr_with_attrs: AttributeConsumer = handle_result!(syn::parse(tokens.into())); + let runtime_attr = expr_with_attrs.get_attribute("insert_runtime_check"); + let tokens = expr_with_attrs.tokens(); + let prusti_tokens = handle_result!(parse_prusti(tokens)); let mut rewriter = rewriter::AstRewriter::new(); let spec_id = rewriter.generate_spec_id(); - let closure = handle_result!(fun(&mut rewriter, spec_id, tokens)); + let closure = handle_result!(fun(&mut rewriter, spec_id, prusti_tokens.clone())); let callsite_span = Span::call_site(); - quote_spanned! {callsite_span=> - #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)] - #[prusti::specs_version = #SPECS_VERSION] - if false { - #closure + let check_id = rewriter.generate_spec_id(); + let checkable = check_type.can_be_checked(); + // emit a warning if #[insert_runtime_check] precedes an item that is not + // checked at runtime. + let insert_runtime_check = + common::RuntimeCheckMode::determine().decide_insertion(runtime_attr.is_some()); + if !checkable && runtime_attr.is_some() { + prusti_tokens + .span() + .unwrap() + .warning("This contract will not be checked at runtime.") + .emit(); + } + if check_type.can_be_checked() && insert_runtime_check { + let check_closure = handle_result!(translate_expression_runtime( + prusti_tokens, + check_id, + check_type + )); + quote_spanned! { callsite_span=> + #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)] + #[prusti::specs_version = #SPECS_VERSION] + if false { + #closure + } else if true { + #check_closure + } + } + } else { + quote_spanned! {callsite_span=> + #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)] + #[prusti::specs_version = #SPECS_VERSION] + if false { + #closure + } } } } @@ -880,6 +1066,7 @@ fn extract_prusti_attributes_for_types( SpecAttributeKind::Invariant => unreachable!("invariant on type"), SpecAttributeKind::Predicate => unreachable!("predicate on type"), SpecAttributeKind::Terminates => unreachable!("terminates on type"), + SpecAttributeKind::InsertRuntimeCheck => unreachable!("runtimecheck on type"), SpecAttributeKind::Trusted | SpecAttributeKind::Model => { assert!(attr.tokens.is_empty(), "Unexpected shape of an attribute."); attr.tokens @@ -927,6 +1114,7 @@ fn generate_spec_and_assertions_for_types( SpecAttributeKind::Invariant => unreachable!(), SpecAttributeKind::RefineSpec => unreachable!(), SpecAttributeKind::Terminates => unreachable!(), + SpecAttributeKind::InsertRuntimeCheck => unreachable!(), SpecAttributeKind::Trusted => generate_for_trusted_for_types(attr_tokens, item), SpecAttributeKind::Model => generate_for_model(attr_tokens, item), SpecAttributeKind::PrintCounterexample => { diff --git a/prusti-contracts/prusti-specs/src/predicate.rs b/prusti-contracts/prusti-specs/src/predicate.rs index 9469215e312..622834dc099 100644 --- a/prusti-contracts/prusti-specs/src/predicate.rs +++ b/prusti-contracts/prusti-specs/src/predicate.rs @@ -1,8 +1,12 @@ //! Predicate parsing use crate::{ - common::{HasMacro, HasSignature}, - rewriter, SpecificationId, SPECS_VERSION, + attribute_consumer::AttributeConsumer, + common::{HasMacro, HasSignature, RuntimeCheckMode}, + rewriter, + runtime_checks::translation::translate_predicate, + specifications::preparser::parse_prusti, + SpecificationId, SPECS_VERSION, }; use proc_macro2::{Span, TokenStream}; use quote::{quote_spanned, ToTokens}; @@ -81,6 +85,13 @@ fn parse_predicate_internal( in_spec_refinement: bool, ) -> syn::Result { let span = tokens.span(); + let mut attr_consumer: AttributeConsumer = syn::parse(tokens.into())?; + let has_runtime_attr = attr_consumer + .get_attribute("insert_runtime_check") + .is_some(); + let runtime_checkable = RuntimeCheckMode::determine().decide_insertion(has_runtime_attr); + // attr_consumer.check_no_remaining_attrs()?; + let tokens = attr_consumer.tokens(); let input: PredicateFnInput = syn::parse2(tokens).map_err(|e| { syn::Error::new( e.span(), @@ -103,7 +114,7 @@ fn parse_predicate_internal( if in_spec_refinement { let patched_function: syn::ImplItemMethod = - patch_predicate_macro_body(&input, span, spec_id); + patch_predicate_macro_body(&input, span, spec_id, runtime_checkable)?; let spec_function = generate_spec_function( input.body.unwrap(), return_type, @@ -116,7 +127,8 @@ fn parse_predicate_internal( patched_function, })) } else { - let patched_function: syn::ItemFn = patch_predicate_macro_body(&input, span, spec_id); + let patched_function: syn::ItemFn = + patch_predicate_macro_body(&input, span, spec_id, runtime_checkable)?; let spec_function = generate_spec_function( input.body.unwrap(), return_type, @@ -130,6 +142,12 @@ fn parse_predicate_internal( })) } } else { + if runtime_checkable { + return Err(syn::Error::new( + span, + "Abstract predicates can not be runtime checked", + )); + } let signature = input.fn_sig; let patched_function = parse_quote_spanned!(span=> #[prusti::abstract_predicate] @@ -143,23 +161,38 @@ fn parse_predicate_internal( } } +/// Either sets the predicates body to unimplemented, or (in case it should +/// be runtime checkable) with the translated runtime check fn patch_predicate_macro_body( predicate: &PredicateFnInput, input_span: Span, spec_id: SpecificationId, -) -> R { + runtime_checkable: bool, +) -> syn::Result { let visibility = &predicate.visibility; let signature = &predicate.fn_sig; let spec_id_str = spec_id.to_string(); + let body = if runtime_checkable { + translate_predicate( + parse_prusti(predicate.body.clone().unwrap())?, + predicate, + input_span, + )? + } else { + quote_spanned! {input_span => unimplemented!("predicate")} + }; + let check_attr = + runtime_checkable.then_some(quote_spanned! {input_span => #[prusti::check_only]}); - parse_quote_spanned!(input_span=> - #[allow(unused_must_use, unused_variables, dead_code)] + Ok(parse_quote_spanned!(input_span=> + #[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = #spec_id_str] #[prusti::specs_version = #SPECS_VERSION] + #check_attr #visibility #signature { - unimplemented!("predicate") + #body } - ) + )) } fn generate_spec_function( @@ -207,3 +240,13 @@ impl syn::parse::Parse for PredicateFnInput { }) } } + +impl HasSignature for PredicateFnInput { + fn sig(&self) -> &syn::Signature { + &self.fn_sig + } + + fn sig_mut(&mut self) -> &mut syn::Signature { + &mut self.fn_sig + } +} diff --git a/prusti-contracts/prusti-specs/src/rewriter.rs b/prusti-contracts/prusti-specs/src/rewriter.rs index a971de3b7a6..e5c0e4be03e 100644 --- a/prusti-contracts/prusti-specs/src/rewriter.rs +++ b/prusti-contracts/prusti-specs/src/rewriter.rs @@ -1,8 +1,9 @@ use crate::{ common::HasSignature, + runtime_checks::{check_type::CheckItemType, translation::translate_runtime_checks}, specifications::{ common::{SpecificationId, SpecificationIdGenerator}, - preparser::{parse_prusti, parse_prusti_assert_pledge, parse_prusti_pledge}, + preparser::parse_prusti, untyped, }, }; @@ -65,7 +66,7 @@ impl AstRewriter { None } - fn generate_result_arg(&self, item: &T) -> syn::FnArg { + pub(crate) fn generate_result_arg(item: &T) -> syn::FnArg { let item_span = item.span(); let output_ty = match &item.sig().output { syn::ReturnType::Default => parse_quote_spanned!(item_span=> ()), @@ -127,7 +128,7 @@ impl AstRewriter { spec_item.sig.inputs = item.sig().inputs.clone(); match spec_type { SpecItemType::Postcondition | SpecItemType::Pledge => { - let fn_arg = self.generate_result_arg(item); + let fn_arg = Self::generate_result_arg(item); spec_item.sig.inputs.push(fn_arg); } _ => (), @@ -146,21 +147,6 @@ impl AstRewriter { self.generate_spec_item_fn(spec_type, spec_id, parse_prusti(tokens)?, item) } - /// Parse a pledge with lhs into a Rust expression - pub fn process_pledge( - &mut self, - spec_id: SpecificationId, - tokens: TokenStream, - item: &untyped::AnyFnItem, - ) -> syn::Result { - self.generate_spec_item_fn( - SpecItemType::Pledge, - spec_id, - parse_prusti_pledge(tokens)?, - item, - ) - } - pub fn process_pure_refinement( &mut self, spec_id: SpecificationId, @@ -190,13 +176,28 @@ impl AstRewriter { &mut self, spec_id_lhs: SpecificationId, spec_id_rhs: SpecificationId, - tokens: TokenStream, + (lhs, rhs): (TokenStream, TokenStream), item: &untyped::AnyFnItem, ) -> syn::Result<(syn::Item, syn::Item)> { - let (lhs, rhs) = parse_prusti_assert_pledge(tokens)?; let lhs_item = self.generate_spec_item_fn(SpecItemType::Pledge, spec_id_lhs, lhs, item)?; let rhs_item = self.generate_spec_item_fn(SpecItemType::Pledge, spec_id_rhs, rhs, item)?; - Ok((lhs_item, rhs_item)) + syn::Result::Ok((lhs_item, rhs_item)) + } + + pub fn create_assert_pledge_check( + &mut self, + check_id: SpecificationId, + (lhs, rhs): (TokenStream, TokenStream), + item: &untyped::AnyFnItem, + ) -> syn::Result<(syn::Item, syn::Item)> { + let lhs_check = translate_runtime_checks(CheckItemType::PledgeLhs, check_id, lhs, item)?; + let rhs_check = translate_runtime_checks( + CheckItemType::PledgeRhs { has_lhs: true }, + check_id, + rhs, + item, + )?; + syn::Result::Ok((lhs_check, rhs_check)) } /// Parse a loop invariant into a Rust expression @@ -259,9 +260,8 @@ impl AstRewriter { &mut self, kind: TokenStream, spec_id: SpecificationId, - tokens: TokenStream, + expr: TokenStream, ) -> syn::Result { - let expr = parse_prusti(tokens)?; let spec_id_str = spec_id.to_string(); Ok(quote_spanned! {expr.span()=> { diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/associated_function_info.rs b/prusti-contracts/prusti-specs/src/runtime_checks/associated_function_info.rs new file mode 100644 index 00000000000..ad69d9d6b49 --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/associated_function_info.rs @@ -0,0 +1,117 @@ +use crate::common::HasSignature; +use proc_macro2::Span; +use rustc_hash::FxHashMap; +use syn::{parse_quote_spanned, spanned::Spanned}; + +/// Information about the function that a specification is attached to +pub(crate) struct AssociatedFunctionInfo { + /// the arguments of the associated function + pub inputs: FxHashMap, +} + +impl AssociatedFunctionInfo { + /// Used for assertions etc., where there is no associated + /// function + pub(crate) fn empty() -> Self { + Self { + inputs: Default::default(), + } + } + + pub(crate) fn new(item: &T) -> syn::Result { + let inputs: FxHashMap = item + .sig() + .inputs + .iter() + .enumerate() + .map(|(id, el)| -> syn::Result<(String, Argument)> { + let arg = create_argument(el, id)?; + Ok((arg.name.clone(), arg)) + }) + .collect::>>()?; + Ok(Self { inputs }) + } + + pub(crate) fn get_mut_arg(&mut self, name: &String) -> Option<&mut Argument> { + self.inputs.get_mut(name) + } +} + +/// Arguments to a function / i.e. specification. This struct is meant to +/// Keep track of them and collect information about how they are used +/// within specs to correctly store their old values for runtime checks. +#[derive(Debug)] +pub struct Argument { + /// name of the argument (stays the same from original function to checks) + pub name: String, + /// the type of this argument. If it's none, then only because this is a Self + /// type. + pub ty: syn::Type, + /// whether this value occurrs in an old expression. Don't want to clone + /// all args, since then they have to implement clone() and the type + /// resulting from cloning them must be known at ast level + pub used_in_old: bool, + /// field in old-tuple (old_values.X) where this argument will be stored + pub index: usize, + /// whether or not this field is a reference. We assume that all ref types + /// start with & (which obviously can be wrong) + pub is_ref: bool, + pub is_mutable: bool, + pub span: Span, +} + +pub(crate) fn create_argument(arg: &syn::FnArg, index: usize) -> syn::Result { + let span = arg.span(); + match arg { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => { + if let syn::Pat::Ident(pat_ident) = *pat.clone() { + let mut is_mutable = pat_ident.mutability.is_some(); + + let is_ref = if let box syn::Type::Reference(ty_ref) = ty { + is_mutable = is_mutable || ty_ref.mutability.is_some(); + true + } else { + false + }; + let arg = Argument { + name: pat_ident.ident.to_string(), + ty: *ty.clone(), + used_in_old: false, + index, + is_ref, + is_mutable, + span, + }; + Ok(arg) + } else { + syn::Result::Err(syn::Error::new( + span, + "For runtime checks, arguments have to be resolved to identifiers.", + )) + } + } + syn::FnArg::Receiver(syn::Receiver { + reference, + mutability, + .. + }) => { + let is_ref = reference.is_some(); + let is_mutable = mutability.is_some(); + let ty: syn::Type = if is_ref { + parse_quote_spanned! {span => &Self} + } else { + parse_quote_spanned! {span => Self} + }; + let arg = Argument { + name: "self".to_string(), + ty, + used_in_old: false, + index, + is_ref, + is_mutable, + span, + }; + Ok(arg) + } + } +} diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/boundary_extraction.rs b/prusti-contracts/prusti-specs/src/runtime_checks/boundary_extraction.rs new file mode 100644 index 00000000000..11a3b7a665a --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/boundary_extraction.rs @@ -0,0 +1,427 @@ +use crate::runtime_checks::utils; +use quote::ToTokens; +use rustc_hash::FxHashSet; +use syn::{self, parse_quote, parse_quote_spanned, spanned::Spanned, visit::Visit, BinOp}; + +type LoopBoundaries = ((String, syn::Type), syn::ExprRange); + +pub(crate) struct BoundExtractor { + // the set of bound variables within that expression + pub name_set: FxHashSet, +} + +/// Try to find manually annotated boundaries with the #[runtime_quantifier_bounds] +/// attribute. Returns None if there is no such attribute, Some(error) +/// if there is one, but the contained ranges are false, and the tokens +/// of the ranges otherwise. +pub(crate) fn manual_bounds( + expr: syn::ExprClosure, + bound_vars: Vec<(String, syn::Type)>, +) -> Option>> { + let manual_bounds = utils::get_attribute_contents( + "prusti :: runtime_quantifier_bounds".to_string(), + &expr.attrs, + )?; + let bounds_expr: syn::Expr = simplify_expression(&syn::parse2(manual_bounds).ok()?); + // there is either one or multiple + let bounds_vec = match bounds_expr { + syn::Expr::Tuple(expr_tuple) => { + let mut ranges: Vec = Vec::new(); + for range in expr_tuple.elems.iter() { + let range = simplify_expression(range); + if let syn::Expr::Range(expr_range) = range { + ranges.push(expr_range); + } else { + return Some(Err(syn::Error::new( + range.span(), + "Runtime checks: quantifier boundaries must be provided as ranges", + ))); + } + } + ranges + } + syn::Expr::Range(range_expr) => vec![range_expr], + _ => { + return Some(Err(syn::Error::new( + bounds_expr.span(), + "Runtime checks: quantifier boundaries must be provided as ranges", + ))); + } + }; + if bounds_vec.len() != bound_vars.len() { + return Some(Err(syn::Error::new( + expr.span(), + "Runtime checks: the provided number of ranges does not match the number of closure arguments."))); + } + assert!(bounds_vec.len() == bound_vars.len()); + Some(Ok(bound_vars.into_iter().zip(bounds_vec).collect())) +} + +/// If no ranges were manually annotated, this function tries to derive them by looking at +/// the contained expression. This only works for a very limited set of cases. +pub(crate) fn derive_ranges( + body: syn::Expr, + args: Vec<(String, syn::Type)>, +) -> syn::Result> { + if args.len() > 1 { + return Err(syn::Error::new( + body.span(), + "Runtime checks: multiple args without manually defined boundaries are not allowed", + )); + } + let name_set: FxHashSet = args.iter().map(|el| el.0.clone()).collect(); + let mut boundaries: Vec<((String, syn::Type), syn::ExprRange)> = Vec::new(); + let bounds_expr = split_implication_lhs(&body).unwrap_or(parse_quote! {()}); + let boundary_extractor = BoundExtractor { name_set }; + for (name, ty) in args.iter() { + let range_expr: syn::ExprRange = if utils::is_primitive_number(ty) { + let bounds = boundary_extractor.extract_bounds_recursive(&bounds_expr, name, false); + let mut upper_bound_opt = None; + let mut lower_bound_opt = None; + + let mut include_upper = true; // if we have the MAX upper limit, + for Boundary { + kind, + bound, + included, + .. + } in bounds.iter() + { + // include it + match *kind { + BoundaryKind::Upper => { + // first idea was to add one here if inclusive, but + // this can lead to overflows! Need to use ..=x syntax + if upper_bound_opt.is_some() { + return Err(syn::Error::new( + body.span(), + format!( + "Runtime checks: multiple upper bounds defined for {}", + name + ), + )); + } + upper_bound_opt = Some(bound.clone()); + include_upper = *included; + } + BoundaryKind::Lower => { + if lower_bound_opt.is_some() { + return Err(syn::Error::new( + body.span(), + format!( + "Runtime checks: multiple lower bounds defined for {}", + name + ), + )); + }; + lower_bound_opt = if *included { + // lower bound works the other way around + Some(bound.clone()) + } else { + Some(parse_quote! { + #bound + 1 + }) + } + } + } + } + let upper_bound = if let Some(upper_bound) = upper_bound_opt { + upper_bound + } else { + // upper bounds are required unless the type is very small + if utils::ty_small_enough(ty) { + parse_quote_spanned! {body.span() => + #ty::MAX + } + } else { + return Err(syn::Error::new( + body.span(), + format!( + "Runtime checks: a quantifier over type {} requires an upper bound", + ty.to_token_stream() + ), + )); + } + }; + let lower_bound = if let Some(lower_bound) = lower_bound_opt { + lower_bound + } else { + // lower bounds are required unless the type is very small or unsigned + if utils::ty_small_enough(ty) || utils::ty_unsigned(ty) { + parse_quote_spanned! {body.span() => + #ty::MIN + } + } else { + return Err(syn::Error::new( + body.span(), + format!( + "Runtime checks: a quantifier over type {} requires a lower bound", + ty.to_token_stream() + ), + )); + } + }; + + if include_upper { + parse_quote_spanned! {body.span() => + (#lower_bound)..=(#upper_bound) + } + } else { + parse_quote_spanned! {body.span() => + (#lower_bound)..(#upper_bound) + } + } + } else { + return Err(syn::Error::new( + body.span(), + format!( + "Runtime checks: quantifier over type {} is not supported", + ty.to_token_stream() + ), + )); + }; + boundaries.push(((name.clone(), ty.clone()), range_expr)); + } + Ok(boundaries) +} + +/// Given the body of a quantifier, if it contains an implication get its lefthandside +pub fn split_implication_lhs(expr: &syn::Expr) -> Option { + // first make sure the expression has the form A ==> B (or equivalent) + let simplified = simplify_expression(expr); + if let syn::Expr::Binary(syn::ExprBinary { + left: + box syn::Expr::Unary(syn::ExprUnary { + op: syn::UnOp::Not(_), + expr: box bound_expr, + .. + }), + op: syn::BinOp::Or(_), + .. + }) = simplified + { + // in this case, the contents of the quantifier have the form + // !(potential boundary condition) || (check) + Some(bound_expr.clone()) + } else { + None + } +} +impl BoundExtractor { + fn extract_bounds_recursive(&self, expr: &syn::Expr, name: &String, exclude_conjunctions: bool) -> Vec { + let simplified = simplify_expression(expr); + match simplified { + syn::Expr::Binary(syn::ExprBinary { + box left, + box right, + op, + .. + }) => { + match op { + // combining results of and: + syn::BinOp::And(_) => { + // stop processing conjunctions if we are "below" a NOT operation + if !exclude_conjunctions { + let mut left_bound = self.extract_bounds_recursive(&left, name, exclude_conjunctions); + let mut right_bound = self.extract_bounds_recursive(&right, name, exclude_conjunctions); + left_bound.append(&mut right_bound); + left_bound + } else { + vec![] + } + } + // generate boundaries from comparisons if one of the sides + // is the name we are currently looking for + BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_) | BinOp::Ge(_) => { + // one of the two operators have to include "name" if this is + // is a boundary: + let variables = self.find_identifiers(expr); + if variables.contains(name) { + // might be a bound for our current variable + // transform it such that left is exactly x, + // and right is the upper or lower boundary + let bound_opt = self.derive_boundary(name, &left, &right, &op); + if let Some(bound) = bound_opt { + vec![bound] + } else { + vec![] + } + } else { + // definitely not a boundary for our variable + vec![] + } + } + + _ => vec![], + } + } + syn::Expr::Unary(syn::ExprUnary { + op: syn::UnOp::Not(_), + expr: box sub_expr, + .. + }) => { + // For a NOT expression, we can not further derive boundaries from conjunctions + // becuase e.g. !(x < 5 && x > 10) is trivially true, but we would derive the range + // 5..10 + let sub_bounds = self.extract_bounds_recursive(&sub_expr, name, true); + // invert all the boundaries derived of the sub_expression + sub_bounds + .iter() + .map(|bound| bound.clone().invert()) + .collect() + } + _ => vec![], + } + } + + /// Given a comparison, derive the corresponding boundary for the variable + /// we are currently dealing with + fn derive_boundary( + &self, + name: &String, + left: &syn::Expr, + right: &syn::Expr, + op: &syn::BinOp, + ) -> Option { + let left_simp = simplify_expression(left); + let right_simp = simplify_expression(right); + let mut new_op = *op; + let mut bound_expr = None; + + // for now do it simple: One of the two expressions has to be + // exactly "name" + let left_name_opt = utils::expression_name(&left_simp); + let right_name_opt = utils::expression_name(&right_simp); + if let Some(left_name) = left_name_opt { + if &left_name == name { + bound_expr = Some(right.clone()); + } + } + if let Some(right_name) = right_name_opt { + if &right_name == name { + // in case they both are just "name", this cannot be used as a + // boundary + if bound_expr.is_some() { + return None; + } + // turn the condition around because implication is of the form + // not cond || expression + new_op = match op { + BinOp::Lt(_) => BinOp::Gt(syn::token::Gt::default()), + BinOp::Gt(_) => BinOp::Lt(syn::token::Lt::default()), + BinOp::Le(_) => BinOp::Ge(syn::token::Ge::default()), + BinOp::Ge(_) => BinOp::Le(syn::token::Le::default()), + _ => panic!(), + }; + bound_expr = Some(left.clone()); + } + } + // now we can be sure (if bound_expr is some) that our comparison has the + // form `name op bound_expr` + // now create boundary: + if let Some(bound) = bound_expr { + let (included, kind) = match new_op { + BinOp::Lt(_) => (false, BoundaryKind::Upper), + BinOp::Gt(_) => (false, BoundaryKind::Lower), + BinOp::Le(_) => (true, BoundaryKind::Upper), + BinOp::Ge(_) => (true, BoundaryKind::Lower), + // this is actually checked before calling this function + _ => unreachable!(), + }; + let dependent_vars = self.find_identifiers(&bound); + Some(Boundary { + bound, + included, + kind, + dependent_vars, + }) + } else { + None + } + } + + fn find_identifiers(&self, expr: &syn::Expr) -> FxHashSet { + // a Visitor to extract identifiers that occurr in an expression + struct IdentifierFinder { + pub bound_vars: FxHashSet, + } + + impl<'ast> syn::visit::Visit<'ast> for IdentifierFinder { + fn visit_expr_path(&mut self, expr_path: &'ast syn::ExprPath) { + if expr_path.path.segments.len() == 1 { + // it is an identifier + let name = expr_path.to_token_stream().to_string(); + self.bound_vars.insert(name); + } + // keep visiting :) + syn::visit::visit_expr_path(self, expr_path); + } + } + let mut finder = IdentifierFinder { + bound_vars: FxHashSet::default(), + }; + finder.visit_expr(expr); + finder + .bound_vars + .into_iter() + .filter(|x| self.name_set.contains(x)) + .collect() + } +} + +/// syn expressions can be part of a block, put into braces etc, all of which make +/// it harder to analyze them. This function removes these. +fn simplify_expression(expr: &syn::Expr) -> syn::Expr { + match expr { + syn::Expr::Block(syn::ExprBlock { block, .. }) => { + if block.stmts.len() == 1 { + let res = block.stmts.get(0).unwrap().clone(); + if let syn::Stmt::Expr(sub_expr) = res { + simplify_expression(&sub_expr) + } else { + expr.clone() + } + } else { + expr.clone() + } + } + syn::Expr::Paren(syn::ExprParen { + expr: box sub_expr, .. + }) => simplify_expression(sub_expr), + syn::Expr::Type(syn::ExprType { expr: sub_expr, .. }) => simplify_expression(sub_expr), + _ => expr.clone(), + } +} + +#[derive(Clone)] +pub(crate) struct Boundary { + pub bound: syn::Expr, + /// Whether or not that value is still in the range + pub included: bool, + /// upper or lower bound? + pub kind: BoundaryKind, + /// other variables that are part of the current quantifier, that this + /// boundary relies on (i.e. must have been defined before) + pub dependent_vars: FxHashSet, +} + +#[derive(Clone, Debug, Copy, PartialEq)] +pub(crate) enum BoundaryKind { + Upper, + Lower, +} + +impl Boundary { + pub fn invert(self) -> Self { + let kind = match self.kind { + BoundaryKind::Upper => BoundaryKind::Lower, + BoundaryKind::Lower => BoundaryKind::Upper, + }; + Self { + bound: self.bound, + included: !self.included, + kind, + dependent_vars: self.dependent_vars, + } + } +} diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/check_type.rs b/prusti-contracts/prusti-specs/src/runtime_checks/check_type.rs new file mode 100644 index 00000000000..8fc5e8efef9 --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/check_type.rs @@ -0,0 +1,91 @@ +#[derive(Clone, Copy, PartialEq)] +pub(crate) enum CheckItemType { + PledgeLhs, + PledgeRhs { has_lhs: bool }, + Requires, + Ensures, + Assert, + Assume, + BodyInvariant, + Predicate, + Unchecked, +} + +impl CheckItemType { + /// Whether the signature of the check function generated from this contract + /// will contain old_values + pub(crate) fn gets_old_args(&self) -> bool { + matches!( + self, + Self::PledgeLhs | Self::PledgeRhs { .. } | Self::Ensures + ) + } + + /// Whether the result of the function needs to be part of the check functions signature. + pub(crate) fn needs_result(&self) -> bool { + matches!( + self, + Self::PledgeLhs | Self::PledgeRhs { .. } | Self::Ensures + ) + } + + /// Whether the original arguments to the function are also passed to + /// the check function. Maybe pledges shouldn't be here. + pub(crate) fn gets_item_args(&self) -> bool { + // maybe pledge_rhs should not get items either + matches!( + self, + Self::PledgeRhs { .. } | Self::Requires | Self::Ensures | Self::Predicate + ) + } + + /// Whether this kind of contract only occurrs within code, not as an annotation + /// of functions + pub(crate) fn is_inlined(&self) -> bool { + matches!(self, Self::Assert | Self::Assume | Self::BodyInvariant) + } + + /// A helper function for pretty printing contracts, allowing us a prettier + /// output for runtime errors + pub(crate) fn wrap_contract(&self, s: &String) -> String { + match self { + Self::PledgeLhs => format!("#[assert_on_expiry({}, ..)]", s), + Self::PledgeRhs { has_lhs } => { + if *has_lhs { + format!("#[assert_on_expiry(.., {})]", s) + } else { + format!("#[after_expiry({})]", s) + } + } + Self::Requires => format!("#[requires({})]", s), + Self::Ensures => format!("#[ensures({})]", s), + Self::Assert => format!("prusti_assert!({})", s), + Self::Assume => format!("prusti_assume!({})", s), + Self::BodyInvariant => format!("body_invariant!({})", s), + Self::Predicate => format!("predicate!{{ {} }}", s), + Self::Unchecked => unreachable!(), + } + } + + pub(crate) fn can_be_checked(&self) -> bool { + !matches!(self, Self::Unchecked) + } +} + +/// Opposed to previous debugging output, this one is used for generating the +/// check function names. +impl std::fmt::Display for CheckItemType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::PledgeLhs => write!(f, "pledge_lhs"), + Self::PledgeRhs { .. } => write!(f, "pledge"), + Self::Requires => write!(f, "pre"), + Self::Ensures => write!(f, "post"), + Self::Assert => write!(f, "assert"), + Self::Assume => write!(f, "assume"), + Self::BodyInvariant => write!(f, "body_invariant"), + Self::Predicate => write!(f, "predicate"), + Self::Unchecked => unreachable!(), + } + } +} diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/error_messages.rs b/prusti-contracts/prusti-specs/src/runtime_checks/error_messages.rs new file mode 100644 index 00000000000..599800a89fc --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/error_messages.rs @@ -0,0 +1,115 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned}; +use syn::{parse_quote_spanned, spanned::Spanned}; + +// the variable names used in the AST to construct and emit meaningful errors +const FAILURE_MESSAGE_STR: &str = "_prusti_rtc_failure_message"; +const BUFFER_IDENT: &str = "_prusti_rtc_info_buffer"; +const BUFFER_LEN_IDENT: &str = "_prusti_rtc_info_len"; + +pub(crate) fn failure_message_ident(span: Span) -> syn::Ident { + syn::Ident::new(FAILURE_MESSAGE_STR, span) +} + +pub(crate) fn buffer_ident(span: Span) -> syn::Ident { + syn::Ident::new(BUFFER_IDENT, span) +} + +pub(crate) fn buffer_len_ident(span: Span) -> syn::Ident { + syn::Ident::new(BUFFER_LEN_IDENT, span) +} + +/// Define the error message (or the corresponding fixed sized buffer) so +/// it can be extended with information at runtime +pub(crate) fn define_failure_message(span: Span, message: &String) -> TokenStream { + if cfg!(feature = "std") { + let ident = failure_message_ident(span); + quote_spanned! {span => + let mut #ident = #message.to_string(); + } + } else { + let failure_message_len = message.len(); + let buffer_length = message.len() * 5; + let buffer_ident = buffer_ident(span); + let buffer_len_ident = buffer_len_ident(span); + quote_spanned! {span => + let mut #buffer_ident = [0u8; #buffer_length]; + let mut #buffer_len_ident = #failure_message_len; + #buffer_ident[..#buffer_len_ident].copy_from_slice(#message.as_bytes()); + } + } +} + +/// Create a str from the buffer of the error message just before it's emitted +/// (only required in case std is not available) +pub(crate) fn construct_failure_message_opt(span: Span) -> Option { + let ident = failure_message_ident(span); + if !cfg!(feature = "std") { + Some(quote_spanned!(span => + let #ident: &str = ::core::str::from_utf8(&_prusti_rtc_info_buffer[.._prusti_rtc_info_len]).unwrap(); + )) + } else { + None + } +} + +/// Creates a call to the check() function, that extends the error with additional +/// information in case that the boolean passed to it evaluates to false +pub(crate) fn call_check_expr(expr: syn::Expr, message: String) -> syn::Expr { + let span = expr.span(); + if cfg!(feature = "std") { + let failure_message_ident = failure_message_ident(span); + parse_quote_spanned! {span=> + ::prusti_contracts::runtime_check_internals::check_expr(#expr, #message, &mut #failure_message_ident) + } + } else { + let buffer_ident = buffer_ident(span); + let buffer_len_ident = buffer_len_ident(span); + parse_quote_spanned! {span => + ::prusti_contracts::runtime_check_internals::check_expr(#expr, #message, &mut #buffer_ident, &mut #buffer_len_ident) + } + } +} + +/// If a quantifier fails at a specific index, extend the error message with that +/// information +pub(crate) fn extend_error_indexed(expr: &syn::Expr, name_token: &TokenStream) -> syn::Block { + let span = expr.span(); + let expr_string = expr + .span() + .source_text() + .unwrap_or_else(|| quote!(expr).to_string()); + let error_string = format!( + "\n\t> expression {} was violated for index {}=", + expr_string, name_token + ); + if cfg!(feature = "std") { + let failure_message_ident = failure_message_ident(span); + parse_quote_spanned! {span=> + { + #failure_message_ident.push_str(#error_string); + #failure_message_ident.push_str(format!("{}", #name_token).as_str()); + } + } + } else { + let buffer_ident = buffer_ident(span); + let buffer_len_ident = buffer_len_ident(span); + let error_len = error_string.len(); + parse_quote_spanned! {span => + { + #buffer_ident[#buffer_len_ident..#buffer_len_ident+#error_len] + .copy_from_slice(#error_string.as_bytes()); + let num_str = ::prusti_contracts::runtime_check_internals::num_to_str( + #name_token, + &mut num_buffer + ); + #buffer_ident[ + #buffer_len_ident+#error_len..#buffer_len_ident+#error_len+num_str.len() + ].copy_from_slice(num_str.as_bytes()); + #buffer_len_ident = #buffer_len_ident + + #error_len + + num_str.len(); + } + } + } +} diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/mod.rs b/prusti-contracts/prusti-specs/src/runtime_checks/mod.rs new file mode 100644 index 00000000000..f1734ec5836 --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/mod.rs @@ -0,0 +1,7 @@ +pub mod translation; +pub mod check_type; +mod boundary_extraction; +mod associated_function_info; +mod quantifiers; +mod error_messages; +mod utils; diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/quantifiers.rs b/prusti-contracts/prusti-specs/src/runtime_checks/quantifiers.rs new file mode 100644 index 00000000000..7f096f6a435 --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/quantifiers.rs @@ -0,0 +1,97 @@ +use crate::runtime_checks::{boundary_extraction, error_messages, utils}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use rustc_hash::FxHashSet; + +pub(crate) enum QuantifierKind { + Forall, + Exists, +} + +/// Given a quantifier, try to translate it to a runtime checkable expression +pub(crate) fn translate_quantifier_expression( + closure: &syn::ExprClosure, + kind: QuantifierKind, + is_outer: bool, +) -> syn::Result { + let mut name_set: FxHashSet = FxHashSet::default(); + // the variables that occurr as arguments + let bound_vars: Vec<(String, syn::Type)> = closure + .inputs + .iter() + .map(|pat: &syn::Pat| { + if let syn::Pat::Type(syn::PatType { + pat: box syn::Pat::Ident(id), + ty: box ty, + .. + }) = pat + { + let name_str = id.to_token_stream().to_string(); + name_set.insert(name_str.clone()); + (name_str, ty.clone()) + } else { + // not throwing a proper compile error because as of now, this will already cause + // an error in the preparser + panic!("quantifiers without type annotations can not be checked at runtime"); + } + }) + .collect(); + + // look for the runtime_quantifier_bounds attribute, or try to derive + // boundaries from expressions of the form `boundary ==> expr` + let manual_bounds_opt = boundary_extraction::manual_bounds(closure.clone(), bound_vars.clone()); + let bounds = if let Some(manual_bounds) = manual_bounds_opt { + manual_bounds? + } else { + boundary_extraction::derive_ranges(*closure.body.clone(), bound_vars)? + }; + + let mut expr = *closure.body.clone(); + + for ((name, ty), range_expr) in bounds.iter().rev() { + let name_token: TokenStream = name.parse().unwrap(); + let info_statements = if utils::is_primitive_number(ty) && is_outer { + Some(error_messages::extend_error_indexed(&expr, &name_token)) + } else { + None + }; + + expr = match kind { + QuantifierKind::Forall => { + let res = quote! { + { + let mut holds_forall = true; + let mut num_buffer = [0u8; 64]; + for #name_token in #range_expr { + if !(#expr) { + // extend the error message + #info_statements; + + holds_forall = false; + break; + } + } + holds_forall + } + }; + syn::parse2(res).unwrap() + } + QuantifierKind::Exists => { + let res = quote! { + { + let mut exists = false; + for #name_token in #range_expr { + if #expr { + exists = true; + break; + } + } + exists + } + }; + syn::parse2(res).unwrap() + } + } + } + Ok(expr) +} diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/translation.rs b/prusti-contracts/prusti-specs/src/runtime_checks/translation.rs new file mode 100644 index 00000000000..2305b30e847 --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/translation.rs @@ -0,0 +1,520 @@ +use crate::{ + common::HasSignature, + rewriter::AstRewriter, + runtime_checks::{ + associated_function_info::{create_argument, Argument, AssociatedFunctionInfo}, + check_type::CheckItemType, + error_messages, + quantifiers::{translate_quantifier_expression, QuantifierKind}, + utils, + }, + specifications::{common::SpecificationId, untyped}, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::{parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut}; + +// generates the check function that can be performed to check whether +// a contract was valid at runtime. +// Note: various modifications on the mir level are needed such that these +// checks are executed correctly. +pub(crate) fn translate_runtime_checks( + check_type: CheckItemType, + check_id: SpecificationId, + // the expression of the actual contract + tokens: TokenStream, + // the function this contract is attached to + item: &untyped::AnyFnItem, +) -> syn::Result { + let span = tokens.span(); + // get signature information about the associated function + let function_info = AssociatedFunctionInfo::new(item)?; + let mut visitor = CheckVisitor::new(function_info, check_type); + + // make the expression checkable at runtime: + let mut expr: syn::Expr = syn::parse2(tokens.clone())?; + visitor.visit_expr_mut(&mut expr); + // get updated function info, telling us if arguments were used in + // old. + let function_info = visitor.function_info; + + let item_name = syn::Ident::new( + &format!( + "prusti_{}_check_item_{}_{}", + check_type, + item.sig().ident, + check_id + ), + span, + ); + let check_id_str = check_id.to_string(); + let item_name_str = item_name.to_string(); + + let forget_statements = generate_forget_statements(item, check_type, span); + let contract_string = span.source_text().unwrap_or_else(|| tokens.to_string()); + let failure_message = format!( + "Prusti Runtime Checks: Contract {} was violated at runtime", + check_type.wrap_contract(&contract_string) + ); + + let failure_message_ident = error_messages::failure_message_ident(span); + let failure_message_definition = error_messages::define_failure_message(span, &failure_message); + let failure_message_construction = error_messages::construct_failure_message_opt(span); + let id_attr: syn::Attribute = if check_type == CheckItemType::PledgeLhs { + parse_quote_spanned! {item.span() => + #[prusti::check_before_expiry_id = #check_id_str] + } + } else { + parse_quote_spanned! { item.span() => + #[prusti::check_id = #check_id_str] + } + }; + // only insert print statements if this flag is set (requires std!) + let debug_print_stmt: Option = if std::env::var("PRUSTI_DEBUG_RUNTIME_CHECKS") + .unwrap_or_default() + == "true" + { + if cfg!(feature = "std") { + Some(quote_spanned! {span => + println!("check function {} is performed", #item_name_str); + }) + } else { + // warn user that debug information will not be emitted in no_std + span.unwrap().warning("enabling PRUSTI_DEBUG_RUNTIME_CHECKS only has an effect if feature \"std\" is enabled too").emit(); + None + } + } else { + None + }; + + let mut check_item: syn::ItemFn = parse_quote_spanned! {item.span() => + #[allow(unused_must_use, unused_parens, unused_variables, dead_code, non_snake_case, forgetting_copy_types)] + #[prusti::spec_only] + #[prusti::check_only] + #id_attr + fn #item_name() { + #debug_print_stmt + #failure_message_definition + if !(#expr) { + #failure_message_construction + #forget_statements + ::core::panic!("{}", #failure_message_ident) + }; + // now forget about all the values since they are still owned + // by the calling function + #forget_statements + } + }; + check_item.sig.generics = item.sig().generics.clone(); + if check_type.gets_item_args() { + check_item.sig.inputs = item.sig().inputs.clone(); + } + if check_type.needs_result() { + let result_arg = AstRewriter::generate_result_arg(item); + check_item.sig.inputs.push(result_arg); + } + if check_type.gets_old_args() { + let old_arg = construct_old_fnarg(&function_info, span); + // put it inside a reference: + check_item.sig.inputs.push(old_arg); + } + if let CheckItemType::PledgeRhs { .. } = check_type { + let output_ty: syn::Type = if let syn::ReturnType::Type(_, box ty) = &item.sig().output { + ty.clone() + } else { + // probably not our job to throw an error here? But a pledge for + // a function with default return type does not make a lot of sense.. + parse_quote! {()} + }; + let before_expiry_arg = parse_quote_spanned! {item.span() => + result_before_expiry: (#output_ty,) + }; + check_item.sig.inputs.push(before_expiry_arg); + } + Ok(syn::Item::Fn(check_item)) +} + +/// Translate a single expression, as contained in specifications such as +/// prusti_assert. Contrary to the other translations, this does not generate +/// a function, but simply a block of code that will be put into user code. +/// To mark this block as part of a runtime check, it starts with a closure that +/// has some attributes. +pub(crate) fn translate_expression_runtime( + tokens: TokenStream, + check_id: SpecificationId, + check_type: CheckItemType, +) -> syn::Result { + // a bit different since we don't generate a function, but just the + // code-fragment performing the check + let function_info = AssociatedFunctionInfo::empty(); + let span = tokens.span(); + let expr_str = span.source_text().unwrap_or_else(|| tokens.to_string()); + let spec_id_str = check_id.to_string(); + let failure_message = format!( + "Prusti Runtime Checks: Contract {} was violated at runtime", + check_type.wrap_contract(&expr_str) + ); + let mut expr: syn::Expr = syn::parse2(tokens)?; + let mut check_visitor = CheckVisitor::new(function_info, check_type); + check_visitor.visit_expr_mut(&mut expr); + + let failure_message_ident = error_messages::failure_message_ident(span); + let failure_message_definition = error_messages::define_failure_message(span, &failure_message); + let failure_message_construction = error_messages::construct_failure_message_opt(span); + Ok(quote_spanned! {span => + { + #[prusti::check_only] + #[prusti::spec_only] + #[prusti::runtime_assertion] + #[prusti::check_id = #spec_id_str] + || -> bool { + true + }; + #failure_message_definition + if !(#expr) { + #failure_message_construction + ::core::panic!("{}", #failure_message_ident); + } + } + }) +} + +/// Generates an executable body for a predicate. +pub(crate) fn translate_predicate( + body: TokenStream, + item: &T, + span: Span, +) -> syn::Result { + let check_type = CheckItemType::Predicate; + // get signature information about the associated function + let function_info = AssociatedFunctionInfo::empty(); + let mut visitor = CheckVisitor::new(function_info, check_type); + // For predicates, we can never extend the error message with additional information + // since we don't have access to it. + // Therefore we never consider a predicate to be part of the "outermost" conjunction + visitor.is_outer = false; + + // make the expression checkable at runtime: + let mut expr: syn::Expr = syn::parse2(body.clone())?; + visitor.visit_expr_mut(&mut expr); + + let fn_ident = &item.sig().ident.to_string(); + let debug_print_stmt: Option = if std::env::var("PRUSTI_DEBUG_RUNTIME_CHECKS") + .unwrap_or_default() + == "true" + { + if cfg!(feature = "std") { + Some(quote_spanned! {span => + println!("predicate {} is executed", #fn_ident); + }) + } else { + // warn user that debug information will not be emitted in no_std + span.unwrap().warning("enabling PRUSTI_DEBUG_RUNTIME_CHECKS only has an effect if feature \"std\" is enabled too").emit(); + None + } + } else { + None + }; + let forget_statements = generate_forget_statements(item, check_type, span); + + Ok(quote_spanned! {span => + #debug_print_stmt + let prusti_predicate_result = #expr; + #forget_statements + prusti_predicate_result + }) +} + +/// Since spec and check functions have the same signatures as the function they +/// are attached to, actually executing causes them to take ownership of values +/// and freeing them in some cases. Inserting these forget statements avoids +/// values being freed within check functions. +pub(crate) fn generate_forget_statements( + item: &T, + check_type: CheckItemType, + span: Span, +) -> syn::Block { + // go through all inputs, if they are not references add a forget + // statement (might not be needed for all of them, but if they're on + // the stack, this will not do anything) + let mut stmts: Vec = Vec::new(); + if check_type.gets_item_args() { + for fn_arg in item.sig().inputs.clone() { + if let Ok(arg) = create_argument(&fn_arg, 0) { + let name: TokenStream = arg.name.parse().unwrap(); + if !arg.is_ref { + stmts.push(parse_quote_spanned! { span => + let _ = ::core::mem::forget(#name); + }) + } + } + } + } + + // result might be double freed too if moved into a check function + if check_type.needs_result() { + stmts.push(parse_quote_spanned! { span => + let _ = ::core::mem::forget(result); + }) + } + syn::Block { + brace_token: syn::token::Brace::default(), + stmts, + } +} + +fn construct_old_fnarg(function_info: &AssociatedFunctionInfo, span: Span) -> syn::FnArg { + let old_values_type: syn::Type = old_values_type(span, function_info); + parse_quote_spanned! {span => + old_values: #old_values_type + } +} + +/// After the visitor was run on an expression, this function +/// can be used to generate the type of the old_values tuple. +/// This type is a tuple, with the same number of fields as the original +/// function has arguments, but if the old value of an argument is never used, +/// this index in the tuple is simply a unit type. On the MIR level we can look +/// at this type, and figure out which arguments need to be cloned depending on +/// if the result type contains the argument type at the corresponding index. +fn old_values_type(span: Span, function_info: &AssociatedFunctionInfo) -> syn::Type { + let mut old_values_type: syn::Type = parse_quote_spanned! {span => ()}; + let mut arguments = function_info.inputs.values().collect::>(); + // order the elements of the map by index + arguments.sort_by(|a, b| a.index.partial_cmp(&b.index).unwrap()); + if let syn::Type::Tuple(syn::TypeTuple { elems, .. }) = &mut old_values_type { + // start adding the elements we want to store: + for arg in arguments { + if arg.used_in_old { + elems.push(arg.ty.clone()); + } else { + // if argument is never used in old, we use a unit type + // in the tuple (this is then also used in the mir processing + // to determine that an argument doesn't need to be cloned) + let unit_type: syn::Type = parse_quote_spanned! {arg.span => ()}; + elems.push(unit_type); + } + } + // if brackets contain only one type, it's not a tuple. Therefore + // make a comma at the end, if there is at least one element + if !elems.empty_or_trailing() { + elems.push_punct(syn::token::Comma::default()); + } + } else { + unreachable!(); + } + old_values_type +} + +/// The visitor that goes through the AST, finds expressions that need to be +/// modified to make them executable, and performs these optimizations +pub(crate) struct CheckVisitor { + /// Information about the function this specification is attached to + pub function_info: AssociatedFunctionInfo, + /// The type of specification + pub check_type: CheckItemType, + /// Used while traversing: set if we are in a child of an old-call + within_old: bool, + /// Just like `within_old`, for `before_expiry` + within_before_expiry: bool, + /// Whether the current expression is part of the outermost + /// conjunction. If yes, and we encounter a conjunction or + /// forall quantifier, we can extend the reported error with + /// more precise information + is_outer: bool, + /// Used to signal to a parent that an expression contains a conjunction + contains_conjunction: bool, +} + +impl CheckVisitor { + pub(crate) fn new(function_info: AssociatedFunctionInfo, check_type: CheckItemType) -> Self { + Self { + function_info, + check_type, + within_old: false, + within_before_expiry: false, + is_outer: true, + contains_conjunction: false, + } + } +} + +impl VisitMut for CheckVisitor { + fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { + let was_outer = self.is_outer; + match expr { + syn::Expr::Path(expr_path) => { + // collect arguments that occurr within old expression + // these are the ones we wanna clone + if let Some(ident) = expr_path.path.get_ident() { + let name = ident.to_token_stream().to_string(); + if let Some(arg) = self.function_info.get_mut_arg(&name) { + // Does argument need to be evaluated in its old state? + // Yes if: it occurrs within old and is a mutable ref OR + // its not a reference (this is unfortunately not always determined + // correctly because of type aliases) + if self.check_type.gets_old_args() + && !arg.is_ref || self.within_old && arg.is_mutable + { + // if it was not already marked to be stored + // needs to be checked for indeces to be correct + arg.used_in_old = true; + // replace the identifier with the correct field access + let index_token: TokenStream = arg.index.to_string().parse().unwrap(); + let new_path: syn::Expr = + parse_quote_spanned! { expr.span() => (old_values.#index_token)}; + *expr = new_path; + } + } else if self.within_before_expiry && name == *"result" { + let new_path: syn::Expr = parse_quote_spanned! { expr.span() => + result_before_expiry.0 + }; + *expr = new_path; + } + } + } + syn::Expr::Call(call) => { + self.is_outer = false; + let path_expr = (*call.func).clone(); + let name = if let Some(name) = utils::expression_name(&path_expr) { + name + } else { + // still visit recursively + syn::visit_mut::visit_expr_mut(self, expr); + return; + }; + match name.as_str() { + ":: prusti_contracts :: old" | "prusti_contracts :: old" | "old" => { + // for this function we can savely try to get + // more precise errors about the contents + self.is_outer = was_outer; + if self.check_type.is_inlined() { + // for prusti_assert etc we can not resolve old here + // just leave it as it is. resolve it on mir level + syn::visit_mut::visit_expr_call_mut(self, call); + } else { + // remove old-call and replace with content expression + let sub_expr = call.args.pop(); + *expr = sub_expr.unwrap().value().clone(); + // Signal to children that all occurrences of function arguments should + // be replaced with `old_values.index` + self.within_old = true; + self.visit_expr_mut(expr); + self.within_old = false; + } + } + ":: prusti_contracts :: before_expiry" + | "prusti_contracts :: before_expiry" + | "before_expiry" => { + // Same thing as for old, except that this can not occurr in + // inlined specs + let sub_expr = call.args.pop(); + *expr = sub_expr.unwrap().value().clone(); + self.within_before_expiry = true; + self.visit_expr_mut(expr); + self.within_before_expiry = false; + } + ":: prusti_contracts :: forall" => { + // Get the closure argument of the quantifier. Disregard triggers. + let quant_closure_expr: syn::Expr = call.args.last().unwrap().clone(); + if let syn::Expr::Closure(mut expr_closure) = quant_closure_expr { + // since this is a conjunction, we can get more information about subexpressions failing + // (however, if we are in no_std, we cannot report errors recursively because of limited buffer size) + self.is_outer = if cfg!(feature = "std") { + was_outer + } else { + false + }; + self.visit_expr_mut(&mut expr_closure.body); + let check_expr = translate_quantifier_expression( + &expr_closure, + QuantifierKind::Forall, + was_outer, + ) + .unwrap_or_else(|err| syn::parse2(err.to_compile_error()).unwrap()); + *expr = check_expr; + } + } + ":: prusti_contracts :: exists" => { + syn::visit_mut::visit_expr_call_mut(self, call); + let closure_expression: syn::Expr = call.args.last().unwrap().clone(); + if let syn::Expr::Closure(expr_closure) = closure_expression { + let check_expr = translate_quantifier_expression( + &expr_closure, + QuantifierKind::Exists, + false, + ) + .unwrap_or_else(|err| syn::parse2(err.to_compile_error()).unwrap()); + *expr = check_expr; + } + } + // some cases where we want to warn users that certain prusti features + // will not be checked correctly: (not complete yet) + ":: prusti_contracts :: snapshot_equality" + | "prusti_contracts :: snapshot_equality" + | "snapshot_equality" + | ":: prusti_contracts :: snap" + | "prusti_contracts :: snap" + | "snap" + | "model" => { + let message = format!( + "Feature {} is not supported for runtime checks, behavior at runtime might be arbitrary", + expr.to_token_stream() + ); + expr.span().unwrap().warning(message).emit(); + } + _ => syn::visit_mut::visit_expr_mut(self, expr), + } + } + // If we have a conjunction on the outer level of the expression, we can + // produce a more precise error. + syn::Expr::Binary(syn::ExprBinary { + left: box left, + op: syn::BinOp::And(_), + right: box right, + .. + }) if was_outer => { + // Figure out if the sub-expressions contain even more conjunctions. + // If yes, visiting the children of our current node will already + // produce more precise errors than we can here. + self.contains_conjunction = false; + syn::visit_mut::visit_expr_mut(self, left); + let left_contains_conjunction = self.contains_conjunction; + self.contains_conjunction = false; + syn::visit_mut::visit_expr_mut(self, right); + let right_contains_conjunction = self.contains_conjunction; + + if !left_contains_conjunction { + let left_str = left + .span() + .source_text() + .unwrap_or_else(|| quote!(left).to_string()); + let left_error_str = format!("\n\t> expression {} was violated.", left_str); + let new_left = error_messages::call_check_expr(left.clone(), left_error_str); + *left = new_left; + } + if !right_contains_conjunction { + let right_str = right + .span() + .source_text() + .unwrap_or_else(|| quote!(right).to_string()); + let right_error_str = format!("\n\t> expression {} was violated.", right_str); + let new_right = error_messages::call_check_expr(right.clone(), right_error_str); + *right = new_right; + } + // signal to parent that it doesnt need to report a precise error for this + // expression, since we already produce a more precise error here + self.contains_conjunction = true; + } + // Make sure simple brackets don't stop us from producing precise errors + syn::Expr::Block(_) | syn::Expr::Paren(_) => { + syn::visit_mut::visit_expr_mut(self, expr); + } + _ => { + self.is_outer = false; + syn::visit_mut::visit_expr_mut(self, expr); + } + } + self.is_outer = was_outer; + } +} diff --git a/prusti-contracts/prusti-specs/src/runtime_checks/utils.rs b/prusti-contracts/prusti-specs/src/runtime_checks/utils.rs new file mode 100644 index 00000000000..3a849eafb06 --- /dev/null +++ b/prusti-contracts/prusti-specs/src/runtime_checks/utils.rs @@ -0,0 +1,52 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; + +// if expression is a identifier, get the name: +pub(crate) fn expression_name(expr: &syn::Expr) -> Option { + if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr { + Some(path.to_token_stream().to_string()) + } else { + None + } +} + +/// Self explanatory. But also, these are the only supported types +/// for runtime checking quantifiers +pub(crate) fn is_primitive_number(ty: &syn::Type) -> bool { + matches!( + ty.to_token_stream().to_string().as_str(), + "i8" | "i16" + | "i32" + | "i64" + | "i128" + | "isize" + | "u8" + | "u16" + | "u32" + | "u64" + | "u128" + | "usize" + ) +} + +pub(crate) fn ty_unsigned(ty: &syn::Type) -> bool { + matches!( + ty.to_token_stream().to_string().as_str(), + "u8" | "u16" | "u32" | "u64" | "u128" | "usize" + ) +} + +/// Whether a type is small enough to be efficiently runtime checked +/// even without an upper / lower bound +pub(crate) fn ty_small_enough(ty: &syn::Type) -> bool { + matches!(ty.to_token_stream().to_string().as_str(), "u8" | "i8") +} + +pub fn get_attribute_contents(name: String, attrs: &Vec) -> Option { + for attr in attrs { + if attr.path.to_token_stream().to_string() == name { + return Some(attr.tokens.clone()); + } + } + None +} diff --git a/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs b/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs index f286cbd317b..4e1206f3ad0 100644 --- a/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs +++ b/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs @@ -18,6 +18,7 @@ pub enum SpecAttributeKind { Terminates = 10, PrintCounterexample = 11, Verified = 12, + InsertRuntimeCheck = 13, } impl TryFrom for SpecAttributeKind { @@ -37,6 +38,7 @@ impl TryFrom for SpecAttributeKind { "model" => Ok(SpecAttributeKind::Model), "print_counterexample" => Ok(SpecAttributeKind::PrintCounterexample), "verified" => Ok(SpecAttributeKind::Verified), + "insert_runtime_check" => Ok(SpecAttributeKind::InsertRuntimeCheck), _ => Err(name), } } diff --git a/prusti-contracts/prusti-specs/src/specifications/preparser.rs b/prusti-contracts/prusti-specs/src/specifications/preparser.rs index 6d64ad4eef9..a5ce1954d3d 100644 --- a/prusti-contracts/prusti-specs/src/specifications/preparser.rs +++ b/prusti-contracts/prusti-specs/src/specifications/preparser.rs @@ -277,9 +277,14 @@ impl PrustiTokenStream { todo!() } Some(PrustiToken::Quantifier(span, kind)) => { - let mut stream = self.pop_group(Delimiter::Parenthesis).ok_or_else(|| { - error(span, "expected parenthesized expression after quantifier") - })?; + let (mut stream, content_span) = self + .pop_group_spanned(Delimiter::Parenthesis) + .ok_or_else(|| { + error(span, "expected parenthesized expression after quantifier") + })?; + let full_span = join_spans(span, content_span); + // attrs_opt is potentially None even if everything is good + let attrs_opt = stream.pop_quantifier_bound_attr(); let args = stream .pop_closure_args() .ok_or_else(|| error(span, "expected quantifier body"))?; @@ -308,9 +313,14 @@ impl PrustiTokenStream { if args.is_empty() { return err(span, "a quantifier must have at least one argument"); } + let attr_parsed = if let Some(attr) = attrs_opt { + attr.parse().ok() + } else { + None + }; let args = args.parse()?; let body = stream.parse()?; - kind.translate(span, triggers, args, body) + kind.translate(full_span, triggers, attr_parsed, args, body) } Some(PrustiToken::SpecEnt(span, _)) | Some(PrustiToken::CallDesc(span, _)) => { @@ -402,6 +412,15 @@ impl PrustiTokenStream { } } + fn pop_group_spanned(&mut self, delimiter: Delimiter) -> Option<(Self, Span)> { + match self.tokens.pop_front() { + Some(PrustiToken::Group(span, del, box stream)) if del == delimiter => { + Some((stream, span)) + } + _ => None, + } + } + fn pop_closure_args(&mut self) -> Option { let mut tokens = VecDeque::new(); @@ -433,6 +452,17 @@ impl PrustiTokenStream { }) } + fn pop_quantifier_bound_attr(&mut self) -> Option { + let start_token = self.tokens.pop_front()?; + if start_token.is_attribute_starter() { + self.pop_group(Delimiter::Bracket) + } else { + // put it back, no reason to fail, there might not be any + self.tokens.push_front(start_token); + None + } + } + fn pop_parenthesized_group(&mut self) -> syn::Result { match self.tokens.pop_front() { Some(PrustiToken::Group(_span, Delimiter::Parenthesis, box group)) => { @@ -738,10 +768,10 @@ impl Quantifier { &self, span: Span, triggers: Vec>, + attr: Option, args: TokenStream, body: TokenStream, ) -> TokenStream { - let full_span = join_spans(span, body.span()); let trigger_sets = triggers .into_iter() .map(|set| { @@ -749,18 +779,19 @@ impl Quantifier { quote_spanned! { trigger.span() => #[prusti::spec_only] | #args | ( #trigger ), } })); - quote_spanned! { full_span => ( #triggers ) } + quote_spanned! { span => ( #triggers ) } }) .collect::>(); - let body = quote_spanned! { body.span() => #body }; + let body = quote_spanned! { body.span() => { #body } }; + let attr = attr.map(|attr| quote_spanned! {attr.span() => #[prusti::#attr]}); match self { - Self::Forall => quote_spanned! { full_span => ::prusti_contracts::forall( + Self::Forall => quote_spanned! { span => ::prusti_contracts::forall( ( #( #trigger_sets, )* ), - #[prusti::spec_only] | #args | -> bool { #body } + #[prusti::spec_only] #attr | #args | -> bool #body ) }, - Self::Exists => quote_spanned! { full_span => ::prusti_contracts::exists( + Self::Exists => quote_spanned! { span => ::prusti_contracts::exists( ( #( #trigger_sets, )* ), - #[prusti::spec_only] | #args | -> bool { #body } + #[prusti::spec_only] #attr | #args | -> bool #body ) }, } } @@ -813,6 +844,11 @@ impl PrustiToken { if p.as_char() == '|' && p.spacing() == proc_macro2::Spacing::Alone) } + fn is_attribute_starter(&self) -> bool { + matches!(self, Self::Token(TokenTree::Punct(p)) + if p.as_char() == '#') + } + fn parse_op2(p1: &Punct, p2: &Punct) -> Option { let span = join_spans(p1.span(), p2.span()); Some(Self::BinOp( diff --git a/prusti-contracts/prusti-specs/src/type_cond_specs/mod.rs b/prusti-contracts/prusti-specs/src/type_cond_specs/mod.rs index bb6b8348d36..be6ead305ce 100644 --- a/prusti-contracts/prusti-specs/src/type_cond_specs/mod.rs +++ b/prusti-contracts/prusti-specs/src/type_cond_specs/mod.rs @@ -5,7 +5,11 @@ use crate::{ use proc_macro2::TokenStream; use syn::{parse_quote_spanned, spanned::Spanned}; -pub fn generate(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { +pub fn generate( + attr: TokenStream, + item: &untyped::AnyFnItem, + runtime_check: bool, +) -> GeneratedResult { let tokens_span = attr.span(); // Parse type-conditional spec refinements information @@ -16,8 +20,9 @@ pub fn generate(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult for nested_spec in type_cond_spec.specs { let (mut generated_items, generated_attrs) = match nested_spec { - NestedSpec::Ensures(tokens) => generate_for_ensures(tokens, item)?, - NestedSpec::Requires(tokens) => generate_for_requires(tokens, item)?, + NestedSpec::Ensures(tokens) => generate_for_ensures(tokens, item, runtime_check)?, + // is_trusted argument above is true because spec-refinements are always trusted + NestedSpec::Requires(tokens) => generate_for_requires(tokens, item, runtime_check)?, NestedSpec::Pure => generate_for_pure_refinements(item)?, }; diff --git a/prusti-interface/src/environment/mod.rs b/prusti-interface/src/environment/mod.rs index 8d7bc98d374..4aaa9400aa8 100644 --- a/prusti-interface/src/environment/mod.rs +++ b/prusti-interface/src/environment/mod.rs @@ -36,8 +36,9 @@ pub use self::{ loops_utils::*, name::EnvName, procedure::{ - get_loop_invariant, is_ghost_begin_marker, is_ghost_end_marker, is_loop_invariant_block, - is_loop_variant_block, is_marked_specification_block, BasicBlockIndex, Procedure, + blocks_dominated_by, get_loop_invariant, is_check_closure, is_ghost_begin_marker, + is_ghost_end_marker, is_loop_invariant_block, is_loop_variant_block, is_marked_check_block, + is_marked_specification_block, BasicBlockIndex, Procedure, }, query::EnvQuery, }; diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index ee81c1d93d9..4f7ca128102 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -33,6 +33,7 @@ pub struct Procedure<'tcx> { loop_info: loops::ProcedureLoops, reachable_basic_blocks: FxHashSet, nonspec_basic_blocks: FxHashSet, + switch_int_targets: FxHashSet, } impl<'tcx> Procedure<'tcx> { @@ -47,6 +48,7 @@ impl<'tcx> Procedure<'tcx> { let reachable_basic_blocks = build_reachable_basic_blocks(&mir, &real_edges); let nonspec_basic_blocks = build_nonspec_basic_blocks(env.query, &mir, &real_edges); let loop_info = loops::ProcedureLoops::new(&mir, &real_edges); + let switch_int_targets = build_switch_int_targets(&mir); Self { tcx: env.tcx(), @@ -57,6 +59,7 @@ impl<'tcx> Procedure<'tcx> { loop_info, reachable_basic_blocks, nonspec_basic_blocks, + switch_int_targets, } } @@ -197,6 +200,10 @@ impl<'tcx> Procedure<'tcx> { pub fn successors(&self, bbi: BasicBlockIndex) -> &[BasicBlockIndex] { self.real_edges.successors(bbi) } + + pub fn is_non_spec_switch_int_target(&self, bbi: BasicBlockIndex) -> bool { + !self.is_spec_block(bbi) && self.switch_int_targets.contains(&bbi) + } } /// Returns the set of basic blocks that are not used as part of the typechecking of Prusti specifications @@ -227,6 +234,10 @@ fn is_spec_closure(env_query: EnvQuery, def_id: def_id::DefId) -> bool { crate::utils::has_spec_only_attr(env_query.get_attributes(def_id)) } +pub fn is_check_closure(env_query: EnvQuery, def_id: def_id::DefId) -> bool { + crate::utils::has_check_only_attr(env_query.get_attributes(def_id)) +} + pub fn is_marked_specification_block(env_query: EnvQuery, bb_data: &BasicBlockData) -> bool { for stmt in &bb_data.statements { if let StatementKind::Assign(box ( @@ -242,6 +253,21 @@ pub fn is_marked_specification_block(env_query: EnvQuery, bb_data: &BasicBlockDa false } +pub fn is_marked_check_block(env_query: EnvQuery, bb_data: &BasicBlockData) -> bool { + for stmt in &bb_data.statements { + if let StatementKind::Assign(box ( + _, + Rvalue::Aggregate(box AggregateKind::Closure(def_id, _), _), + )) = &stmt.kind + { + if is_check_closure(env_query, *def_id) { + return true; + } + } + } + false +} + pub fn get_loop_invariant<'tcx>( env_query: EnvQuery, bb_data: &BasicBlockData<'tcx>, @@ -333,7 +359,7 @@ fn blocks_definitely_leading_to( blocks } -fn blocks_dominated_by(mir: &Body, dominator: BasicBlock) -> FxHashSet { +pub fn blocks_dominated_by(mir: &Body, dominator: BasicBlock) -> FxHashSet { let dominators = mir.basic_blocks.dominators(); let mut blocks = FxHashSet::default(); for bb in mir.basic_blocks.indices() { @@ -425,3 +451,18 @@ fn build_nonspec_basic_blocks( get_nonspec_basic_blocks(env_query, bb_graph, mir) } + +/// Returns the set of basic blocks that are a target of a switchInt terminator +fn build_switch_int_targets(body: &Body) -> FxHashSet { + let mut switch_targets: FxHashSet = Default::default(); + for (_bb, bb_data) in body.basic_blocks.iter_enumerated() { + if let Some(mir::Terminator { + kind: mir::TerminatorKind::SwitchInt { targets, .. }, + .. + }) = &bb_data.terminator + { + switch_targets.extend(targets.all_targets()); + } + } + switch_targets +} diff --git a/prusti-interface/src/globals.rs b/prusti-interface/src/globals.rs new file mode 100644 index 00000000000..1cc6e0932aa --- /dev/null +++ b/prusti-interface/src/globals.rs @@ -0,0 +1,136 @@ +use prusti_rustc_interface::{ + middle::mir, + span::def_id::{CrateNum, DefId}, +}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; + +use crate::{ + environment::{polonius_info::PoloniusInfo, Environment}, + specs::typed::DefSpecificationMap, +}; + +// data that is persistent across queries will be stored here. +thread_local! { + pub static CHECK_ERROR: RefCell = RefCell::new(false); + pub static SPECS: RefCell> = RefCell::new(None); + pub static ENV: RefCell>> = RefCell::new(None); + pub static VERIFIED: RefCell> = RefCell::new(Default::default()); + pub static POLONIUS: RefCell>> = RefCell::default(); + pub static REACHABILITY_CHECKS: RefCell>> = + Default::default(); + // for each encoded assertion, after verification stores true if this assertion + // will never panic, or false if it possibly panics (after verification) + pub static VERIFIED_ASSERTIONS: RefCell>> = Default::default(); +} + +pub fn set_check_error() { + CHECK_ERROR.with(|cell| *cell.borrow_mut() = true) +} + +pub fn get_check_error() -> bool { + CHECK_ERROR.with(|cell| *cell.borrow()) +} + +/// If we encoded assertions in the hope of being able to eliminate them, we +/// store their locations here, so we can later eliminate them in case they +/// don't generate an error +pub fn add_encoded_assertion(def_id: DefId, bb: mir::BasicBlock) { + VERIFIED_ASSERTIONS.with(|cell| { + let mut map = cell.borrow_mut(); + let entry = map.entry(def_id); + // insert true first, if it generates an error set if to false + // (if it doesnt generate an error it's either unreachable, or + // actually verifiably true and can be removed later) + entry.or_default().insert(bb, true); + }) +} + +/// Mark an assertion as violated, meaning we can not eliminate it. +pub fn set_assertion_violated(def_id: DefId, bb: mir::BasicBlock) { + VERIFIED_ASSERTIONS.with(|cell| { + let mut map = cell.borrow_mut(); + let fn_map = map.get_mut(&def_id).unwrap(); + assert!(fn_map.get(&bb).is_some()); + fn_map.insert(bb, false); + }) +} + +/// For each function returns a map telling us which assertions can be eliminated +pub fn get_assertion_map(def_id: DefId) -> Option> { + VERIFIED_ASSERTIONS.with(|cell| { + let map = cell.borrow(); + map.get(&def_id).cloned() + }) +} + +// Add the location when inserting refute(false) into encoding. +// Assume it's reachable, if we catch an error for it later we know it's not! +pub fn add_reachability_check(def_id: DefId, bb: mir::BasicBlock) { + REACHABILITY_CHECKS.with(|cell| { + let mut map = cell.borrow_mut(); + let entry = map.entry(def_id); + entry.or_default().insert(bb, true); + }) +} + +pub fn set_block_unreachable(def_id: DefId, bb: mir::BasicBlock) { + REACHABILITY_CHECKS.with(|cell| { + let mut map = cell.borrow_mut(); + let fn_map = map.get_mut(&def_id).unwrap(); + // make sure this location is actually in here + assert!(fn_map.get(&bb).is_some()); + fn_map.insert(bb, false); + }) +} + +pub fn get_reachability_map(def_id: DefId) -> Option> { + REACHABILITY_CHECKS.with(|cell| { + let map = cell.borrow(); + map.get(&def_id).cloned() + }) +} + +/// Store specifications and environment. +pub fn store_spec_env<'tcx>(def_spec: DefSpecificationMap, env: Environment<'tcx>) { + let static_env: Environment<'static> = unsafe { std::mem::transmute(env) }; + SPECS.with(|specs| { + *specs.borrow_mut() = Some(def_spec); + }); + ENV.with(|env| { + *env.borrow_mut() = Some(static_env); + }) +} + +pub fn get_defspec() -> DefSpecificationMap { + SPECS.with(|specs| specs.take().unwrap()) +} + +pub fn get_env<'tcx>() -> Environment<'tcx> { + let env_static = ENV.with(|env| env.take().unwrap()); + let env: Environment<'tcx> = unsafe { std::mem::transmute(env_static) }; + env +} + +/// Whether verification has already been executed for a specific crate +pub fn verified(krate_id: CrateNum) -> bool { + VERIFIED.with(|verified| verified.borrow().contains(&krate_id)) +} + +/// Mark a crate as verified +pub fn set_verified(krate_id: CrateNum) { + VERIFIED.with(|verified| verified.borrow_mut().insert(krate_id)); +} + +pub fn store_polonius_info(def_id: DefId, polonius_info: PoloniusInfo<'_, '_>) { + // very unsafe, why do I have to do this.. + // have to make sure addresses in env stay accurate + let static_info: PoloniusInfo<'static, 'static> = unsafe { std::mem::transmute(polonius_info) }; + POLONIUS.with(|info| info.borrow_mut().insert(def_id, static_info)); +} + +// first lifetime corresponds to lifetime of env +pub fn get_polonius_info<'a, 'tcx>(def_id: DefId) -> Option> { + let static_polonius = POLONIUS.with(|info| info.borrow_mut().remove(&def_id)); + unsafe { std::mem::transmute(static_polonius) } +} diff --git a/prusti-interface/src/lib.rs b/prusti-interface/src/lib.rs index d1b7bdaf632..607fed6081e 100644 --- a/prusti-interface/src/lib.rs +++ b/prusti-interface/src/lib.rs @@ -25,6 +25,7 @@ pub mod data; pub mod environment; pub mod specs; pub mod utils; +pub mod globals; pub use prusti_error::*; diff --git a/prusti-interface/src/prusti_error.rs b/prusti-interface/src/prusti_error.rs index 89b8683a3d3..68a89df8977 100644 --- a/prusti-interface/src/prusti_error.rs +++ b/prusti-interface/src/prusti_error.rs @@ -28,6 +28,12 @@ pub struct PrustiError { /// currently verify functions multiple times. Once this is fixed, this /// field should be removed. is_disabled: bool, + /// ignore these errors because we introduced them ourselves by + /// modifying mir (inserting assertions for reachability) + /// Different to `is_disabled` because errors with is_disabled still cause + /// a verification failure. Also separated because apparently is_disabled + /// is temporary + ignore: bool, message: String, span: Box, help: Option, @@ -60,6 +66,7 @@ impl PrustiError { PrustiError { kind: PrustiErrorKind::Error, is_disabled: false, + ignore: false, message, span: Box::new(span), help: None, @@ -86,6 +93,19 @@ impl PrustiError { error } + pub fn ignore_verification(message: S, span: MultiSpan) -> Self { + check_message(message.to_string()); + let mut error = PrustiError::new( + format!( + "[Prusti: expected error. Bug if a user sees this!] {}", + message.to_string() + ), + span, + ); + error.ignore = true; + error + } + /// Report an unsupported feature of the verified Rust code (e.g. dereferencing raw pointers) pub fn unsupported(message: S, span: MultiSpan) -> Self { check_message(message.to_string()); @@ -160,6 +180,13 @@ impl PrustiError { self.is_disabled } + // Errors that should be ignored and not reported to the user. + // Used for errors that originate from assertions that we + // automatically insert into MIR to analyse reachability + pub fn is_ignored(&self) -> bool { + self.ignore + } + #[must_use] pub fn set_help(mut self, message: S) -> Self { self.help = Some(message.to_string()); diff --git a/prusti-interface/src/specs/checker/common.rs b/prusti-interface/src/specs/checker/common.rs index 6d920e7f147..5814674fedb 100644 --- a/prusti-interface/src/specs/checker/common.rs +++ b/prusti-interface/src/specs/checker/common.rs @@ -1,4 +1,8 @@ -use crate::{environment::Environment, utils::has_spec_only_attr, PrustiError}; +use crate::{ + environment::Environment, + utils::{has_check_only_attr, has_spec_only_attr}, + PrustiError, +}; use prusti_rustc_interface::{ hir::{ self as hir, @@ -72,6 +76,29 @@ impl<'tcx, T: NonSpecExprVisitor<'tcx>> Visitor<'tcx> for NonSpecExprVisitorWrap intravisit::walk_expr(self, ex); } + fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) { + // runtime checks can introduce blocks that call predicates. This will make + // sure they are skipped. They are marked with #[spec_only] too, so if they + // can be skipped in general, this function could be adjusted. + if let Some(hir::Stmt { + kind: + hir::StmtKind::Semi(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { def_id, .. }), + .. + }), + .. + }) = block.stmts.get(0) + { + // check if this def_id has a check_only attribute: + let tcx = self.wrapped.tcx(); + let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(*def_id)); + if has_check_only_attr(attrs) { + return; + } + } + intravisit::walk_block(self, block) + } + fn visit_fn( &mut self, fk: intravisit::FnKind<'tcx>, @@ -83,7 +110,7 @@ impl<'tcx, T: NonSpecExprVisitor<'tcx>> Visitor<'tcx> for NonSpecExprVisitorWrap // Stop checking inside `prusti::spec_only` functions let tcx = self.wrapped.tcx(); let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(local_id)); - if has_spec_only_attr(attrs) { + if has_spec_only_attr(attrs) || has_check_only_attr(attrs) { return; } diff --git a/prusti-interface/src/specs/checker/predicate_checks.rs b/prusti-interface/src/specs/checker/predicate_checks.rs index 7a798594955..c29d79f7b2f 100644 --- a/prusti-interface/src/specs/checker/predicate_checks.rs +++ b/prusti-interface/src/specs/checker/predicate_checks.rs @@ -1,7 +1,9 @@ use super::common::*; use crate::{ environment::{EnvQuery, Environment}, - utils::{has_abstract_predicate_attr, has_prusti_attr}, + utils::{ + has_abstract_predicate_attr, has_check_only_attr, has_prusti_attr, has_spec_only_attr, + }, PrustiError, }; use log::debug; @@ -33,12 +35,29 @@ impl<'tcx> SpecCheckerStrategy<'tcx> for IllegalPredicateUsagesChecker { "Abstract predicates with bodies: {:?}", collected_predicates.abstract_predicate_with_bodies ); + let illegal_rtc_pred_usages = + self.collect_illegal_predicate_usages_rtc(&collected_predicates.predicates, env.query); + debug!( + "Predicate usages from runtime check code: {:?}", + illegal_rtc_pred_usages + ); + let illegal_pred_usages = self.collect_illegal_predicate_usages(collected_predicates.predicates, env.query); debug!("Predicate usages: {:?}", illegal_pred_usages); // TODO: check behavioral subtyping of implemented predicates against default implementation + let illegal_pred_usages_rtc = illegal_rtc_pred_usages.into_iter().map(|(usage_span, def_span)|{ + PrustiError::incorrect( + "Referring to predicate that is not runtime checkable in specifications that should be runtime checked is not allowed".to_string(), + MultiSpan::from_span(usage_span), + ).add_note( + "this predicate can not be checked at runtime, make sure it has a body and mark it with #[insert_runtime_check]", + Some(def_span) + ) + }); + let illegal_usage_errors = illegal_pred_usages .into_iter() .map(|(usage_span, def_span)| { @@ -50,7 +69,8 @@ impl<'tcx> SpecCheckerStrategy<'tcx> for IllegalPredicateUsagesChecker { "this is a specification-only predicate function", Some(def_span), ) - }); + }) + .chain(illegal_pred_usages_rtc); illegal_usage_errors.collect() } @@ -74,9 +94,10 @@ impl IllegalPredicateUsagesChecker { } /// Span of use and definition of predicates used outside of specifications, collected in the second pass. + /// Ignores calls to predicates within code generated by runtime checks fn collect_illegal_predicate_usages( &self, - predicates: FxHashMap, + predicates: FxHashMap, env_query: EnvQuery, ) -> Vec<(Span, Span)> { let mut visit = CheckPredicatesVisitor { @@ -91,13 +112,34 @@ impl IllegalPredicateUsagesChecker { visit.wrapped.pred_usages } + + /// Similar to previous check, but now we look at the code generated by runtime checks. + /// For the predicates that are called here, we need to make sure that they are + /// indeed runtime checkable (i.e. they have been marked with a #[check_only] attribute, + /// which means an impelementation for them has been generated) + fn collect_illegal_predicate_usages_rtc<'tcx>( + &self, + predicates: &FxHashMap, + env_query: EnvQuery<'tcx>, + ) -> Vec<(Span, Span)> { + let mut visit = RuntimeCheckablePredicatesVisitor { + env_query, + predicates, + bad_usages: Vec::new(), + within_check_code: false, + }; + env_query.hir().visit_all_item_likes_in_crate(&mut visit); + visit.bad_usages + } } /// First predicate checks visitor: collect all function items that originate /// from predicates struct CollectPredicatesVisitor<'tcx> { env_query: EnvQuery<'tcx>, - predicates: FxHashMap, + /// Maps from predicates to their Span + a boolean telling us whether this predicate + /// can be checked at runtime + predicates: FxHashMap, abstract_predicate_with_bodies: FxHashSet, } @@ -120,8 +162,9 @@ impl<'tcx> intravisit::Visitor<'tcx> for CollectPredicatesVisitor<'tcx> { // collect this fn's DefId if predicate function let attrs = self.env_query.get_local_attributes(local_id); if has_prusti_attr(attrs, "pred_spec_id_ref") { + let runtime_checkable = has_check_only_attr(attrs); let def_id = local_id.to_def_id(); - self.predicates.insert(def_id, s); + self.predicates.insert(def_id, (s, runtime_checkable)); } intravisit::walk_fn(self, fk, fd, b, local_id); @@ -133,7 +176,7 @@ impl<'tcx> intravisit::Visitor<'tcx> for CollectPredicatesVisitor<'tcx> { if has_abstract_predicate_attr(attrs) { let span = self.env_query.get_def_span(def_id); - self.predicates.insert(def_id, span); + self.predicates.insert(def_id, (span, false)); } else if has_prusti_attr(attrs, "pred_spec_id_ref") { if let hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)) = &ti.kind { self.abstract_predicate_with_bodies.insert(def_id); @@ -148,8 +191,7 @@ impl<'tcx> intravisit::Visitor<'tcx> for CollectPredicatesVisitor<'tcx> { /// from non-specification code struct CheckPredicatesVisitor<'tcx> { env_query: EnvQuery<'tcx>, - - predicates: FxHashMap, + predicates: FxHashMap, pred_usages: Vec<(Span, Span)>, } @@ -159,41 +201,144 @@ impl<'v, 'tcx: 'v> NonSpecExprVisitor<'tcx> for CheckPredicatesVisitor<'tcx> { } fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { - let owner_def_id = ex.hir_id.owner.def_id; - - // General check: The "path" of a predicate doesn't appear anywhere - // (e.g. as in a function call or an argument when we pass the predicate to another function) - if let hir::ExprKind::Path(ref path) = ex.kind { - if self.env_query.has_body(owner_def_id) { - let res = self - .env_query - .tcx() - .typeck(owner_def_id) - .qpath_res(path, ex.hir_id); - if let hir::def::Res::Def(_, def_id) = res { - if let Some(pred_def_span) = self.predicates.get(&def_id) { - self.pred_usages.push((ex.span, *pred_def_span)); - } - } + if let Some(def_id) = resolve_path(ex, self.env_query) { + if let Some((pred_def_span, _)) = self.predicates.get(&def_id) { + self.pred_usages.push((ex.span, *pred_def_span)); } } - // When we deal with predicates in impls, the above path resolving is not enough, - // i.e. when Foo::bar is a predicate and we call `foo.bar()` on some `foo: Foo`, - // we do not observe the called def id `bar` via path resolution. - if self.env_query.has_body(owner_def_id) { - let resolved_called_method = self - .env_query + if let Some(called_def_id) = resolve_impl(ex, self.env_query) { + if let Some((pred_def_span, _)) = self.predicates.get(&called_def_id) { + self.pred_usages.push((ex.span, *pred_def_span)); + } + } + } +} + +fn resolve_path<'tcx>(ex: &'tcx hir::Expr<'tcx>, env_query: EnvQuery<'tcx>) -> Option { + let owner_def_id = ex.hir_id.owner.def_id; + + // General check: The "path" of a predicate doesn't appear anywhere + // (e.g. as in a function call or an argument when we pass the predicate to another function) + if let hir::ExprKind::Path(ref path) = ex.kind { + if env_query.has_body(owner_def_id) { + let res = env_query .tcx() .typeck(owner_def_id) - .type_dependent_def_id(ex.hir_id); - if let Some(called_def_id) = resolved_called_method { - if !self.env_query.tcx().is_constructor(called_def_id) { - if let Some(pred_def_span) = self.predicates.get(&called_def_id) { - self.pred_usages.push((ex.span, *pred_def_span)); - } + .qpath_res(path, ex.hir_id); + if let hir::def::Res::Def(_, def_id) = res { + return Some(def_id); + } + } + } + None +} + +fn resolve_impl<'tcx>(ex: &'tcx hir::Expr<'tcx>, env_query: EnvQuery<'tcx>) -> Option { + let owner_def_id = ex.hir_id.owner.def_id; + // When we deal with predicates in impls, the above path resolving is not enough, + // i.e. when Foo::bar is a predicate and we call `foo.bar()` on some `foo: Foo`, + // we do not observe the called def id `bar` via path resolution. + if env_query.has_body(owner_def_id) { + let resolved_called_method = env_query + .tcx() + .typeck(owner_def_id) + .type_dependent_def_id(ex.hir_id); + if let Some(called_def_id) = resolved_called_method { + if !env_query.tcx().is_constructor(called_def_id) { + return Some(called_def_id); + } + } + } + None +} + +/// This visitors purpose is to detect calls to predicates within blocks marked with +/// #[check_only] that are not actually runtime checkable (because they were not +/// marked with #[insert_runtime_check] for example) +struct RuntimeCheckablePredicatesVisitor<'tcx, 'a> { + env_query: EnvQuery<'tcx>, + predicates: &'a FxHashMap, + bad_usages: Vec<(Span, Span)>, + within_check_code: bool, +} + +impl<'tcx, 'a> intravisit::Visitor<'tcx> for RuntimeCheckablePredicatesVisitor<'tcx, 'a> { + type Map = Map<'tcx>; + type NestedFilter = prusti_rustc_interface::middle::hir::nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.env_query.hir() + } + + fn visit_fn( + &mut self, + fk: intravisit::FnKind<'tcx>, + fd: &'tcx hir::FnDecl<'tcx>, + b: hir::BodyId, + _s: Span, + local_id: LocalDefId, + ) { + // for closures we still need to store this. + let old_within_check_code = self.within_check_code; + let tcx = self.env_query.tcx(); + let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(local_id)); + let check_only_attr = has_check_only_attr(attrs); + // skip normal spec functions, but not if they are also check functions + if has_spec_only_attr(attrs) && !check_only_attr { + return; + } + // the case where the whole function was generated for runtime checks, e.g. + // for preconditions + if check_only_attr { + self.within_check_code = true; + } + intravisit::walk_fn(self, fk, fd, b, local_id); + self.within_check_code = old_within_check_code; + } + + // find blocks generated for a runtime check of e.g. a prusti_assert or similar, + fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) { + let old_within_check_code = self.within_check_code; + + if let Some(hir::Stmt { + kind: + hir::StmtKind::Semi(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { def_id, .. }), + .. + }), + .. + }) = block.stmts.get(0) + { + // check if this def_id has a check_only attribute: + let tcx = self.env_query.tcx(); + let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(*def_id)); + if has_check_only_attr(attrs) { + // signal to members of this block that they are within code + // generated by runtime checks + self.within_check_code = true; + } + } + intravisit::walk_block(self, block); + self.within_check_code = old_within_check_code; + } + + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { + if let Some(def_id) = resolve_path(ex, self.env_query) { + if let Some((pred_def_span, runtime_checkable)) = self.predicates.get(&def_id) { + if !runtime_checkable && self.within_check_code { + self.bad_usages.push((ex.span, *pred_def_span)); + } + } + } + + if let Some(called_def_id) = resolve_impl(ex, self.env_query) { + if let Some((pred_def_span, runtime_checkable)) = self.predicates.get(&called_def_id) { + if !runtime_checkable && self.within_check_code { + self.bad_usages.push((ex.span, *pred_def_span)); } } } + intravisit::walk_expr(self, ex); } } diff --git a/prusti-interface/src/specs/mod.rs b/prusti-interface/src/specs/mod.rs index d7125c83353..69c053fa92d 100644 --- a/prusti-interface/src/specs/mod.rs +++ b/prusti-interface/src/specs/mod.rs @@ -75,9 +75,14 @@ pub struct SpecCollector<'a, 'tcx> { /// Map from specification IDs to their typed expressions. spec_functions: FxHashMap, + /// the functions who's specifications have associated checks. + check_functions: FxHashMap, + check_before_expiry_functions: FxHashMap, /// Map from functions/loops/types to their specifications. procedure_specs: FxHashMap, + /// The procedures that contain runtime checks. + procedure_checks: FxHashMap>, loop_specs: Vec, loop_variants: Vec, type_specs: FxHashMap, @@ -94,7 +99,10 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { extern_resolver: ExternSpecResolver::new(env), env, spec_functions: FxHashMap::default(), + check_functions: FxHashMap::default(), procedure_specs: FxHashMap::default(), + procedure_checks: FxHashMap::default(), + check_before_expiry_functions: FxHashMap::default(), loop_specs: vec![], loop_variants: vec![], type_specs: FxHashMap::default(), @@ -116,6 +124,8 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { pub fn build_def_specs(&mut self) -> typed::DefSpecificationMap { let mut def_spec = typed::DefSpecificationMap::new(); self.determine_procedure_specs(&mut def_spec); + + self.determine_checks(&mut def_spec); self.determine_extern_specs(&mut def_spec); self.determine_loop_specs(&mut def_spec); self.determine_type_specs(&mut def_spec); @@ -123,12 +133,57 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { self.determine_prusti_assumptions(&mut def_spec); self.determine_prusti_refutations(&mut def_spec); self.determine_ghost_begin_ends(&mut def_spec); + // TODO: remove spec functions (make sure none are duplicated or left over) // Load all local spec MIR bodies, for export and later use self.ensure_local_mirs_fetched(&def_spec); + def_spec } + fn determine_checks(&self, def_spec: &mut typed::DefSpecificationMap) { + // local_id: defId of the annotated function + // checks: uuids + kind of the check + for (local_id, checks) in self.procedure_checks.iter() { + let mut function_checks = Vec::new(); + for check in checks { + let kind = match check { + SpecIdRef::Precondition(id) => { + let fn_id = self.check_functions.get(id).unwrap(); + typed::CheckKind::Pre(fn_id.to_def_id()) + } + SpecIdRef::Postcondition(id) => { + let fn_id = self.check_functions.get(id).unwrap(); + typed::CheckKind::Post { + check: fn_id.to_def_id(), + } + } + SpecIdRef::Pledge { rhs, .. } => { + // can we treat both assert_on_expiry and after_expiry treat the same? + let check = self.check_functions.get(rhs).unwrap().to_def_id(); + let check_before_expiry = self + .check_before_expiry_functions + .get(rhs) + .map(|id| id.to_def_id()); + typed::CheckKind::Pledge { + check, + check_before_expiry, + } + } + SpecIdRef::Predicate(id) => { + let fn_id = self.check_functions.get(id).unwrap(); + typed::CheckKind::Predicate(fn_id.to_def_id()) + } + _ => unreachable!(), + }; + function_checks.push(kind); + } + def_spec + .checks + .insert(local_id.to_def_id(), function_checks); + } + } + fn determine_procedure_specs(&self, def_spec: &mut typed::DefSpecificationMap) { for (local_id, refs) in self.procedure_specs.iter() { let mut spec = SpecGraph::new(ProcedureSpecification::empty(local_id.to_def_id())); @@ -211,6 +266,11 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { let spec = def_spec.proc_specs.remove(spec_id).unwrap(); def_spec.proc_specs.insert(target_def_id, spec); + + // also deal with checks for extern specs: + if let Some(check) = def_spec.checks.remove(spec_id) { + def_spec.checks.insert(target_def_id, check); + } } } @@ -441,6 +501,38 @@ fn get_procedure_spec_ids(def_id: DefId, attrs: &[ast::Attribute]) -> Option Vec { + let mut res = Vec::new(); + read_prusti_attrs("pre_check_id_ref", attrs) + .iter() + .for_each(|x| { + res.push(SpecIdRef::Precondition(parse_spec_id( + x.to_string(), + def_id, + ))) + }); + read_prusti_attrs("post_check_id_ref", attrs) + .iter() + .for_each(|x| { + res.push(SpecIdRef::Postcondition(parse_spec_id( + x.to_string(), + def_id, + ))) + }); + read_prusti_attrs("assert_pledge_check_ref", attrs) + .iter() + .for_each(|x| { + res.push(SpecIdRef::Pledge { + lhs: None, + rhs: parse_spec_id(x.to_string(), def_id), + }); + }); + read_prusti_attr("pred_check_id_ref", attrs) + .iter() + .for_each(|x| res.push(SpecIdRef::Predicate(parse_spec_id(x.to_string(), def_id)))); + res +} + impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { type Map = Map<'tcx>; type NestedFilter = prusti_rustc_interface::middle::hir::nested_filter::All; @@ -461,6 +553,8 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { if let Some(procedure_spec_ref) = get_procedure_spec_ids(def_id, attrs) { self.procedure_specs.insert(local_id, procedure_spec_ref); } + let check_id = get_procedure_check_ids(def_id, attrs); + self.procedure_checks.insert(local_id, check_id); } fn visit_fn( @@ -550,6 +644,15 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { if has_prusti_attr(attrs, "ghost_end") { self.ghost_end.push(local_id); } + } else if let Some(raw_check_id) = read_prusti_attr("check_id", attrs) { + // check_id work just like spec_ids + let check_id = parse_spec_id(raw_check_id, def_id); + self.check_functions.insert(check_id, local_id); + } else if let Some(raw_before_expiry_check_id) = + read_prusti_attr("check_before_expiry_id", attrs) + { + let id = parse_spec_id(raw_before_expiry_check_id, def_id); + self.check_before_expiry_functions.insert(id, local_id); } else { // Don't collect specs "for" spec items @@ -565,6 +668,8 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { if let Some(procedure_spec_ref) = get_procedure_spec_ids(def_id, attrs) { self.procedure_specs.insert(local_id, procedure_spec_ref); } + let check_ids = get_procedure_check_ids(def_id, attrs); + self.procedure_checks.insert(local_id, check_ids); // Collect model type flag if has_to_model_fn_attr(attrs) { diff --git a/prusti-interface/src/specs/typed.rs b/prusti-interface/src/specs/typed.rs index 2041266c555..e5efa91ac8d 100644 --- a/prusti-interface/src/specs/typed.rs +++ b/prusti-interface/src/specs/typed.rs @@ -20,6 +20,23 @@ pub struct DefSpecificationMap { pub prusti_refutations: FxHashMap, pub ghost_begin: FxHashMap, pub ghost_end: FxHashMap, + /// for every annotated method, maps to a list of check functions to check + /// its contracts + pub checks: FxHashMap>, +} + +#[derive(Debug, Clone)] +pub enum CheckKind { + Pre(DefId), + Post { + check: DefId, + }, // actual check and old_store function + Assume(DefId), + Pledge { + check: DefId, + check_before_expiry: Option, + }, + Predicate(DefId), } impl DefSpecificationMap { @@ -59,6 +76,63 @@ impl DefSpecificationMap { self.ghost_end.get(def_id) } + pub fn get_runtime_checks(&self, def_id: &DefId) -> Vec { + self.checks.get(def_id).cloned().unwrap_or(Vec::new()) + } + pub fn get_pre_checks(&self, def_id: &DefId) -> Vec { + let checks_opt = self.checks.get(def_id); + if let Some(checks) = checks_opt { + checks + .iter() + .filter_map(|el| { + if let CheckKind::Pre(id) = el { + Some(id) + } else { + None + } + }) + .cloned() + .collect() + } else { + Vec::new() + } + } + + pub fn get_post_checks(&self, def_id: &DefId) -> Vec { + let checks_opt = self.checks.get(def_id); + if let Some(checks) = checks_opt { + checks + .iter() + .filter_map(|el| { + if let CheckKind::Post { check } = el { + Some(*check) + } else { + None + } + }) + .collect() + } else { + Vec::new() + } + } + + pub fn get_predicate_check(&self, def_id: &DefId) -> Option { + let mut check: Vec = self + .checks + .get(def_id)? + .iter() + .filter_map(|el| { + if let CheckKind::Predicate(def_id) = el { + Some(*def_id) + } else { + None + } + }) + .collect(); + assert!(check.len() <= 1); + check.pop() + } + pub(crate) fn defid_for_export( &self, ) -> ( diff --git a/prusti-interface/src/utils.rs b/prusti-interface/src/utils.rs index 4dfed681a7e..4011a24fa91 100644 --- a/prusti-interface/src/utils.rs +++ b/prusti-interface/src/utils.rs @@ -213,6 +213,12 @@ pub fn has_spec_only_attr(attrs: &[ast::Attribute]) -> bool { has_prusti_attr(attrs, "spec_only") } +/// Check if `prusti::check_only` is among the attributes. +/// runtime check blocks +pub fn has_check_only_attr(attrs: &[ast::Attribute]) -> bool { + has_prusti_attr(attrs, "check_only") +} + /// Check if `prusti::extern_spec` is among the attributes. pub fn has_extern_spec_attr(attrs: &[ast::Attribute]) -> bool { has_prusti_attr(attrs, "extern_spec") diff --git a/prusti-launch/src/bin/cargo-prusti.rs b/prusti-launch/src/bin/cargo-prusti.rs index d3ebce4ea57..49e2acdb3cd 100644 --- a/prusti-launch/src/bin/cargo-prusti.rs +++ b/prusti-launch/src/bin/cargo-prusti.rs @@ -58,6 +58,15 @@ where "PRUSTI_NO_VERIFY_DEPS", config::no_verify_deps().to_string(), ) + // flags that influence ast rewriting + .env( + "PRUSTI_DEBUG_RUNTIME_CHECKS", + config::debug_runtime_checks().to_string(), + ) + .env( + "PRUSTI_INSERT_RUNTIME_CHECKS", + config::insert_runtime_checks(), + ) // Category A* flags: .env("DEFAULT_PRUSTI_QUIET", "true") .env("DEFAULT_PRUSTI_FULL_COMPILATION", "true") diff --git a/prusti-rustc-interface/src/lib.rs b/prusti-rustc-interface/src/lib.rs index d8a1415b539..378e4d913b7 100644 --- a/prusti-rustc-interface/src/lib.rs +++ b/prusti-rustc-interface/src/lib.rs @@ -19,6 +19,8 @@ pub extern crate rustc_infer as infer; pub extern crate rustc_interface as interface; pub extern crate rustc_macros as macros; pub extern crate rustc_metadata as metadata; +pub extern crate rustc_mir_build as mir_build; +pub extern crate rustc_mir_transform as mir_transform; pub extern crate rustc_serialize as serialize; pub extern crate rustc_session as session; pub extern crate rustc_span as span; diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index f5b0b75919c..07f2dd29e03 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -37,9 +37,16 @@ impl ViperBackendConfig { let mut verifier_args = config::extra_verifier_args(); match backend { VerificationBackend::Silicon => { + // If we want to remove dead code, numberOfErrorsToReport has to + // be set to 0 (so all reachable errors are reported!) + let num_errors_per_function = if config::remove_dead_code() { + 0 + } else { + config::num_errors_per_function() + }; verifier_args.push(format!( "--numberOfErrorsToReport={}", - config::num_errors_per_function() + num_errors_per_function )); if config::use_more_complete_exhale() { verifier_args.push("--enableMoreCompleteExhale".to_string()); diff --git a/prusti-tests/Cargo.toml b/prusti-tests/Cargo.toml index 3c5ae2a7673..aef2c5f7cf3 100644 --- a/prusti-tests/Cargo.toml +++ b/prusti-tests/Cargo.toml @@ -13,6 +13,7 @@ cargo-test-support = { git = "https://github.com/rust-lang/cargo.git", rev = "ec ureq = "2.1" log = { version = "0.4", features = ["release_max_level_info"] } env_logger = "0.10" +ui_test = "0.17.0" [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/prusti-tests/tests/cargo_verify/test_no_std/Cargo.toml b/prusti-tests/tests/cargo_verify/test_no_std/Cargo.toml index 1785eb1d4b5..11697f3260f 100644 --- a/prusti-tests/tests/cargo_verify/test_no_std/Cargo.toml +++ b/prusti-tests/tests/cargo_verify/test_no_std/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] libc = { version = "0.2", default-features = false } -prusti-contracts = { path = "prusti-contracts/prusti-contracts" } # The test suite will prepare a symbolic link for this +prusti-contracts = { path = "prusti-contracts/prusti-contracts", default-features = false } # The test suite will prepare a symbolic link for this [profile.dev] panic = "abort" diff --git a/prusti-tests/tests/mir_optimizations/checked_add.rs b/prusti-tests/tests/mir_optimizations/checked_add.rs new file mode 100644 index 00000000000..ad81e8fcea0 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/checked_add.rs @@ -0,0 +1,17 @@ +//@run +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +#[trusted] +fn main() { + println!("3 + 5 = {}", add(3, 5)); + // this one will overflow, but given the contract of + // add, the overflow check can "safely" be eliminated, + // meaning this should not panic! + println!("usize::MAX + 1 = {}", add(usize::MAX, 1)); +} + +#[requires(x < 1000 && y < 1000)] +fn add(x: usize, y: usize) -> usize { + x + y +} diff --git a/prusti-tests/tests/mir_optimizations/checked_add.stdout b/prusti-tests/tests/mir_optimizations/checked_add.stdout new file mode 100644 index 00000000000..b05c744263d --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/checked_add.stdout @@ -0,0 +1,2 @@ +3 + 5 = 8 +usize::MAX + 1 = 0 diff --git a/prusti-tests/tests/mir_optimizations/checked_multiplication.rs b/prusti-tests/tests/mir_optimizations/checked_multiplication.rs new file mode 100644 index 00000000000..053e3c9a364 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/checked_multiplication.rs @@ -0,0 +1,17 @@ +//@run +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +#[trusted] +fn main() { + println!("3 * 5 = {}", mul(3, 5)); + // this one will overflow, but given the contract of + // add, the overflow check can "safely" be eliminated, + // meaning this should not panic! + println!("(usize::MAX - 1) * 2 = {}", mul(usize::MAX - 1, 1)); +} + +#[requires(x < 1000 && y < 1000)] +fn mul(x: usize, y: usize) -> usize { + x * y +} diff --git a/prusti-tests/tests/mir_optimizations/checked_multiplication.stdout b/prusti-tests/tests/mir_optimizations/checked_multiplication.stdout new file mode 100644 index 00000000000..4a83bfd92c3 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/checked_multiplication.stdout @@ -0,0 +1,2 @@ +3 * 5 = 15 +(usize::MAX - 1) * 2 = 18446744073709551614 diff --git a/prusti-tests/tests/mir_optimizations/enum.rs b/prusti-tests/tests/mir_optimizations/enum.rs new file mode 100644 index 00000000000..a4b3aaf8caa --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/enum.rs @@ -0,0 +1,46 @@ +//@run +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +// for variants we can not really test the behavior if we call an +// optimized version with arguments violating the precondition, because +// matching leads to a switchInt where otherwise points to an unreachable +// block. Altough we might be able to eliminate that one too? + +// Test with 3 enums that have the same memory layout: +// (so "bad" casting should not lead to errors) +enum Variants { + Case1(i32), + Case2(i32), + Case3(i32), +} + +#[trusted] +fn main() { + let x = Variants::Case2(5); + let y = Variants::Case3(42); + let z = Variants::Case1(72); + // valid call: expected result 10 + println!("{}", foo(x)); + // valid call: expected result 236 + println!("{}", foo(y)); + // invalid call, without optimizations result would be 72, + // but because of Case3 to be made into otherwise target, + // leading to result 216 + println!("{}", foo(z)); +} + +#[requires(!matches!(x, Variants::Case1(_)))] +fn foo(x: Variants) -> i32 { + match x { + Variants::Case1(x) => { + x + }, + Variants::Case2(x) => { + 2 * x + }, + Variants::Case3(x) => { + 3 * x + } + } +} diff --git a/prusti-tests/tests/mir_optimizations/enum.stdout b/prusti-tests/tests/mir_optimizations/enum.stdout new file mode 100644 index 00000000000..5851ef9a4a7 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/enum.stdout @@ -0,0 +1,3 @@ +10 +126 +216 diff --git a/prusti-tests/tests/mir_optimizations/multiple-asserts.rs b/prusti-tests/tests/mir_optimizations/multiple-asserts.rs new file mode 100644 index 00000000000..a5bdba559a7 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/multiple-asserts.rs @@ -0,0 +1,36 @@ +//@run +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +#[trusted] +fn main() { + println!("{}", foo(&mut 31)); // expected result: 60 + println!("{}", foo(&mut 1)); // expected result: 0 + println!("{}", foo(&mut 2)); + // should print 3 in theory, but with optimizations enabled + // we always take the second branch, leading to result: 2 +} + +// optimally this could be simplified to just: +// ``` +// let y = x * 2; +// y +// ``` +// However, if precondition is violated (as in main:11), the result +// will be "wrong" +#[requires(*x % 15 == 1)] +fn foo(x: &mut i32) -> i32 { + *x = *x - 1; + // prusti thinks: *x % 15 == 0 + let y = *x * 2; + if *x % 3 != 0 { + y + 1 + } else if *x % 5 == 0 { + // if precondition holds we always reach this branch + y + } else { + y - 1 + } +} + + diff --git a/prusti-tests/tests/mir_optimizations/multiple-asserts.stdout b/prusti-tests/tests/mir_optimizations/multiple-asserts.stdout new file mode 100644 index 00000000000..0dff4cef9cd --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/multiple-asserts.stdout @@ -0,0 +1,3 @@ +60 +0 +2 diff --git a/prusti-tests/tests/mir_optimizations/no_contract.rs b/prusti-tests/tests/mir_optimizations/no_contract.rs new file mode 100644 index 00000000000..dec2d9773e6 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/no_contract.rs @@ -0,0 +1,41 @@ +//@run +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +// takeaway: we need a better example! +#[trusted] +fn main() { + let mut x = 0; + for i in 0..i32::MAX/5 { + x += foo(i); + } + println!("x: {x}"); +} + +// Example of a function where a traditional compiler might struggle, +// but with prusti we can simplify this to just: +// ``` +// fn foo(x: i32) -> i32 { +// 30 +// } +// ``` +// Note: actually it seems like llvm performs this optimization too.. +// This test doesn't really check that the optimization is actually +// performed, but rather that it doesn't break the program. +// To make sure it is performed we need to inspect the MIR. +fn foo(x: i32) -> i32 { + let mut y = 0; + // a set of operations that in the end, will not modify y + y = y + x; + let z = 4 * x; + y = y - z; + y = y + (3 * x); + + if y != 0 { + // unreachable! + return 42 + } else { + return 1 + } +} + diff --git a/prusti-tests/tests/mir_optimizations/no_contract.stdout b/prusti-tests/tests/mir_optimizations/no_contract.stdout new file mode 100644 index 00000000000..3e902a403a7 --- /dev/null +++ b/prusti-tests/tests/mir_optimizations/no_contract.stdout @@ -0,0 +1 @@ +x: 429496729 diff --git a/prusti-tests/tests/parse/ui/nostd.rs b/prusti-tests/tests/parse/ui/nostd.rs index 2a60ddb3ee4..0e8abe9e0c9 100644 --- a/prusti-tests/tests/parse/ui/nostd.rs +++ b/prusti-tests/tests/parse/ui/nostd.rs @@ -1,3 +1,13 @@ +// ignore-test +// We changed a few things with regards to no_std. We still want to support it +// with cargo-prusti, but from now on std is a feature of the prusti-contracts +// trait that is enabled by default. Can be disabled by setting default-features=false. +// +// Since prusti-rustc works with a precompiled version of prusti-contracts, +// we decided to enable it permanently there, which is why this test is ignored now. +// Another solution would be to compile 2 versions of prusti-contracts and make it +// configurable which version is linked, but for now we didn't see this as necessary. + //! This ui-test makes sure that `prusti-contracts` does not depend on the Rust standard library. //! If this test fails (i.e. this file compiles successfully), then `prusti-contracts` depends on the standard library. #![no_std] diff --git a/prusti-tests/tests/parse/ui/predicates-visibility.stdout b/prusti-tests/tests/parse/ui/predicates-visibility.stdout index f1a453b2e48..db9310e4391 100644 --- a/prusti-tests/tests/parse/ui/predicates-visibility.stdout +++ b/prusti-tests/tests/parse/ui/predicates-visibility.stdout @@ -32,7 +32,8 @@ mod foo { }; prusti_result } - #[allow(unused_must_use, unused_variables, dead_code)] + #[allow(unused_must_use, unused_variables, dead_code, + forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] pub fn pred1(a: bool) -> bool { diff --git a/prusti-tests/tests/parse/ui/predicates.stdout b/prusti-tests/tests/parse/ui/predicates.stdout index aea6d0a2371..948686f878b 100644 --- a/prusti-tests/tests/parse/ui/predicates.stdout +++ b/prusti-tests/tests/parse/ui/predicates.stdout @@ -33,7 +33,7 @@ fn prusti_pred_item_pred1_$(NUM_UUID)(a: bool) -> bool { }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn pred1(a: bool) -> bool { @@ -65,7 +65,7 @@ fn prusti_pred_item_pred2_$(NUM_UUID)(a: bool) -> bool { }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn pred2(a: bool) -> bool { @@ -99,7 +99,7 @@ fn prusti_pred_item_forall_implication_$(NUM_UUID)() }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn forall_implication() -> bool { @@ -122,7 +122,7 @@ fn prusti_pred_item_exists_implication_$(NUM_UUID)() }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn exists_implication() -> bool { diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/assert-old.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/assert-old.rs new file mode 100644 index 00000000000..884704b1e78 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/assert-old.rs @@ -0,0 +1,15 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + let mut a = 42; + foo(&mut a); +} + +#[trusted] +fn foo(x: &mut i32) { + *x = 1; + // fails: *x needs to be evaluated in old state + prusti_assert!(#[insert_runtime_check] old(*x) == 1); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/assert-old.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/assert-old.stderr new file mode 100644 index 00000000000..e3f8798493e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/assert-old.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/assert-old.rs:14:44: +Prusti Runtime Checks: Contract prusti_assert!(old(*x) == 1) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/forall.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/forall.rs new file mode 100644 index 00000000000..0f1d4baa95a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/forall.rs @@ -0,0 +1,7 @@ +//@run: 101 +use prusti_contracts::*; +#[trusted] +fn main() { + let v = vec![1,2,3,4,5,6]; + prusti_assume!(#[insert_runtime_check]forall(|x: usize| x < 6 ==> v[x] < 6)); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/forall.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/forall.stderr new file mode 100644 index 00000000000..82df2915187 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/forall.stderr @@ -0,0 +1,4 @@ +thread 'main' panicked at $DIR/forall.rs:6:43: +Prusti Runtime Checks: Contract prusti_assume!(forall(|x: usize| x < 6 ==> v[x] < 6)) was violated at runtime + > expression x < 6 ==> v[x] < 6 was violated for index x=5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant-old.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant-old.rs new file mode 100644 index 00000000000..ef5381d4ddb --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant-old.rs @@ -0,0 +1,16 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + mul(&mut 50); +} + +#[trusted] +fn mul(x: &mut i32) { + let mut res = 1; + while *x > 0 { + body_invariant!(#[insert_runtime_check]old(*x) * 5 == res + *x * 5); + res = res + 5; + *x = *x-1; + } +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant-old.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant-old.stderr new file mode 100644 index 00000000000..afbeb18d37a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant-old.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/invariant-old.rs:12:48: +Prusti Runtime Checks: Contract body_invariant!(old(*x) * 5 == res + *x * 5) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant.rs new file mode 100644 index 00000000000..bc2ea95b13d --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant.rs @@ -0,0 +1,13 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + let mut x = 10; + let mut y = 0; + while x >= 0 { + body_invariant!(#[insert_runtime_check] x + y == 11); + x = x - 1; + y = y + 1; + } +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant.stderr new file mode 100644 index 00000000000..38ae490736c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/invariant.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/invariant.rs:9:49: +Prusti Runtime Checks: Contract body_invariant!(x + y == 11) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-locals.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-locals.rs new file mode 100644 index 00000000000..dfc64e3f300 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-locals.rs @@ -0,0 +1,31 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + // passes + test1(&mut 1); + // passes + test2(&mut 10); + // fails: + test3(&mut 2); +} + +#[trusted] +fn test1(x: &mut i32) { + *x = 2; + let a = x; + prusti_assert!(#[insert_runtime_check] old(*a) == 2); +} + +#[trusted] +fn test2(x: &mut i32) { + *x = 1; + prusti_assert!(#[insert_runtime_check] {let a = x; old(*a) == 1}); +} + +#[trusted] +fn test3(x: &mut i32) { + *x = 1; + // fails: *a is assigned x in old state. + prusti_assert!(#[insert_runtime_check] old({let a = x; *a == 1})); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-locals.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-locals.stderr new file mode 100644 index 00000000000..a699c309a4f --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-locals.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/old-locals.rs:30:44: +Prusti Runtime Checks: Contract prusti_assert!(old({let a = x; *a == 1})) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move-struct.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move-struct.rs new file mode 100644 index 00000000000..02f5982cf25 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move-struct.rs @@ -0,0 +1,17 @@ +//@run: 101 +use prusti_contracts::*; + +#[derive(Clone)] +struct SomeStruct(i32); + +fn main() { + let s = SomeStruct(42); + foo(s); +} + +#[trusted] +fn foo(mut s: SomeStruct) { + s.0 = 52; + // fails because evaluated in old state + prusti_assert!(#[insert_runtime_check] { old(s.0) == 52}); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move-struct.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move-struct.stderr new file mode 100644 index 00000000000..8e05d6702f0 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move-struct.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/old-move-struct.rs:16:44: +Prusti Runtime Checks: Contract prusti_assert!({ old(s.0) == 52}) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move.rs new file mode 100644 index 00000000000..e4e4a4da09d --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move.rs @@ -0,0 +1,14 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + foo(43); +} + +#[trusted] +fn foo(mut x: i32) { + x = 50; + prusti_assert!(#[insert_runtime_check] x == 50); + // this one fails because evaluated in old state! + prusti_assert!(#[insert_runtime_check] old(x == 50)); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move.stderr new file mode 100644 index 00000000000..9407eb84d71 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/old-move.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/old-move.rs:13:44: +Prusti Runtime Checks: Contract prusti_assert!(old(x == 50)) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/precise-error.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/precise-error.rs new file mode 100644 index 00000000000..dc7b6fd0b7b --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/precise-error.rs @@ -0,0 +1,10 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + let (x, y, z) = (5, 7, 6); + // should fail with an additional message stating which + // part of the conjunction failed first + prusti_assert!(#[insert_runtime_check]x == 5 && y == 6 && z == 7); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/precise-error.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/precise-error.stderr new file mode 100644 index 00000000000..48000e537f6 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/precise-error.stderr @@ -0,0 +1,4 @@ +thread 'main' panicked at $DIR/precise-error.rs:9:43: +Prusti Runtime Checks: Contract prusti_assert!(x == 5 && y == 6 && z == 7) was violated at runtime + > expression y == 6 was violated. +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/sanity.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/sanity.rs new file mode 100644 index 00000000000..fce0fa5fdf6 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/sanity.rs @@ -0,0 +1,8 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + // fails + prusti_assume!(#[insert_runtime_check] false); + prusti_assert!(#[insert_runtime_check] false); +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/sanity.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/sanity.stderr new file mode 100644 index 00000000000..24178fca14c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/sanity.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/sanity.rs:6:44: +Prusti Runtime Checks: Contract prusti_assume!(false) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/tuple.rs b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/tuple.rs new file mode 100644 index 00000000000..a4aa5010bef --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/tuple.rs @@ -0,0 +1,13 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + bar((7,4)); +} + +#[trusted] +fn bar(mut x: (i32, i32)) { + x.0 = 10; + x.1 = 20; + prusti_assert!(#[insert_runtime_check] old(x.0 + x.1) == 10) +} diff --git a/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/tuple.stderr b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/tuple.stderr new file mode 100644 index 00000000000..aaba0a3dfee --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/assert-assume-invariant/tuple.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/tuple.rs:12:44: +Prusti Runtime Checks: Contract prusti_assert!(old(x.0 + x.1) == 10) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/extern_specs/option.rs b/prusti-tests/tests/runtime_checks/fail/extern_specs/option.rs new file mode 100644 index 00000000000..3b876d4bc08 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/extern_specs/option.rs @@ -0,0 +1,28 @@ +//@run: 101 +use prusti_contracts::*; + +#[extern_spec] +impl std::option::Option { + #[pure] + #[insert_runtime_check] + #[ensures(matches!(*self, Some(_)) == result)] + pub fn is_some(&self) -> bool; + + #[pure] + #[insert_runtime_check] + #[ensures(self.is_some() == !result)] + pub fn is_none(&self) -> bool; + + #[trusted] + #[insert_runtime_check] + #[requires(self.is_some())] + pub fn unwrap(self) -> T; +} + +#[trusted] +fn main() { + let x: Option<()> = None; + // obviously panics! but with RT checks it should panic + // because precondition check fails! + x.unwrap(); +} diff --git a/prusti-tests/tests/runtime_checks/fail/extern_specs/option.stderr b/prusti-tests/tests/runtime_checks/fail/extern_specs/option.stderr new file mode 100644 index 00000000000..42bd3a49371 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/extern_specs/option.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/option.rs:16:5: +Prusti Runtime Checks: Contract #[requires(self.is_some())] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/extern_specs/option.stdout b/prusti-tests/tests/runtime_checks/fail/extern_specs/option.stdout new file mode 100644 index 00000000000..5fbe6ad6ebf --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/extern_specs/option.stdout @@ -0,0 +1,2 @@ +check function prusti_pre_check_item_unwrap is performed +check function prusti_post_check_item_is_some is performed diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.rs b/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.rs new file mode 100644 index 00000000000..5e33533ebd4 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.rs @@ -0,0 +1,38 @@ +//@run: 101 +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn increment(x: &mut usize) { + *x = *x + 1; +} + +fn main() { + // this one fails because index_mut_other is called + foo(); +} + +// This testcase exists, because here the expiration location is a call +// terminator. Right after calling increment(r), r expires. +// This is handled differently than an expiration at an arbitrary statement. +#[trusted] +fn foo() { + let mut p = Percentage(100); + let r = index_mut(&mut p); + increment(r); + // r expires here, and is too high, so the check fails + println!("p now has value: {}", p.0); +} + diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.stderr b/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.stderr new file mode 100644 index 00000000000..8c02526d3ed --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/call_expiration.rs:14:1: +Prusti Runtime Checks: Contract #[assert_on_expiry(*result <= 100, ..)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.stdout b/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.stdout new file mode 100644 index 00000000000..b7019a341c3 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/call_expiration.stdout @@ -0,0 +1,3 @@ +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/conditional.rs b/prusti-tests/tests/runtime_checks/fail/pledges/conditional.rs new file mode 100644 index 00000000000..94e9376ecdf --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/conditional.rs @@ -0,0 +1,44 @@ +//@run: 101 +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 >= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result >= 100, p.0 >= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut_other(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn main() { + // this one fails because index_mut_other is called + foo(false); +} + +#[trusted] +fn foo(b: bool) { + let mut p = Percentage(100); + let r = if b { + index_mut(&mut p) + } else { + index_mut_other(&mut p) + }; + // this is only a "valid" assignment if foo is called with + // b = true + *r = 72; +} diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/conditional.stderr b/prusti-tests/tests/runtime_checks/fail/pledges/conditional.stderr new file mode 100644 index 00000000000..9ca1b0d44c5 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/conditional.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/conditional.rs:24:1: +Prusti Runtime Checks: Contract #[assert_on_expiry(*result >= 100, ..)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/conditional.stdout b/prusti-tests/tests/runtime_checks/fail/pledges/conditional.stdout new file mode 100644 index 00000000000..4a5d257c382 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/conditional.stdout @@ -0,0 +1,3 @@ +check function prusti_pre_check_item_index_mut_other is performed +check function prusti_post_check_item_index_mut_other is performed +check function prusti_pledge_lhs_check_item_index_mut_other is performed diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.rs b/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.rs new file mode 100644 index 00000000000..1a616df46c2 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.rs @@ -0,0 +1,60 @@ +//@run: 101 +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +// if these are not trusted, prusti fails.. is it a bug? +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 >= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result >= 100, p.0 >= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut_other(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn main() { + // this once succeeds because index_mut is called + // and *r = 72 right before expiration + foo(true, true); + + // this one succeeds because index_mut_other is + // called and *r is assigned 101 before expiry + foo(false, false); + + // this one fails! + foo(true, false); +} + +#[trusted] +fn foo(b1: bool, b2: bool) { + let mut p = Percentage(100); + let r = if b1 { + index_mut(&mut p) + } else { + index_mut_other(&mut p) + }; + *r = 101; + if b2 { + *r = 72; + // pledge expires here, if !b1 this causes + // an error + } else { + // pledge expires here, before assignment + // if b1, this should cause an error! + p.0 = 101; + } +} diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.stderr b/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.stderr new file mode 100644 index 00000000000..b1068a2df4e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/conditional2.rs:14:1: +Prusti Runtime Checks: Contract #[assert_on_expiry(*result <= 100, ..)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.stdout b/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.stdout new file mode 100644 index 00000000000..bc5af2410d7 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/conditional2.stdout @@ -0,0 +1,11 @@ +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed +check function prusti_pledge_check_item_index_mut is performed +check function prusti_pre_check_item_index_mut_other is performed +check function prusti_post_check_item_index_mut_other is performed +check function prusti_pledge_lhs_check_item_index_mut_other is performed +check function prusti_pledge_check_item_index_mut_other is performed +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/percentage.rs b/prusti-tests/tests/runtime_checks/fail/pledges/percentage.rs new file mode 100644 index 00000000000..590250e7e5d --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/percentage.rs @@ -0,0 +1,36 @@ +//@run: 101 +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn main() { + // this one passes because *r will be set to 72 + foo(true); + // this one fails because *r is set to 101 + foo(false); +} + +#[trusted] +fn foo(b: bool) { + let mut p = Percentage(50); + let r = index_mut(&mut p); + if b { + *r = 72; + } else { + // this assignment violates the pledge + *r = 101; + p.0 = 43 + } + p.0 = 32; +} diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/percentage.stderr b/prusti-tests/tests/runtime_checks/fail/pledges/percentage.stderr new file mode 100644 index 00000000000..f85f8618db4 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/percentage.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/percentage.rs:13:1: +Prusti Runtime Checks: Contract #[assert_on_expiry(*result <= 100, ..)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/percentage.stdout b/prusti-tests/tests/runtime_checks/fail/pledges/percentage.stdout new file mode 100644 index 00000000000..35151282828 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/percentage.stdout @@ -0,0 +1,7 @@ +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed +check function prusti_pledge_check_item_index_mut is performed +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.rs b/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.rs new file mode 100644 index 00000000000..f717570fc1c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.rs @@ -0,0 +1,66 @@ +//@run: 101 +use prusti_contracts::*; + +#[derive(Clone)] +pub struct VecWrapperI32 { + pub v: Vec +} + +impl VecWrapperI32 { + #[trusted] + #[insert_runtime_check] + #[ensures(result.len() == 5)] + pub fn new() -> Self { + Self { + v: vec![1,2,3,4,5], + } + } + + #[trusted] + #[pure] + pub fn len(&self) -> usize { + self.v.len() + } + + /// A ghost function for specifying values stored in the vector. + #[trusted] + #[pure] + #[insert_runtime_check] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.v[index] + } + + #[trusted] + pub fn insert(&mut self, value: i32) { + self.v.push(value); + } + + #[trusted] + #[insert_runtime_check] + #[requires(index < self.len())] + #[insert_runtime_check] + #[ensures(*result == old(self.lookup(index)))] + // failing pledge: the quantifier iterates over all elements + // including the one that can be changed. + #[insert_runtime_check] + #[after_expiry( + self.len() == old(self.len()) && + self.lookup(index) == before_expiry(*result) && + forall( + |i: usize| (i < self.len()) ==> + self.lookup(i) == old(self.lookup(i)) + ) + )] + pub fn index_mut(&mut self, index: usize) -> &mut i32 { + self.v.get_mut(index).unwrap() + } +} + +fn main() { + let mut vw = VecWrapperI32::new(); + let x = vw.index_mut(3); + *x = 42; + vw.insert(50) +} + diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.stderr b/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.stderr new file mode 100644 index 00000000000..86a346a3eec --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.stderr @@ -0,0 +1,14 @@ +thread 'main' panicked at $DIR/vecwrapper.rs:55:5: +Prusti Runtime Checks: Contract #[after_expiry(self.len() == old(self.len()) && + self.lookup(index) == before_expiry(*result) && + forall( + |i: usize| (i < self.len()) ==> + self.lookup(i) == old(self.lookup(i)) + ))] was violated at runtime + > expression (i < self.len()) ==> + self.lookup(i) == old(self.lookup(i)) was violated for index i=3 + > expression forall( + |i: usize| (i < self.len()) ==> + self.lookup(i) == old(self.lookup(i)) + ) was violated. +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.stdout b/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.stdout new file mode 100644 index 00000000000..593cbcc951e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/vecwrapper.stdout @@ -0,0 +1,14 @@ +check function prusti_post_check_item_new is performed +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pledge_check_item_index_mut is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/zombie.rs b/prusti-tests/tests/runtime_checks/fail/pledges/zombie.rs new file mode 100644 index 00000000000..8ce1537b084 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/zombie.rs @@ -0,0 +1,45 @@ +//@run: 101 +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn main() { + // runs through correctly, because at expiration p.0 is 72 + foo(true); + // fails because p.0 = 102 + foo(false); +} + +#[trusted] +fn foo(b: bool) { + let mut p = Percentage(50); + let mut r = index_mut(&mut p); + let mut z = 1; + if !b { + let s = r; + *s = 102; + } else { + // this branch doesn't cause a failure! + *r = 105; + let t = r; + r = &mut z; + *t = 78; // temporary violate pledge + // expiration + p.0 = 105; + // violating the condition right after pledge should be checked, + // increases chances of us catching an error if it's inserted in the + // wrong place + } + p.0 = 32; +} diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/zombie.stderr b/prusti-tests/tests/runtime_checks/fail/pledges/zombie.stderr new file mode 100644 index 00000000000..9c80b7d4837 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/zombie.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/zombie.rs:13:1: +Prusti Runtime Checks: Contract #[assert_on_expiry(*result <= 100, ..)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/pledges/zombie.stdout b/prusti-tests/tests/runtime_checks/fail/pledges/zombie.stdout new file mode 100644 index 00000000000..35151282828 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/pledges/zombie.stdout @@ -0,0 +1,7 @@ +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed +check function prusti_pledge_check_item_index_mut is performed +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.rs new file mode 100644 index 00000000000..4094f394d49 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.rs @@ -0,0 +1,30 @@ +//@run: 101 +use prusti_contracts::*; + +// Tests that generics are properly handled, and that if there are multiple +// postconditions, they are properly chained and both executed. +fn main() { + let mut x = GenericStruct { x: 40}; + let mut y = GenericStruct { x: 32}; + let c = x.stuff(&mut y); +} + +#[derive(Clone)] +struct GenericStruct { + pub x: T, +} + +impl> GenericStruct { + #[trusted] + // fails because of the +1 + #[insert_runtime_check] + #[ensures(self.x == old(other.x))] + #[insert_runtime_check] + #[ensures(other.x == old(self.x))] + pub fn stuff(&mut self, other: &mut Self) { + std::mem::swap(&mut self.x, &mut other.x); + // make check fail: + self.x = self.x + other.x; + } +} + diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.stderr new file mode 100644 index 00000000000..bd84cb109de --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/generic-old.rs:24:5: +Prusti Runtime Checks: Contract #[ensures(self.x == old(other.x))] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.stdout new file mode 100644 index 00000000000..ac1c314e027 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/generic-old.stdout @@ -0,0 +1,2 @@ +check function prusti_post_check_item_stuff is performed +check function prusti_post_check_item_stuff is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/generic.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/generic.rs new file mode 100644 index 00000000000..c8ca7c330fe --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/generic.rs @@ -0,0 +1,25 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + let a: i32 = 50; + let x = GenericStruct { x: a}; + let y = GenericStruct { x: a}; + let c = x.stuff(&y); +} + +#[derive(Clone)] +struct GenericStruct { + pub x: T, +} + +impl GenericStruct { + #[trusted] + // fails because of the +1 + #[insert_runtime_check] + #[ensures(result == self.x + other.x + 1)] + pub fn stuff(&self, other: &Self) -> i32 { + self.x + other.x + } +} + diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/generic.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/generic.stderr new file mode 100644 index 00000000000..c5a49db89ce --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/generic.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/generic.rs:21:5: +Prusti Runtime Checks: Contract #[ensures(result == self.x + other.x + 1)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/generic.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/generic.stdout new file mode 100644 index 00000000000..a8f6389766e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/generic.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_stuff is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.rs new file mode 100644 index 00000000000..beb5d318558 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.rs @@ -0,0 +1,24 @@ +//@run: 101 +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +#[derive(Clone)] +struct Something { + pub field: i32, +} + +// has to fail, since x.field in specification refers +// to old state too +#[trusted] +#[insert_runtime_check] +#[ensures(old(x.field) + 4 == x.field)] +fn nonsense(mut x: Something) { + x.field = x.field + 4; +} + +fn main() { + let s = Something { + field: 1, + }; + nonsense(s); +} diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.stderr new file mode 100644 index 00000000000..a35fb3be2ea --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/old-non-ref.rs:15:1: +Prusti Runtime Checks: Contract #[ensures(old(x.field) + 4 == x.field)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.stdout new file mode 100644 index 00000000000..ff9419f1223 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/old-non-ref.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_nonsense is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.rs new file mode 100644 index 00000000000..facee4a940a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.rs @@ -0,0 +1,22 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + let mut x = 5; + let mut y = 6; + swap(&mut x, &mut y); + println!("function executed to the end"); +} + +// specifications are checked in reverse order (apparently) +// to check that both specifications are checked at runtime, +#[trusted] +#[insert_runtime_check] +#[ensures(old(*x) == *y && old(*y) == *x)] +fn swap(x: &mut i32, y: &mut i32) { + let z = *x; + // this causes postcondition to fail + *x = *y + 1; + *y = z; +} diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.stderr new file mode 100644 index 00000000000..288e61ad862 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.stderr @@ -0,0 +1,4 @@ +thread 'main' panicked at $DIR/old-pointers.rs:17:1: +Prusti Runtime Checks: Contract #[ensures(old(*x) == *y && old(*y) == *x)] was violated at runtime + > expression old(*y) == *x was violated. +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.stdout new file mode 100644 index 00000000000..67c5b52ad28 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/old-pointers.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_swap is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.rs new file mode 100644 index 00000000000..9dcef87ff07 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.rs @@ -0,0 +1,14 @@ +//@run: 101 +use prusti_contracts::*; + +fn main() { + foo(3); + foo(2); +} + +#[trusted] +#[insert_runtime_check] +#[ensures(result % 2 == 0)] +fn foo(x: i32) -> i32 { + x + 1 +} diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.stderr new file mode 100644 index 00000000000..0144c57ea5e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/post-simple.rs:12:1: +Prusti Runtime Checks: Contract #[ensures(result % 2 == 0)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.stdout new file mode 100644 index 00000000000..04b932cc1e9 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/post-simple.stdout @@ -0,0 +1,2 @@ +check function prusti_post_check_item_foo is performed +check function prusti_post_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.rs new file mode 100644 index 00000000000..59bba99aad8 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.rs @@ -0,0 +1,30 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + let mut yxe = Something::new(); + yxe.increment(); + println!("getting to the end"); +} + +#[derive(Clone)] +struct Something { + x: i32, +} + +impl Something { + pub fn new() -> Self { + Self { + x: 5, + } + } + + #[trusted] + // failing postcondition + #[insert_runtime_check] + #[ensures(old(self.x) == self.x)] + pub fn increment(&mut self) { + self.x += 1; + } +} diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.stderr new file mode 100644 index 00000000000..0a65b2d97ac --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/self-impl.rs:27:5: +Prusti Runtime Checks: Contract #[ensures(old(self.x) == self.x)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.stdout new file mode 100644 index 00000000000..c9214128642 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/self-impl.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_increment is performed diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.rs b/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.rs new file mode 100644 index 00000000000..4aeb0f67119 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.rs @@ -0,0 +1,21 @@ +//@run: 101 +use prusti_contracts::*; +#[derive(Clone)] +struct SomeStruct { + pub x: T +} + +type TypeAlias<'a, T> = &'a mut SomeStruct; + +fn main() { + let mut s1 = SomeStruct { x: 2}; + let mut s2 = SomeStruct { x: 3}; + foo(&mut s1, &mut s2); +} + +#[trusted] +#[insert_runtime_check] +#[ensures(old(x.x) == y.x)] +fn foo>(x: TypeAlias, y: TypeAlias) { + y.x = x.x + y.x; +} diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.stderr b/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.stderr new file mode 100644 index 00000000000..858c0467626 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/type-alias.rs:19:1: +Prusti Runtime Checks: Contract #[ensures(old(x.x) == y.x)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.stdout b/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.stdout new file mode 100644 index 00000000000..58140c90ea5 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/postconditions/type-alias.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.rs b/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.rs new file mode 100644 index 00000000000..dd214848638 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.rs @@ -0,0 +1,20 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(41, 42); // first contract fails, which should be checked second +} + +// This test is about checking if multiple precondition checks are +// properly chained. +// They are currently executed bottom up. If this ever breaks, maybe +// reorder the contracts, and make sure both checks are executed +// (meaning stdout file contains to check messages) +#[insert_runtime_check] +#[requires(x == 42)] +#[insert_runtime_check] +#[requires(y == 42)] +fn foo(x: i32, y: i32) { + +} diff --git a/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.stderr b/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.stderr new file mode 100644 index 00000000000..b66a37d3391 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/multiple.rs:18:1: +Prusti Runtime Checks: Contract #[requires(x == 42)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.stdout b/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.stdout new file mode 100644 index 00000000000..304645d659a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/preconditions/multiple.stdout @@ -0,0 +1,2 @@ +check function prusti_pre_check_item_foo is performed +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.rs b/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.rs new file mode 100644 index 00000000000..b614f4bb08d --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.rs @@ -0,0 +1,12 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(2); + foo(3); +} + +#[insert_runtime_check] +#[requires(_x % 2 == 0)] +fn foo(_x: i32) {} diff --git a/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.stderr b/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.stderr new file mode 100644 index 00000000000..975efb1ff26 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/pre-simple.rs:12:1: +Prusti Runtime Checks: Contract #[requires(_x % 2 == 0)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.stdout b/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.stdout new file mode 100644 index 00000000000..304645d659a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/preconditions/pre-simple.stdout @@ -0,0 +1,2 @@ +check function prusti_pre_check_item_foo is performed +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/abstract-error.rs b/prusti-tests/tests/runtime_checks/fail/predicates/abstract-error.rs new file mode 100644 index 00000000000..8ac22cd3915 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/abstract-error.rs @@ -0,0 +1,11 @@ +//@rustc-env: PRUSTI_QUIET=true +use prusti_contracts::*; + +// This test makes sure that trying to runtime check abstract predicates gives +// a proper error +predicate! { + #[insert_runtime_check] //~ERROR: Abstract predicates can not be runtime checked + fn some_abstract_predicate(x: i32) -> bool; +} + +fn main() {} diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/abstract-error.stderr b/prusti-tests/tests/runtime_checks/fail/predicates/abstract-error.stderr new file mode 100644 index 00000000000..2df9cabe83f --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/abstract-error.stderr @@ -0,0 +1,9 @@ +error: Abstract predicates can not be runtime checked + --> $DIR/abstract-error.rs:7:5 + | +7 | / #[insert_runtime_check] +8 | | fn some_abstract_predicate(x: i32) -> bool; + | |_______________________________________________^ + +error: aborting due to previous error + diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/check-error.rs b/prusti-tests/tests/runtime_checks/fail/predicates/check-error.rs new file mode 100644 index 00000000000..498858b5ee9 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/check-error.rs @@ -0,0 +1,27 @@ +//@rustc-env: PRUSTI_QUIET=true +use prusti_contracts::*; + +predicate! { + //#[insert_runtime_check] -> this would fix it + fn some_pred(x: i32) -> bool { + x == 42 + } +} + +// for predicates to be used in specifications that are checked at runtime, they need +// to be marked with #[insert_runtime_check] too, which is not the case here +#[insert_runtime_check] +#[requires(some_pred(_x))] //~ ERROR: Referring to predicate that is not runtime checkable +fn foo(_x: i32) {} + +#[trusted] +fn bar(x: i32) { + // same problem here + prusti_assert!(#[insert_runtime_check]some_pred(x)) + //~^ ERROR: Referring to predicate that is not runtime checkable +} + +fn main() { + bar(42); + foo(42); +} diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/check-error.stderr b/prusti-tests/tests/runtime_checks/fail/predicates/check-error.stderr new file mode 100644 index 00000000000..cee5da1aebe --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/check-error.stderr @@ -0,0 +1,30 @@ +error: [Prusti: invalid specification] Referring to predicate that is not runtime checkable in specifications that should be runtime checked is not allowed + --> $DIR/check-error.rs:14:12 + | +14 | #[requires(some_pred(_x))] + | ^^^^^^^^^ + | +note: this predicate can not be checked at runtime, make sure it has a body and mark it with #[insert_runtime_check] + --> $DIR/check-error.rs:6:5 + | +6 | / fn some_pred(x: i32) -> bool { +7 | | x == 42 +8 | | } + | |_____^ + +error: [Prusti: invalid specification] Referring to predicate that is not runtime checkable in specifications that should be runtime checked is not allowed + --> $DIR/check-error.rs:20:43 + | +20 | prusti_assert!(#[insert_runtime_check]some_pred(x)) + | ^^^^^^^^^ + | +note: this predicate can not be checked at runtime, make sure it has a body and mark it with #[insert_runtime_check] + --> $DIR/check-error.rs:6:5 + | +6 | / fn some_pred(x: i32) -> bool { +7 | | x == 42 +8 | | } + | |_____^ + +error: aborting due to 2 previous errors + diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/nested-predicate.rs b/prusti-tests/tests/runtime_checks/fail/predicates/nested-predicate.rs new file mode 100644 index 00000000000..f09cf2d7a6d --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/nested-predicate.rs @@ -0,0 +1,20 @@ +//@compile-flags: -Pquiet=true +use prusti_contracts::*; + +// For this predicate to be checkable at runtime, the one it calls would also have to +// be checkable. +predicate!{ + #[insert_runtime_check] + fn not_even(x: i32) -> bool { + !even(x) //~ ERROR: Referring to predicate that is not runtime checkable + } +} + +predicate!{ + // #[insert_runtime_check] // this would fix it + fn even(x: i32) -> bool { + x % 2 == 0 + } +} + +fn main() {} diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/nested-predicate.stderr b/prusti-tests/tests/runtime_checks/fail/predicates/nested-predicate.stderr new file mode 100644 index 00000000000..855cc5bcda5 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/nested-predicate.stderr @@ -0,0 +1,16 @@ +error: [Prusti: invalid specification] Referring to predicate that is not runtime checkable in specifications that should be runtime checked is not allowed + --> $DIR/nested-predicate.rs:9:10 + | +9 | !even(x) + | ^^^^ + | +note: this predicate can not be checked at runtime, make sure it has a body and mark it with #[insert_runtime_check] + --> $DIR/nested-predicate.rs:15:5 + | +15 | / fn even(x: i32) -> bool { +16 | | x % 2 == 0 +17 | | } + | |_____^ + +error: aborting due to previous error + diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.rs b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.rs new file mode 100644 index 00000000000..c2563b18453 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.rs @@ -0,0 +1,21 @@ +//@run: 101 +use prusti_contracts::*; + +// This test has 2 purposes: +// 1. Prusti's specification checker makes sure that predicates are not called from +// normal user code. With runtime checks this got a bit more complicated, since now +// predicates can (sometimes, after ast rewriting) be called from within user code. +// But then they're in a block starting with a #[check_only] marked closure. This test +// makes sure prusti properly recognizes this block +// 2. It also checks that the runtime check is properly executed and inserted +predicate!{ + #[insert_runtime_check] + fn even(x: i32) -> bool { + x % 2 == 0 + } +} + +#[trusted] +fn main() { + prusti_assert!(#[insert_runtime_check] even(3)); +} diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.stderr b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.stderr new file mode 100644 index 00000000000..a5633d9d21a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/predicate-assert.rs:20:44: +Prusti Runtime Checks: Contract prusti_assert!(even(3)) was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.stdout b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.stdout new file mode 100644 index 00000000000..7987b8b66ae --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-assert.stdout @@ -0,0 +1 @@ +predicate even is executed diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.rs b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.rs new file mode 100644 index 00000000000..2461500505c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.rs @@ -0,0 +1,18 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(42); + foo(41); +} +predicate! { + #[insert_runtime_check] + fn is_even(x: i32) -> bool { + x % 2 == 0 + } +} + +#[insert_runtime_check] +#[requires(is_even(x))] +fn foo(x: i32) {} diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.stderr b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.stderr new file mode 100644 index 00000000000..57e48651988 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/predicate-pre.rs:18:1: +Prusti Runtime Checks: Contract #[requires(is_even(x))] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.stdout b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.stdout new file mode 100644 index 00000000000..f2dcde62818 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/predicates/predicate-pre.stdout @@ -0,0 +1,4 @@ +check function prusti_pre_check_item_foo is performed +predicate is_even is executed +check function prusti_pre_check_item_foo is performed +predicate is_even is executed diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.rs b/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.rs new file mode 100644 index 00000000000..2cbff3ba201 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.rs @@ -0,0 +1,53 @@ +//@run: 101 +use prusti_contracts::*; + +#[derive(Clone)] +struct VecWrapper { + content: Vec, +} + +impl VecWrapper { + #[trusted] + #[insert_runtime_check] + #[ensures(result.len() == 8)] + pub fn new() -> Self { + Self { + content: vec![1,2,3,4,5,6,7,8], + } + } + + #[pure] + #[trusted] + #[insert_runtime_check] + #[requires(i < self.len())] + pub fn lookup(&self, i: usize) -> i32 { + *self.content.get(i).unwrap() + } + + #[pure] + #[trusted] + pub fn len(&self) -> usize { + self.content.len() + } + + #[trusted] + #[insert_runtime_check] + #[requires(i < self.len())] + #[insert_runtime_check] + #[ensures(old(self.len()) == self.len())] + // failing quantifier: all elements stay untouched + #[insert_runtime_check] + #[ensures(forall(|j: usize| (j < self.len()) ==> self.lookup(j) == old(self.lookup(j))))] + pub fn set(&mut self, i: usize, x: i32) { + self.content[i] = x; + } +} + +#[trusted] +fn main() { + let mut vec = VecWrapper::new(); + vec.set(4, 55); + println!("executed"); + +} + diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.stderr b/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.stderr new file mode 100644 index 00000000000..13c16c59f15 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.stderr @@ -0,0 +1,4 @@ +thread 'main' panicked at $DIR/exists-old.rs:41:5: +Prusti Runtime Checks: Contract #[ensures(forall(|j: usize| (j < self.len()) ==> self.lookup(j) == old(self.lookup(j))))] was violated at runtime + > expression (j < self.len()) ==> self.lookup(j) == old(self.lookup(j)) was violated for index j=4 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.stdout b/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.stdout new file mode 100644 index 00000000000..d87aca787bb --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/exists-old.stdout @@ -0,0 +1,13 @@ +check function prusti_post_check_item_new is performed +check function prusti_pre_check_item_set is performed +check function prusti_post_check_item_set is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.rs b/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.rs new file mode 100644 index 00000000000..5211c325dbc --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.rs @@ -0,0 +1,19 @@ +//@run: 101 +use prusti_contracts::*; + +// here the sum can reach 42 and violate this precondition +#[trusted] +#[insert_runtime_check] +#[requires( + forall( + #[runtime_quantifier_bounds(0..=20, 12..=22)] + |x: usize, y: usize| (x <= 20) && (y <= 22) && y >= 12 ==> x + y <= 41 + ) +)] +fn bar() {} + +#[trusted] +fn main() { + bar(); + println!("program intact"); +} diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.stderr b/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.stderr new file mode 100644 index 00000000000..d3aff2d57bb --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.stderr @@ -0,0 +1,8 @@ +thread 'main' panicked at $DIR/manual_bounds.rs:13:1: +Prusti Runtime Checks: Contract #[requires(forall( + #[runtime_quantifier_bounds(0..=20, 12..=22)] + |x: usize, y: usize| (x <= 20) && (y <= 22) && y >= 12 ==> x + y <= 41 + ))] was violated at runtime + > expression (x <= 20) && (y <= 22) && y >= 12 ==> x + y <= 41 was violated for index y=22 + > expression #[trusted] was violated for index x=20 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.stdout b/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.stdout new file mode 100644 index 00000000000..6f489c7402e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/manual_bounds.stdout @@ -0,0 +1 @@ +check function prusti_pre_check_item_bar is performed diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.rs b/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.rs new file mode 100644 index 00000000000..dbfb565a8e3 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.rs @@ -0,0 +1,15 @@ +//@run: 101 +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(400); +} + +// failing quantifier, because z + x is never 0, comparison +// would have to be z >= -x +#[insert_runtime_check] +#[requires(forall(|y: i32| (y >= 0 && y <= x) ==> exists( + |z: i32| z > -x && z <= 0 ==> z + y == 0) +))] +fn foo(x: i32) {} diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.stderr b/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.stderr new file mode 100644 index 00000000000..4673b6d5601 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.stderr @@ -0,0 +1,7 @@ +thread 'main' panicked at $DIR/nested.rs:15:1: +Prusti Runtime Checks: Contract #[requires(forall(|y: i32| (y >= 0 && y <= x) ==> exists( + |z: i32| z > -x && z <= 0 ==> z + y == 0) +))] was violated at runtime + > expression (y >= 0 && y <= x) ==> exists( + |z: i32| z > -x && z <= 0 ==> z + y == 0) was violated for index y=400 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.stdout b/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.stdout new file mode 100644 index 00000000000..408e13fdcbc --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/nested.stdout @@ -0,0 +1 @@ +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.rs b/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.rs new file mode 100644 index 00000000000..e93e039d6fd --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.rs @@ -0,0 +1,11 @@ +//@run:101 +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(30, 25); +} + +#[insert_runtime_check] +#[requires(a % 2 == 0 && forall(|x: u8| a + b + x <= 100 && a + b - x > 50))] +fn foo(a: u8, b: u8) {} diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.stderr b/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.stderr new file mode 100644 index 00000000000..618c1a571ed --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.stderr @@ -0,0 +1,5 @@ +thread 'main' panicked at $DIR/precise-info.rs:11:1: +Prusti Runtime Checks: Contract #[requires(a % 2 == 0 && forall(|x: u8| a + b + x <= 100 && a + b - x > 50))] was violated at runtime + > expression a + b - x > 50 was violated. + > expression a + b + x <= 100 && a + b - x > 50 was violated for index x=5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.stdout b/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.stdout new file mode 100644 index 00000000000..408e13fdcbc --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/quantifiers/precise-info.stdout @@ -0,0 +1 @@ +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/fail/refined/shape.rs b/prusti-tests/tests/runtime_checks/fail/refined/shape.rs new file mode 100644 index 00000000000..4d7d8cba0b0 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/refined/shape.rs @@ -0,0 +1,39 @@ +//@run: 101 +use prusti_contracts::*; + +trait Shape { + type T; + fn area(&self) -> Self::T; +} + +struct Rectangle { + height: T, + width: T, +} + +impl Shape for Rectangle +where + T: std::ops::Mul + Copy, +{ + type T = T; + fn area(&self) -> T { + self.height * self.width + } +} + +#[trusted] +#[insert_runtime_check] +#[refine_spec(where S: PartialOrd, [ + ensures(result.1 >= result.0) +])] +fn area_pair(a: &dyn Shape, b: &dyn Shape) -> (S, S) { + (a.area(), b.area()) +} + + +#[trusted] +fn main() { + let r1 = Rectangle { height: 6, width: 10}; + let r2 = Rectangle { height: 5, width: 10}; + area_pair(&r1, &r2); +} diff --git a/prusti-tests/tests/runtime_checks/fail/refined/shape.stderr b/prusti-tests/tests/runtime_checks/fail/refined/shape.stderr new file mode 100644 index 00000000000..2acf3a92b20 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/refined/shape.stderr @@ -0,0 +1,3 @@ +thread 'main' panicked at $DIR/shape.rs:29:1: +Prusti Runtime Checks: Contract #[ensures(result.1 >= result.0)] was violated at runtime +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/prusti-tests/tests/runtime_checks/fail/refined/shape.stdout b/prusti-tests/tests/runtime_checks/fail/refined/shape.stdout new file mode 100644 index 00000000000..791e6c72f09 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/fail/refined/shape.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_area_pair is performed diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/assert-old.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/assert-old.rs new file mode 100644 index 00000000000..8f28e123693 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/assert-old.rs @@ -0,0 +1,13 @@ +//@run +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(&mut 42); +} + +#[trusted] +fn foo(x: &mut i32) { + *x = 1; + prusti_assert!(old(*x) == 42); +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/forall.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/forall.rs new file mode 100644 index 00000000000..c56bf48252e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/forall.rs @@ -0,0 +1,8 @@ +//@run +use prusti_contracts::*; +#[trusted] +fn main() { + let v = vec![1,2,3,4,5,6]; + // success, all elements of v are smaller-eq than 6 + prusti_assume!(forall(|x: usize| x < 6 ==> v[x] <= 6)); +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/invariant-old.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/invariant-old.rs new file mode 100644 index 00000000000..812a033abac --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/invariant-old.rs @@ -0,0 +1,16 @@ +//@run +use prusti_contracts::*; + +fn main() { + mul(&mut 50); +} + +#[trusted] +fn mul(x: &mut i32) { + let mut res = 0; + while *x > 0 { + body_invariant!(old(*x) * 5 == res + *x * 5); + res = res + 5; + *x = *x-1; + } +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/invariant.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/invariant.rs new file mode 100644 index 00000000000..8dacc70a620 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/invariant.rs @@ -0,0 +1,13 @@ +//@run +use prusti_contracts::*; + +#[trusted] +fn main() { + let mut x = 10; + let mut y = 0; + while x > 0 { + body_invariant!(x + y == 10); + x = x - 1; + y = y + 1; + } +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-locals.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-locals.rs new file mode 100644 index 00000000000..143a7be847c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-locals.rs @@ -0,0 +1,27 @@ +//@run +use prusti_contracts::*; + +fn main() { + test1(&mut 1); + test2(&mut 10); + test3(&mut 5); +} + +#[trusted] +fn test1(x: &mut i32) { + *x = 2; + let a = x; + prusti_assert!(old(*a) == 2); +} + +#[trusted] +fn test2(x: &mut i32) { + *x = 1; + prusti_assert!({let a = x; old(*a) == 1}); +} + +#[trusted] +fn test3(x: &mut i32) { + *x = 1; + prusti_assert!(old({let a = x; *a == 5})); +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-move-struct.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-move-struct.rs new file mode 100644 index 00000000000..8a1c8001ea8 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-move-struct.rs @@ -0,0 +1,17 @@ +//@run +use prusti_contracts::*; + +#[derive(Clone)] +struct SomeStruct(i32); + +fn main() { + let s = SomeStruct(42); + foo(s); +} + +#[trusted] +fn foo(mut s: SomeStruct) { + s.0 = 52; + // fails because evaluated in old state + prusti_assert!({ old(s.0) == 42}); +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-move.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-move.rs new file mode 100644 index 00000000000..dc3021ca0be --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/old-move.rs @@ -0,0 +1,14 @@ +//@run +use prusti_contracts::*; + +fn main() { + foo(43); +} + +#[trusted] +fn foo(mut x: i32) { + x = 50; + prusti_assert!(x == 50); + // this one fails because evaluated in old state! + prusti_assert!(old(x == 43)); +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/sanity.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/sanity.rs new file mode 100644 index 00000000000..02316a3c3d4 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/sanity.rs @@ -0,0 +1,7 @@ +//@run +use prusti_contracts::*; + +fn main() { + prusti_assume!(true); + prusti_assert!(true); +} diff --git a/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/tuple.rs b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/tuple.rs new file mode 100644 index 00000000000..55d9d848225 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/assert-assume-invariant/tuple.rs @@ -0,0 +1,13 @@ +//@run +use prusti_contracts::*; + +fn main() { + bar((6,4)); +} + +#[trusted] +fn bar(mut x: (i32, i32)) { + x.0 = 10; + x.1 = 20; + prusti_assert!(old(x.0 + x.1) == 10) +} diff --git a/prusti-tests/tests/runtime_checks/pass/compilation/warn-unsupported-snapeq.rs b/prusti-tests/tests/runtime_checks/pass/compilation/warn-unsupported-snapeq.rs new file mode 100644 index 00000000000..210b74926ab --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/compilation/warn-unsupported-snapeq.rs @@ -0,0 +1,24 @@ +//@compile-flags: -Pcheck_overflows=false -Phide_uuids=true -Pquiet=true +//@check-pass +use prusti_contracts::*; + +#[trusted] +fn main() { + let s1 = S { x: 1 }; + let s2 = S { x: 1 }; + foo(s1, s2); +} + +// this should generate a warning, since snapshot equality currently +// is not checked correctly at runtime. Unfortunately the emitted +// warning is currently not properly parsed by ui_test +// if annotated with a comment. +#[insert_runtime_check] +#[requires(x === y)] +fn foo(x: S, y: S) -> i32 { + x.x + y.x +} + +struct S { + x: i32, +} diff --git a/prusti-tests/tests/runtime_checks/pass/compilation/warn-unsupported-snapeq.stderr b/prusti-tests/tests/runtime_checks/pass/compilation/warn-unsupported-snapeq.stderr new file mode 100644 index 00000000000..dfab8794380 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/compilation/warn-unsupported-snapeq.stderr @@ -0,0 +1,8 @@ +warning: Feature snapshot_equality(& (x), & (y)) is not supported for runtime checks, behavior at runtime might be arbitrary + --> $DIR/warn-unsupported-snapeq.rs:17:12 + | +17 | #[requires(x === y)] + | ^^^^^^^ + +warning: 1 warning emitted + diff --git a/prusti-tests/tests/runtime_checks/pass/extern_specs/option.rs b/prusti-tests/tests/runtime_checks/pass/extern_specs/option.rs new file mode 100644 index 00000000000..f31b797e30b --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/extern_specs/option.rs @@ -0,0 +1,26 @@ +//@run +use prusti_contracts::*; + +#[extern_spec] +impl std::option::Option { + #[pure] + #[insert_runtime_check] + #[ensures(matches!(*self, Some(_)) == result)] + pub fn is_some(&self) -> bool; + + #[pure] + #[insert_runtime_check] + #[ensures(self.is_some() == !result)] + pub fn is_none(&self) -> bool; + + #[trusted] + #[insert_runtime_check] + #[requires(self.is_some())] + pub fn unwrap(self) -> T; +} + +#[trusted] +fn main() { + let x = Some(5); + x.unwrap(); +} diff --git a/prusti-tests/tests/runtime_checks/pass/extern_specs/option.stdout b/prusti-tests/tests/runtime_checks/pass/extern_specs/option.stdout new file mode 100644 index 00000000000..5fbe6ad6ebf --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/extern_specs/option.stdout @@ -0,0 +1,2 @@ +check function prusti_pre_check_item_unwrap is performed +check function prusti_post_check_item_is_some is performed diff --git a/prusti-tests/tests/runtime_checks/pass/pledges/conditional.rs b/prusti-tests/tests/runtime_checks/pass/pledges/conditional.rs new file mode 100644 index 00000000000..ebdb9f9a762 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/pledges/conditional.rs @@ -0,0 +1,44 @@ +//@run +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 >= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result >= 100, p.0 >= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut_other(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn main() { + // this one passes because index_mut is called + foo(true); +} + +#[trusted] +fn foo(b: bool) { + let mut p = Percentage(100); + let r = if b { + index_mut(&mut p) + } else { + index_mut_other(&mut p) + }; + // this is only a "valid" assignment if foo is called with + // b = true + *r = 72; +} diff --git a/prusti-tests/tests/runtime_checks/pass/pledges/conditional.stdout b/prusti-tests/tests/runtime_checks/pass/pledges/conditional.stdout new file mode 100644 index 00000000000..d4c3a8faf7f --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/pledges/conditional.stdout @@ -0,0 +1,4 @@ +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed +check function prusti_pledge_check_item_index_mut is performed diff --git a/prusti-tests/tests/runtime_checks/pass/pledges/percentage.rs b/prusti-tests/tests/runtime_checks/pass/pledges/percentage.rs new file mode 100644 index 00000000000..a488aa0c048 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/pledges/percentage.rs @@ -0,0 +1,34 @@ +//@run +use prusti_contracts::*; +#[derive(Clone)] +struct Percentage(usize); + +#[trusted] +#[insert_runtime_check] +#[requires(p.0 <= 100)] +#[insert_runtime_check] +#[assert_on_expiry(*result <= 100, p.0 <= 100)] +#[insert_runtime_check] +#[ensures(old(p.0) == p.0)] +fn index_mut(p: &mut Percentage) -> &mut usize { + &mut p.0 +} + +fn main() { + // this one passes because *r will be set to 72 + foo(true); + foo(false); +} + +#[trusted] +fn foo(b: bool) { + let mut p = Percentage(50); + let r = index_mut(&mut p); + if b { + *r = 72; + } else { + *r = 68; + p.0 = 43 + } + p.0 = 32; +} diff --git a/prusti-tests/tests/runtime_checks/pass/pledges/percentage.stdout b/prusti-tests/tests/runtime_checks/pass/pledges/percentage.stdout new file mode 100644 index 00000000000..dc8816b68f5 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/pledges/percentage.stdout @@ -0,0 +1,8 @@ +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed +check function prusti_pledge_check_item_index_mut is performed +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pledge_lhs_check_item_index_mut is performed +check function prusti_pledge_check_item_index_mut is performed diff --git a/prusti-tests/tests/runtime_checks/pass/pledges/vecwrapper.rs b/prusti-tests/tests/runtime_checks/pass/pledges/vecwrapper.rs new file mode 100644 index 00000000000..db957848cd7 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/pledges/vecwrapper.rs @@ -0,0 +1,66 @@ +//@run +use prusti_contracts::*; + +#[derive(Clone)] +pub struct VecWrapperI32 { + pub v: Vec +} + +impl VecWrapperI32 { + #[trusted] + #[insert_runtime_check] + #[ensures(result.len() == 5)] + pub fn new() -> Self { + Self { + v: vec![1,2,3,4,5], + } + } + + #[trusted] + #[pure] + pub fn len(&self) -> usize { + self.v.len() + } + + /// A ghost function for specifying values stored in the vector. + #[trusted] + #[pure] + #[insert_runtime_check] + #[requires(index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.v[index] + } + + #[trusted] + pub fn insert(&mut self, value: i32) { + self.v.push(value); + } + + #[trusted] + #[insert_runtime_check] + #[requires(index < self.len())] + #[insert_runtime_check] + #[ensures(*result == old(self.lookup(index)))] + // failing pledge: the quantifier iterates over all elements + // including the one that can be changed. + #[insert_runtime_check] + #[after_expiry( + self.len() == old(self.len()) && + self.lookup(index) == before_expiry(*result) && + forall( + |i: usize| (i < self.len()) && i != index ==> + self.lookup(i) == old(self.lookup(i)) + ) + )] + pub fn index_mut(&mut self, index: usize) -> &mut i32 { + self.v.get_mut(index).unwrap() + } +} + +fn main() { + let mut vw = VecWrapperI32::new(); + let x = vw.index_mut(3); + *x = 42; + vw.insert(50) +} + diff --git a/prusti-tests/tests/runtime_checks/pass/pledges/vecwrapper.stdout b/prusti-tests/tests/runtime_checks/pass/pledges/vecwrapper.stdout new file mode 100644 index 00000000000..593cbcc951e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/pledges/vecwrapper.stdout @@ -0,0 +1,14 @@ +check function prusti_post_check_item_new is performed +check function prusti_pre_check_item_index_mut is performed +check function prusti_post_check_item_index_mut is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pledge_check_item_index_mut is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/custom-clone.rs b/prusti-tests/tests/runtime_checks/pass/postconditions/custom-clone.rs new file mode 100644 index 00000000000..cd5e5d9579c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/custom-clone.rs @@ -0,0 +1,53 @@ +//@run +use prusti_contracts::*; + +struct SomeStruct { + pub v: Vec, +} + +impl SomeStruct { + #[pure] + #[trusted] + pub fn len(&self) -> usize { + self.v.len() + } + + #[pure] + #[trusted] + #[insert_runtime_check] + #[requires(el < self.len())] + pub fn lookup(&self, el: usize) -> i32 { + self.v[el] + } + + #[trusted] + #[insert_runtime_check] + #[ensures(old(self.len()) + 1 == self.len())] + #[insert_runtime_check] + #[ensures(self.lookup(self.len() - 1) == el)] + pub fn push(&mut self, el: i32) { + self.v.push(el); + } +} + +// a custom clone implementation that is not +// actually used in our code, but will be used +// to check old expressions +impl Clone for SomeStruct { + #[trusted] + fn clone(&self) -> SomeStruct { + println!("custom clone is called"); + SomeStruct { + v: self.v.clone(), + } + } +} + +#[trusted] +fn main() { + let mut s = SomeStruct { + v: vec![1,2,3,4], + }; + s.push(5); +} + diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/custom-clone.stdout b/prusti-tests/tests/runtime_checks/pass/postconditions/custom-clone.stdout new file mode 100644 index 00000000000..75f212c2c57 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/custom-clone.stdout @@ -0,0 +1,4 @@ +custom clone is called +check function prusti_post_check_item_push is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_post_check_item_push is performed diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/generic-old.rs b/prusti-tests/tests/runtime_checks/pass/postconditions/generic-old.rs new file mode 100644 index 00000000000..af3d1f30e00 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/generic-old.rs @@ -0,0 +1,26 @@ +//@run +use prusti_contracts::*; + +fn main() { + let mut x = GenericStruct { x: 40}; + let mut y = GenericStruct { x: 32}; + let c = x.stuff(&mut y); +} + +#[derive(Clone)] +struct GenericStruct { + pub x: T, +} + +impl> GenericStruct { + #[trusted] + // fails because of the +1 + #[insert_runtime_check] + #[ensures(self.x == old(other.x))] + #[insert_runtime_check] + #[ensures(other.x == old(self.x))] + pub fn stuff(&mut self, other: &mut Self) { + std::mem::swap(&mut self.x, &mut other.x); + } +} + diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/generic-old.stdout b/prusti-tests/tests/runtime_checks/pass/postconditions/generic-old.stdout new file mode 100644 index 00000000000..ac1c314e027 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/generic-old.stdout @@ -0,0 +1,2 @@ +check function prusti_post_check_item_stuff is performed +check function prusti_post_check_item_stuff is performed diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/generic.rs b/prusti-tests/tests/runtime_checks/pass/postconditions/generic.rs new file mode 100644 index 00000000000..72132724306 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/generic.rs @@ -0,0 +1,25 @@ +//@run +use prusti_contracts::*; + +fn main() { + let a: i32 = 50; + let x = GenericStruct { x: a}; + let y = GenericStruct { x: a}; + let c = x.stuff(&y); +} + +#[derive(Clone)] +struct GenericStruct { + pub x: T, +} + +impl GenericStruct { + #[trusted] + // fails because of the +1 + #[insert_runtime_check] + #[ensures(result == self.x + other.x)] + pub fn stuff(&self, other: &Self) -> i32 { + self.x + other.x + } +} + diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/generic.stdout b/prusti-tests/tests/runtime_checks/pass/postconditions/generic.stdout new file mode 100644 index 00000000000..a8f6389766e --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/generic.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_stuff is performed diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/old-non-ref.rs b/prusti-tests/tests/runtime_checks/pass/postconditions/old-non-ref.rs new file mode 100644 index 00000000000..5f1624de031 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/old-non-ref.rs @@ -0,0 +1,24 @@ +//@run +//@compile-flags: -Pcheck_overflows=false +use prusti_contracts::*; + +#[derive(Clone)] +struct Something { + pub field: i32, +} + +// even though x.field is modified, since it not a reference +// if it occurrs in specifications it will be evaluated in +// its old state anyways +#[insert_runtime_check] +#[ensures(old(x.field) == x.field)] +fn nonsense(mut x: Something) { + x.field = x.field + 4; +} + +fn main() { + let s = Something { + field: 1, + }; + nonsense(s); +} diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/old-non-ref.stdout b/prusti-tests/tests/runtime_checks/pass/postconditions/old-non-ref.stdout new file mode 100644 index 00000000000..ff9419f1223 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/old-non-ref.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_nonsense is performed diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/self-impl.rs b/prusti-tests/tests/runtime_checks/pass/postconditions/self-impl.rs new file mode 100644 index 00000000000..98808d5b25a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/self-impl.rs @@ -0,0 +1,29 @@ +//@run +use prusti_contracts::*; + +#[trusted] +fn main() { + let mut yxe = Something::new(); + yxe.increment(); + println!("getting to the end"); +} + +#[derive(Clone)] +struct Something { + x: i32, +} + +impl Something { + pub fn new() -> Self { + Self { + x: 5, + } + } + + #[trusted] + #[insert_runtime_check] + #[ensures(old(self.x) + 1 == self.x)] + pub fn increment(&mut self) { + self.x += 1; + } +} diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/self-impl.stdout b/prusti-tests/tests/runtime_checks/pass/postconditions/self-impl.stdout new file mode 100644 index 00000000000..c675e33be1c --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/self-impl.stdout @@ -0,0 +1,2 @@ +check function prusti_post_check_item_increment is performed +getting to the end diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/type-alias.rs b/prusti-tests/tests/runtime_checks/pass/postconditions/type-alias.rs new file mode 100644 index 00000000000..d49a41923a6 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/type-alias.rs @@ -0,0 +1,21 @@ +//@run +use prusti_contracts::*; +#[derive(Clone)] +struct SomeStruct { + pub x: T +} + +type TypeAlias<'a, T> = &'a mut SomeStruct; + +fn main() { + let mut s1 = SomeStruct { x: 2}; + let mut s2 = SomeStruct { x: 3}; + foo(&mut s1, &mut s2); +} + +#[trusted] +#[insert_runtime_check] +#[ensures(old(x.x) == y.x)] +fn foo(x: TypeAlias, y: TypeAlias) { + y.x = x.x; +} diff --git a/prusti-tests/tests/runtime_checks/pass/postconditions/type-alias.stdout b/prusti-tests/tests/runtime_checks/pass/postconditions/type-alias.stdout new file mode 100644 index 00000000000..58140c90ea5 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/postconditions/type-alias.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/pass/preconditions/multiple.rs b/prusti-tests/tests/runtime_checks/pass/preconditions/multiple.rs new file mode 100644 index 00000000000..a726d71f466 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/preconditions/multiple.rs @@ -0,0 +1,18 @@ +//@run +use prusti_contracts::*; + +#[trusted] +fn main() { + foo(42, 42); +} + +// This test is about checking if multiple precondition checks are +// properly chained. +// They are currently executed bottom up. If this ever breaks, maybe +// reorder the contracts, and make sure both checks are executed +// (meaning stdout file contains to check messages) +#[insert_runtime_check] +#[requires(x == 42)] +#[insert_runtime_check] +#[requires(y == 42)] +fn foo(x: i32, y: i32) {} diff --git a/prusti-tests/tests/runtime_checks/pass/preconditions/multiple.stdout b/prusti-tests/tests/runtime_checks/pass/preconditions/multiple.stdout new file mode 100644 index 00000000000..304645d659a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/preconditions/multiple.stdout @@ -0,0 +1,2 @@ +check function prusti_pre_check_item_foo is performed +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-minimal-2.rs b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-minimal-2.rs new file mode 100644 index 00000000000..ae41e4fb8c7 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-minimal-2.rs @@ -0,0 +1,11 @@ +//@run +use prusti_contracts::*; + +#[insert_runtime_check] +#[requires(exists(|x: i8| x <= i8::MAX ==> x == i8::MAX))] +fn foo() {} + +#[trusted] +fn main() { + foo(); +} diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-minimal-2.stdout b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-minimal-2.stdout new file mode 100644 index 00000000000..408e13fdcbc --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-minimal-2.stdout @@ -0,0 +1 @@ +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-old.rs b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-old.rs new file mode 100644 index 00000000000..fffa6c0991a --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-old.rs @@ -0,0 +1,56 @@ +//@run +use prusti_contracts::*; + +#[derive(Clone)] +struct VecWrapper { + content: Vec, +} + +impl VecWrapper { + #[trusted] + #[insert_runtime_check] + #[ensures(result.len() == 8)] + pub fn new() -> Self { + Self { + content: vec![1,2,3,4,5,6,7,8], + } + } + + #[pure] + #[trusted] + #[insert_runtime_check] + #[requires(i < self.len())] + pub fn lookup(&self, i: usize) -> i32 { + *self.content.get(i).unwrap() + } + + #[pure] + #[trusted] + pub fn len(&self) -> usize { + self.content.len() + } + + #[trusted] + #[insert_runtime_check] + #[requires(i < self.len())] + #[insert_runtime_check] + #[ensures(old(self.len()) == self.len())] + // if index is not i, element stays untouched + #[insert_runtime_check] + #[ensures(forall(|j: usize| (j < self.len()) && j!=i ==> self.lookup(j) == old(self.lookup(j))))] + // there is now an element with value x + #[insert_runtime_check] + #[ensures(exists(|j: usize| (j < self.len()) ==> self.lookup(j) == x))] + pub fn set(&mut self, i: usize, x: i32) { + self.content[i] = x; + } +} + +#[trusted] +fn main() { + let mut vec = VecWrapper::new(); + vec.set(4, 55); + println!("executed"); + +} + diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-old.stdout b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-old.stdout new file mode 100644 index 00000000000..92a18e5578f --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/exists-old.stdout @@ -0,0 +1,25 @@ +check function prusti_post_check_item_new is performed +check function prusti_pre_check_item_set is performed +check function prusti_post_check_item_set is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_post_check_item_set is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_pre_check_item_lookup is performed +check function prusti_post_check_item_set is performed +executed diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/forall-minimal.rs b/prusti-tests/tests/runtime_checks/pass/quantifiers/forall-minimal.rs new file mode 100644 index 00000000000..d2570b73541 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/forall-minimal.rs @@ -0,0 +1,10 @@ +//@run +use prusti_contracts::*; + +#[insert_runtime_check] +#[requires(forall(|x: usize| (x < 20) ==> true))] +fn foo() {} + +fn main() { + foo(); +} diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/forall-minimal.stdout b/prusti-tests/tests/runtime_checks/pass/quantifiers/forall-minimal.stdout new file mode 100644 index 00000000000..408e13fdcbc --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/forall-minimal.stdout @@ -0,0 +1 @@ +check function prusti_pre_check_item_foo is performed diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/manual_bounds.rs b/prusti-tests/tests/runtime_checks/pass/quantifiers/manual_bounds.rs new file mode 100644 index 00000000000..c70160f61a4 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/manual_bounds.rs @@ -0,0 +1,26 @@ +//@run +use prusti_contracts::*; + +#[insert_runtime_check] +#[requires( + forall( + |x: usize| (x < 20) ==> true + ) +)] +fn foo() {} + +#[insert_runtime_check] +#[requires( + forall( + #[runtime_quantifier_bounds(0..20, 12..=22)] + |x: usize, y: usize| (x < 20) && (y <= 22) && y >= 12 ==> x + y <= 41 + ) +)] +fn bar() {} + +#[trusted] +fn main() { + foo(); + bar(); + println!("program intact"); +} diff --git a/prusti-tests/tests/runtime_checks/pass/quantifiers/manual_bounds.stdout b/prusti-tests/tests/runtime_checks/pass/quantifiers/manual_bounds.stdout new file mode 100644 index 00000000000..8cd65f300d0 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/quantifiers/manual_bounds.stdout @@ -0,0 +1,3 @@ +check function prusti_pre_check_item_foo is performed +check function prusti_pre_check_item_bar is performed +program intact diff --git a/prusti-tests/tests/runtime_checks/pass/refined/shape.rs b/prusti-tests/tests/runtime_checks/pass/refined/shape.rs new file mode 100644 index 00000000000..0509e14c753 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/refined/shape.rs @@ -0,0 +1,40 @@ +//@run +use prusti_contracts::*; + +trait Shape { + type T; + fn area(&self) -> Self::T; +} + +struct Rectangle { + height: T, + width: T, +} + +impl Shape for Rectangle +where + T: std::ops::Mul + Copy, +{ + type T = T; + fn area(&self) -> T { + self.height * self.width + } +} + +#[trusted] +#[insert_runtime_check] +#[refine_spec(where S: PartialOrd, [ + ensures(result.1 >= result.0) +])] +fn area_pair(a: &dyn Shape, b: &dyn Shape) -> (S, S) { + (a.area(), b.area()) +} + + +#[trusted] +fn main() { + let r1 = Rectangle { height: 6, width: 10}; + let r2 = Rectangle { height: 5, width: 10}; + // larger one is passed first -> fails! + area_pair(&r2, &r1); +} diff --git a/prusti-tests/tests/runtime_checks/pass/refined/shape.stdout b/prusti-tests/tests/runtime_checks/pass/refined/shape.stdout new file mode 100644 index 00000000000..791e6c72f09 --- /dev/null +++ b/prusti-tests/tests/runtime_checks/pass/refined/shape.stdout @@ -0,0 +1 @@ +check function prusti_post_check_item_area_pair is performed diff --git a/prusti-tests/tests/runtimetest.rs b/prusti-tests/tests/runtimetest.rs new file mode 100644 index 00000000000..ad39c9cc2ea --- /dev/null +++ b/prusti-tests/tests/runtimetest.rs @@ -0,0 +1,132 @@ +#![feature(custom_test_frameworks)] +#![test_runner(test_runner)] + +use prusti_server::spawn_server_thread; +use std::{env, num::NonZeroUsize, path::PathBuf}; +use ui_test::*; + +// copied from compiletest. Maybe create a module with common utilities? +fn find_prusti_rustc_path() -> PathBuf { + let target_directory = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + let executable_name = if cfg!(windows) { + "prusti-rustc.exe" + } else { + "prusti-rustc" + }; + let local_prusti_rustc_path: PathBuf = ["target", target_directory, executable_name] + .iter() + .collect(); + if local_prusti_rustc_path.exists() { + return local_prusti_rustc_path; + } + let workspace_prusti_rustc_path: PathBuf = ["..", "target", target_directory, executable_name] + .iter() + .collect(); + if workspace_prusti_rustc_path.exists() { + return workspace_prusti_rustc_path; + } + panic!( + "Could not find the {target_directory:?} prusti-rustc binary to be used in tests. \ + It might be that Prusti has not been compiled correctly." + ); +} + +enum TestKind { + RustcRuntimeChecks, + RustcDeadCodeRemoval, +} + +impl TestKind { + fn build_config(&self, group_name: &str) -> Config { + let path: PathBuf = ["tests", group_name].iter().collect(); + let prusti_path: PathBuf = find_prusti_rustc_path(); + let mut prusti_cmd = CommandBuilder { + out_dir_flag: Some("--out-dir".into()), + args: vec!["--error-format=json".into()], + ..CommandBuilder::cmd(prusti_path) + }; + + match self { + Self::RustcRuntimeChecks => { + prusti_cmd + .envs + .push(("PRUSTI_FULL_COMPILATION".into(), Some("true".into()))); + prusti_cmd.envs.push(( + "PRUSTI_INSERT_RUNTIME_CHECKS".into(), + Some("selective".into()), + )); + prusti_cmd + .envs + .push(("PRUSTI_DEBUG_RUNTIME_CHECKS".into(), Some("true".into()))); + } + Self::RustcDeadCodeRemoval => { + prusti_cmd + .envs + .push(("PRUSTI_FULL_COMPILATION".into(), Some("true".into()))); + prusti_cmd + .envs + .push(("PRUSTI_REMOVE_DEAD_CODE".into(), Some("true".into()))); + } + } + + let mut config = Config { + program: prusti_cmd, + edition: Some("2018".to_string()), + ..Config::rustc(path) + }; + // this filters out the uuid's for the printed messages that tell us + // which tests are performed. + config.stdout_filter(r"_([0-9, a-z]+) is performed", " is performed"); + if std::env::var_os("BLESS").is_some() { + println!("It's a blessing :)"); + config.output_conflict_handling = OutputConflictHandling::Bless + } + config + } +} + +fn compile_and_run(group_name: &str, filter: &Option, test_kind: TestKind) { + let config = test_kind.build_config(group_name); + + // multiple threads lead to problems atm. If we want multiple threads, all + // files need unique names. + let mut args = Args::test().unwrap(); + args.threads = NonZeroUsize::new(1).unwrap(); + let _ = run_tests_generic( + vec![config], + args, + move |path, args, config| { + filter + .as_ref() + .map(|filter| path.display().to_string().contains(filter.as_str())) + .unwrap_or(true) + && default_file_filter(path, args, config) + }, + default_per_file_config, + status_emitter::Text::verbose(), + ); +} + +fn test_runner(_tests: &[&()]) { + let server_address = spawn_server_thread(); + env::set_var("PRUSTI_SERVER_ADDRESS", server_address.to_string()); + + let filter = std::env::args().nth(1); + println!("filter: {:?}", filter); + // Cargo tests don't work at the moment + // compile_and_run( + // &"runtime_checks_cargo", + // &filter, + // TestKind::CargoRuntimeChecks, + // ); + compile_and_run(&"runtime_checks", &filter, TestKind::RustcRuntimeChecks); + compile_and_run( + &"mir_optimizations", + &filter, + TestKind::RustcDeadCodeRemoval, + ); +} diff --git a/prusti-tests/tests/verify/ui/forall_verify.stderr b/prusti-tests/tests/verify/ui/forall_verify.stderr index 63d52476fb4..c65a4cf40e7 100644 --- a/prusti-tests/tests/verify/ui/forall_verify.stderr +++ b/prusti-tests/tests/verify/ui/forall_verify.stderr @@ -2,7 +2,7 @@ error: [Prusti: verification error] postcondition might not hold. --> $DIR/forall_verify.rs:18:11 | 18 | #[ensures(forall(|x: i32| identity(x) == x + 1))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: the error originates here --> $DIR/forall_verify.rs:19:1 diff --git a/prusti-tests/tests/verify/ui/predicate.stdout b/prusti-tests/tests/verify/ui/predicate.stdout index 1164fc9e071..8302974e170 100644 --- a/prusti-tests/tests/verify/ui/predicate.stdout +++ b/prusti-tests/tests/verify/ui/predicate.stdout @@ -41,7 +41,7 @@ fn prusti_pred_item_true_p1_$(NUM_UUID)() -> bool { }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn true_p1() -> bool { @@ -62,7 +62,7 @@ fn prusti_pred_item_true_p2_$(NUM_UUID)() -> bool { }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn true_p2() -> bool { @@ -84,7 +84,7 @@ fn prusti_pred_item_forall_identity_$(NUM_UUID)() }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn forall_identity() -> bool { @@ -107,7 +107,7 @@ fn prusti_pred_item_exists_identity_$(NUM_UUID)() }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn exists_identity() -> bool { @@ -168,7 +168,7 @@ fn prusti_pred_item_false_p_$(NUM_UUID)() -> bool { let prusti_result: bool = { false }; prusti_result } -#[allow(unused_must_use, unused_variables, dead_code)] +#[allow(unused_must_use, unused_variables, dead_code, forgetting_copy_types)] #[prusti::pred_spec_id_ref = "$(NUM_UUID)"] #[prusti::specs_version = $(SPECS_VERSION)] fn false_p() -> bool { diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index b137f929c6e..6f66bd35698 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -146,6 +146,10 @@ lazy_static::lazy_static! { settings.set_default("cargo_path", "cargo").unwrap(); settings.set_default("cargo_command", "check").unwrap(); + settings.set_default("insert_runtime_checks", "never").unwrap(); + settings.set_default("debug_runtime_checks", "false").unwrap(); + settings.set_default("remove_dead_code", "false").unwrap(); + // Flags for testing. settings.set_default::>("verification_deadline", None).unwrap(); settings.set_default("use_smt_wrapper", false).unwrap(); @@ -1030,3 +1034,27 @@ pub fn enable_type_invariants() -> bool { pub fn num_errors_per_function() -> u32 { read_setting("num_errors_per_function") } + +/// Whether checks for contracts should be inserted into the generated +/// executables +/// Options: +/// * `never`: don't check any contracts, even if annotated +/// * `selective`: check the contracts annotated with #[insert_runtime_check] attribute +/// * `all`: check all contracts. Note: if any contract contains unsupported features +/// this will fail. +/// for methods marked with #[insert_runtime_check] +pub fn insert_runtime_checks() -> String { + read_setting("insert_runtime_checks") +} + +/// When enabled, runtime check will print messages to stdout +/// when they are executed. Used to check that they are +/// properly inserted / performed, for example for tests +pub fn debug_runtime_checks() -> bool { + read_setting("debug_runtime_checks") +} + +/// Try to use verification to identify unused blocks and eliminate them +pub fn remove_dead_code() -> bool { + read_setting("remove_dead_code") +} diff --git a/prusti-utils/src/launch/mod.rs b/prusti-utils/src/launch/mod.rs index c546e107c2c..96bb51e704e 100644 --- a/prusti-utils/src/launch/mod.rs +++ b/prusti-utils/src/launch/mod.rs @@ -328,7 +328,7 @@ fn classify_line(line: &str) -> Option { let cname = line.next()?; // We want to allow having crates which enable the `prusti` feature, thus anything that // depends on them need not add `prusti-contracts` as a direct dependency. - if cname != "prusti-contracts" || feats != "[]" { + if cname != "prusti-contracts" && !feats.contains("prusti") { return None; } depth.parse().ok() diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 53d5e0e6c3d..c8787f48a3e 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -8,10 +8,11 @@ use std::fmt::Debug; use vir_crate::polymorphic::Position; use rustc_hash::FxHashMap; -use prusti_rustc_interface::span::source_map::SourceMap; +use prusti_rustc_interface::span::{source_map::SourceMap, def_id::DefId}; +use prusti_rustc_interface::middle::mir::BasicBlock; use prusti_rustc_interface::errors::MultiSpan; use viper::VerificationError; -use prusti_interface::PrustiError; +use prusti_interface::{PrustiError, globals}; use log::debug; use super::PositionManager; use prusti_interface::data::ProcedureDefId; @@ -99,10 +100,19 @@ pub enum ErrorCtxt { /// Finding the value of the termination measure at the begin of a method unexpectedly caused an error UnexpectedAssignMethodTerminationMeasure, /// A Viper `assert false` that encodes the failure (panic) of an `assert` Rust terminator - /// Arguments: the message of the Rust assertion - AssertTerminator(String), + /// Arguments: + /// * the message of the Rust assertion + /// * 2 fields to identify this assertion in the MIR for optimizations + /// * bool whether to ignore this error (meaning not report it to user, + /// because it's only required for optimizations) + AssertTerminator(String, DefId, BasicBlock, bool), + /// A Viper `assert false` in the context of a bounds check in specifications + BoundsCheckAssertSpec, /// A Viper `assert false` in the context of a bounds check - BoundsCheckAssert, + /// Arguments: + /// * 2 fields to identify this assertion in the MIR for optimizations + /// * bool whether to ignore this error (meaning not report it to user, + BoundsCheckAssert(DefId, BasicBlock, bool), /// A Viper `assert false` in the context of a hardcoded bounds check (e.g. when we hardcode a `index`) /// TODO: remove this in favor of extern_spec for e.g. the stdlib `fn index(...)` SliceRangeBoundsCheckAssert(String), @@ -188,6 +198,7 @@ pub enum ErrorCtxt { /// The state that fold-unfold algorithm deduced as unreachable, is actually /// reachable. UnreachableFoldingState, + InsertedReachabilityRefutation(DefId, BasicBlock) } /// The error manager @@ -385,9 +396,14 @@ impl<'tcx> ErrorManager<'tcx> { .set_failing_assertion(opt_cause_span) } - ("assert.failed:assertion.false", ErrorCtxt::AssertTerminator(ref message)) => { - PrustiError::verification(format!("assertion might fail with \"{message}\""), error_span) - .set_failing_assertion(opt_cause_span) + ("assert.failed:assertion.false", ErrorCtxt::AssertTerminator(ref message, def_id, bb, ignore)) => { + if *ignore { + globals::set_assertion_violated(*def_id, *bb); + PrustiError::ignore_verification("failing assertion used for mir optimization, bug if seen by a user", error_span) + } else { + PrustiError::verification(format!("assertion might fail with \"{message}\""), error_span) + .set_failing_assertion(opt_cause_span) + } } ("assert.failed:assertion.false", ErrorCtxt::AbortTerminator) => { @@ -540,6 +556,7 @@ impl<'tcx> ErrorManager<'tcx> { "application.precondition:assertion.false", ErrorCtxt::PureFunctionAssertTerminator(ref message), ) => { + PrustiError::disabled_verification( format!("assertion might fail with \"{message}\""), error_span @@ -607,8 +624,21 @@ impl<'tcx> ErrorManager<'tcx> { .set_help("The implemented method's postcondition should imply the trait's postcondition.") } - ("assert.failed:assertion.false", ErrorCtxt::BoundsCheckAssert) | - ("application.precondition:assertion.false", ErrorCtxt::BoundsCheckAssert) => { + ("assert.failed:assertion.false", ErrorCtxt::BoundsCheckAssert(def_id, bb, ignore)) => { + if *ignore { + globals::set_assertion_violated(*def_id, *bb); + PrustiError::ignore_verification( + "an assertion failed but is only relevant for optimizations. Bug if seen by a user".to_string(), + error_span + ) + } else { + PrustiError::verification( + "the array or slice index may be out of bounds".to_string(), + error_span, + ).set_failing_assertion(opt_cause_span) + } + } + ("application.precondition:assertion.false", ErrorCtxt::BoundsCheckAssertSpec) => { PrustiError::verification( "the array or slice index may be out of bounds".to_string(), error_span, @@ -704,6 +734,13 @@ impl<'tcx> ErrorManager<'tcx> { error_span, ) } + (_err_id, ErrorCtxt::InsertedReachabilityRefutation(def_id, bb)) => { + // this location is reachable. Mark it: + globals::set_block_unreachable(*def_id, *bb); + // return a cancelled error, because these should not cause + // a failure + PrustiError::ignore_verification("Inserted assertion is reachable", error_span) + } (full_err_id, ErrorCtxt::Unexpected) => { PrustiError::internal( diff --git a/prusti-viper/src/encoder/mir/places/interface.rs b/prusti-viper/src/encoder/mir/places/interface.rs index 80e2e901785..9ef7ecadbdf 100644 --- a/prusti-viper/src/encoder/mir/places/interface.rs +++ b/prusti-viper/src/encoder/mir/places/interface.rs @@ -327,7 +327,8 @@ impl<'v, 'tcx: 'v> PlacesEncoderInterface<'tcx> for super::super::super::Encoder if !matches!( op, mir::BinOp::Add | mir::BinOp::Sub | mir::BinOp::Mul | mir::BinOp::Shl | mir::BinOp::Shr - ) || !prusti_common::config::check_overflows() + ) || (!prusti_common::config::check_overflows() + && !prusti_common::config::remove_dead_code()) { Ok(false.into()) } else { diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs b/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs index 7a552096133..bc70a463d31 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs @@ -24,14 +24,17 @@ use crate::encoder::{ }; use log::debug; use prusti_common::config; -use prusti_interface::environment::{ - debug_utils::to_text::ToText, - mir_analyses::{ - allocation::{compute_definitely_allocated, DefinitelyAllocatedAnalysisResult}, - initialization::{compute_definitely_initialized, DefinitelyInitializedAnalysisResult}, +use prusti_interface::{ + environment::{ + debug_utils::to_text::ToText, + mir_analyses::{ + allocation::{compute_definitely_allocated, DefinitelyAllocatedAnalysisResult}, + initialization::{compute_definitely_initialized, DefinitelyInitializedAnalysisResult}, + }, + mir_body::borrowck::{facts::RichLocation, lifetimes::Lifetimes}, + Procedure, }, - mir_body::borrowck::{facts::RichLocation, lifetimes::Lifetimes}, - Procedure, + globals, }; use prusti_rustc_interface::{ abi::FieldIdx, @@ -1261,6 +1264,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { msg, *target, *unwind, + location, )?, // TerminatorKind::Yield { .. } => { // graph.add_exit_edge(bb, "yield"); @@ -1818,6 +1822,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { msg: &mir::AssertMessage<'tcx>, target: mir::BasicBlock, unwind: mir::UnwindAction, + location: mir::Location, ) -> SpannedEncodingResult { let condition = self .encoder @@ -1830,18 +1835,35 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir_high::Expression::not(condition) }; + if config::remove_dead_code() { + globals::add_encoded_assertion(self.def_id, location.block); + } + let ignore = config::remove_dead_code() + && (!self.check_panics + || (!config::check_overflows() + && matches!( + msg, + mir::AssertKind::Overflow(..) | mir::AssertKind::OverflowNeg(..) + ))); + let (assert_msg, error_ctxt) = if let mir::AssertKind::BoundsCheck { .. } = msg { let mut s = String::new(); msg.fmt_assert_args(&mut s).unwrap(); - (s, ErrorCtxt::BoundsCheckAssert) + ( + s, + ErrorCtxt::BoundsCheckAssert(self.def_id, location.block, ignore), + ) } else { let assert_msg = msg.description().to_string(); - (assert_msg.clone(), ErrorCtxt::AssertTerminator(assert_msg)) + ( + assert_msg.clone(), + ErrorCtxt::AssertTerminator(assert_msg, self.def_id, location.block, ignore), + ) }; let target_label = self.encode_basic_block_label(target); block_builder.add_comment(format!("Rust assertion: {assert_msg}")); - if self.check_panics { + if self.check_panics || config::remove_dead_code() { block_builder.add_statement(self.encoder.set_statement_error_ctxt( vir_high::Statement::assert_no_pos(guard.clone()), span, diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs b/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs index c7912742909..d9dfd4255bb 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs @@ -1,6 +1,6 @@ use prusti_interface::environment::{ is_ghost_begin_marker, is_ghost_end_marker, is_loop_invariant_block, is_loop_variant_block, - is_marked_specification_block, EnvQuery, Procedure, + is_marked_specification_block, EnvQuery, Procedure, is_marked_check_block, }; use prusti_rustc_interface::{data_structures::graph::WithSuccessors, middle::mir}; use std::collections::{BTreeMap, BTreeSet}; @@ -38,10 +38,14 @@ impl SpecificationBlocks { ) -> Self { // Blocks that contain closures marked with `#[spec_only]` attributes. let mut marked_specification_blocks = BTreeSet::new(); + let mut check_blocks = BTreeSet::new(); for (bb, block) in body.basic_blocks.iter_enumerated() { if is_marked_specification_block(env_query, block) { marked_specification_blocks.insert(bb); } + if is_marked_check_block(env_query, block) { + check_blocks.insert(bb); + } } let mut specification_blocks = marked_specification_blocks; @@ -118,6 +122,7 @@ impl SpecificationBlocks { for successor in body.basic_blocks.successors(bb) { if specification_blocks.contains(&successor) && !loop_spec_blocks_flat.contains(&successor) + && !check_blocks.contains(&successor) // no entry blocks for check_blocks { specification_entry_blocks.insert(successor); } diff --git a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs index 5730657c396..9a62c6813b4 100644 --- a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs +++ b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs @@ -979,7 +979,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> }; let error_ctxt = if let box mir::AssertKind::BoundsCheck { .. } = msg { - ErrorCtxt::BoundsCheckAssert + ErrorCtxt::BoundsCheckAssertSpec } else { let assert_msg = msg.description().to_string(); ErrorCtxt::PureFunctionAssertTerminator(assert_msg) diff --git a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs index 8d20ad75dbb..22c457432b5 100644 --- a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs +++ b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_poly.rs @@ -735,7 +735,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> }; let error_ctxt = if let box mir::AssertKind::BoundsCheck { .. } = msg { - ErrorCtxt::BoundsCheckAssert + ErrorCtxt::BoundsCheckAssertSpec } else { let assert_msg = msg.description().to_string(); ErrorCtxt::PureFunctionAssertTerminator(assert_msg) diff --git a/prusti-viper/src/encoder/mir_encoder/mod.rs b/prusti-viper/src/encoder/mir_encoder/mod.rs index 3304c97ea9a..d9e5ce0a22b 100644 --- a/prusti-viper/src/encoder/mir_encoder/mod.rs +++ b/prusti-viper/src/encoder/mir_encoder/mod.rs @@ -552,7 +552,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> MirEncoder<'p, 'v, 'tcx> { right: vir::Expr, ty: ty::Ty<'tcx>, ) -> EncodingResult { - if !matches!(op, mir::BinOp::Add | mir::BinOp::Sub | mir::BinOp::Mul | mir::BinOp::Shl | mir::BinOp::Shr) || !config::check_overflows() { + if !matches!(op, mir::BinOp::Add | mir::BinOp::Sub | mir::BinOp::Mul | mir::BinOp::Shl | mir::BinOp::Shr) + || (!config::check_overflows() && !config::remove_dead_code()) { Ok(false.into()) } else { let result = self.encode_bin_op_expr(op, left, right.clone(), ty)?; diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index 1d4183c5198..598245fcd98 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -29,6 +29,7 @@ use prusti_common::{ vir::{ToGraphViz, fixes::fix_ghost_vars}, vir_local, vir_expr, vir_stmt }; +use prusti_interface::globals; use vir_crate::{ polymorphic::{ self as vir, @@ -591,6 +592,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { |writer| method_with_fold_unfold.to_graphviz(writer), ); } + // store polonius info for runtime checks: + globals::store_polonius_info(self.proc_def_id, self.polonius_info.take().unwrap()); Ok(method_with_fold_unfold) } @@ -1259,6 +1262,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); } + if config::remove_dead_code() && self.procedure.is_non_spec_switch_int_target(bbi) { + self.encode_reachability_refute(bbi, curr_block)?; + } self.encode_execution_flag(bbi, curr_block)?; let opt_successor = self.encode_block_statements(bbi, curr_block)?; let mir_successor: MirSuccessor = if let Some(successor) = opt_successor { @@ -1328,6 +1334,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(()) } + /// Insert a refute fals to deterimine rechability + fn encode_reachability_refute(&mut self, bbi: BasicBlockIndex, cfg_block: CfgBlockIndex) -> SpannedEncodingResult<()> { + let pos = self.register_error(self.mir_encoder.get_span_of_basic_block(bbi), ErrorCtxt::InsertedReachabilityRefutation(self.proc_def_id, bbi)); + globals::add_reachability_check(self.proc_def_id, bbi); + let false_const = vir::Expr::Const(vir::ConstExpr { + value: vir::Const::Bool(false), + position: pos + }); + self.cfg_method.add_stmt( + cfg_block, + vir::Stmt::Refute(vir::Refute { + expr: false_const, + position: pos + }) + ); + Ok(()) + } + /// Encode the statements of the block. /// In case of unsupported statements, this function will return `MirSuccessor::Kill`. #[tracing::instrument(level = "debug", skip(self))] @@ -2750,18 +2774,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir::Expr::not(cond_var.into()) }; + if config::remove_dead_code() { + globals::add_encoded_assertion(self.proc_def_id, location.block); + } + // whether the error generated here should be ignored, i.e. not reported + // to the user (because it was only inserted for optimization) + let ignore = config::remove_dead_code() + && (!self.check_panics + || (!config::check_overflows() + && matches!(msg, box mir::AssertKind::Overflow(..) | box mir::AssertKind::OverflowNeg(..)))); // Check or assume the assertion let (assert_msg, error_ctxt) = if let box mir::AssertKind::BoundsCheck { .. } = msg { let mut s = String::new(); msg.fmt_assert_args(&mut s).unwrap(); - (s, ErrorCtxt::BoundsCheckAssert) + (s, ErrorCtxt::BoundsCheckAssert(self.proc_def_id, location.block, ignore)) } else { let assert_msg = msg.description().to_string(); - (assert_msg.clone(), ErrorCtxt::AssertTerminator(assert_msg)) + (assert_msg.clone(), ErrorCtxt::AssertTerminator(assert_msg, self.proc_def_id, location.block, ignore)) }; stmts.push(vir::Stmt::comment(format!("Rust assertion: {assert_msg}"))); - if self.check_panics { + if self.check_panics || config::remove_dead_code() { stmts.push(vir::Stmt::Assert( vir::Assert { expr: viper_guard, position: self.register_error( diff --git a/prusti-viper/src/verifier.rs b/prusti-viper/src/verifier.rs index 019abb0ed40..0247de65bd5 100644 --- a/prusti-viper/src/verifier.rs +++ b/prusti-viper/src/verifier.rs @@ -146,6 +146,9 @@ impl<'v, 'tcx> Verifier<'v, 'tcx> { debug!("Verification error in {}: {:?}", method, verification_error); let mut prusti_error = error_manager.translate_verification_error(&verification_error); + if prusti_error.is_ignored() { + continue + } // annotate with counterexample, if requested if config::counterexample() { if config::unsafe_core_proof() { diff --git a/prusti/Cargo.toml b/prusti/Cargo.toml index 81900537208..3b5aad18817 100644 --- a/prusti/Cargo.toml +++ b/prusti/Cargo.toml @@ -21,6 +21,7 @@ lazy_static = "1.4.0" tracing = { path = "../tracing" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-chrome = "0.7" +rustc-hash = "1.1.0" [build-dependencies] chrono = { version = "0.4.22", default-features = false, features = ["clock"] } diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index b622a8b1ad0..c163d9a7aa5 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,22 +1,28 @@ -use crate::verifier::verify; +use crate::{modify_mir::mir_modify, verifier::verify}; + use prusti_common::config; use prusti_interface::{ environment::{mir_storage, Environment}, - specs::{self, cross_crate::CrossCrateSpecs, is_spec_fn}, + globals, + specs::{self, cross_crate::CrossCrateSpecs, is_spec_fn, typed::DefSpecificationMap}, }; use prusti_rustc_interface::{ borrowck::consumers, data_structures::steal::Steal, driver::Compilation, - hir::{def::DefKind, def_id::LocalDefId}, + hir::def::DefKind, index::IndexVec, - interface::{interface::Compiler, Config, Queries}, + interface::{interface::Compiler, Config, Queries, DEFAULT_QUERY_PROVIDERS}, middle::{ - mir::{self, BorrowCheckResult}, + mir::{self, BorrowCheckResult, MirPass, START_BLOCK}, query::{ExternProviders, Providers}, - ty::TyCtxt, + ty::{self, TyCtxt, TypeVisitableExt}, }, + mir_build, + mir_transform::{self, inline}, session::Session, + span::def_id::{DefId, LocalDefId, LOCAL_CRATE}, + trait_selection::traits, }; #[derive(Default)] @@ -72,6 +78,105 @@ fn mir_promoted<'tcx>( result } +/// a copy of the rust compilers implementation of this query + +/// + verification on its first call +/// + dead code elimination if enabled +/// + insertion of runtime checks if enabled +pub(crate) fn mir_drops_elaborated(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal> { + // run verification here, otherwise we can't rely on results in + // drops elaborated + let def_id = def.to_def_id(); + let default_query = DEFAULT_QUERY_PROVIDERS.mir_drops_elaborated_and_const_checked; + // For constant items, mir_drops_elaborated is constructed before mir_promoted + // is constructed for other items. If we go into verification at this point this + // will lead to problems. + if config::no_verify() || !is_non_const_function(tcx, def_id) || globals::get_check_error() { + return default_query(tcx, def); + } + if !globals::verified(def_id.krate) { + globals::set_verified(def_id.krate); + // make sure mir_promoted was created. Usually this is the case + // but in some testcases it wasn't + if let Ok((def_spec, env)) = obtain_specs(tcx, None) { + let env = verify(env, def_spec.clone()); + globals::store_spec_env(def_spec, env); + } else { + // dont continue verification or modifications because specifications were bad + // but can we trigger a proper error here? + // Because I (cedihegi) don't think we can, we set a global variable + // to skip all modifications and verification, until we run into the + // next callback, where the error will be triggered + globals::set_check_error(); + return default_query(tcx, def); + } + } + + // original compiler code: make sure it's up to date from time to time + // source: https://github.com/rust-lang/rust/blob/master/compiler/rustc_mir_transform/src/lib.rs + if tcx.sess.opts.unstable_opts.drop_tracking_mir + && let DefKind::Generator = tcx.def_kind(def) + { + tcx.ensure_with_value().mir_generator_witnesses(def); + } + let mir_borrowck = tcx.mir_borrowck(def); + + let is_fn_like = tcx.def_kind(def).is_fn_like(); + if is_fn_like { + // Do not compute the mir call graph without said call graph actually being used. + if inline::Inline.is_enabled(tcx.sess) { + tcx.ensure_with_value() + .mir_inliner_callees(ty::InstanceDef::Item(def.to_def_id())); + } + } + + let (body, _) = tcx.mir_promoted(def); + let mut body = body.steal(); + + // ################################################ + // Inserted Modifications + // ################################################ + let local_decls = body.local_decls.clone(); + if config::remove_dead_code() { + mir_modify::dead_code_elimination(tcx, &mut body, def_id); + } + if matches!( + config::insert_runtime_checks().as_str(), + "all" | "selective" + ) { + mir_modify::insert_runtime_checks(&mut body, def_id, tcx, &local_decls); + } + // ################################################ + // End of Modifications, back to original compiler + // code! + // ################################################ + + if let Some(error_reported) = mir_borrowck.tainted_by_errors { + body.tainted_by_errors = Some(error_reported); + } + let predicates = tcx + .predicates_of(body.source.def_id()) + .predicates + .iter() + .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); + if traits::impossible_predicates(tcx, traits::elaborate(tcx, predicates).collect()) { + // Clear the body to only contain a single `unreachable` statement. + let bbs = body.basic_blocks.as_mut(); + bbs.raw.truncate(1); + bbs[START_BLOCK].statements.clear(); + bbs[START_BLOCK].terminator_mut().kind = mir::TerminatorKind::Unreachable; + body.var_debug_info.clear(); + body.local_decls.raw.truncate(body.arg_count + 1); + } + + mir_transform::run_analysis_to_runtime_passes(tcx, &mut body); + + // Now that drop elaboration has been performed, we can check for + // unconditional drop recursion. + mir_build::lints::check_drop_recursion(tcx, &body); + + tcx.alloc_steal_mir(body) +} + impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { fn config(&mut self, config: &mut Config) { assert!(config.override_queries.is_none()); @@ -79,6 +184,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { |_session: &Session, providers: &mut Providers, _external: &mut ExternProviders| { providers.mir_borrowck = mir_borrowck; providers.mir_promoted = mir_promoted; + providers.mir_drops_elaborated_and_const_checked = mir_drops_elaborated; }, ); } @@ -139,28 +245,19 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { ) -> Compilation { compiler.session().abort_if_errors(); queries.global_ctxt().unwrap().enter(|tcx| { - let mut env = Environment::new(tcx, env!("CARGO_PKG_VERSION")); - let spec_checker = specs::checker::SpecChecker::new(); - spec_checker.check(&env); - compiler.session().abort_if_errors(); - - let hir = env.query.hir(); - let mut spec_collector = specs::SpecCollector::new(&mut env); - spec_collector.collect_specs(hir); - - let mut def_spec = spec_collector.build_def_specs(); - // Do print_typeckd_specs prior to importing cross crate - if config::print_typeckd_specs() { - for value in def_spec.all_values_debug(config::hide_uuids()) { - println!("{value}"); + // because of runtime checks, verification might already have happened in + // drops elaborated query. This avoids verifying multiple times. + if !globals::verified(LOCAL_CRATE) { + // already stops if we had an error + let (def_spec, env) = obtain_specs(tcx, Some(compiler)).unwrap(); + if !config::no_verify() { + let env = verify(env, def_spec.clone()); + + globals::set_verified(LOCAL_CRATE); + globals::store_spec_env(def_spec, env); } } - CrossCrateSpecs::import_export_cross_crate(&mut env, &mut def_spec); - if !config::no_verify() { - verify(env, def_spec); - } }); - compiler.session().abort_if_errors(); if config::full_compilation() { Compilation::Continue @@ -169,3 +266,35 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { } } } + +/// Check if a def_id belongs to a constant function +fn is_non_const_function(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) && !tcx.is_const_fn(def_id) +} + +pub fn obtain_specs<'tcx>( + tcx: TyCtxt<'tcx>, + compiler_opt: Option<&Compiler>, +) -> Result<(DefSpecificationMap, Environment<'tcx>), ()> { + let mut env = Environment::new(tcx, env!("CARGO_PKG_VERSION")); + + let spec_checker = specs::checker::SpecChecker::new(); + spec_checker.check(&env); + if let Some(compiler) = compiler_opt { + compiler.session().abort_if_errors(); + } else if env.diagnostic.has_errors() { + return Err(()); + } + let hir = env.query.hir(); + let mut spec_collector = specs::SpecCollector::new(&mut env); + spec_collector.collect_specs(hir); + + let mut def_spec = spec_collector.build_def_specs(); + if config::print_typeckd_specs() { + for value in def_spec.all_values_debug(config::hide_uuids()) { + println!("{value}"); + } + } + CrossCrateSpecs::import_export_cross_crate(&mut env, &mut def_spec); + Ok((def_spec, env)) +} diff --git a/prusti/src/driver.rs b/prusti/src/driver.rs index eb6744a4604..5a315eb0bf4 100644 --- a/prusti/src/driver.rs +++ b/prusti/src/driver.rs @@ -7,11 +7,14 @@ #![feature(rustc_private)] #![feature(proc_macro_internals)] #![feature(decl_macro)] +#![feature(box_patterns)] +#![feature(let_chains)] #![deny(unused_must_use)] mod arg_value; mod callbacks; mod verifier; +mod modify_mir; use arg_value::arg_value; use callbacks::PrustiCompilerCalls; @@ -185,6 +188,15 @@ fn main() { )); } + // these flags influence the behavior of the ast rewriter, where we can't use config + if config::debug_runtime_checks() { + std::env::set_var("PRUSTI_DEBUG_RUNTIME_CHECKS", "true"); + } + std::env::set_var( + "PRUSTI_INSERT_RUNTIME_CHECKS", + config::insert_runtime_checks(), + ); + let mut callbacks = PrustiCompilerCalls; driver::RunCompiler::new(&rustc_args, &mut callbacks).run() diff --git a/prusti/src/modify_mir/mir_helper.rs b/prusti/src/modify_mir/mir_helper.rs new file mode 100644 index 00000000000..dc8174da6dc --- /dev/null +++ b/prusti/src/modify_mir/mir_helper.rs @@ -0,0 +1,303 @@ +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{self, patch::MirPatch, visit::MutVisitor, Body, TerminatorKind}, + ty::{self, TyCtxt}, + }, + span::{self, def_id::DefId, DUMMY_SP}, +}; +use rustc_hash::FxHashMap; + +// A set of functions that are often used during mir modifications + +/// Check whether this local is an argument and mutable, or a mutable reference +pub fn is_mutable_arg( + body: &Body<'_>, + local: mir::Local, + local_decls: &IndexVec>, +) -> bool { + let args: Vec = body.args_iter().collect(); + if args.contains(&local) { + let local_decl = local_decls.get(local).unwrap(); + if local_decl.mutability == mir::Mutability::Mut { + return true; + } + matches!(local_decl.ty.ref_mutability(), Some(mir::Mutability::Mut)) + } else { + false + } +} + +pub fn fn_signature<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: DefId, + generics_opt: Option>, +) -> ty::FnSig<'tcx> { + let fn_sig_binder = if let Some(generics) = generics_opt { + tcx.fn_sig(def_id).instantiate(tcx, generics) + } else { + tcx.fn_sig(def_id).instantiate_identity() + }; + let param_env = tcx.param_env(def_id); + tcx.normalize_erasing_late_bound_regions(param_env, fn_sig_binder) +} + +pub fn dummy_source_info() -> mir::SourceInfo { + mir::SourceInfo { + span: span::DUMMY_SP, + scope: mir::SourceScope::from_usize(0), + } +} + +pub fn dummy_region(tcx: TyCtxt<'_>) -> ty::Region<'_> { + let kind = ty::RegionKind::ReErased; + ty::Region::new_from_kind(tcx, kind) +} + +pub fn unit_const(tcx: TyCtxt<'_>) -> mir::Operand<'_> { + let unit_ty = ty::Ty::new_unit(tcx); + let constant_kind = mir::ConstantKind::zero_sized(unit_ty); + let constant = mir::Constant { + span: DUMMY_SP, + user_ty: None, + literal: constant_kind, + }; + mir::Operand::Constant(Box::new(constant)) +} + +/// Creates an Rvalue containing a references to a local variable +pub fn rvalue_reference_to_local<'tcx>( + tcx: TyCtxt<'tcx>, + place: mir::Place<'tcx>, + mutable: bool, +) -> mir::Rvalue<'tcx> { + let dummy_region = dummy_region(tcx); + let borrow_kind = if mutable { + mir::BorrowKind::Mut { + kind: mir::MutBorrowKind::Default, + } + } else { + mir::BorrowKind::Shared + }; + mir::Rvalue::Ref( + dummy_region, + borrow_kind, + place, // the local to be borrowed + ) +} + +pub fn create_reference_type<'tcx>(tcx: TyCtxt<'tcx>, ty: ty::Ty<'tcx>) -> ty::Ty<'tcx> { + let mutability = mir::Mutability::Not; + let region = dummy_region(tcx); + ty::Ty::new_ref( + tcx, + region, + ty::TypeAndMut { + ty, + mutbl: mutability, + }, + ) +} + +/// Find the def_id of clone +pub fn get_clone_defid(tcx: TyCtxt<'_>) -> Option { + let trait_defid = tcx.lang_items().clone_trait()?; + tcx.associated_items(trait_defid) + .find_by_name_and_kind( + tcx, + span::symbol::Ident::from_str("clone"), + ty::AssocKind::Fn, + trait_defid, + ) + .map(|x| x.def_id) +} + +/// Given a function body, determine the set of arguments +pub fn args_from_body<'tcx>(body: &Body<'tcx>) -> Vec> { + let mut args = Vec::new(); + let caller_nr_args = body.arg_count; + // now the final mapping to operands: + for local in body.args_iter() { + let index = local.index(); + if index != 0 && index <= caller_nr_args { + args.push(mir::Operand::Copy(mir::Place { + local, + projection: ty::List::empty(), + })); + } + } + args +} + +/// Given a body, put a Goto block at the start. +pub fn prepend_dummy_block(body: &mut Body) -> mir::BasicBlock { + let mut patch = MirPatch::new(body); + let terminator_kind = mir::TerminatorKind::Goto { + target: mir::START_BLOCK, + }; + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: terminator_kind, + }; + let blockdata = mir::BasicBlockData::new(Some(terminator)); + let new_block_id = patch.new_block(blockdata); + patch.apply(body); + + body.basic_blocks_mut().swap(mir::START_BLOCK, new_block_id); + + // fix all terminators to point to correct block + for b in body.basic_blocks.as_mut().iter_mut() { + replace_outgoing_edges(b, mir::START_BLOCK, mir::BasicBlock::MAX); + replace_outgoing_edges(b, new_block_id, mir::START_BLOCK); + replace_outgoing_edges(b, mir::BasicBlock::MAX, new_block_id); + } + new_block_id +} + +// If we re-order the IndexVec containing the basic blocks, we will need to adjust +// some the basic blocks that terminators point to. This is what this function does +pub fn replace_outgoing_edges( + data: &mut mir::BasicBlockData, + from: mir::BasicBlock, + to: mir::BasicBlock, +) { + fn update_if_equals(dest: &mut T, from: T, to: T) { + if *dest == from { + *dest = to; + } + } + match &mut data.terminator_mut().kind { + TerminatorKind::Goto { target } => update_if_equals(target, from, to), + TerminatorKind::SwitchInt { targets, .. } => { + for bb1 in &mut targets.all_targets_mut().iter_mut() { + update_if_equals(bb1, from, to); + } + } + TerminatorKind::Call { target, unwind, .. } => { + if let Some(target) = target { + update_if_equals(target, from, to); + } + if let mir::UnwindAction::Cleanup(cleanup) = unwind { + update_if_equals(cleanup, from, to); + } + } + TerminatorKind::Assert { target, unwind, .. } + | TerminatorKind::Drop { target, unwind, .. } + | TerminatorKind::FalseUnwind { + real_target: target, + unwind, + } => { + update_if_equals(target, from, to); + if let mir::UnwindAction::Cleanup(bb) = unwind { + update_if_equals(bb, from, to); + } + } + TerminatorKind::InlineAsm { + destination, + unwind, + .. + } => { + // is this prettier? does this even modify the blockdata? + if let Some(bb) = destination { + update_if_equals(bb, from, to); + } + if let mir::UnwindAction::Cleanup(bb) = unwind { + update_if_equals(bb, from, to) + } + } + TerminatorKind::FalseEdge { + real_target, + imaginary_target, + } => { + update_if_equals(real_target, from, to); + update_if_equals(imaginary_target, from, to); + } + TerminatorKind::Yield { resume, drop, .. } => { + update_if_equals(resume, from, to); + if let Some(bb) = drop { + update_if_equals(bb, from, to); + } + } + TerminatorKind::UnwindResume + | TerminatorKind::UnwindTerminate(_) + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::GeneratorDrop => {} + } +} + +/// Given a call terminator, change it's target (the target is the basic +/// block the execution will return to, once the function is finished) +pub fn replace_call_target(terminator: &mut mir::Terminator, new_target: mir::BasicBlock) { + if let mir::TerminatorKind::Call { target, .. } = &mut terminator.kind { + *target = Some(new_target); + } +} + +pub struct ArgumentReplacer<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + args_to_replace: &'a FxHashMap, +} + +impl<'a, 'tcx> ArgumentReplacer<'a, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, args_to_replace: &'a FxHashMap) -> Self { + Self { + tcx, + args_to_replace, + } + } +} + +impl<'tcx, 'a> MutVisitor<'tcx> for ArgumentReplacer<'a, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_local( + &mut self, + local: &mut mir::Local, + context: mir::visit::PlaceContext, + _location: mir::Location, + ) { + if let Some(replace) = self.args_to_replace.get(local) { + assert!(!matches!(context, mir::visit::PlaceContext::NonUse(_))); + *local = *replace; + } + } +} + +// If this block is a goto block, and if it is returns its current target +pub fn get_goto_block_target(body: &Body<'_>, block: mir::BasicBlock) -> Option { + let terminator: &Option = &body.basic_blocks.get(block)?.terminator; + if let Some(mir::Terminator { + kind: mir::TerminatorKind::Goto { target }, + .. + }) = terminator + { + Some(*target) + } else { + None + } +} + +/// Get successor(s) of a mir Location +pub fn get_successors(body: &Body<'_>, location: mir::Location) -> Vec { + let statements_len = body[location.block].statements.len(); + if location.statement_index < statements_len { + vec![mir::Location { + statement_index: location.statement_index + 1, + ..location + }] + } else { + body[location.block] + .terminator + .as_ref() + .unwrap() + .successors() + .map(|block| mir::Location { + block, + statement_index: 0, + }) + .collect() + } +} diff --git a/prusti/src/modify_mir/mir_info_collector.rs b/prusti/src/modify_mir/mir_info_collector.rs new file mode 100644 index 00000000000..91357747c8a --- /dev/null +++ b/prusti/src/modify_mir/mir_info_collector.rs @@ -0,0 +1,343 @@ +use super::mir_helper::*; + +use prusti_interface::{ + environment::{blocks_dominated_by, is_check_closure, EnvQuery, Environment}, + globals, + specs::typed::DefSpecificationMap, +}; +use prusti_rustc_interface::{ + middle::{ + mir::{self, visit::Visitor}, + ty::TyCtxt, + }, + span::{def_id::DefId, Span}, +}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::hash::Hash; + +// Info about a specific MIR Body that can be collected before we +// actuallye start modifying it. +// Note that depending on the modifications we perform, some of the +// information (e.g. about blocks might no longer be accurate) +pub struct MirInfo<'tcx> { + pub def_id: DefId, + pub specs: DefSpecificationMap, + pub env: Environment<'tcx>, + /// function arguments that have to be cloned on entry of the function + pub args_to_be_cloned: FxHashSet, + /// statements in which we should replace the occurrence of a function + /// argument with their clone + pub stmts_to_substitute_rhs: FxHashSet, + /// the basic blocks that are marked with #[check_only] or dominated by a + /// block that is + pub check_blocks: FxHashSet, +} + +impl<'tcx> MirInfo<'tcx> { + /// Given a body, collect information about it. + pub fn collect_mir_info( + tcx: TyCtxt<'tcx>, + body: mir::Body<'tcx>, + def_id: DefId, + ) -> MirInfo<'tcx> { + let specs = globals::get_defspec(); + let env = globals::get_env(); + let check_blocks = collect_check_blocks(tcx, &body); + let (args_to_be_cloned, stmts_to_substitute_rhs) = + determine_modifications_old_resolution(tcx, body, &check_blocks); + MirInfo { + def_id, + specs, + env, + args_to_be_cloned, + stmts_to_substitute_rhs, + check_blocks, + } + } + // when MirInfo is no longer required, put specs and env back into global statics + // (because we only want to / can compute them once) + pub fn store_specs_env(self) { + let MirInfo { specs, env, .. } = self; + globals::store_spec_env(specs, env); + } +} + +/// Figures out which arguments need to be cloned, and in which locations +/// arguments need to be replaced with their clones +fn determine_modifications_old_resolution<'tcx>( + tcx: TyCtxt<'tcx>, + body: mir::Body<'tcx>, + check_blocks: &FxHashSet, +) -> (FxHashSet, FxHashSet) { + // find_old_spans.. + let (old_spans, old_args) = find_old_spans_and_args(tcx, &body, check_blocks); + + let mut args_to_clone = FxHashSet::::default(); + let mut stmts_to_adjust = FxHashSet::::default(); + let mut encountered = FxHashSet::::default(); + + let mut visitor = DependencyCollector { + old_spans, + locals_dependencies: Default::default(), + assignment_locations: Default::default(), + body: body.clone(), + }; + visitor.visit_body(&body); + + for old_arg in old_args.iter() { + // For each old argument, try to figure out on which function arguments + // it depends on, and in which locations of the MIR these function arguments + // need to be replaced with their old values + let mut to_process = vec![*old_arg]; + while let Some(local) = to_process.pop() { + let deps = visitor.locals_dependencies.get(&local).unwrap(); + // all the locations this variable has been assigned to + let assignment_locations = visitor + .assignment_locations + .get(&local) + .cloned() + .unwrap_or_default(); + deps.iter().for_each(|dep| { + if dep.is_mutable_arg { + // the case where we find a dependency on a function argument, meaning + // at this location the function argument will have to be replaced with + // an old clone + // 1. mark this function argument to be cloned + args_to_clone.insert(dep.local); + // 2. In the locations where this local is assigned to, we will have to + // replace occurrences of this function argument on the rhs with the old clone + assignment_locations.iter().for_each(|location| { + stmts_to_adjust.insert(*location); + }); + } else if (!dep.is_user_declared || dep.declared_within_old) + && !encountered.contains(&dep.local) + { + // process this variable too! + to_process.push(dep.local); + encountered.insert(dep.local); + } + }); + } + } + (args_to_clone, stmts_to_adjust) +} + +// A MIR Visitor that collects information before we actually start modifying +// the MIR. It's responsibilities are: +// - finding function arguments that need to be cloned +// - finding basic-blocks, that can contain old expressions that should be +// resolved (check_only blocks) +struct DependencyCollector<'tcx> { + old_spans: Vec, + /// dependencies between locals, for each local get a list of other locals + /// that it depends on + locals_dependencies: FxHashMap>, + /// locations where we assign values to locals: + assignment_locations: FxHashMap>, + /// a body of the copy + body: mir::Body<'tcx>, +} + +#[derive(Hash, Clone, Debug, PartialEq, Eq)] +struct Dependency { + local: mir::Local, + is_user_declared: bool, + declared_within_old: bool, + is_mutable_arg: bool, +} + +impl<'tcx> DependencyCollector<'tcx> { + // determine all the relevant facts about this local + fn create_dependency(&self, local: mir::Local) -> Dependency { + let local_decl = self.body.local_decls.get(local); + + // calling is_user_variable directly leads to panics for certain variables.. + let is_user_declared = if let Some(local_decl) = local_decl { + matches!(local_decl.local_info.as_ref(), mir::ClearCrossCrate::Set(_)) + && local_decl.is_user_variable() + } else { + false + }; + // if a variable is not user declared this doesn't matter + let declared_within_old = is_user_declared + && self + .old_spans + .iter() + .any(|old_span| old_span.contains(local_decl.unwrap().source_info.span)); + let is_mutable_arg = is_mutable_arg(&self.body, local, &self.body.local_decls); + Dependency { + local, + is_user_declared, + declared_within_old, + is_mutable_arg, + } + } +} + +impl<'tcx> Visitor<'tcx> for DependencyCollector<'tcx> { + fn visit_statement(&mut self, statement: &mir::Statement<'tcx>, location: mir::Location) { + self.super_statement(statement, location); + if let mir::StatementKind::Assign(box (recv, rvalue)) = &statement.kind { + // store this location as one where we assign to this local + self.assignment_locations + .entry(recv.local) + .or_default() + .insert(location); + // collect all locals contained in rhs rvalue and add them as dependencies + let dependencies = rvalue_dependencies(rvalue, location); + dependencies.iter().for_each(|local| { + let dep = self.create_dependency(*local); + self.locals_dependencies + .entry(recv.local) + .or_default() + .insert(dep); + }); + } + } + + fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: mir::Location) { + self.super_terminator(terminator, location); + // find calls and their dependencies: + if let mir::TerminatorKind::Call { + args, destination, .. + } = &terminator.kind + { + // Store this location as one where we assign to this local + self.assignment_locations + .entry(destination.local) + .or_default() + .insert(location); + // Add each argument as a dependency for the lefhandside of this call + args.iter().for_each(|arg| { + if let mir::Operand::Move(place) | mir::Operand::Copy(place) = arg { + let dep = self.create_dependency(place.local); + self.locals_dependencies + .entry(destination.local) + .or_default() + .insert(dep); + } + }); + } + } +} + +fn rvalue_dependencies(rvalue: &mir::Rvalue<'_>, location: mir::Location) -> Vec { + struct RvalueVisitor { + pub dependencies: Vec, + } + impl<'tcx> Visitor<'tcx> for RvalueVisitor { + fn visit_local( + &mut self, + local: mir::Local, + _context: mir::visit::PlaceContext, + _location: mir::Location, + ) { + self.dependencies.push(local); + } + } + let mut visitor = RvalueVisitor { + dependencies: vec![], + }; + visitor.visit_rvalue(rvalue, location); + visitor.dependencies +} + +fn find_old_spans_and_args<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + check_blocks: &FxHashSet, +) -> (Vec, FxHashSet) { + struct OldSpanFinder<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + old_spans: Vec, + old_args: FxHashSet, + check_blocks: &'a FxHashSet, + } + // spans of old calls need to be resolved first, so we can determine + // whether locals are defined inside them later. + impl<'tcx, 'a> Visitor<'tcx> for OldSpanFinder<'tcx, 'a> { + fn visit_terminator( + &mut self, + terminator: &mir::Terminator<'tcx>, + location: mir::Location, + ) { + self.super_terminator(terminator, location); + if let mir::TerminatorKind::Call { + func, + args, + fn_span, + .. + } = &terminator.kind + { + if let Some((call_id, _)) = func.const_fn_def() { + let item_name = self.tcx.def_path_str(call_id); + if item_name == "prusti_contracts::old" + && self.check_blocks.contains(&location.block) + { + self.old_spans.push(*fn_span); + assert!(args.len() == 1); + if let mir::Operand::Copy(place) | mir::Operand::Move(place) = + args.get(0).unwrap() + { + self.old_args.insert(place.local); + } + } + } + } + } + } + let mut finder = OldSpanFinder { + tcx, + old_spans: Default::default(), + old_args: Default::default(), + check_blocks, + }; + finder.visit_body(body); + (finder.old_spans, finder.old_args) +} + +/// Figure out which of the blocks of this body contain a closure marked with +/// #[check_only] or are dominated by such a block +pub fn collect_check_blocks<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, +) -> FxHashSet { + let env_query = EnvQuery::new(tcx); + let mut marked_check_blocks = FxHashSet::default(); + for (bb, bb_data) in body.basic_blocks.iter_enumerated() { + if is_check_block(env_query, bb_data) { + marked_check_blocks.insert(bb); + } + } + // all the blocks that are dominated by one of these check blocks, are check + // blocks of the same kind too. No two blocks should be dominated by + // more than one block containing such a closure. + let mut check_blocks = marked_check_blocks.clone(); + for bb in marked_check_blocks { + let dominated_blocks = blocks_dominated_by(body, bb); + for bb_dominated in dominated_blocks { + if bb_dominated != bb { + check_blocks.insert(bb_dominated); + } + } + } + check_blocks +} + +/// Goes through the statements of a block, and looks for a closure that is +/// annotated with #[check_only], marking the blocks that start a runtime +/// check for things like prusti_assert, prusti_assume, and loop_invariant +fn is_check_block(env_query: EnvQuery<'_>, bb_data: &mir::BasicBlockData) -> bool { + for stmt in &bb_data.statements { + if let mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Aggregate(box mir::AggregateKind::Closure(def_id, _), _), + )) = stmt.kind + { + if is_check_closure(env_query, def_id) { + return true; + } + } + } + false +} diff --git a/prusti/src/modify_mir/mir_modifications.rs b/prusti/src/modify_mir/mir_modifications.rs new file mode 100644 index 00000000000..284ff3f54d6 --- /dev/null +++ b/prusti/src/modify_mir/mir_modifications.rs @@ -0,0 +1,439 @@ +use super::{mir_helper::*, passes::PledgeToProcess}; + +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{ + self, patch::MirPatch, BasicBlock, BasicBlockData, Constant, Operand, Place, + Terminator, TerminatorKind, + }, + ty::{self, TyCtxt, TyKind}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; + +use std::{cell::RefMut, u128}; + +/// A general set of operations that are used to modify the MIR +pub trait MirModifier<'tcx> { + // to be implemented! + fn tcx(&self) -> TyCtxt<'tcx>; + fn patcher(&self) -> RefMut>; + fn def_id(&self) -> DefId; + fn local_decls(&self) -> &IndexVec>; + + fn prepend_call( + &self, + fn_id: DefId, + caller_block: BasicBlock, + args: Vec>, + generics: ty::GenericArgsRef<'tcx>, + terminator: Terminator<'tcx>, + dest_place: Place<'tcx>, + ) -> BasicBlock { + // get the bodies of the store and check function + let new_block_data = BasicBlockData::new(Some(terminator)); + let new_block = self.patcher().new_block(new_block_data); + + let func_ty = self.tcx().mk_ty_from_kind(TyKind::FnDef(fn_id, generics)); + let func = Operand::Constant(Box::new(Constant { + span: DUMMY_SP, + user_ty: None, + literal: mir::ConstantKind::zero_sized(func_ty), + })); + + let call_terminator = TerminatorKind::Call { + func, + args: args.clone(), + destination: dest_place, + target: Some(new_block), + // is terminating on unwind sometimes not actually what we want? + unwind: mir::UnwindAction::Continue, + call_source: mir::CallSource::Normal, + fn_span: DUMMY_SP, + }; + self.patcher() + .patch_terminator(caller_block, call_terminator); + new_block + } + + // given a set of arguments, and the type that the old tuple should have + // create a chain of basic blocks that clones each of the arguments + // and puts them into a tuple + fn prepend_old_cloning( + &self, + terminator: Terminator<'tcx>, + old_dest_place: mir::Place<'tcx>, + old_ty: ty::Ty<'tcx>, + args: Vec>, + clones_passed_into_function: bool, + ) -> (BasicBlock, BasicBlock, Vec) { + let new_block_data = BasicBlockData::new(Some(terminator)); + let current_caller = self.patcher().new_block(new_block_data); + let mut current_target = current_caller; + + let mut old_tuple = Vec::new(); + let old_tuple_fields = old_ty.tuple_fields(); + + let mut locals_to_drop = Vec::new(); + for (id, operand) in args.iter().enumerate() { + let old_values_ty = old_tuple_fields.get(id).unwrap(); + if old_values_ty.is_unit() { + // we already know from ast, that this variable does not need + // to be cloned + let unit_const = unit_const(self.tcx()); + old_tuple.push(unit_const); + } else { + match operand { + mir::Operand::Constant(_) => { + old_tuple.push(operand.clone()); + } + mir::Operand::Move(place) | mir::Operand::Copy(place) => { + let target_ty = Some(old_tuple_fields[id]); + // prepends clone blocks before the actual function is called + let (start_block, destination, to_drop) = self + .insert_clone_argument( + *place, + current_target, + None, + target_ty, + clones_passed_into_function, + ) + .unwrap(); + if let Some(to_drop) = to_drop { + locals_to_drop.push(to_drop); + } + current_target = start_block; + // add the result to our tuple: + old_tuple.push(mir::Operand::Move(destination.into())); + } + } + } + } + let old_rvalue = mir::Rvalue::Aggregate( + Box::new(mir::AggregateKind::Tuple), + IndexVec::from_raw(old_tuple), + ); + let stmt_kind = mir::StatementKind::Assign(Box::new((old_dest_place, old_rvalue))); + let location = mir::Location { + block: current_caller, + statement_index: 0, + }; + self.patcher().add_statement(location, stmt_kind); + + // (start of clone-chain, block calling annotated function) + (current_target, current_caller, locals_to_drop) + } + + fn insert_clone_argument( + &self, + arg: mir::Place<'tcx>, + target: BasicBlock, + destination: Option, + target_type_opt: Option>, + results_dropped_by_function: bool, + ) -> Result<(BasicBlock, mir::Local, Option), ()> { + // if we deal with a reference, we can directly call clone on it + // otherwise we have to create a reference first, to pass to the clone + // function. + let clone_defid = get_clone_defid(self.tcx()).ok_or(())?; + let param_env = self.tcx().param_env(self.def_id()); + // let clone_trait_defid = self.tcx.lang_items().clone_trait().unwrap(); + // + let arg_ty = if let Some(target_type) = target_type_opt { + target_type + } else { + arg.ty(self.local_decls(), self.tcx()).ty + }; + let mutable_ref = matches!(arg_ty.ref_mutability(), Some(mir::Mutability::Mut)); + + let dest = destination.unwrap_or_else(|| self.patcher().new_temp(arg_ty, DUMMY_SP)); + if !arg_ty.is_ref() { + // non-ref arg means we first deref and then clone. + // destination only needs to be dropped if it's not later passed into + // a function that drops it + let to_drop = (!results_dropped_by_function + && arg_ty.needs_drop(self.tcx(), param_env)) + .then_some(dest); + let ref_ty = create_reference_type(self.tcx(), arg_ty); + let ref_arg = self.patcher().new_temp(ref_ty, DUMMY_SP); + // add a statement to deref the old_argument + let rvalue = rvalue_reference_to_local(self.tcx(), arg, false); + // the statement to be added to the block that has the call clone + // terminator + let ref_stmt = + mir::StatementKind::Assign(Box::new((mir::Place::from(ref_arg), rvalue))); + + // create the substitution since clone is generic: + let generics = self.tcx().mk_args(&[ty::GenericArg::from(arg_ty)]); + // create the function operand: + let clone_args = vec![Operand::Move(mir::Place::from(ref_arg))]; + // create a new basicblock: + // TODO: make part of new mir helper too + let (new_block, _) = self.create_call_block( + clone_defid, + clone_args, + generics, + Some(dest.into()), + Some(target), + )?; + self.patcher().add_statement( + mir::Location { + block: new_block, + statement_index: 0, + }, + ref_stmt, + ); + + Ok((new_block, dest, to_drop)) + // let block_data = BasicBlockData::new() + } else { + // create a new local to store the result of clone: + let deref_ty = arg_ty.builtin_deref(false).ok_or(())?.ty; + let clone_dest = self.patcher().new_temp(deref_ty, DUMMY_SP); + let to_drop = deref_ty + .needs_drop(self.tcx(), param_env) + .then_some(clone_dest); + + let generics = self.tcx().mk_args(&[ty::GenericArg::from(deref_ty)]); + let clone_args = vec![Operand::Move(arg)]; + // add an additional simple block afterwards, that dereferences + // the the cloned value into the original receiver of old: + // create it beforehand, so we can set the precedors target correctly + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: TerminatorKind::Goto { target }, + }; + let block_data = BasicBlockData::new(Some(terminator)); + let second_block = self.patcher().new_block(block_data); + + // borrow the clone result + let rvalue = rvalue_reference_to_local(self.tcx(), clone_dest.into(), mutable_ref); + let ref_stmt = mir::StatementKind::Assign(Box::new((dest.into(), rvalue))); + self.patcher().add_statement( + mir::Location { + block: second_block, + statement_index: 0, + }, + ref_stmt, + ); + + let (new_block, _) = self.create_call_block( + clone_defid, + clone_args, + generics, + Some(clone_dest.into()), + Some(second_block), + )?; + + Ok((new_block, dest, to_drop)) + } + } + + fn create_pledge_call_chain( + &self, + pledge: &PledgeToProcess<'tcx>, + target: BasicBlock, + ) -> Result<(BasicBlock, BasicBlock), ()> { + // given a location, insert the call chain to check a pledge: + // since we need to know the targets for each call, the blocks need to be created + // in reversed order. + + // annoying, but we have to create a block to start off the dropping chain + // early.. Even though at this point we don't know which locals we will + // have to drop + let drop_start_term = Terminator { + source_info: dummy_source_info(), + kind: TerminatorKind::Goto { target }, + }; + let drop_block_data = BasicBlockData::new(Some(drop_start_term)); + let drop_block = self.patcher().new_block(drop_block_data); + + // Create call to check function: + let mut check_args = pledge.args.clone(); + check_args.push(mir::Operand::Move(pledge.result_copy_place)); //result arg + check_args.push(mir::Operand::Move(pledge.old_values_place)); + check_args.push(mir::Operand::Move(pledge.before_expiry_place)); + let (check_after_block, _) = self.create_call_block( + pledge.check, + check_args, + pledge.generics, + None, + Some(drop_block), + )?; + + // If there is a check_before_expiry block, creat it + let next_target = if let Some(check_before_expiry) = pledge.check_before_expiry { + let before_check_args = vec![ + mir::Operand::Move(pledge.result_copy_place), + mir::Operand::Move(pledge.old_values_place), + ]; + let (new_block, _) = self.create_call_block( + check_before_expiry, + before_check_args, + pledge.generics, + None, + Some(check_after_block), + )?; + new_block + } else { + check_after_block + }; + + // clone the result of the pledge, so it can be referred to with before_expiry + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: mir::TerminatorKind::Goto { + target: next_target, + }, + }; + let (clone_chain_start, _, locals_to_drop_before_expiry) = self.prepend_old_cloning( + terminator, + pledge.before_expiry_place, + pledge.before_expiry_ty, + vec![mir::Operand::Move(pledge.result_copy_place)], + true, // this clone will be passed to a check function + ); + // so far we can only drop the values created for before_expiry + let (drop_chain_start, _) = + self.create_drop_chain(locals_to_drop_before_expiry, Some(target)); + // adjust the check block's terminator + self.patcher().patch_terminator( + drop_block, + mir::TerminatorKind::Goto { + target: drop_chain_start, + }, + ); + + // wrap an `if pledge_guard around the whole thing` + let term_switchint = mir::TerminatorKind::SwitchInt { + discr: mir::Operand::Copy(pledge.guard_place), + targets: mir::terminator::SwitchTargets::new( + [(u128::MIN, target)].into_iter(), + clone_chain_start, + ), + }; + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: term_switchint, + }; + let check_guard_block = mir::BasicBlockData::new(Some(terminator)); + let start_block = self.patcher().new_block(check_guard_block); + let unset_guard_stmt = self.set_bool_stmt(pledge.guard_place, false); + self.patcher().add_statement( + mir::Location { + block: clone_chain_start, + statement_index: 0, + }, + unset_guard_stmt, + ); + Ok((start_block, drop_block)) + } + + /// Create a chain of drop calls to drop the provided list of locals. + /// If there are no locals to drop, this function simply returns `target` + /// If there is no target, we return after dropping all values + fn create_drop_chain( + &self, + locals_to_drop: Vec, + target_opt: Option, + ) -> (BasicBlock, BasicBlock) { + let target = if let Some(target) = target_opt { + target + } else { + // create a return block: + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: mir::TerminatorKind::Return, + }; + let block_data = mir::BasicBlockData::new(Some(terminator)); + self.patcher().new_block(block_data) + }; + let last_block = target; + let mut current_target = target; + for local in locals_to_drop.iter() { + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: mir::TerminatorKind::Drop { + place: (*local).into(), + target: current_target, + unwind: mir::UnwindAction::Continue, + replace: false, + }, + }; + let block_data = mir::BasicBlockData::new(Some(terminator)); + current_target = self.patcher().new_block(block_data); + } + (current_target, last_block) + } + + /// Create a call block to the function with the given def_id. + /// + /// * `call_id`: DefId of the called function + /// * `args`: arguments passed to the function + /// * `generics`: generic arguments passed to the function + /// * `destination`: where the functions result should be stored. If None, a new + /// local will be created + /// * `target`: the basic block where function should jump to after returning + /// + /// returns (id of new basic block, destination) + fn create_call_block( + &self, + call_id: DefId, + args: Vec>, + generics: ty::GenericArgsRef<'tcx>, + destination: Option>, + target: Option, + ) -> Result<(mir::BasicBlock, mir::Place<'tcx>), ()> { + // construct the function call + let func_ty = self + .tcx() + .mk_ty_from_kind(ty::TyKind::FnDef(call_id, generics)); + let func = mir::Operand::Constant(Box::new(mir::Constant { + span: DUMMY_SP, + user_ty: None, + literal: mir::ConstantKind::zero_sized(func_ty), + })); + // either use passed destination or create a new one + let destination = destination.unwrap_or_else(|| { + // find return type + let ret_ty = fn_signature(self.tcx(), call_id, Some(generics)).output(); + mir::Place::from(self.patcher().new_temp(ret_ty, DUMMY_SP)) + }); + + // args have to be constructed beforehand, including result or old_values + let terminator_kind = mir::TerminatorKind::Call { + func, + args, + destination, + target, + unwind: mir::UnwindAction::Continue, + call_source: mir::CallSource::Normal, + fn_span: self.tcx().def_span(call_id), + }; + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: terminator_kind, + }; + let blockdata = mir::BasicBlockData::new(Some(terminator)); + let new_block_id = self.patcher().new_block(blockdata); + + Ok((new_block_id, destination)) + } + + /// Create a statement that assigns a boolean to a given destination + fn set_bool_stmt( + &self, + destination: mir::Place<'tcx>, + value: bool, + ) -> mir::StatementKind<'tcx> { + let const_kind = mir::ConstantKind::from_bool(self.tcx(), value); + let const_true = mir::Constant { + span: DUMMY_SP, + user_ty: None, + literal: const_kind, + }; + let rhs = mir::Rvalue::Use(mir::Operand::Constant(Box::new(const_true))); + mir::StatementKind::Assign(Box::new((destination, rhs))) + } +} diff --git a/prusti/src/modify_mir/mir_modify.rs b/prusti/src/modify_mir/mir_modify.rs new file mode 100644 index 00000000000..9585818ba11 --- /dev/null +++ b/prusti/src/modify_mir/mir_modify.rs @@ -0,0 +1,57 @@ +use crate::modify_mir::{mir_helper::*, mir_info_collector::MirInfo, passes}; +use prusti_interface::utils; +use prusti_rustc_interface::{ + hir::def::DefKind, + index::IndexVec, + middle::{mir, ty::TyCtxt}, + span::def_id::DefId, +}; + +/// Insert checks into the MIR +pub(crate) fn insert_runtime_checks<'tcx>( + body: &mut mir::Body<'tcx>, + def_id: DefId, + tcx: TyCtxt<'tcx>, + local_decls: &IndexVec>, +) { + if matches!(tcx.def_kind(def_id), DefKind::AnonConst) { + return; + } + + let mir_info = MirInfo::collect_mir_info(tcx, body.clone(), def_id); + // MAKE MODIFICATIONS: + let mut old_arg_replacer = passes::CloneOldArgs::new(tcx, &mir_info, def_id, local_decls); + // first we just create locals for old clones and replace them where arguments + // should be evaluated in an old state. We need to do this early, because we rely + // on positions of assignments to variables to be accurate. We can not insert the + // cloning here too, because that would also offset locations for other passes + // that follow + old_arg_replacer.create_and_replace_variables(body); + + // insert pledge checks: + passes::PledgeInserter::run(tcx, &mir_info, def_id, local_decls, body); + + // insert a dummy goto block at the beginning of the body, so we can easily + // insert blocks at the beginning of the function + prepend_dummy_block(body); + // insert preconditions + passes::PreconditionInserter::run(tcx, &mir_info, def_id, local_decls, body); + // insert postconditions + passes::PostconditionInserter::run(tcx, &mir_info, def_id, local_decls, body); + + // insert cloning of arguments if they are used within old, and drop them again + old_arg_replacer.clone_and_drop_variables(body); + + // store specs and env back into globals + mir_info.store_specs_env(); +} + +/// Perform optimizations based on the verification results +pub fn dead_code_elimination<'tcx>(tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>, def_id: DefId) { + // no modifications to spec functions! + let attrs = tcx.get_attrs_unchecked(def_id); + if utils::has_spec_only_attr(attrs) { + return; + } + passes::DeadCodeElimination::run(tcx, def_id, body); +} diff --git a/prusti/src/modify_mir/mod.rs b/prusti/src/modify_mir/mod.rs new file mode 100644 index 00000000000..0d7b5226d54 --- /dev/null +++ b/prusti/src/modify_mir/mod.rs @@ -0,0 +1,5 @@ +pub mod mir_modify; +pub mod mir_helper; +pub mod mir_modifications; +mod passes; +mod mir_info_collector; diff --git a/prusti/src/modify_mir/passes/insert_pledge_checks.rs b/prusti/src/modify_mir/passes/insert_pledge_checks.rs new file mode 100644 index 00000000000..1b600cfb7b1 --- /dev/null +++ b/prusti/src/modify_mir/passes/insert_pledge_checks.rs @@ -0,0 +1,482 @@ +use super::super::{mir_info_collector::MirInfo, mir_modifications::MirModifier}; +use crate::modify_mir::mir_helper::{ + dummy_source_info, fn_signature, get_goto_block_target, get_successors, replace_call_target, +}; +use prusti_interface::{ + environment::{borrowck::facts::Loan, polonius_info::PoloniusInfo, Procedure}, + globals, + specs::typed::CheckKind, +}; +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{self, patch::MirPatch, visit::MutVisitor}, + ty::{self, TyCtxt}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; +use rustc_hash::FxHashMap; +use std::{ + cell::{RefCell, RefMut}, + cmp::Ordering, +}; + +pub struct PledgeInserter<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + patch_opt: Option>>, + pledges_to_process: FxHashMap, PledgeToProcess<'tcx>>, + def_id: DefId, + local_decls: &'a IndexVec>, + /// For simplicity we use the same visitor twice, once to find all the + /// calls with pledges, create locals and extract information, but without + /// shifting any locations in the MIR, on the second one we prepend the + /// old and before_expiry storing, and setting the guard booleans + /// before each call. + first_pass: bool, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum ExpirationLocation { + Location(mir::Location), + Edge(mir::BasicBlock, mir::BasicBlock), +} + +impl ExpirationLocation { + fn block(&self) -> mir::BasicBlock { + match self { + ExpirationLocation::Location(loc) => loc.block, + // for edges we only modify the second block + ExpirationLocation::Edge(_, bb) => *bb, + } + } + fn statement_index(&self) -> usize { + match self { + Self::Location(loc) => loc.statement_index + 1, + Self::Edge(_, _) => 0, + } + } +} +// we need to order them by descending block and statement index such that inserting +// checks will not offset the modifications that will be inserted later +impl PartialOrd for ExpirationLocation { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for ExpirationLocation { + fn cmp(&self, other: &Self) -> Ordering { + match self.block().cmp(&other.block()) { + Ordering::Equal => self.statement_index().cmp(&other.statement_index()), + other => other, + } + } +} + +#[derive(Debug, Clone)] +pub struct PledgeToProcess<'tcx> { + pub name: String, + pub check: DefId, + pub check_before_expiry: Option, + pub result_copy_place: mir::Place<'tcx>, + pub old_values_place: mir::Place<'tcx>, + pub old_values_ty: ty::Ty<'tcx>, + pub before_expiry_place: mir::Place<'tcx>, + pub before_expiry_ty: ty::Ty<'tcx>, + pub destination: mir::Place<'tcx>, + // the args the function was called with + pub args: Vec>, + pub generics: ty::GenericArgsRef<'tcx>, + // a local that is set when a function with a pledge is called + // and then checked before a runtime check at an expiration location + // is performed. + pub guard_place: mir::Place<'tcx>, + pub location: mir::Location, + pub drop_blocks: Vec, + pub locals_to_drop: Option>, +} + +impl<'tcx, 'a> PledgeInserter<'tcx, 'a> { + pub fn run( + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + def_id: DefId, + local_decls: &'a IndexVec>, + body: &mut mir::Body<'tcx>, + ) { + let mut inserter = Self { + tcx, + body_info, + patch_opt: Some(MirPatch::new(body).into()), + pledges_to_process: Default::default(), + def_id, + local_decls, + first_pass: true, + }; + inserter.modify(body); + } + + fn modify(&mut self, body: &mut mir::Body<'tcx>) { + // find all calls to functions with pledges, and create local variables + // to store: old_values, before_expiry_place, + self.visit_body(body); + // If there are no pledges, we can stop here. + if self.pledges_to_process.is_empty() { + return; + } + // apply the patch generated during visitation + // this should only create new locals! + let patcher_ref = self.patch_opt.take().unwrap(); + patcher_ref.into_inner().apply(body); + + let procedure = Procedure::new(&self.body_info.env, self.def_id); + let polonius_info = if let Some(info) = globals::get_polonius_info(self.def_id) { + info + } else { + // for trusted methods this might not have been constructed before + PoloniusInfo::new(&self.body_info.env, &procedure, &FxHashMap::default()).unwrap() + }; + + // collect the loans that belong to some call with a pledge attached + let pledge_loans: FxHashMap> = self + .pledges_to_process + .iter() + .map(|(place, PledgeToProcess { location, .. })| { + ( + polonius_info.get_call_loan_at_location(*location).unwrap(), + *place, + ) + }) + .collect(); + // Now, use polonius to find all expiration locations: + // note: each pledge can potentially expire in multiple locations: + let mut modification_list: Vec<(ExpirationLocation, mir::Place<'tcx>)> = Vec::new(); + for (bb, bb_data) in body.basic_blocks.iter_enumerated() { + let nr_statements = bb_data.statements.len(); + for statement_index in 0..nr_statements + 1 { + let location = mir::Location { + block: bb, + statement_index, + }; + let loans_dying = polonius_info.get_loans_dying_at(location, false); + for loan in loans_dying.iter() { + // check if any of the loans are associated with one of + // our pledges: + if let Some(place) = pledge_loans.get(loan) { + // a pledge is associated with this loan and dies here!! + modification_list.push((ExpirationLocation::Location(location), *place)); + } + } + // For switchInts, also look at the expirations of loans on edges, since + // in that case they can not be found when simply looking at the location + if statement_index == nr_statements + && matches!( + body[bb].terminator.as_ref().unwrap().kind, + mir::TerminatorKind::SwitchInt { .. } + ) + { + let successors = get_successors(body, location); + for successor in successors.iter() { + let loans_dying = + polonius_info.get_loans_dying_between(location, *successor, false); + for loan in loans_dying { + if let Some(place) = pledge_loans.get(&loan) { + modification_list + .push((ExpirationLocation::Edge(bb, successor.block), *place)); + } + } + } + } + } + } + // sort the modification list according to location, so they + // don't offset each other: + modification_list.sort_by_key(|el| el.0); + modification_list.reverse(); + // insert the checks: + // store the places where we still need to insert drops + // once we know which locals need to be dropped + for (modification_location, place) in modification_list.iter() { + // remove pledge temporarily so we can modify it + let mut pledge = self.pledges_to_process.remove(place).unwrap(); + let (location, is_edge) = match modification_location { + ExpirationLocation::Location(loc) => (*loc, false), + ExpirationLocation::Edge(_, bb2) => { + // in case of an edge, we will insert the check at index 0 + // of the block the edge points to + ( + mir::Location { + block: *bb2, + statement_index: 0, + }, + true, + ) + } + }; + let either_stmt_or_terminator = body.stmt_at(location); + if either_stmt_or_terminator.is_left() || is_edge { + // it's just a statement: split the block. + let bb_data = &mut body.basic_blocks_mut()[location.block]; + let nr_stmts = bb_data.statements.len(); + let start_index = if !is_edge { + location.statement_index + 1 + } else { + 0 + }; + // in the case of edges this will drain all statements + let after = bb_data.statements.drain(start_index..nr_stmts); + // create a new block with the original block's terminator and all + // the statements following our location: + let term = bb_data.terminator.clone(); + let mut new_bb_data = mir::BasicBlockData::new(term); + new_bb_data.statements = after.collect(); + self.patch_opt = Some(MirPatch::new(body).into()); + let new_block = self.patcher().new_block(new_bb_data); + + // create the check call chain that continues executing new_block once + // the checks are done: + let (start_block, drop_insertion_block) = + self.create_pledge_call_chain(&pledge, new_block).unwrap(); + // store the drop insertion block so we can later insert the required + // drops + pledge.drop_blocks.push(drop_insertion_block); + + let new_terminator = mir::TerminatorKind::Goto { + target: start_block, + }; + // override the original terminator to point to our check-chain + self.patcher() + .patch_terminator(location.block, new_terminator); + let patcher_ref = self.patch_opt.take().unwrap(); + patcher_ref.into_inner().apply(body); + } else { + // In this case, we want to insert our check after a terminator + let term = either_stmt_or_terminator.right().unwrap(); + self.patch_opt = Some(MirPatch::new(body).into()); + match term.kind { + mir::TerminatorKind::Call { + target: Some(target), + .. + } => { + // create the call chain with the calls target: + let (start_block, drop_insertion_block) = + self.create_pledge_call_chain(&pledge, target).unwrap(); + // we need to store the block where we can later insert + // the drops for values we cloned for old + pledge.drop_blocks.push(drop_insertion_block); + + let mut new_call_term = term.clone(); + // After the call is finished, jump to our check-chain + replace_call_target(&mut new_call_term, start_block); + self.patcher() + .patch_terminator(location.block, new_call_term.kind); + } + _ => { + // Can pledges expire at other kinds of terminators? + todo!() + } + } + let patcher_ref = self.patch_opt.take().unwrap(); + patcher_ref.into_inner().apply(body); + } + // insert pledge again. + self.pledges_to_process.insert(*place, pledge); + } + + // run the visitor again, now for phase 2: inserting the cloning + self.patch_opt = Some(MirPatch::new(body).into()); + self.first_pass = false; + self.visit_body(body); + let patcher_ref = self.patch_opt.take().unwrap(); + patcher_ref.into_inner().apply(body); + + // finally, drop the old values at each expiration check: + // We do this here, because cloning all the values when we traverse the MIR + // the first time, would offset locations. Only after cloning do we actually + // know which values will need to be dropped. + self.patch_opt = Some(MirPatch::new(body).into()); + for pledge in self.pledges_to_process.values() { + for drop_block in pledge.drop_blocks.iter() { + // get the target of the current drop block (always a goto, + // exactly because we need to be able to append to it) + if let Some(target) = get_goto_block_target(body, *drop_block) { + let (chain_start, _) = self.create_drop_chain( + pledge.locals_to_drop.as_ref().unwrap().clone(), + Some(target), + ); + let new_terminator = mir::TerminatorKind::Goto { + target: chain_start, + }; + // make the drop block, which we created earlier point the the drop chain + self.patcher().patch_terminator(*drop_block, new_terminator); + } else { + unreachable!(); + } + } + } + // apply patcher for a final time + let patcher_ref = self.patch_opt.take().unwrap(); + patcher_ref.into_inner().apply(body); + } + + /// This generates a BasicBlock that does the following: + /// If a pledge has been created, set it's corresponding guard to true, + /// such that subsequent checks at expiration locations will be performed + pub fn set_guard_true_block( + &self, + destination: mir::Place<'tcx>, + target: mir::BasicBlock, + ) -> mir::BasicBlock { + let stmt_kind = self.set_bool_stmt(destination, true); + let stmt = mir::Statement { + source_info: dummy_source_info(), + kind: stmt_kind, + }; + let terminator = mir::Terminator { + source_info: dummy_source_info(), + kind: mir::TerminatorKind::Goto { target }, + }; + let mut new_block = mir::BasicBlockData::new(Some(terminator)); + new_block.statements.push(stmt); + self.patcher().new_block(new_block) + } +} + +impl<'tcx, 'a> MutVisitor<'tcx> for PledgeInserter<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_terminator( + &mut self, + terminator: &mut mir::Terminator<'tcx>, + location: mir::Location, + ) { + if let mir::TerminatorKind::Call { + func, + destination, + args, + target, + .. + } = &terminator.kind + { + if let Some((call_id, generics)) = func.const_fn_def() { + for check_kind in self.body_info.specs.get_runtime_checks(&call_id) { + if let CheckKind::Pledge { + check, + check_before_expiry, + } = check_kind + { + if self.first_pass { + let name = self.tcx.def_path_debug_str(call_id); + let check_sig = fn_signature(self.tcx, check, Some(generics)); + let inputs = check_sig.inputs(); + // look at the check function signature to determine + // which values need cloning. + let old_values_ty = inputs[inputs.len() - 2]; + let before_expiry_ty = inputs[inputs.len() - 1]; + // create temporary locals for them + let old_values_place = + mir::Place::from(self.patcher().new_temp(old_values_ty, DUMMY_SP)); + let before_expiry_place = mir::Place::from( + self.patcher().new_temp(before_expiry_ty, DUMMY_SP), + ); + + // create a copy of the result in case it becomes a zombie later + let result_ty = destination.ty(self.local_decls(), self.tcx).ty; + let result_copy_place = + mir::Place::from(self.patcher().new_temp(result_ty, DUMMY_SP)); + + // create a guard local + let bool_ty = self.tcx.mk_ty_from_kind(ty::TyKind::Bool); + let local_guard = self.patcher().new_temp(bool_ty, DUMMY_SP).into(); + let pledge_to_process = PledgeToProcess { + name, + check, + check_before_expiry, + result_copy_place, + old_values_place, + old_values_ty, + before_expiry_place, + before_expiry_ty, + destination: *destination, + args: args.clone(), + generics, + guard_place: local_guard, + location, + drop_blocks: Vec::new(), + locals_to_drop: None, + }; + // TODO: create a test where the result is assigned + // to the field of a tuple + // Make sure this location is actually unique: + assert!(self.pledges_to_process.get(destination).is_none()); + self.pledges_to_process + .insert(*destination, pledge_to_process); + } else { + let mut pledge = self.pledges_to_process.remove(destination).unwrap(); + // create the clones of old that will be passed to + // the check function. Make the chain end with the + // original function call + let (chain_start, _new_caller, locals_to_drop) = self + .prepend_old_cloning( + terminator.clone(), + pledge.old_values_place, + pledge.old_values_ty, + args.clone(), + true, + ); + // those have yet to be dropped + pledge.locals_to_drop = Some(locals_to_drop); + let set_guard_block = + self.set_guard_true_block(pledge.guard_place, chain_start); + // of our function + self.patcher().patch_terminator( + location.block, + mir::TerminatorKind::Goto { + target: set_guard_block, + }, + ); + // Copy the result after the function has been called: + // If it has a target, otherwise it doesn't matter at all.. + if let Some(target) = target { + let location = mir::Location { + block: *target, + statement_index: 0, + }; + let result_operand = mir::Operand::Copy(pledge.destination); + let stmt = mir::StatementKind::Assign(Box::new(( + pledge.result_copy_place, + mir::Rvalue::Use(result_operand), + ))); + self.patcher().add_statement(location, stmt); + } + self.pledges_to_process.insert(*destination, pledge); + } + } + } + } + } + } +} + +impl<'tcx, 'a> MirModifier<'tcx> for PledgeInserter<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn patcher(&self) -> RefMut> { + self.patch_opt + .as_ref() + .expect("Bug: MirPatch was not initialized for MirModifier") + .borrow_mut() + } + + fn def_id(&self) -> DefId { + self.def_id + } + + fn local_decls(&self) -> &'a IndexVec> { + self.local_decls + } +} diff --git a/prusti/src/modify_mir/passes/insert_postconditions.rs b/prusti/src/modify_mir/passes/insert_postconditions.rs new file mode 100644 index 00000000000..06469d3ac23 --- /dev/null +++ b/prusti/src/modify_mir/passes/insert_postconditions.rs @@ -0,0 +1,179 @@ +use super::super::{mir_helper::*, mir_info_collector::MirInfo, mir_modifications::MirModifier}; +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{self, patch::MirPatch, visit::MutVisitor}, + ty::{self, TyCtxt}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; +use std::cell::{RefCell, RefMut}; + +pub struct PostconditionInserter<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + patch_opt: Option>>, + def_id: DefId, + local_decls: &'a IndexVec>, +} + +impl<'tcx, 'a> PostconditionInserter<'tcx, 'a> { + pub fn run( + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + def_id: DefId, + local_decls: &'a IndexVec>, + body: &mut mir::Body<'tcx>, + ) { + let mut inserter = Self { + tcx, + body_info, + patch_opt: Some(MirPatch::new(body).into()), + def_id, + local_decls, + }; + inserter.visit_body(body); + // apply the patch + let patch_ref = inserter.patch_opt.take().unwrap(); + patch_ref.into_inner().apply(body); + } + + /// Given a call, surround it with the following: + /// 1. cloning of all the old values that are required (before) + /// 2. performing the check function (after) + /// 3. dropping all the cloned values (after) + /// + /// this function returns: + /// * `result.0` the basic block where the original call is moved to + /// * `result.1` the basic block that calls the check function + #[allow(clippy::too_many_arguments)] + fn surround_call_with_store_and_check( + &self, + check_id: DefId, + caller_block: mir::BasicBlock, + target: Option, + result_operand: mir::Place<'tcx>, + args: Vec>, + generics: ty::GenericArgsRef<'tcx>, + original_terminator: mir::Terminator<'tcx>, + ) -> (mir::BasicBlock, Option) { + // find the type of that local + let check_sig = fn_signature(self.tcx, check_id, Some(generics)); + // old_ty is always the last input of postconditions + let old_ty = *check_sig.inputs().last().unwrap(); + assert!(matches!(old_ty.kind(), ty::Tuple(_))); + + let old_dest_place = mir::Place::from(self.patcher().new_temp(old_ty, DUMMY_SP)); + + // create a drop-block, where we can later insert the drop chain. + let drop_block_data = mir::BasicBlockData::new(Some(mir::Terminator { + source_info: dummy_source_info(), + kind: mir::TerminatorKind::Goto { + target: mir::BasicBlock::MAX, + }, + })); + let drop_start = self.patcher().new_block(drop_block_data); + + // construct arguments: first the arguments the function is called with, then the result of + // that call, then the old values: + let mut new_args = args.clone(); + new_args.push(mir::Operand::Move(result_operand)); + new_args.push(mir::Operand::Move(old_dest_place)); + + // We store the target, create a new block per check function + // chain these with the final call having the original target, + // change the target of the call to the first block of our chain. + let (check_block, _) = self + .create_call_block(check_id, new_args, generics, None, Some(drop_start)) + .unwrap(); + + // the terminator that calls the original function, but in this case jumps to + // a check function after instead of original target + // for now we just construct it, this does not modify the terminator + // in the CFG yet + let mut call_terminator = original_terminator; + replace_call_target(&mut call_terminator, check_block); + + // create a chain of clone calls, that jumps to the original function + // call afterwards + let (chain_start, new_caller, locals_to_drop) = + self.prepend_old_cloning(call_terminator, old_dest_place, old_ty, args, true); + + // create the drop chain and append it to the block we created earlier + // for this purpose + let (drop_chain_start, _) = self.create_drop_chain(locals_to_drop, target); + self.patcher().patch_terminator( + drop_start, + mir::TerminatorKind::Goto { + target: drop_chain_start, + }, + ); + + // make the original caller_block point to the first clone block + self.patcher().patch_terminator( + caller_block, + mir::TerminatorKind::Goto { + target: chain_start, + }, + ); + (new_caller, Some(check_block)) + } +} + +impl<'tcx, 'a> MutVisitor<'tcx> for PostconditionInserter<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_terminator( + &mut self, + terminator: &mut mir::Terminator<'tcx>, + location: mir::Location, + ) { + if let mir::TerminatorKind::Call { + func, + target, + destination, + args, + .. + } = &terminator.kind + { + if let Some((call_id, generics)) = func.const_fn_def() { + let mut caller_block = location.block; + let mut current_target = *target; + // get all the checks that need to be performed + for check_id in self.body_info.specs.get_post_checks(&call_id) { + (caller_block, current_target) = self.surround_call_with_store_and_check( + check_id, + caller_block, + current_target, + *destination, + args.clone(), + generics, + terminator.clone(), + ); + } + } + } + } +} + +impl<'tcx, 'a> MirModifier<'tcx> for PostconditionInserter<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + fn patcher(&self) -> RefMut> { + self.patch_opt + .as_ref() + .expect("Bug: MirPatch for inserting preconditions was not initialized") + .borrow_mut() + } + + fn def_id(&self) -> DefId { + self.def_id + } + + fn local_decls(&self) -> &'a IndexVec> { + self.local_decls + } +} diff --git a/prusti/src/modify_mir/passes/insert_precondition_checks.rs b/prusti/src/modify_mir/passes/insert_precondition_checks.rs new file mode 100644 index 00000000000..ddc43c9c6aa --- /dev/null +++ b/prusti/src/modify_mir/passes/insert_precondition_checks.rs @@ -0,0 +1,133 @@ +use super::super::{mir_helper::*, mir_info_collector::MirInfo, mir_modifications::MirModifier}; +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{self, patch::MirPatch, visit::MutVisitor}, + ty::{self, TyCtxt}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; +use std::cell::{RefCell, RefMut}; + +/// This pass inserts precondition checks at the beginning of the +/// body, and also in front of each function that is called +pub struct PreconditionInserter<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + /// making modifications from a Visitor often requires access + /// to a patcher! But from the visiting methods we don't have + /// direct access to a mutable body + patch_opt: Option>>, + def_id: DefId, + local_decls: &'a IndexVec>, +} + +impl<'tcx, 'a> PreconditionInserter<'tcx, 'a> { + pub fn run( + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + def_id: DefId, + local_decls: &'a IndexVec>, + body: &mut mir::Body<'tcx>, + ) { + let mut inserter = Self { + tcx, + body_info, + patch_opt: Some(MirPatch::new(body).into()), + def_id, + local_decls, + }; + inserter.modify(body); + } + + fn modify(&mut self, body: &mut mir::Body<'tcx>) { + let mut current_target = get_goto_block_target(body, mir::START_BLOCK) + .expect("Bug: Body must start with a Goto block at this stage"); + let generics = ty::GenericArgs::identity_for_item(self.tcx, self.body_info.def_id); + // 1. check the preconditions of the current body + let args = args_from_body(body); + for check_id in self.body_info.specs.get_pre_checks(&self.body_info.def_id) { + (current_target, _) = self + .create_call_block(check_id, args.clone(), generics, None, Some(current_target)) + .unwrap(); + } + // make the starting block (which is already be a dummy goto block) + // point to the first precondition check + self.patcher().patch_terminator( + mir::START_BLOCK, + mir::TerminatorKind::Goto { + target: current_target, + }, + ); + // apply patch first + let patch_ref = self.patch_opt.take().unwrap(); + patch_ref.into_inner().apply(body); + // new patch for other modifications: + self.patch_opt = Some(RefCell::new(MirPatch::new(body))); + // Visit the body for calls where we also check their preconditions + self.visit_body(body); + let patch_ref = self.patch_opt.take().unwrap(); + patch_ref.into_inner().apply(body); + } +} + +impl<'tcx, 'a> MutVisitor<'tcx> for PreconditionInserter<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_terminator( + &mut self, + terminator: &mut mir::Terminator<'tcx>, + location: mir::Location, + ) { + if let mir::TerminatorKind::Call { + func, target, args, .. + } = &terminator.kind + { + if let Some((call_id, generics)) = func.const_fn_def() { + let mut caller_block = location.block; + let _current_target = target; + if call_id.is_local() { + // If the call is local, we can be sure that when the mir of + // this function is processed, the precondition check will be inserted + // anyways. No reason to check it twice. + return; + } + for check_id in self.body_info.specs.get_pre_checks(&call_id) { + let return_ty = fn_signature(self.tcx, check_id, Some(generics)).output(); + assert!(return_ty.is_unit()); + let res = self.patcher().new_temp(return_ty, DUMMY_SP); + caller_block = self.prepend_call( + check_id, + caller_block, + args.clone(), + generics, + terminator.clone(), + res.into(), + ); + } + } + } + } +} + +impl<'tcx, 'a> MirModifier<'tcx> for PreconditionInserter<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + fn patcher(&self) -> RefMut> { + self.patch_opt + .as_ref() + .expect("Bug: MirPatch for inserting preconditions was not initialized") + .borrow_mut() + } + + fn def_id(&self) -> DefId { + self.def_id + } + + fn local_decls(&self) -> &'a IndexVec> { + self.local_decls + } +} diff --git a/prusti/src/modify_mir/passes/mod.rs b/prusti/src/modify_mir/passes/mod.rs new file mode 100644 index 00000000000..69e04c1fe7f --- /dev/null +++ b/prusti/src/modify_mir/passes/mod.rs @@ -0,0 +1,11 @@ +mod insert_precondition_checks; +mod insert_pledge_checks; +mod insert_postconditions; +mod replace_old_args; +mod remove_dead_code; + +pub(crate) use insert_pledge_checks::{PledgeInserter, PledgeToProcess}; +pub(crate) use insert_postconditions::PostconditionInserter; +pub(crate) use insert_precondition_checks::PreconditionInserter; +pub(crate) use remove_dead_code::DeadCodeElimination; +pub(crate) use replace_old_args::CloneOldArgs; diff --git a/prusti/src/modify_mir/passes/remove_check_blocks.rs b/prusti/src/modify_mir/passes/remove_check_blocks.rs new file mode 100644 index 00000000000..019387a3f61 --- /dev/null +++ b/prusti/src/modify_mir/passes/remove_check_blocks.rs @@ -0,0 +1,56 @@ +use crate::modify_mir::mir_helper::is_check_block; +use prusti_interface::{ + environment::{blocks_dominated_by, is_check_closure, EnvQuery, Environment}, + globals, + specs::typed::DefSpecificationMap, +}; +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{self, patch::MirPatch, visit::MutVisitor}, + ty::{self, TyCtxt}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; + +pub struct RemoveCheckBlocks<'tcx> { + env_query: EnvQuery<'tcx>, +} + +struct SwitchIntBlock { + block: mir::BasicBlock, + check_target: mir::BasicBlock, +} + +impl<'tcx> RemoveCheckBlocks<'tcx> { + fn run(tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) -> Result<(), ()> { + let mut adjust_blocks = vec![]; + let env_query = EnvQuery::new(tcx); + // identify switchInts in front of check_blocks + for (bb, bb_data) in body.basic_blocks.iter_enumerated() { + if let Some( + term @ mir::Terminator { + kind: mir::TerminatorKind::SwitchInt { .. }, + .. + }, + ) = bb_data.terminator + { + for target_bb in term.successors() { + let bb_data = body.basic_blocks.basic_blocks.get(target_bb).unwrap(); + if is_check_block(env_query, bb_data) { + adjust_blocks.push(SwitchIntBlock { + block: bb, + check_target: target_bb, + }) + } + } + } + } + // simplify the switchInt blocks: + for remove_target in adjust_blocks.into_iter() { + + } + + Ok(()) + } +} diff --git a/prusti/src/modify_mir/passes/remove_dead_code.rs b/prusti/src/modify_mir/passes/remove_dead_code.rs new file mode 100644 index 00000000000..3b22253e8d1 --- /dev/null +++ b/prusti/src/modify_mir/passes/remove_dead_code.rs @@ -0,0 +1,214 @@ +use prusti_interface::globals; +use prusti_rustc_interface::{ + middle::{ + mir::{ + self, + patch::MirPatch, + visit::{self, MutVisitor}, + AssertKind, + }, + ty::{self, TyCtxt}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::modify_mir::mir_helper::dummy_source_info; + +pub struct DeadCodeElimination<'tcx> { + tcx: TyCtxt<'tcx>, + unreachable_blocks: FxHashSet, + removable_assertions: FxHashSet, +} + +impl<'tcx> DeadCodeElimination<'tcx> { + pub fn run(tcx: TyCtxt<'tcx>, def_id: DefId, body: &mut mir::Body<'tcx>) { + // collect all the blocks that were inserted but didnt generate + // a verification error: + let reachability_map = globals::get_reachability_map(def_id).unwrap_or_default(); + let unreachable_blocks: FxHashSet = reachability_map + .iter() + .filter_map(|(bb, reachable)| (!reachable).then_some(*bb)) + .collect(); + + let assertion_map = globals::get_assertion_map(def_id).unwrap_or_default(); + let removable_assertions: FxHashSet = assertion_map + .iter() + .filter_map(|(bb, verified)| verified.then_some(*bb)) + .collect(); + let mut modifier = Self { + tcx, + unreachable_blocks, + removable_assertions, + }; + modifier.remove_assertions(body); + if modifier.unreachable_blocks.is_empty() { + return; + } + // this visit will remove all edges from Goto blocks to unreachable targets. + // Later this will lead to the compiler eliminating these blocks. + modifier.visit_body(body); + } + + fn remove_assertions(&self, body: &mut mir::Body<'tcx>) { + let mut locals_to_replace: Vec<(mir::Local, ty::Ty<'tcx>)> = Default::default(); + for block in self.removable_assertions.iter() { + // take the existing terminator, which has to be an assert or something + // went very wrong. + let terminator = body.basic_blocks_mut()[*block].terminator.take().unwrap(); + if let mir::TerminatorKind::Assert { + cond, + msg: box kind, + target, + .. + } = terminator.kind + { + // Assertions associated with checked operations + if matches!(kind, AssertKind::Overflow(..) | AssertKind::OverflowNeg(_)) { + // this place should be of the form `_x.1` (the check field + // of the result of a checked operation) + let place = cond.place().unwrap(); + // If generated by a checkedAdd or similar, this will be + // a tuple with 1 projection. + if place.projection.len() == 1 { + // figure out the type of the first field (the one that + // contains the actual value): + let base_place: mir::Place<'tcx> = place.local.into(); + let ty = base_place.ty(&body.local_decls, self.tcx).ty; + let res_type = if let ty::TyKind::Tuple(typelist) = ty.kind() { + *typelist.get(0).unwrap() + } else { + unreachable!(); + }; + locals_to_replace.push((place.local, res_type)); + } + } + + // change the terminator to a goto + let new_term = mir::Terminator { + source_info: dummy_source_info(), + kind: mir::TerminatorKind::Goto { target }, + }; + body.basic_blocks_mut()[*block].terminator = Some(new_term); + } else { + // this only happens if our locations are inaccurate, which is + // hopefully never if we order our modifications correctly + unreachable!("Somehow mir must have been modified in a bad way between verification and here"); + } + } + let mut patcher = MirPatch::new(body); + // now we can replace the checked operations with unchecked ones, and if + // let's say _x = CheckedAdd() is replaced with _y = Add(), we have to replace + // every occurrence of _x.0 with _y + // 1. create the new locals with the previously derived types + let replacements: FxHashMap = locals_to_replace + .into_iter() + .map(|(local, ty)| (local, patcher.new_temp(ty, DUMMY_SP))) + .collect(); + patcher.apply(body); + + // replace the operations and occurrences of locals + let mut op_replacer = OperationReplacer { + tcx: self.tcx, + replacements, + }; + op_replacer.visit_body(body); + } +} + +impl<'tcx> MutVisitor<'tcx> for DeadCodeElimination<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_terminator( + &mut self, + terminator: &mut mir::Terminator<'tcx>, + _location: mir::Location, + ) { + let new_term_opt = match &mut terminator.kind { + mir::TerminatorKind::SwitchInt { discr, targets } => { + // filter out unreachable blocks + let mut targets_vec: Vec<(u128, mir::BasicBlock)> = targets + .iter() + .filter_map(|(value, bb)| { + (!self.unreachable_blocks.contains(&bb)).then_some((value, bb)) + }) + .collect(); + let otherwise_opt = if !self.unreachable_blocks.contains(&targets.otherwise()) { + Some(targets.otherwise()) + } else { + // unreachable otherwise block, take one of the + // targets + targets_vec.pop().map(|tup| tup.1) + }; + if let Some(otherwise) = otherwise_opt { + if targets_vec.is_empty() { + Some(mir::TerminatorKind::Goto { target: otherwise }) + } else { + let switch_targets = + mir::terminator::SwitchTargets::new(targets_vec.into_iter(), otherwise); + Some(mir::TerminatorKind::SwitchInt { + discr: discr.clone(), + targets: switch_targets, + }) + } + } else { + // if none of the targets is reachable, then this block + // must be unreachable itself!! + // make it actually unreachable? + Some(mir::TerminatorKind::Unreachable) + } + } + // potentially perform some checks on sanity of our results. + // E.g. a goto to a unreachable block can only come from + // another unreachable block. + _ => None, + }; + if let Some(new_term) = new_term_opt { + terminator.kind = new_term; + } + } +} + +struct OperationReplacer<'tcx> { + tcx: TyCtxt<'tcx>, + replacements: FxHashMap, +} + +impl<'tcx> MutVisitor<'tcx> for OperationReplacer<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_assign( + &mut self, + place: &mut mir::Place<'tcx>, + rvalue: &mut mir::Rvalue<'tcx>, + location: mir::Location, + ) { + // is rhs a checked operation? + if let Some(replacing_local) = self.replacements.get(&place.local) { + if let mir::Rvalue::CheckedBinaryOp(bin_op, box (op1, op2)) = rvalue { + // replace the statement with an unchecked operation + *place = (*replacing_local).into(); + *rvalue = mir::Rvalue::BinaryOp(*bin_op, Box::new((op1.clone(), op2.clone()))); + } + } + self.super_assign(place, rvalue, location); + } + + fn visit_place( + &mut self, + place: &mut mir::Place<'tcx>, + _context: visit::PlaceContext, + _location: mir::Location, + ) { + if let Some(replacing_local) = self.replacements.get(&place.local) { + // if this is an access to _local.0, replace it + if let mir::ProjectionElem::Field(id, _) = place.projection.first().unwrap() && id.index() == 0 { + *place = mir::Place::from(*replacing_local); + } + } + } +} diff --git a/prusti/src/modify_mir/passes/replace_old_args.rs b/prusti/src/modify_mir/passes/replace_old_args.rs new file mode 100644 index 00000000000..a2c2ab93b1b --- /dev/null +++ b/prusti/src/modify_mir/passes/replace_old_args.rs @@ -0,0 +1,179 @@ +use super::super::{mir_helper::*, mir_info_collector::MirInfo, mir_modifications::MirModifier}; + +use prusti_rustc_interface::{ + index::IndexVec, + middle::{ + mir::{self, patch::MirPatch, visit::MutVisitor}, + ty::{self, TyCtxt}, + }, + span::{def_id::DefId, DUMMY_SP}, +}; +use rustc_hash::FxHashMap; +use std::cell::{RefCell, RefMut}; + +pub struct CloneOldArgs<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + patch_opt: Option>>, + def_id: DefId, + local_decls: &'a IndexVec>, + stored_arguments: FxHashMap, +} + +impl<'tcx, 'a> CloneOldArgs<'tcx, 'a> { + pub fn new( + tcx: TyCtxt<'tcx>, + body_info: &'a MirInfo<'tcx>, + def_id: DefId, + local_decls: &'a IndexVec>, + ) -> Self { + Self { + tcx, + body_info, + patch_opt: None, + def_id, + local_decls, + stored_arguments: Default::default(), + } + } + + /// Creates locals for the clones of arguments, and replaces them in + /// the correct places such that old behaves correctly. This function + /// relies on the locations in `stmts_to_substitue_rhs` to be accurate, so it + /// needs to be executed before any of the modifications that alter block indeces + /// happen. It does not modify any block indeces itself. + pub fn create_and_replace_variables(&mut self, body: &mut mir::Body<'tcx>) { + let mut patcher = MirPatch::new(body); + // create the temporary variables + for arg in &self.body_info.args_to_be_cloned { + let ty = self.local_decls.get(*arg).unwrap().ty; + let new_var = patcher.new_temp(ty, DUMMY_SP); + self.stored_arguments.insert(*arg, new_var); + } + // replace the function arguments with the new temporary variables + // according to information we collected earlier + let mut replacer = ArgumentReplacer::new(self.tcx, &self.stored_arguments); + for (block, bb_data) in body.basic_blocks_mut().iter_enumerated_mut() { + for (statement_index, stmt) in bb_data.statements.iter_mut().enumerate() { + let loc = mir::Location { + block, + statement_index, + }; + if self.body_info.stmts_to_substitute_rhs.contains(&loc) { + replacer.visit_statement(stmt, loc); + } + } + } + patcher.apply(body); + } + + /// For variables that need to be cloned at the beginning of a function + /// (opposed to in front of a function call as for postconditions for example) + /// this function inserts a chain of clone calls at the beginning of the function + /// and makes sure these values are dropped again before the function returns. + pub fn clone_and_drop_variables(&mut self, body: &mut mir::Body<'tcx>) { + let patch = MirPatch::new(body); + self.patch_opt = Some(patch.into()); + let mut drop_on_return = Vec::new(); + // clone the arguments: + let mut current_target = get_goto_block_target(body, mir::START_BLOCK) + .expect("Bug: Body must start with a Goto block at this stage"); + for local in self.body_info.args_to_be_cloned.iter() { + let place: mir::Place = (*local).into(); + let destination = *self.stored_arguments.get(local).unwrap(); + let (new_target, _, to_drop) = self + .insert_clone_argument(place, current_target, Some(destination), None, false) + .unwrap(); + current_target = new_target; + if let Some(to_drop) = to_drop { + drop_on_return.push(to_drop); + } + } + let terminator_kind = mir::TerminatorKind::Goto { + target: current_target, + }; + self.patcher() + .patch_terminator(mir::START_BLOCK, terminator_kind); + + let (drop_chain_start, drop_chain_end) = self.create_drop_chain(drop_on_return, None); + + let patch_ref = self.patch_opt.take().unwrap(); + patch_ref.into_inner().apply(body); + + // insert jumps to drop chain wherever function returns: + let mut visitor = DropBeforeReturnVisitor { + drop_chain_start, + drop_chain_end, + tcx: self.tcx, + }; + visitor.visit_body(body); + } +} + +impl<'tcx, 'a> MutVisitor<'tcx> for CloneOldArgs<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_local( + &mut self, + local: &mut mir::Local, + context: mir::visit::PlaceContext, + _location: mir::Location, + ) { + if let Some(replace) = self.stored_arguments.get(local) { + assert!(!matches!(context, mir::visit::PlaceContext::NonUse(_))); + *local = *replace; + } + } +} + +struct DropBeforeReturnVisitor<'tcx> { + drop_chain_start: mir::BasicBlock, + drop_chain_end: mir::BasicBlock, + tcx: ty::TyCtxt<'tcx>, +} + +impl<'tcx> MutVisitor<'tcx> for DropBeforeReturnVisitor<'tcx> { + fn tcx(&self) -> ty::TyCtxt<'tcx> { + self.tcx + } + fn visit_terminator( + &mut self, + terminator: &mut mir::Terminator<'tcx>, + location: mir::Location, + ) { + MutVisitor::super_terminator(self, terminator, location); + if location.block == self.drop_chain_end { + // The end of the drop chain also contains a return, so we need + // to skip this one + return; + } + // Replace return terminator with a goto to our drop chain + if matches!(terminator.kind, mir::TerminatorKind::Return) { + terminator.kind = mir::TerminatorKind::Goto { + target: self.drop_chain_start, + }; + } + } +} + +impl<'tcx, 'a> MirModifier<'tcx> for CloneOldArgs<'tcx, 'a> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + fn patcher(&self) -> RefMut> { + self.patch_opt + .as_ref() + .expect("Bug: MirPatch for inserting preconditions was not initialized") + .borrow_mut() + } + + fn def_id(&self) -> DefId { + self.def_id + } + + fn local_decls(&self) -> &'a IndexVec> { + self.local_decls + } +} diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 74775f0d628..fa1dcd113ee 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -10,9 +10,10 @@ use prusti_interface::{ use prusti_viper::verifier::Verifier; #[tracing::instrument(name = "prusti::verify", level = "debug", skip(env))] -pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { +pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) -> Environment<'_> { if env.diagnostic.has_errors() { warn!("The compiler reported an error, so the program will not be verified."); + env } else { debug!("Prepare verification task..."); // TODO: can we replace `get_annotated_procedures` with information @@ -81,5 +82,6 @@ pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { ); } }; + env } }