From 30beba6615ad05347dcad80d34064601ffe55153 Mon Sep 17 00:00:00 2001 From: trk Date: Mon, 3 Jun 2024 13:16:16 +0200 Subject: [PATCH 01/53] Fix domain function name garbling when running over prusti-server Also comment out a line that crashes when not run from the same directory. This was problematic for working with Prusti Assistant. --- prusti-encoder/src/lib.rs | 2 +- vir/src/viper_ident.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index c354bdb1510..ab7e228efca 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -141,7 +141,7 @@ pub fn test_entrypoint<'tcx>( program_methods.push(output.method_assign); } - std::fs::write("local-testing/simple.vpr", viper_code).unwrap(); + // std::fs::write("local-testing/simple.vpr", viper_code).unwrap(); let program = vir::with_vcx(|vcx| vcx.mk_program( vcx.alloc_slice(&program_fields), diff --git a/vir/src/viper_ident.rs b/vir/src/viper_ident.rs index b3724cbbe30..342470fda6b 100644 --- a/vir/src/viper_ident.rs +++ b/vir/src/viper_ident.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Display, Formatter}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)] #[serde(transparent)] -pub struct ViperIdent<'vir>(&'vir str); +pub struct ViperIdent<'vir>(#[serde(with = "crate::serde::serde_str")] &'vir str); impl Display for ViperIdent<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { From ec4035e736d07de5ee8cdc6edd9fa0e4d6ff3c2b Mon Sep 17 00:00:00 2001 From: trk Date: Sat, 8 Jun 2024 11:34:38 +0200 Subject: [PATCH 02/53] Use new VerificationResult type Ported change from PR https://github.com/viperproject/prusti-dev/pull/1334 --- prusti-server/src/backend.rs | 4 ++-- prusti-server/src/process_verification.rs | 15 ++++++++++--- prusti-server/tests/basic_requests.rs | 8 +++---- prusti/src/verifier.rs | 2 +- viper/src/verification_result.rs | 27 +++++++++++++++++++++-- viper/src/verifier.rs | 12 +++++----- viper/tests/invalid_programs.rs | 4 ++-- viper/tests/simple_programs.rs | 4 ++-- 8 files changed, 54 insertions(+), 22 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 15270d9316f..3da934253a1 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -3,14 +3,14 @@ use prusti_utils::{ config, Stopwatch, }; -use viper::{VerificationContext, VerificationResult}; +use viper::{VerificationContext, VerificationResultKind}; pub enum Backend<'a> { Viper(viper::Verifier<'a>, &'a VerificationContext<'a>), } impl<'a> Backend<'a> { - pub fn verify(&mut self, program: vir::ProgramRef) -> VerificationResult { + pub fn verify(&mut self, program: vir::ProgramRef) -> VerificationResultKind { match self { Backend::Viper(viper, context) => { let mut stopwatch = diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 7c3480e8d02..1e7a687d754 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -14,7 +14,7 @@ use prusti_utils::{ }; use std::{fs::create_dir_all, path::PathBuf}; use viper::{ - smt_manager::SmtManager, Cache, VerificationBackend, VerificationContext, VerificationResult, + smt_manager::SmtManager, Cache, VerificationBackend, VerificationContext, VerificationResult, VerificationResultKind }; #[tracing::instrument(level = "debug", skip_all, fields(program = %request.program.get_name()))] @@ -116,10 +116,19 @@ pub fn process_verification_request<'v, 't: 'v>( }; stopwatch.start_next("backend verification"); - let result = backend.verify(request.program); + // let result = backend.verify(request.program); + let mut result = VerificationResult { + item_name: request.program.get_name().to_string(), + kind: VerificationResultKind::Success, + cached: false, + time_ms: 0, + }; + // result.kind = backend.verify(request.program, sender.clone()); + result.kind = backend.verify(request.program); + result.time_ms = stopwatch.finish().as_millis(); // Don't cache Java exceptions, which might be due to misconfigured paths. - if config::enable_cache() && !matches!(result, VerificationResult::JavaException(_)) { + if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { info!( "Storing new cached result {:?} for program {}", &result, diff --git a/prusti-server/tests/basic_requests.rs b/prusti-server/tests/basic_requests.rs index edb7aca6768..7c7b462fb87 100644 --- a/prusti-server/tests/basic_requests.rs +++ b/prusti-server/tests/basic_requests.rs @@ -4,7 +4,7 @@ use prusti_server::{ spawn_server_thread, tokio::runtime::Builder, PrustiClient, VerificationRequest, ViperBackendConfig, }; -use viper::VerificationResult; +use viper::VerificationResultKind; lazy_static! { // only start the jvm & server once @@ -21,7 +21,7 @@ fn consistency_error() { }); match result { - VerificationResult::ConsistencyErrors(errors) => assert_eq!(errors.len(), 1), + VerificationResultKind::ConsistencyErrors(errors) => assert_eq!(errors.len(), 1), other => panic!("consistency errors not identified, instead found {other:?}"), } } @@ -31,12 +31,12 @@ fn empty_program() { let result = process_program(|_| ()); match result { - VerificationResult::Success => {} + VerificationResultKind::Success => {} other => panic!("empty program not verified successfully, instead found {other:?}"), } } -fn process_program(configure: F) -> VerificationResult +fn process_program(configure: F) -> VerificationResultKind where F: FnOnce(&mut Program), { diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 1b05031a019..1b4f37267e5 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -55,7 +55,7 @@ pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { let results = prusti_server::verify_programs(vec![program]); println!("verification results: {results:?}"); - if !results.iter().all(|(_, r)| matches!(r, viper::VerificationResult::Success)) { + if !results.iter().all(|(_, r)| matches!(r.kind, viper::VerificationResultKind::Success)) { // TODO: This will be unnecessary if diagnostic errors are emitted // earlier, it's useful for now to ensure that Prusti returns an // error code when verification fails diff --git a/viper/src/verification_result.rs b/viper/src/verification_result.rs index 81e931c186b..13811b86db6 100644 --- a/viper/src/verification_result.rs +++ b/viper/src/verification_result.rs @@ -6,9 +6,32 @@ use crate::{silicon_counterexample::SiliconCounterexample, JavaException}; +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct VerificationResult { + pub item_name: String, + pub kind: VerificationResultKind, + pub cached: bool, + pub time_ms: u128, +} + +impl VerificationResult { + pub fn is_success(&self) -> bool { + self.kind.is_success() + } + + pub fn dummy_success() -> Self { + VerificationResult { + item_name: "".to_string(), + kind: VerificationResultKind::Success, + cached: false, + time_ms: 0, + } + } +} + /// The result of a verification request on a Viper program. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum VerificationResult { +pub enum VerificationResultKind { /// The program verified. Success, /// The program did not verify. @@ -25,7 +48,7 @@ pub struct ConsistencyError { pub pos_id: Option } -impl VerificationResult { +impl VerificationResultKind { pub fn is_success(&self) -> bool { matches!(self, Self::Success) } diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index 24c2abdf565..467898241a8 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -11,7 +11,7 @@ use crate::{ silicon_counterexample::SiliconCounterexample, smt_manager::SmtManager, verification_backend::VerificationBackend, - verification_result::{VerificationError, VerificationResult}, ConsistencyError, + verification_result::{VerificationError, VerificationResultKind}, ConsistencyError, }; use jni::{errors::Result, objects::JObject, JNIEnv}; use log::{debug, error, info}; @@ -184,7 +184,7 @@ impl<'a> Verifier<'a> { } #[tracing::instrument(name = "viper::verify", level = "debug", skip_all)] - pub fn verify(&mut self, program: Program) -> VerificationResult { + pub fn verify(&mut self, program: Program) -> VerificationResultKind { let ast_utils = self.ast_utils; ast_utils.with_local_frame(16, || { debug!( @@ -196,7 +196,7 @@ impl<'a> Verifier<'a> { let consistency_errors = match self.ast_utils.check_consistency(program) { Ok(errors) => errors, Err(java_exception) => { - return VerificationResult::JavaException(java_exception); + return VerificationResultKind::JavaException(java_exception); } }; ); @@ -209,7 +209,7 @@ impl<'a> Verifier<'a> { "The provided Viper program has {} consistency errors.", consistency_errors.len() ); - return VerificationResult::ConsistencyErrors( + return VerificationResultKind::ConsistencyErrors( consistency_errors .into_iter() .map(|e| { @@ -399,9 +399,9 @@ impl<'a> Verifier<'a> { )) } - VerificationResult::Failure(errors) + VerificationResultKind::Failure(errors) } else { - VerificationResult::Success + VerificationResultKind::Success } }) } diff --git a/viper/tests/invalid_programs.rs b/viper/tests/invalid_programs.rs index 13fb3caa2ad..d2d401fe9f8 100644 --- a/viper/tests/invalid_programs.rs +++ b/viper/tests/invalid_programs.rs @@ -32,7 +32,7 @@ fn runtime_error() { assert!(matches!( verification_result, - VerificationResult::JavaException(_) + VerificationResultKind::JavaException(_) )); } @@ -99,7 +99,7 @@ where let verification_result = verifier.verify(program); match verification_result { - VerificationResult::ConsistencyErrors(_) => (), + VerificationResultKind::ConsistencyErrors(_) => (), other => panic!("consistency errors not identified, instead found {other:?}"), } } diff --git a/viper/tests/simple_programs.rs b/viper/tests/simple_programs.rs index 798f284c63a..2f491f9bdfa 100644 --- a/viper/tests/simple_programs.rs +++ b/viper/tests/simple_programs.rs @@ -56,7 +56,7 @@ fn failure_with_assert_false() { let verification_result = verifier.verify(program); - if let VerificationResult::Failure(errors) = verification_result { + if let VerificationResultKind::Failure(errors) = verification_result { assert_eq!(errors.len(), 1); assert_eq!( errors[0].full_id, @@ -204,7 +204,7 @@ fn failure_with_assign_if_and_assert() { let verification_result = verifier.verify(program); - if let VerificationResult::Failure(errors) = verification_result { + if let VerificationResultKind::Failure(errors) = verification_result { assert_eq!(errors.len(), 1); assert_eq!( errors[0].full_id, From 4e6cc8a22920c60af8643c88129cebab62bf9378 Mon Sep 17 00:00:00 2001 From: trk Date: Wed, 12 Jun 2024 19:15:19 +0200 Subject: [PATCH 03/53] Change prusti-server workflow Port basic functionality for the new prusti-server work flow according to PR https://github.com/viperproject/prusti-dev/pull/1334 . --- Cargo.lock | 121 ++++++++++++- prusti-server/Cargo.toml | 14 +- prusti-server/src/backend.rs | 29 +++- prusti-server/src/client.rs | 103 ++++++----- prusti-server/src/lib.rs | 198 +++++++++++++++++----- prusti-server/src/process_verification.rs | 160 +++++++++++++++-- prusti-server/src/server.rs | 93 +++++----- prusti-server/src/server_message.rs | 33 ++++ prusti-server/src/verification_request.rs | 20 ++- prusti/Cargo.toml | 2 + prusti/src/callbacks.rs | 60 +++++-- prusti/src/driver.rs | 1 + prusti/src/ide_helper/fake_error.rs | 16 ++ prusti/src/verifier.rs | 26 +-- viper-sys/build.rs | 14 ++ viper/src/lib.rs | 6 +- viper/src/verification_context.rs | 4 + viper/src/verifier.rs | 11 +- 18 files changed, 722 insertions(+), 189 deletions(-) create mode 100644 prusti-server/src/server_message.rs create mode 100644 prusti/src/ide_helper/fake_error.rs diff --git a/Cargo.lock b/Cargo.lock index 2fc7185a20d..03f49bed973 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,6 +304,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "async-task" version = "4.5.0" @@ -387,6 +409,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -662,13 +693,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", ] @@ -1194,6 +1234,15 @@ dependencies = [ "hashbrown 0.14.2", ] +[[package]] +name = "input_buffer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" +dependencies = [ + "bytes", +] + [[package]] name = "instant" version = "0.1.12" @@ -1550,6 +1599,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.59" @@ -1791,6 +1846,8 @@ dependencies = [ "prusti-server", "prusti-utils", "prusti-viper", + "serde", + "serde_json", "tracing 0.1.0", "tracing-chrome", "tracing-subscriber", @@ -1855,22 +1912,31 @@ version = "0.1.0" name = "prusti-server" version = "0.1.0" dependencies = [ + "async-stream", "bincode", "clap", "env_logger", + "futures", + "futures-util", + "jni", "lazy_static", "log", "num_cpus", "once_cell", + "prusti-interface", + "prusti-rustc-interface", "prusti-utils", "prusti-viper", "reqwest", "rustc-hash", "serde", + "serde_json", "tokio", + "tokio-tungstenite 0.13.0", "tracing 0.1.0", "url", "viper", + "viper-sys", "vir", "warp", ] @@ -2304,6 +2370,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2312,7 +2391,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2323,7 +2402,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2563,6 +2642,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite 0.12.0", +] + [[package]] name = "tokio-tungstenite" version = "0.20.1" @@ -2572,7 +2664,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.20.1", ] [[package]] @@ -2725,6 +2817,25 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tungstenite" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "input_buffer", + "log", + "rand", + "sha-1", + "url", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.20.1" @@ -2977,7 +3088,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-stream", - "tokio-tungstenite", + "tokio-tungstenite 0.20.1", "tokio-util", "tower-service", "tracing 0.1.40", diff --git a/prusti-server/Cargo.toml b/prusti-server/Cargo.toml index 053afaf0784..7e510584dcc 100644 --- a/prusti-server/Cargo.toml +++ b/prusti-server/Cargo.toml @@ -20,8 +20,11 @@ doctest = false log = { version = "0.4", features = ["release_max_level_info"] } vir = { path = "../vir" } viper = { path = "../viper" } +prusti-interface = { path = "../prusti-interface" } prusti-viper = { path = "../prusti-viper" } prusti-utils = { path = "../prusti-utils" } +prusti-rustc-interface = { path = "../prusti-rustc-interface" } +viper-sys = { path = "../viper-sys" } tracing = { path = "../tracing" } env_logger = "0.10" clap = { version = "4.0", features = ["derive"] } @@ -29,11 +32,20 @@ bincode = "1.0" url = "2.2.2" num_cpus = "1.14" serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +jni = { version = "0.20", features = ["invocation"] } +# the tungstenite version used by warp 0.3 +tokio-tungstenite = "0.13" +futures = "0.3.25" +futures-util = "0.3.25" +async-stream = "0.3.3" reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } warp = "0.3" -tokio = "1.20" +# tokio = "1.20" +tokio = { version = "1.20", features = ["io-util", "net", "rt", "sync"] } rustc-hash = "1.1.0" once_cell = "1.17.1" + [dev-dependencies] lazy_static = "1.4.0" diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 3da934253a1..d0445f233a5 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -1,18 +1,32 @@ -use crate::dump_viper_program; +use crate::{dump_viper_program, ServerMessage}; +use log::{debug, info}; use prusti_utils::{ config, Stopwatch, }; -use viper::{VerificationContext, VerificationResultKind}; +use std::{ + sync::{mpsc, Arc}, + thread, time, +}; +use viper::{jni_utils::JniUtils, VerificationContext, VerificationResultKind, Viper}; +use viper_sys::wrappers::{java, viper::*}; pub enum Backend<'a> { - Viper(viper::Verifier<'a>, &'a VerificationContext<'a>), + Viper( + viper::Verifier<'a>, + &'a VerificationContext<'a>, + &'a Arc, + ), } impl<'a> Backend<'a> { - pub fn verify(&mut self, program: vir::ProgramRef) -> VerificationResultKind { + pub fn verify( + &mut self, + program: vir::ProgramRef, + sender: mpsc::Sender, + ) -> VerificationResultKind { match self { - Backend::Viper(viper, context) => { + Backend::Viper(ref mut verifier, context, viper_arc) => { let mut stopwatch = Stopwatch::start("prusti-server backend", "construction of JVM objects"); @@ -36,9 +50,10 @@ impl<'a> Backend<'a> { } stopwatch.start_next("viper verification"); - viper.verify(viper_program) + + verifier.verify(viper_program) }) } } } -} +} \ No newline at end of file diff --git a/prusti-server/src/client.rs b/prusti-server/src/client.rs index 61549e0dd80..838529c14cd 100644 --- a/prusti-server/src/client.rs +++ b/prusti-server/src/client.rs @@ -4,58 +4,75 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::VerificationRequest; +use crate::{ServerMessage, VerificationRequest}; +use futures_util::{ + sink::SinkExt, + stream::{Stream, StreamExt}, +}; use prusti_utils::config; -use reqwest::Client; -use url::{ParseError, Url}; -use viper::VerificationResult; +use tokio_tungstenite::{ + connect_async, + tungstenite::{error::Error, Message}, +}; +use url::Url; -pub struct PrustiClient { - client: Client, - server_url: Url, -} +pub struct PrustiClient; impl PrustiClient { - pub fn new(server_address: S) -> Result { + pub async fn verify( + server_address: S, + request: VerificationRequest, + ) -> impl Stream { let mut address = server_address.to_string(); - if !address.starts_with("http") { - address = format!("http://{address}"); + if !address.starts_with("ws") { + address = format!("ws://{address}"); } - Ok(Self { - client: Client::new(), - server_url: Url::parse(address.as_str())?, - }) - } - pub async fn verify( - &self, - request: VerificationRequest, - ) -> reqwest::Result { + let server_url = Url::parse(address.as_str()).unwrap(); let use_json = config::json_communication(); - let base = self.client.post( - self.server_url - .join(if use_json { "json/" } else { "bincode/" }) - .unwrap() - .join("verify/") - .unwrap(), - ); - let response = if use_json { - base.json(&request) - .send() - .await? - .error_for_status()? - .json() - .await? + + let uri = server_url + .join(if use_json { "json/" } else { "bincode/" }) + .unwrap() + .join("verify/") + .unwrap(); + let (mut socket, _) = connect_async(uri).await.unwrap(); + let msg = if use_json { + Message::text( + serde_json::to_string(&request) + .expect("error encoding verification request in json"), + ) } else { - let bytes = base - .body(bincode::serialize(&request).expect("error encoding verification request")) - .send() - .await? - .error_for_status()? - .bytes() - .await?; - bincode::deserialize(&bytes).expect("error decoding verification result") + Message::binary( + bincode::serialize(&request) + .expect("error encoding verification request as binary"), + ) + }; + socket.send(msg).await.unwrap(); + let json_map = |ws_msg| { + if let Message::Text(json) = ws_msg { + serde_json::from_str(&json).expect("error decoding verification result from json") + } else { + panic!("Invalid response from the server."); + } + }; + let bin_map = |ws_msg| { + if let Message::Binary(bytes) = ws_msg { + bincode::deserialize(&bytes).expect("error decoding verification result") + } else { + panic!("Invalid response from the server."); + } + }; + let filter_close = |msg_result: Result| async { + let msg = msg_result.unwrap(); + match msg { + Message::Close(_) => None, + _ => Some(msg), + } }; - Ok(response) + + socket + .filter_map(filter_close) + .map(if use_json { json_map } else { bin_map }) } } diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 30a62485943..c03bdae7322 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -5,15 +5,32 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![warn(clippy::disallowed_types)] +#![feature(rustc_private)] -use log::info; +// use log::info; +use ::log::{debug, error, info}; +use crate::{ + spawn_server_thread, PrustiClient, ServerMessage, VerificationRequest, + VerificationRequestProcessing, ViperBackendConfig, +}; use prusti_utils::{config, Stopwatch}; -use viper::{PersistentCache, Viper}; +use prusti_interface::{ + data::{VerificationResult, VerificationTask}, + environment::EnvDiagnostic, + specs::typed, + PrustiError, +}; +use prusti_rustc_interface::{ + span::DUMMY_SP, + errors::MultiSpan, +}; +use viper::{self, PersistentCache, Viper}; use once_cell::sync::Lazy; mod client; mod process_verification; mod server; +mod server_message; mod verification_request; mod backend; @@ -21,14 +38,23 @@ pub use backend::*; pub use client::*; pub use process_verification::*; pub use server::*; +pub use server_message::*; pub use verification_request::*; // Futures returned by `Client` need to be executed in a compatible tokio runtime. pub use tokio; +use tokio::runtime::Builder; +use rustc_hash::FxHashMap; +use serde_json::json; +use async_stream::stream; +use futures_util::{pin_mut, Stream, StreamExt}; /// Verify a list of programs. /// Returns a list of (program_name, verification_result) tuples. -pub fn verify_programs(programs: Vec) -> Vec<(String, viper::VerificationResult)> { +pub fn verify_programs( + env_diagnostic: &EnvDiagnostic<'_>, + programs: Vec + ) -> VerificationResult { let verification_requests = programs.into_iter().map(|mut program| { let rust_program_name = program.get_rust_name().to_string(); let program_name = program.get_name().to_string(); @@ -41,47 +67,133 @@ pub fn verify_programs(programs: Vec) -> Vec<(String, viper::Ve }; (program_name, request) }); - if let Some(server_address) = config::server_address() { - let server_address = if server_address == "MOCK" { - spawn_server_thread().to_string() + + let mut stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); + // runtime used either for client connecting to server sequentially + // or to sequentially verify the requests -> single thread is sufficient + // why single thread if everything is asynchronous? VerificationRequestProcessing in + // prusti-server/src/process_verification.rs only has a single thread and the underlying + // viper instance already uses as many cores as possible + let rt = Builder::new_current_thread() + .thread_name("prusti-viper") + .enable_all() + .build() + .expect("failed to construct Tokio runtime"); + + let overall_result = rt.block_on(async { + if let Some(server_address) = config::server_address() { + let verification_messages = verify_requests_server(verification_requests.collect(), server_address); + handle_stream(env_diagnostic, verification_messages).await } else { - server_address - }; - info!("Connecting to Prusti server at {}", server_address); - let client = PrustiClient::new(&server_address).unwrap_or_else(|error| { - panic!("Could not parse server address ({server_address}) due to {error:?}") - }); - // Here we construct a Tokio runtime to block until completion of the futures returned by - // `client.verify`. However, to report verification errors as early as possible, - // `verify_programs` should return an asynchronous stream of verification results. - let runtime = tokio::runtime::Builder::new_current_thread() - .thread_name("prusti-viper") - .enable_all() - .build() - .expect("failed to construct Tokio runtime"); - verification_requests - .map(|(program_name, request)| { - let remote_result = runtime.block_on(client.verify(request)); - let result = remote_result.unwrap_or_else(|error| { - panic!("Verification request of program {program_name} failed: {error:?}") - }); - (program_name, result) - }) - .collect() - } else { - let mut stopwatch = Stopwatch::start("prusti-viper", "JVM startup"); - stopwatch.start_next("attach current thread to the JVM"); - let viper = - Lazy::new(|| Viper::new_with_args(&config::viper_home(), config::extra_jvm_args())); - let viper_thread = Lazy::new(|| viper.attach_current_thread()); - stopwatch.finish(); + let vrp = VerificationRequestProcessing::new(); + let verification_messages = verify_requests_local(verification_requests.collect(), &vrp); + handle_stream(env_diagnostic, verification_messages).await + } + }); + stopwatch.finish(); + overall_result +} + +async fn handle_stream( + env_diagnostic: &EnvDiagnostic<'_>, + verification_messages: impl Stream, +) -> VerificationResult { + let mut overall_result = VerificationResult::Success; + let mut prusti_errors: Vec<_> = vec![]; + + pin_mut!(verification_messages); + + while let Some((program_name, server_msg)) = verification_messages.next().await { + match server_msg { + ServerMessage::Termination(result) => handle_termination_message(env_diagnostic, program_name, result, &mut prusti_errors, &mut overall_result), + } + } + + overall_result +} - let mut cache = PersistentCache::load_cache(config::cache_path()); - verification_requests - .map(|(program_name, request)| { - let result = process_verification_request(&viper_thread, request, &mut cache); - (program_name, result) - }) - .collect() +fn handle_termination_message( + env_diagnostic: &EnvDiagnostic<'_>, + // &self, + program_name: String, + result: viper::VerificationResult, + prusti_errors: &mut Vec, + overall_result: &mut VerificationResult +) { + debug!("Received termination message with result {result:?} in verification of {program_name}"); + match result.kind { + // nothing to do + viper::VerificationResultKind::Success => (), + viper::VerificationResultKind::ConsistencyErrors(errors) => { + for error in errors { + PrustiError::internal( + format!("consistency error in {program_name}: {error:?}"), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + } + *overall_result = VerificationResult::Failure; + } + viper::VerificationResultKind::Failure(errors) => { + for verification_error in errors { + debug!( + "Verification error in {}: {:?}", + program_name.clone(), + verification_error + ); + // temporary error emition, delete when verification errors can be backtranslated again + env_diagnostic.span_err_with_help_and_notes( + MultiSpan::new(), + &format!( + "Verification error in {}: {:?}", + program_name.clone(), + verification_error + ), + &None, + &[], + ); + } + *overall_result = VerificationResult::Failure; + } + viper::VerificationResultKind::JavaException(exception) => { + error!("Java exception: {}", exception.get_stack_trace()); + PrustiError::internal( + format!("in {program_name}: {exception}"), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + // .emit(&self.env.diagnostic); + *overall_result = VerificationResult::Failure; + } } } + +fn verify_requests_server( + verification_requests: Vec<(String, VerificationRequest)>, + server_address: String, +) -> impl Stream { + let server_address = if server_address == "MOCK" { + spawn_server_thread().to_string() + } else { + server_address + }; + info!("Connecting to Prusti server at {}", server_address); + let verification_stream = stream! { + for (program_name, request) in verification_requests { + yield PrustiClient::verify(server_address.clone(), request).await.map(move |msg| (program_name.clone(), msg)); + } + }; + verification_stream.flatten() +} + +fn verify_requests_local<'a>( + verification_requests: Vec<(String, VerificationRequest)>, + vrp: &'a VerificationRequestProcessing, +) -> impl Stream + 'a { + let verification_stream = stream! { + for (program_name, request) in verification_requests { + yield vrp.verify(request).map(move |msg| (program_name.clone(), msg)); + } + }; + verification_stream.flatten() +} diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 1e7a687d754..c572711d064 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -4,25 +4,148 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{Backend, VerificationRequest, ViperBackendConfig}; -use log::info; +use crate::{Backend, ServerMessage, VerificationRequest, ViperBackendConfig}; +use futures::{lock, stream::Stream}; +use log::{debug, info}; use once_cell::sync::Lazy; use prusti_utils::{ config, report::log::{report, to_legal_file_name}, Stopwatch, }; -use std::{fs::create_dir_all, path::PathBuf}; +use std::{ + fs::create_dir_all, + path::PathBuf, + sync::{self, mpsc, Arc}, + thread, +}; use viper::{ - smt_manager::SmtManager, Cache, VerificationBackend, VerificationContext, VerificationResult, VerificationResultKind + smt_manager::SmtManager, Cache, PersistentCache, VerificationBackend, VerificationContext, + VerificationResult, VerificationResultKind, Viper, }; +enum ServerRequest { + Verification(VerificationRequest), + SaveCache, +} + +struct ThreadJoin { + handle: Option>, +} + +// we join the thread after dropping the sender for the ServerRequests, so +// that the verification thread actually terminates +impl Drop for ThreadJoin { + fn drop(&mut self) { + self.handle.take().unwrap().join().unwrap(); + } +} + +pub struct VerificationRequestProcessing { + mtx_rx_servermsg: lock::Mutex>, + mtx_tx_verreq: sync::Mutex>, + // mtx_tx_verreq has to be dropped before thread_join + #[allow(dead_code)] + thread_join: ThreadJoin, +} + +impl Default for VerificationRequestProcessing { + fn default() -> Self { + Self::new() + } +} + +/// A structure that lives for all the requests and has a single thread working on all the +/// requests sequentially. +/// On reception of a verification request, we send it through a channel to the already running +/// thread. +impl VerificationRequestProcessing { + pub fn new() -> Self { + let (tx_servermsg, rx_servermsg) = mpsc::channel(); + let (tx_verreq, rx_verreq) = mpsc::channel(); + let mtx_rx_servermsg = lock::Mutex::new(rx_servermsg); + let mtx_tx_verreq = sync::Mutex::new(tx_verreq); + + let handle = thread::spawn(move || verification_thread(rx_verreq, tx_servermsg)); + Self { + mtx_rx_servermsg, + mtx_tx_verreq, + thread_join: ThreadJoin { + handle: Some(handle), + }, + } + } + + pub fn verify(&self, request: VerificationRequest) -> impl Stream + '_ { + self.mtx_tx_verreq + .lock() + .unwrap() + .send(ServerRequest::Verification(request)) + .unwrap(); + // return a stream that has as last non-None message the ServerMessage::Termination + futures::stream::unfold(false, move |done: bool| async move { + if done { + return None; + } + let msg = self.mtx_rx_servermsg.lock().await.recv().unwrap(); + let mut done = false; + if let ServerMessage::Termination(_) = msg { + done = true; + } + Some((msg, done)) + }) + } + + pub fn save_cache(&self) { + self.mtx_tx_verreq + .lock() + .unwrap() + .send(ServerRequest::SaveCache) + .unwrap(); + } +} + +fn verification_thread( + rx_verreq: mpsc::Receiver, + tx_servermsg: mpsc::Sender, +) { + debug!("Verification thread started."); + // The vcx is thread local, so we need to initialize it anew for this thread + vir::init_vcx(vir::VirCtxt::new_without_tcx()); + let viper = Lazy::new(|| { + Arc::new(Viper::new_with_args( + &config::viper_home(), + config::extra_jvm_args(), + )) + }); + let viper_thread = Lazy::new(|| viper.attach_current_thread()); + let mut cache = PersistentCache::load_cache(config::cache_path()); + + while let Ok(request) = rx_verreq.recv() { + match request { + ServerRequest::Verification(verification_request) => process_verification_request( + &viper, + &viper_thread, + &mut cache, + &tx_servermsg, + verification_request, + ), + ServerRequest::SaveCache => cache.save(), + } + } + debug!("Verification thread finished."); +} + #[tracing::instrument(level = "debug", skip_all, fields(program = %request.program.get_name()))] pub fn process_verification_request<'v, 't: 'v>( + viper_arc: &Lazy, impl Fn() -> Arc>, verification_context: &'v Lazy, impl Fn() -> VerificationContext<'t>>, - request: VerificationRequest, cache: impl Cache, -) -> viper::VerificationResult { + sender: &mpsc::Sender, + mut request: VerificationRequest, +) { + // TODO: if I'm not mistaken, this currently triggers the creation of the JVM on every request, + // so this should be changed once there are actually backends that not use a JVM /* let ast_utils = verification_context.new_ast_utils(); @@ -79,12 +202,18 @@ pub fn process_verification_request<'v, 't: 'v>( let _ = build_or_dump_viper_program(); }); } - return viper::VerificationResult::Success; + // return viper::VerificationResult::Success; + sender + .send(ServerMessage::Termination( + VerificationResult::dummy_success(), + )) + .unwrap(); + return; } */ // Early return in case of cache hit if config::enable_cache() { - if let Some(result) = cache.get(hash) { + if let Some(mut result) = cache.get(hash) { info!( "Using cached result {:?} for program {}", &result, @@ -96,7 +225,9 @@ pub fn process_verification_request<'v, 't: 'v>( }); } normalization_info.denormalize_result(&mut result);*/ - return result; + result.cached = true; + sender.send(ServerMessage::Termination(result)).unwrap(); + return; } }; @@ -112,19 +243,18 @@ pub fn process_verification_request<'v, 't: 'v>( request.backend_config, ), verification_context, + Lazy::force(viper_arc), ), }; - stopwatch.start_next("backend verification"); - // let result = backend.verify(request.program); let mut result = VerificationResult { item_name: request.program.get_name().to_string(), kind: VerificationResultKind::Success, cached: false, time_ms: 0, }; - // result.kind = backend.verify(request.program, sender.clone()); - result.kind = backend.verify(request.program); + stopwatch.start_next("backend verification"); + result.kind = backend.verify(request.program, sender.clone()); result.time_ms = stopwatch.finish().as_millis(); // Don't cache Java exceptions, which might be due to misconfigured paths. @@ -137,8 +267,8 @@ pub fn process_verification_request<'v, 't: 'v>( cache.insert(hash, result.clone()); } - /*normalization_info.denormalize_result(&mut result);*/ - result + /*normalization_info.denormalize_result(&mut result.kind);*/ + sender.send(ServerMessage::Termination(result)).unwrap(); } pub fn dump_viper_program( diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index 3f412ffde84..2f32a20ec6f 100644 --- a/prusti-server/src/server.rs +++ b/prusti-server/src/server.rs @@ -4,13 +4,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{process_verification_request, VerificationRequest}; +use crate::VerificationRequestProcessing; +use futures_util::{pin_mut, SinkExt, StreamExt}; use log::info; use once_cell::sync::Lazy; -use prusti_utils::{config, Stopwatch}; use std::{ net::{Ipv4Addr, SocketAddr}, - sync::{mpsc, Arc, Mutex}, + sync::mpsc, thread, }; use tokio::runtime::Builder; @@ -42,64 +42,81 @@ pub fn spawn_server_thread() -> SocketAddr { receiver.recv().unwrap() } +// This VerificationRequestProcessing object is starting the verification thread (for more details +// see the file process_verification.rs). +// It has to have a static lifetime because warp websockets need their closures to have a static +// lifetime and we need to access this object in them. +static VERIFICATION_REQUEST_PROCESSING: Lazy = + Lazy::new(VerificationRequestProcessing::new); + fn listen_on_port_with_address_callback(port: u16, address_callback: F) -> ! where F: FnOnce(SocketAddr), { - let stopwatch = Stopwatch::start("prusti-server", "JVM startup"); - let viper = Arc::new(Lazy::new(|| { - Viper::new_with_args(&config::viper_home(), config::extra_jvm_args()) - })); - - stopwatch.finish(); - - let cache_data = PersistentCache::load_cache(config::cache_path()); - let cache = Arc::new(Mutex::new(cache_data)); fn init_vcx(data: T) -> T { // initialise a new arena every time, so the data from previous // verification runs is deallocated vir::init_vcx(vir::VirCtxt::new_without_tcx()); data } - let build_verification_request_handler = |viper_arc: Arc>, cache| { - move |request: VerificationRequest| { - let stopwatch = Stopwatch::start("prusti-server", "attach thread to JVM"); - let viper_thread = Lazy::new(|| viper_arc.attach_current_thread()); - stopwatch.finish(); - process_verification_request(&viper_thread, request, &cache) - } - }; let json_verify = warp::path!("json" / "verify") - .and(warp::body::json()) + .and(warp::filters::ws::ws()) + // unsure why these are needed since the thread encoding the viper program is a different one .map(init_vcx) - .map(build_verification_request_handler( - viper.clone(), - cache.clone(), - )) - .map(|response| warp::reply::json(&response)); + .map(|ws: warp::filters::ws::Ws| { + ws.on_upgrade(|websocket| async { + let (mut ws_send, mut ws_recv) = websocket.split(); + let req_msg = ws_recv.next().await.unwrap().unwrap(); + let verification_request = req_msg + .to_str() + .and_then(|s: &str| serde_json::from_str(s).unwrap()) + .unwrap(); + let stream = VERIFICATION_REQUEST_PROCESSING.verify(verification_request); + pin_mut!(stream); + while let Some(server_msg) = stream.next().await { + ws_send + .send(warp::filters::ws::Message::text( + serde_json::to_string(&server_msg).unwrap(), + )) + .await + .unwrap(); + } + ws_send.close().await.unwrap(); + // receive the client close to complete the handshake + ws_recv.next().await.unwrap().unwrap(); + }) + }); let bincode_verify = warp::path!("bincode" / "verify") - .and(warp::body::bytes()) + .and(warp::filters::ws::ws()) .map(init_vcx) - .and_then(|buf: warp::hyper::body::Bytes| async move { - bincode::deserialize(&buf).map_err(|err| { - info!("request bincode body error: {}", err); - warp::reject::custom(BincodeReject(err)) + .map(|ws: warp::filters::ws::Ws| { + ws.on_upgrade(|websocket| async { + let (mut ws_send, mut ws_recv) = websocket.split(); + let req_msg = ws_recv.next().await.unwrap().unwrap(); + let verification_request = bincode::deserialize(req_msg.as_bytes()).unwrap(); + let stream = VERIFICATION_REQUEST_PROCESSING.verify(verification_request); + pin_mut!(stream); + while let Some(server_msg) = stream.next().await { + ws_send + .send(warp::filters::ws::Message::binary( + bincode::serialize(&server_msg).unwrap(), + )) + .await + .unwrap(); + } + ws_send.close().await.unwrap(); + // receive the client close to complete the handshake + ws_recv.next().await.unwrap().unwrap(); }) - }) - .map(build_verification_request_handler(viper, cache.clone())) - .map(|result| { - warp::http::Response::new( - bincode::serialize(&result).expect("could not encode verification result"), - ) }); let save_cache = warp::post() .and(warp::path("save")) .and(warp::path::end()) .map(move || { - cache.lock().unwrap().save(); + VERIFICATION_REQUEST_PROCESSING.save_cache(); warp::reply::html("Saved") }); diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs new file mode 100644 index 00000000000..5cbdda94a2f --- /dev/null +++ b/prusti-server/src/server_message.rs @@ -0,0 +1,33 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use viper::VerificationResult; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +/// A message from a Prusti server to a Prusti client. It may contain a result +/// of a verification or anything a server might send to a client. +pub enum ServerMessage { + /// Contains the result of a verification and signals that this verification + /// has terminated. + Termination(VerificationResult), + + // /// A message created by the Viper backend with Z3 about + // /// the number of instantiations of the named quantifier. + // QuantifierInstantiation { + // q_name: String, + // insts: u64, + // pos_id: u64, + // }, + + // /// Also created by the Viper backend. The viper_quant is the expression the + // /// quantifier was translated to in silver syntax while the triggers are the + // /// triggers provided or automatically derived for this quantifier. + // QuantifierChosenTriggers { + // viper_quant: String, + // triggers: String, + // pos_id: u64, + // }, +} \ No newline at end of file diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index dc73215db10..17222b410f1 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -49,16 +49,18 @@ impl ViperBackendConfig { "--assertTimeout".to_string(), config::assert_timeout().to_string(), "--proverConfigArgs".to_string(), - // model.partial changes the default case of functions in counterexamples - // to #unspecified - format!( - "smt.qi.eager_threshold={} model.partial={}", - config::smt_qi_eager_threshold(), - config::counterexample() - ), - "--logLevel".to_string(), - "ERROR".to_string(), ]); + // model.partial changes the default case of functions in counterexamples + // to #unspecified + let mut prover_args = format!( + "smt.qi.eager_threshold={} model.partial={}", + config::smt_qi_eager_threshold(), + config::counterexample() + ); + + verifier_args.push(prover_args); + + verifier_args.extend(vec!["--logLevel".to_string(), "ERROR".to_string()]); if let Some(check_timeout) = config::check_timeout() { verifier_args.push("--checkTimeout".to_string()); diff --git a/prusti/Cargo.toml b/prusti/Cargo.toml index 6f76df4c703..040a5b6a080 100644 --- a/prusti/Cargo.toml +++ b/prusti/Cargo.toml @@ -20,6 +20,8 @@ prusti-viper = { path = "../prusti-viper" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } log = { version = "0.4", features = ["release_max_level_info"] } lazy_static = "1.4.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tracing = { path = "../tracing" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-chrome = "0.7" diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index bddd1bb9fd3..af4a7eb9e8a 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,9 +1,14 @@ -use crate::verifier::verify; +use crate::{ + ide_helper::fake_error::fake_error, + verifier::verify, +}; use mir_state_analysis::test_free_pcs; use prusti_utils::config; use prusti_interface::{ + data::VerificationTask, environment::{mir_storage, Environment}, specs::{self, cross_crate::CrossCrateSpecs, is_spec_fn}, + PrustiError, }; use prusti_rustc_interface::{ borrowck::consumers, @@ -22,6 +27,7 @@ use prusti_rustc_interface::{ ty::TyCtxt, }, session::{EarlyErrorHandler, Session}, + span::DUMMY_SP, }; #[derive(Default)] @@ -161,20 +167,46 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { } } CrossCrateSpecs::import_export_cross_crate(&mut env, &mut def_spec); - if !config::no_verify() { - /* - if config::test_free_pcs() { - for proc_id in env.get_annotated_procedures_and_types().0.iter() { - let name = env.name.get_unique_item_name(*proc_id); - println!("Calculating FPCS for: {name}"); + // if !config::no_verify() { + // /* + // if config::test_free_pcs() { + // for proc_id in env.get_annotated_procedures_and_types().0.iter() { + // let name = env.name.get_unique_item_name(*proc_id); + // println!("Calculating FPCS for: {name}"); + + // let current_procedure = env.get_procedure(*proc_id); + // let mir = current_procedure.get_mir_rc(); + // test_free_pcs(&mir, tcx); + // } + // } else {*/ + // verify(env, def_spec); + // //} + // } + + // TODO: can we replace `get_annotated_procedures` with information + // that is already in `def_spec`? + let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); - let current_procedure = env.get_procedure(*proc_id); - let mir = current_procedure.get_mir_rc(); - test_free_pcs(&mir, tcx); - } - } else {*/ - verify(env, def_spec); - //} + // if config::show_ide_info() && !config::no_verify() { + // let compiler_info = + // compiler_info::IdeInfo::collect(&env, &annotated_procedures, &def_spec); + // let out = serde_json::to_string(&compiler_info).unwrap(); + // PrustiError::message(format!("compilerInfo{out}"), DUMMY_SP.into()) + // .emit(&env.diagnostic); + // } + // as long as we have to throw a fake error we need to check this + let is_primary_package = std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); + + // collect and output Information used by IDE: + if !config::no_verify() { + let verification_task = VerificationTask { + procedures: annotated_procedures, + types, + }; + verify(env, def_spec, verification_task); + } else if !config::no_verify() && is_primary_package { + // add a fake error, reason explained in issue #1261 + fake_error(&env); } }); diff --git a/prusti/src/driver.rs b/prusti/src/driver.rs index d606454aa9a..02a8cdac644 100644 --- a/prusti/src/driver.rs +++ b/prusti/src/driver.rs @@ -12,6 +12,7 @@ mod arg_value; mod callbacks; mod verifier; +mod ide_helper; use arg_value::arg_value; use callbacks::PrustiCompilerCalls; diff --git a/prusti/src/ide_helper/fake_error.rs b/prusti/src/ide_helper/fake_error.rs new file mode 100644 index 00000000000..d33919cdaa3 --- /dev/null +++ b/prusti/src/ide_helper/fake_error.rs @@ -0,0 +1,16 @@ +use prusti_interface::environment::Environment; +use prusti_rustc_interface::{errors::MultiSpan, span::DUMMY_SP}; + +/// This error will be thrown when skipping verification (which is done +/// when we just collect information for prusti-assistant), because then +/// a successful result will be cached and subsequent actual verifications succeed +/// (see issue #1261) +pub fn fake_error(env: &Environment) { + let sp = MultiSpan::from_span(DUMMY_SP); + let message = String::from("[Prusti: FakeError]"); + let help = None; + let notes = []; + + env.diagnostic + .span_err_with_help_and_notes(sp, &message, &help, ¬es); +} \ No newline at end of file diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 1b4f37267e5..6834ed4f685 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -10,18 +10,22 @@ use prusti_interface::{ use prusti_rustc_interface::errors::MultiSpan; #[tracing::instrument(name = "prusti::verify", level = "debug", skip(env))] -pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { +pub fn verify<'tcx>( + env: Environment<'tcx>, + def_spec: typed::DefSpecificationMap, + verification_task: VerificationTask<'tcx>, +) { if env.diagnostic.has_errors() { warn!("The compiler reported an error, so the program will not be verified."); } else { debug!("Prepare verification task..."); - // TODO: can we replace `get_annotated_procedures` with information - // that is already in `def_spec`? - let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); - let verification_task = VerificationTask { - procedures: annotated_procedures, - types, - }; + // // TODO: can we replace `get_annotated_procedures` with information + // // that is already in `def_spec`? + // let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); + // let verification_task = VerificationTask { + // procedures: annotated_procedures, + // types, + // }; debug!("Verification task: {:?}", &verification_task); user::message(format!( @@ -53,9 +57,9 @@ pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { ); let program = request.program; - let results = prusti_server::verify_programs(vec![program]); - println!("verification results: {results:?}"); - if !results.iter().all(|(_, r)| matches!(r.kind, viper::VerificationResultKind::Success)) { + let result = prusti_server::verify_programs(&env.diagnostic, vec![program]); + println!("verification result: {result:?}"); + if !matches!(result, VerificationResult::Success) { // TODO: This will be unnecessary if diagnostic errors are emitted // earlier, it's useful for now to ensure that Prusti returns an // error code when verification fails diff --git a/viper-sys/build.rs b/viper-sys/build.rs index 6d8dfd70785..75438f39952 100644 --- a/viper-sys/build.rs +++ b/viper-sys/build.rs @@ -158,6 +158,19 @@ fn main() { java_class!("viper.silver.reporter.NoopReporter$", vec![ object_getter!(), ]), + java_class!("viper.silver.reporter.PollingReporter", vec![ + constructor!("(Ljava/lang/String;Lviper/silver/reporter/Reporter;)V"), + method!("hasNewMessage"), + method!("getNewMessage"), + ]), + // java_class!("viper.silver.reporter.QuantifierChosenTriggersMessage", vec![ + // method!("quantifier"), + // method!("triggers_string"), + // ]), + // java_class!("viper.silver.reporter.QuantifierInstantiationsMessage", vec![ + // method!("quantifier"), + // method!("instantiations"), + // ]), java_class!("viper.silver.verifier.Verifier", vec![ method!("name"), method!("buildVersion"), @@ -165,6 +178,7 @@ fn main() { method!("start"), method!("stop"), method!("verify"), + method!("reporter"), ]), java_class!("viper.silver.frontend.DefaultStates", vec![ method!("ConsistencyCheck"), diff --git a/viper/src/lib.rs b/viper/src/lib.rs index 747f7fc1261..a46f8f0b7a1 100644 --- a/viper/src/lib.rs +++ b/viper/src/lib.rs @@ -10,11 +10,13 @@ mod ast_factory; mod ast_utils; pub mod errors; -mod jni_utils; +// used by prusti-server +pub mod jni_utils; #[macro_use] pub mod utils; mod cache; -mod java_exception; +// used by prusti-server +pub mod java_exception; pub mod silicon_counterexample; pub mod smt_manager; mod verification_backend; diff --git a/viper/src/verification_context.rs b/viper/src/verification_context.rs index 2537431c837..09d44ac535a 100644 --- a/viper/src/verification_context.rs +++ b/viper/src/verification_context.rs @@ -25,6 +25,10 @@ impl<'a> VerificationContext<'a> { VerificationContext { env: env_guard } } + pub fn env(&self) -> &AttachGuard<'a> { + &self.env + } + pub fn new_ast_factory(&self) -> AstFactory { AstFactory::new(&self.env) } diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index 467898241a8..f3d17bac8ea 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -42,7 +42,7 @@ impl<'a> Verifier<'a> { let frontend_wrapper = silver::frontend::SilFrontend::with(env); let frontend_instance = jni.unwrap_result(env.with_local_frame(16, || { - let reporter = if let Some(real_report_path) = report_path { + let pass_through_reporter = if let Some(real_report_path) = report_path { jni.unwrap_result(silver::reporter::CSVReporter::with(env).new( jni.new_string("csv_reporter"), jni.new_string(real_report_path.to_str().unwrap()), @@ -51,6 +51,11 @@ impl<'a> Verifier<'a> { jni.unwrap_result(silver::reporter::NoopReporter_object::with(env).singleton()) }; + let reporter = jni.unwrap_result( + silver::reporter::PollingReporter::with(env) + .new(jni.new_string("polling_reporter"), pass_through_reporter), + ); + let debug_info = jni.new_seq(&[]); let backend_unwrapped = Self::instantiate_verifier(backend, env, reporter, debug_info); @@ -405,6 +410,10 @@ impl<'a> Verifier<'a> { } }) } + + pub fn verifier_instance(&self) -> &JObject<'a> { + &self.verifier_instance + } } impl<'a> Drop for Verifier<'a> { From 0849d356fec9df9a570738efd578f62cc67a4930 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 13 Jun 2024 13:20:29 +0200 Subject: [PATCH 04/53] Port `show_ide_info` and `skip_verification` flags Querying signatures, spans of call contract collection and call finding does not work yet. PR: https://github.com/viperproject/prusti-dev/pull/1334 --- .../src/environment/diagnostic.rs | 9 ++ prusti-interface/src/prusti_error.rs | 33 +++++-- prusti-server/src/ide/encoding_info.rs | 14 +++ .../src/ide/ide_verification_result.rs | 29 ++++++ prusti-server/src/ide/mod.rs | 3 + prusti-server/src/ide/vsc_span.rs | 34 +++++++ prusti-server/src/lib.rs | 46 +++++++++- prusti-utils/src/config.rs | 19 ++++ prusti/src/callbacks.rs | 20 ++-- prusti/src/ide_helper/compiler_info.rs | 92 +++++++++++++++++++ prusti/src/ide_helper/mod.rs | 2 + 11 files changed, 277 insertions(+), 24 deletions(-) create mode 100644 prusti-server/src/ide/encoding_info.rs create mode 100644 prusti-server/src/ide/ide_verification_result.rs create mode 100644 prusti-server/src/ide/mod.rs create mode 100644 prusti-server/src/ide/vsc_span.rs create mode 100644 prusti/src/ide_helper/compiler_info.rs create mode 100644 prusti/src/ide_helper/mod.rs diff --git a/prusti-interface/src/environment/diagnostic.rs b/prusti-interface/src/environment/diagnostic.rs index 6991e6141a9..8b1fe91cfd9 100644 --- a/prusti-interface/src/environment/diagnostic.rs +++ b/prusti-interface/src/environment/diagnostic.rs @@ -78,6 +78,15 @@ impl<'tcx> EnvDiagnostic<'tcx> { diagnostic.buffer(&mut self.warn_buffer.borrow_mut()); } + /// Emits a note + pub fn span_note + Clone>( + &self, + sp: S, + msg: &str + ) { + self.tcx.sess.span_note_without_error(sp, msg.to_string()); + } + /// Returns true if an error has been emitted pub fn has_errors(&self) -> bool { self.tcx.sess.has_errors().is_some() diff --git a/prusti-interface/src/prusti_error.rs b/prusti-interface/src/prusti_error.rs index 505259a5761..d6bd3513223 100644 --- a/prusti-interface/src/prusti_error.rs +++ b/prusti-interface/src/prusti_error.rs @@ -20,7 +20,7 @@ use prusti_rustc_interface::{errors::MultiSpan, span::Span}; /// `SpannedEncodingError` and similar types to something less confusing.) #[derive(Clone, Debug, PartialEq, Eq)] pub struct PrustiError { - kind: PrustiErrorKind, + kind: PrustiDiagnosticKind, /// If `true`, it should not be reported to the user. We need this in cases /// when the same error could be reported twice. /// @@ -35,11 +35,12 @@ pub struct PrustiError { } /// Determines how a `PrustiError` is reported. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum PrustiErrorKind { +pub enum PrustiDiagnosticKind { Error, Warning, /// A warning which is only shown if at least one error is emitted. WarningOnError, + Message, } impl PartialOrd for PrustiError { @@ -60,7 +61,7 @@ impl PrustiError { /// Private constructor. Use one of the following methods. fn new(message: String, span: MultiSpan) -> Self { PrustiError { - kind: PrustiErrorKind::Error, + kind: PrustiDiagnosticKind::Error, is_disabled: false, message, span: Box::new(span), @@ -114,7 +115,7 @@ impl PrustiError { pub fn warning(message: S, span: MultiSpan) -> Self { check_message(message.to_string()); let mut err = PrustiError::new(format!("[Prusti: warning] {}", message.to_string()), span); - err.kind = PrustiErrorKind::Warning; + err.kind = PrustiDiagnosticKind::Warning; err } @@ -123,7 +124,7 @@ impl PrustiError { pub fn warning_on_error(message: S, span: MultiSpan) -> Self { check_message(message.to_string()); let mut err = PrustiError::new(format!("[Prusti: warning] {}", message.to_string()), span); - err.kind = PrustiErrorKind::WarningOnError; + err.kind = PrustiDiagnosticKind::WarningOnError; err } @@ -146,13 +147,21 @@ impl PrustiError { error } + /// Report a message + pub fn message(message: S, span: MultiSpan) -> Self { + check_message(message.to_string()); + let mut msg = PrustiError::new(message.to_string(), span); + msg.kind = PrustiDiagnosticKind::Message; + msg + } + /// Set that this Prusti error should be reported as a warning to the user pub fn set_warning(&mut self) { - self.kind = PrustiErrorKind::Warning; + self.kind = PrustiDiagnosticKind::Warning; } pub fn is_error(&self) -> bool { - matches!(self.kind, PrustiErrorKind::Error) + matches!(self.kind, PrustiDiagnosticKind::Error) } // FIXME: This flag is a temporary workaround for having duplicate errors @@ -185,24 +194,28 @@ impl PrustiError { pub fn emit(self, env_diagnostic: &EnvDiagnostic) { assert!(!self.is_disabled); match self.kind { - PrustiErrorKind::Error => env_diagnostic.span_err_with_help_and_notes( + PrustiDiagnosticKind::Error => env_diagnostic.span_err_with_help_and_notes( *self.span, &self.message, &self.help, &self.notes, ), - PrustiErrorKind::Warning => env_diagnostic.span_warn_with_help_and_notes( + PrustiDiagnosticKind::Warning => env_diagnostic.span_warn_with_help_and_notes( *self.span, &self.message, &self.help, &self.notes, ), - PrustiErrorKind::WarningOnError => env_diagnostic.span_warn_on_err_with_help_and_notes( + PrustiDiagnosticKind::WarningOnError => env_diagnostic.span_warn_on_err_with_help_and_notes( *self.span, &self.message, &self.help, &self.notes, ), + PrustiDiagnosticKind::Message => env_diagnostic.span_note( + *self.span, + &self.message + ), }; } diff --git a/prusti-server/src/ide/encoding_info.rs b/prusti-server/src/ide/encoding_info.rs new file mode 100644 index 00000000000..e49abec9032 --- /dev/null +++ b/prusti-server/src/ide/encoding_info.rs @@ -0,0 +1,14 @@ +use serde::Serialize; +use super::vsc_span::VscSpan; + +#[derive(Serialize)] +pub struct EncodingInfo { + pub call_contract_spans: String, +} + +impl EncodingInfo { + pub fn to_json_string(self) -> String { + serde_json::to_string(&self).unwrap() + } +} + diff --git a/prusti-server/src/ide/ide_verification_result.rs b/prusti-server/src/ide/ide_verification_result.rs new file mode 100644 index 00000000000..378dc0a9bd4 --- /dev/null +++ b/prusti-server/src/ide/ide_verification_result.rs @@ -0,0 +1,29 @@ +use serde::Serialize; +use viper::VerificationResult; + +/// Generated for each verification item, containing information +/// about the result of the verification. This information will be emitted +/// if the show_ide_info flag is set, and it's purpose is to be +/// consumed by prusti-assistant. +#[derive(Serialize)] +pub struct IdeVerificationResult { + /// the name / defpath of the method + item_name: String, + /// whether the verification of that method was successful + success: bool, + /// how long the verification took + time_ms: u128, + /// whether this result was cached or is fresh + cached: bool, +} + +impl From<&VerificationResult> for IdeVerificationResult { + fn from(res: &VerificationResult) -> Self { + Self { + item_name: res.item_name.clone(), + success: res.is_success(), + time_ms: res.time_ms, + cached: res.cached, + } + } +} diff --git a/prusti-server/src/ide/mod.rs b/prusti-server/src/ide/mod.rs new file mode 100644 index 00000000000..08c75998eb4 --- /dev/null +++ b/prusti-server/src/ide/mod.rs @@ -0,0 +1,3 @@ +pub mod ide_verification_result; +pub mod encoding_info; +pub mod vsc_span; \ No newline at end of file diff --git a/prusti-server/src/ide/vsc_span.rs b/prusti-server/src/ide/vsc_span.rs new file mode 100644 index 00000000000..e452e74bec1 --- /dev/null +++ b/prusti-server/src/ide/vsc_span.rs @@ -0,0 +1,34 @@ +use prusti_rustc_interface::span::{source_map::SourceMap, Span}; +use serde::Serialize; + +/// a representation of spans that is more usable with VSCode. +#[derive(Serialize, Clone)] +pub struct VscSpan { + column_end: usize, + column_start: usize, + line_end: usize, + line_start: usize, + file_name: String, + is_primary: bool, +} + +impl VscSpan { + pub fn from_span(sp: &Span, sourcemap: &SourceMap) -> Self { + let span_filename = sourcemap.span_to_filename(*sp); + let diag_filename = sourcemap.filename_for_diagnostics(&span_filename); + let file_name = format!("{diag_filename}"); + + let (l1, l2) = sourcemap.is_valid_span(*sp).expect("Invalid span"); + Self { + column_start: l1.col.0, + column_end: l2.col.0, + line_start: l1.line, + line_end: l2.line, + file_name, + // the following one is not relevant here, we just want to be + // able to reuse the existing methods and the parser + // for spans in VSCode + is_primary: false, + } + } +} diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index c03bdae7322..b34b8e235ce 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -7,7 +7,6 @@ #![warn(clippy::disallowed_types)] #![feature(rustc_private)] -// use log::info; use ::log::{debug, error, info}; use crate::{ spawn_server_thread, PrustiClient, ServerMessage, VerificationRequest, @@ -25,6 +24,7 @@ use prusti_rustc_interface::{ errors::MultiSpan, }; use viper::{self, PersistentCache, Viper}; +use ide::{encoding_info::EncodingInfo, ide_verification_result::IdeVerificationResult}; use once_cell::sync::Lazy; mod client; @@ -33,6 +33,7 @@ mod server; mod server_message; mod verification_request; mod backend; +pub mod ide; pub use backend::*; pub use client::*; @@ -68,6 +69,10 @@ pub fn verify_programs( (program_name, request) }); + if config::show_ide_info() { + emit_contract_spans(env_diagnostic); + } + let mut stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); // runtime used either for client connecting to server sequentially // or to sequentially verify the requests -> single thread is sufficient @@ -109,18 +114,39 @@ async fn handle_stream( } } + // if we are in an ide, we already emit the errors asynchronously, otherwise we wait for + // all of them because we want the errors to be reported sortedly + if !config::show_ide_info() { + prusti_errors.sort(); + + for prusti_error in prusti_errors { + debug!("Prusti error: {:?}", prusti_error); + prusti_error.emit(env_diagnostic); + } + } + overall_result } fn handle_termination_message( env_diagnostic: &EnvDiagnostic<'_>, - // &self, program_name: String, result: viper::VerificationResult, prusti_errors: &mut Vec, overall_result: &mut VerificationResult ) { debug!("Received termination message with result {result:?} in verification of {program_name}"); + if config::show_ide_info() { + PrustiError::message( + format!( + "ideVerificationResult{}", + serde_json::to_string(&IdeVerificationResult::from(&result)) + .unwrap() + ), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + } match result.kind { // nothing to do viper::VerificationResultKind::Success => (), @@ -141,9 +167,9 @@ fn handle_termination_message( program_name.clone(), verification_error ); - // temporary error emition, delete when verification errors can be backtranslated again + // FIXME: temporary error emition, delete when the above is implemented again env_diagnostic.span_err_with_help_and_notes( - MultiSpan::new(), + MultiSpan::from_span(DUMMY_SP.into()), &format!( "Verification error in {}: {:?}", program_name.clone(), @@ -197,3 +223,15 @@ fn verify_requests_local<'a>( }; verification_stream.flatten() } + +pub fn emit_contract_spans(env_diagnostic: &EnvDiagnostic<'_>) { + let encoding_info = EncodingInfo { + // call_contract_spans: self.encoder.spans_of_call_contracts.borrow().to_vec(), + call_contract_spans: "call contract spans not implemented".to_string(), + }; + PrustiError::message( + format!("encodingInfo{}", encoding_info.to_json_string()), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); +} diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index e11da7863fc..c59d6d43c46 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -170,6 +170,10 @@ lazy_static::lazy_static! { settings.set_default::>("verify_only_basic_block_path", vec![]).unwrap(); settings.set_default::>("delete_basic_blocks", vec![]).unwrap(); + // Flags specifically for Prusti-Assistant: + settings.set_default("show_ide_info", false).unwrap(); + settings.set_default("skip_verification", false).unwrap(); + // Get the list of all allowed flags. let mut allowed_keys = get_keys(&settings); allowed_keys.insert("server_max_stored_verifiers".to_string()); @@ -1029,3 +1033,18 @@ pub fn enable_type_invariants() -> bool { pub fn test_free_pcs() -> bool { read_setting("test_free_pcs") } + +/// When enabled, prusti should return various Data structures that are +/// used by prusti-assistant, such as a list of method calls, +/// a list of all procedures to be verified, etc. +pub fn show_ide_info() -> bool { + read_setting("show_ide_info") +} + +/// When enabled, verification is skipped. Similar to no_verify but needed +/// because no_verify is also set automatically for dependencies, independent +/// of whether the user passed this flag. In general only required because +/// of issue #1261 +pub fn skip_verification() -> bool { + read_setting("skip_verification") +} \ No newline at end of file diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index af4a7eb9e8a..022c4cf7f52 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,5 +1,5 @@ use crate::{ - ide_helper::fake_error::fake_error, + ide_helper::{compiler_info, fake_error::fake_error}, verifier::verify, }; use mir_state_analysis::test_free_pcs; @@ -187,24 +187,24 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // that is already in `def_spec`? let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); - // if config::show_ide_info() && !config::no_verify() { - // let compiler_info = - // compiler_info::IdeInfo::collect(&env, &annotated_procedures, &def_spec); - // let out = serde_json::to_string(&compiler_info).unwrap(); - // PrustiError::message(format!("compilerInfo{out}"), DUMMY_SP.into()) - // .emit(&env.diagnostic); - // } + if config::show_ide_info() && !config::no_verify() { + let compiler_info = + compiler_info::IdeInfo::collect(&env, &annotated_procedures, &def_spec); + let out = serde_json::to_string(&compiler_info).unwrap(); + PrustiError::message(format!("compilerInfo{out}"), DUMMY_SP.into()) + .emit(&env.diagnostic); + } // as long as we have to throw a fake error we need to check this let is_primary_package = std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); // collect and output Information used by IDE: - if !config::no_verify() { + if !config::no_verify() && !config::skip_verification() { let verification_task = VerificationTask { procedures: annotated_procedures, types, }; verify(env, def_spec, verification_task); - } else if !config::no_verify() && is_primary_package { + } else if config::skip_verification() && !config::no_verify() && is_primary_package { // add a fake error, reason explained in issue #1261 fake_error(&env); } diff --git a/prusti/src/ide_helper/compiler_info.rs b/prusti/src/ide_helper/compiler_info.rs new file mode 100644 index 00000000000..bf207de9690 --- /dev/null +++ b/prusti/src/ide_helper/compiler_info.rs @@ -0,0 +1,92 @@ +use prusti_interface::{environment::Environment, specs::typed}; +use prusti_rustc_interface::{ + hir::def_id::DefId, + span::{source_map::SourceMap, Span}, +}; +use prusti_server::ide::vsc_span::VscSpan; +use serde::Serialize; + +/// This struct will be passed to prusti-assistant containing information +/// about the program that is currently being verified +#[derive(Serialize)] +pub struct IdeInfo { + procedure_defs: Vec, +} + +impl IdeInfo { + pub fn collect( + env: &Environment<'_>, + procedures: &Vec, + def_spec: &typed::DefSpecificationMap, + ) -> Self { + let procedure_defs = collect_procedures(env, procedures, def_spec); + let source_map = env.tcx().sess.source_map(); + + // For declaring external specifications: + // let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); + Self { + procedure_defs, + } + } +} + +/// A struct that contains either a reference to a procedure that can be verified +/// (for selective verification) or a function call (so a user can query +/// external_spec blocks for it). The name contains the defpath. +#[derive(Serialize)] +pub struct ProcDef { + pub name: String, + #[serde(skip)] + pub defid: DefId, + pub span: VscSpan, +} + +/// collect information about the program that will be passed to IDE. +/// This should find all non-trusted functions that can be verified +fn collect_procedures( + env: &Environment<'_>, + procedures: &Vec, + def_spec: &typed::DefSpecificationMap, +) -> Vec { + let sourcemap: &SourceMap = env.tcx().sess.source_map(); + let mut procs = Vec::new(); + for defid in procedures { + let defpath = env.name.get_unique_item_name(*defid); + let span = env.query.get_def_span(defid); + let vscspan = VscSpan::from_span(&span, sourcemap); + + // Filter out the predicates and trusted methods, + // since we don't want to allow selective verification + // for them + let mut is_predicate = false; + let mut is_trusted = false; + + let proc_spec_opt = def_spec.get_proc_spec(defid); + if let Some(proc_spec) = proc_spec_opt { + let kind_spec = proc_spec + .base_spec + .kind + .extract_with_selective_replacement(); + let trusted_spec = proc_spec + .base_spec + .trusted + .extract_with_selective_replacement(); + if let Some(typed::ProcedureSpecificationKind::Predicate(..)) = kind_spec { + is_predicate = true; + } + if let Some(true) = trusted_spec { + is_trusted = true; + } + } + + if !is_trusted && !is_predicate { + procs.push(ProcDef { + name: defpath, + defid: *defid, + span: vscspan, + }); + } + } + procs +} + diff --git a/prusti/src/ide_helper/mod.rs b/prusti/src/ide_helper/mod.rs new file mode 100644 index 00000000000..c9ac3792d15 --- /dev/null +++ b/prusti/src/ide_helper/mod.rs @@ -0,0 +1,2 @@ +pub mod compiler_info; +pub mod fake_error; \ No newline at end of file From 5270e09a80ec6dd5e8ec9d456b342cffe74600fb Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 13 Jun 2024 15:13:15 +0200 Subject: [PATCH 05/53] Port `verify_only_defpath` and `report_viper_messages` flags Defpath only verification does not work since test_entrypoint currently just puts the entire program into the request regardless of the verification tasks at hand. Update the flags in the documentation. PR: https://github.com/viperproject/prusti-dev/pull/1334 --- docs/dev-guide/src/config/flags.md | 26 +++++++++++- prusti-server/src/backend.rs | 64 +++++++++++++++++++++++++++++- prusti-server/src/lib.rs | 1 - prusti-utils/src/config.rs | 14 +++++++ prusti/src/callbacks.rs | 32 ++++++++++++--- 5 files changed, 129 insertions(+), 8 deletions(-) diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index b3ef1913dc9..7472db5ca94 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -56,13 +56,16 @@ | [`PRINT_HASH`](#print_hash) | `bool` | `false` | A | | [`PRINT_TYPECKD_SPECS`](#print_typeckd_specs) | `bool` | `false` | A | | [`QUIET`](#quiet) | `bool` | `false` | A* | +| [`REPORT_VIPER_MESSAGES`](#report_viper_messages) | `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 | +| [`SHOW_IDE_INFO`](#show_ide_info) | `bool` | `false` | A | | [`SIMPLIFY_ENCODING`](#simplify_encoding) | `bool` | `true` | A | | [`SKIP_UNSUPPORTED_FEATURES`](#skip_unsupported_features) | `bool` | `false` | A | +| [`SKIP_VERIFICATION`](#skip_verification) | `bool` | `false` | A | | [`SMT_QI_BOUND_GLOBAL`](#smt_qi_bound_global) | `Option` | `None` | A | -[`SMT_QI_BOUND_GLOBAL_KIND`](#smt_qi_bound_global_kind) | `Option` | `None` | A | +| [`SMT_QI_BOUND_GLOBAL_KIND`](#smt_qi_bound_global_kind) | `Option` | `None` | A | | [`SMT_QI_BOUND_TRACE`](#smt_qi_bound_trace) | `Option` | `None` | A | | [`SMT_QI_BOUND_TRACE_KIND`](#smt_qi_bound_trace_kind) | `Option` | `None` | A | | [`SMT_QI_IGNORE_BUILTIN`](#smt_qi_ignore_builtin) | `bool` | `true` | A | @@ -76,6 +79,7 @@ | [`USE_SMT_WRAPPER`](#use_smt_wrapper) | `bool` | `false` | A | | [`VERIFICATION_DEADLINE`](#verification_deadline) | `Option` | `None` | A | | [`VERIFY_ONLY_BASIC_BLOCK_PATH`](#verify_only_basic_block_path) | `Vec` | `vec![]` | A | +| [`VERIFY_ONLY_DEFPATH`](#verify_only_defpath) | `Option` | `None` | A | | [`VERIFY_ONLY_PREAMBLE`](#verify_only_preamble) | `bool` | `false` | A | | [`VIPER_BACKEND`](#viper_backend) | `String` | `"Silicon"` | A | | [`VIPER_HOME`](#viper_home) | `Option` | `None` | A | @@ -357,6 +361,14 @@ When enabled, user messages are not printed. Otherwise, messages output into `st > **Note:** `cargo prusti` sets this flag with `DEFAULT_PRUSTI_QUIET=true`. +## `REPORT_VIPER_MESSAGES` + +When enabled for both server and client, certain supported Viper messages will be reported to the user. + +## `VERIFY_ONLY_DEFPATH` + +When set to the defpath of a local method, prusti will only verify the specified method. A fake error will be generated to avoid caching of a success. + ## `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. @@ -373,6 +385,10 @@ Maximum amount of instantiated Viper verifiers the server will keep around for r > **Note:** This does _not_ limit how many verification requests the server handles concurrently, only the size of what is essentially its verifier cache. +## `SHOW_IDE_INFO` + +When enabled, we emit various json data structures containing information about the program, its encoding, and the results of the verification. This flag intended for prusti-assistant (IDE). + ## `SIMPLIFY_ENCODING` When enabled, the encoded program is simplified before it is passed to the Viper backend. @@ -381,6 +397,10 @@ When enabled, the encoded program is simplified before it is passed to the Viper When enabled, features not supported by Prusti will be reported as warnings rather than errors. +## `SKIP_VERIFICATION` + +When enabled, verification will be skipped. Opposed to `NO_VERIFY`, this flag will cause fake errors to stop the compiler from caching the result. + ## `SMT_QI_BOUND_GLOBAL` If not `None`, checks that the number of global quantifier instantiations reported by the SMT wrapper is smaller than the specified bound. @@ -468,6 +488,10 @@ Verify only the single execution path goes through the given basic blocks. All b > **Note:** This option is only for debugging Prusti. +## `VERIFY_ONLY_DEFPATH` + +When set to the defpath of a local method, prusti will only verify the specified method. A fake error will be generated to avoid caching of a success. + ## `VERIFY_ONLY_PREAMBLE` When enabled, only the preamble will be verified: domains, functions, and predicates. diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index d0445f233a5..c5e0f942e32 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -51,9 +51,71 @@ impl<'a> Backend<'a> { stopwatch.start_next("viper verification"); - verifier.verify(viper_program) + if config::report_viper_messages() { + verify_and_poll_msgs(verifier, context, viper_arc, viper_program, sender) + } else { + verifier.verify(viper_program) + } }) } } } +} + +fn verify_and_poll_msgs( + verifier: &mut viper::Verifier, + verification_context: &viper::VerificationContext, + viper_arc: &Arc, + viper_program: viper::Program, + sender: mpsc::Sender, +) -> VerificationResultKind { + let mut kind = VerificationResultKind::Success; + + // get the reporter global reference outside of the thread scope because it needs to + // be dropped by thread attached to the jvm. This is also why we pass it as reference + // and not per value + let env = &verification_context.env(); + let jni = JniUtils::new(env); + let verifier_wrapper = silver::verifier::Verifier::with(env); + let reporter = jni.unwrap_result(verifier_wrapper.call_reporter(*verifier.verifier_instance())); + let rep_glob_ref = env.new_global_ref(reporter).unwrap(); + + debug!("Starting viper message polling thread"); + + // start thread for polling messages + thread::scope(|scope| { + let polling_thread = scope.spawn(|| polling_function(viper_arc, &rep_glob_ref, sender)); + kind = verifier.verify(viper_program); + polling_thread.join().unwrap(); + }); + debug!("Viper message polling thread terminated"); + kind +} + +fn polling_function( + viper_arc: &Arc, + rep_glob_ref: &jni::objects::GlobalRef, + sender: mpsc::Sender, +) { + let verification_context = viper_arc.attach_current_thread(); + let env = verification_context.env(); + let jni = JniUtils::new(env); + let reporter_instance = rep_glob_ref.as_obj(); + let reporter_wrapper = silver::reporter::PollingReporter::with(env); + loop { + while reporter_wrapper + .call_hasNewMessage(reporter_instance) + .unwrap() + { + let msg = reporter_wrapper + .call_getNewMessage(reporter_instance) + .unwrap(); + debug!("Polling thread received {}", jni.class_name(msg).as_str()); + match jni.class_name(msg).as_str() { + "viper.silver.reporter.VerificationTerminationMessage" => return, + _ => (), + } + } + thread::sleep(time::Duration::from_millis(10)); + } } \ No newline at end of file diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index b34b8e235ce..9adde618b8c 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -188,7 +188,6 @@ fn handle_termination_message( DUMMY_SP.into(), ) .emit(env_diagnostic); - // .emit(&self.env.diagnostic); *overall_result = VerificationResult::Failure; } } diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index c59d6d43c46..7651f3bfc0e 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -103,6 +103,7 @@ lazy_static::lazy_static! { settings.set_default("quiet", false).unwrap(); settings.set_default("assert_timeout", 10_000).unwrap(); settings.set_default("smt_qi_eager_threshold", 1000).unwrap(); + settings.set_default("report_viper_messages", false).unwrap(); settings.set_default("use_more_complete_exhale", true).unwrap(); settings.set_default("skip_unsupported_features", false).unwrap(); settings.set_default("internal_errors_as_warnings", false).unwrap(); @@ -173,6 +174,7 @@ lazy_static::lazy_static! { // Flags specifically for Prusti-Assistant: settings.set_default("show_ide_info", false).unwrap(); settings.set_default("skip_verification", false).unwrap(); + settings.set_default::>("verify_only_defpath", None).unwrap(); // Get the list of all allowed flags. let mut allowed_keys = get_keys(&settings); @@ -506,6 +508,12 @@ pub fn smt_qi_eager_threshold() -> u64 { read_setting("smt_qi_eager_threshold") } +/// Whether to report the messages produced by the viper backend (e.g. quantifier instantiations, +/// quantifier triggers) +pub fn report_viper_messages() -> bool { + read_setting("report_viper_messages") +} + /// Maximum time (in milliseconds) for the verifier to spend on checks. /// Set to None uses the verifier's default value. Maps to the verifier command-line /// argument `--checkTimeout`. @@ -1047,4 +1055,10 @@ pub fn show_ide_info() -> bool { /// of issue #1261 pub fn skip_verification() -> bool { read_setting("skip_verification") +} + +/// Used for selective verification, can be passed a String containing +/// the DefPath of the method to be verified +pub fn verify_only_defpath() -> Option { + read_setting("verify_only_defpath") } \ No newline at end of file diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 022c4cf7f52..b13cd3291d0 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -29,6 +29,7 @@ use prusti_rustc_interface::{ session::{EarlyErrorHandler, Session}, span::DUMMY_SP, }; +use ::log::debug; #[derive(Default)] pub struct PrustiCompilerCalls; @@ -199,11 +200,32 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // collect and output Information used by IDE: if !config::no_verify() && !config::skip_verification() { - let verification_task = VerificationTask { - procedures: annotated_procedures, - types, - }; - verify(env, def_spec, verification_task); + if let Some(target_def_path) = config::verify_only_defpath() { + // if we do selective verification, then definitely only + // for methods of the primary package. Check needed because + // of the fake_error, otherwise verification stops early + // with local dependencies + if is_primary_package { + let procedures = annotated_procedures + .into_iter() + .filter(|x| env.name.get_unique_item_name(*x) == target_def_path) + .collect(); + debug!("selected procedures: {:?}", procedures); + let selective_task = VerificationTask { procedures, types }; + // fake_error because otherwise a verification-success + // (for a single method for example) will cause this result + // to be cached by compiler at the moment + verify(env, def_spec, selective_task); + // FIXME: problematic because env is moved above + // fake_error(&env); + } + } else { + let verification_task = VerificationTask { + procedures: annotated_procedures, + types, + }; + verify(env, def_spec, verification_task); + } } else if config::skip_verification() && !config::no_verify() && is_primary_package { // add a fake error, reason explained in issue #1261 fake_error(&env); From 39bfc55fc6bba37eedb41f2a45f10019e6820ec7 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 14 Jun 2024 12:07:43 +0200 Subject: [PATCH 06/53] Add support for block level messaging and bump version Requires the viperserver backend to support these messages too. --- Cargo.lock | 2 +- docs/dev-guide/src/config/flags.md | 5 +++ prusti-server/src/backend.rs | 33 +++++++++++++++ prusti-server/src/lib.rs | 49 +++++++++++++++++++++++ prusti-server/src/server_message.rs | 20 +++++++++ prusti-server/src/verification_request.rs | 4 ++ prusti-utils/src/config.rs | 7 ++++ prusti/Cargo.toml | 2 +- prusti/src/verifier.rs | 2 +- viper-sys/build.rs | 11 +++++ 10 files changed, 132 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03f49bed973..38fc05cb543 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1833,7 +1833,7 @@ dependencies = [ [[package]] name = "prusti" -version = "0.2.2" +version = "0.3.0" dependencies = [ "chrono", "env_logger", diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index 7472db5ca94..f10edadb327 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -57,6 +57,7 @@ | [`PRINT_TYPECKD_SPECS`](#print_typeckd_specs) | `bool` | `false` | A | | [`QUIET`](#quiet) | `bool` | `false` | A* | | [`REPORT_VIPER_MESSAGES`](#report_viper_messages) | `bool` | `false` | A | +| [`REPORT_BLOCK_MESSAGES`](#report_block_messages) | `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 | @@ -365,6 +366,10 @@ When enabled, user messages are not printed. Otherwise, messages output into `st When enabled for both server and client, certain supported Viper messages will be reported to the user. +## `REPORT_BLOCK_MESSAGES` + +When enabled for both server and client, messages for individual basic blocks will be reported to the user. Does nothing if [`REPORT_VIPER_MESSAGES`](#report_viper_messages) is not enabled. Intended for usage with the Prusti Assistant (IDE). + ## `VERIFY_ONLY_DEFPATH` When set to the defpath of a local method, prusti will only verify the specified method. A fake error will be generated to avoid caching of a success. diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index c5e0f942e32..c85db67fdde 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -113,6 +113,39 @@ fn polling_function( debug!("Polling thread received {}", jni.class_name(msg).as_str()); match jni.class_name(msg).as_str() { "viper.silver.reporter.VerificationTerminationMessage" => return, + "viper.silver.reporter.BlockReachedMessage" => { + let msg_wrapper = silver::reporter::BlockReachedMessage::with(env); + let method_name = + jni.get_string(jni.unwrap_result(msg_wrapper.call_methodName(msg))); + let label = + jni.get_string(jni.unwrap_result(msg_wrapper.call_label(msg))); + let path_id = jni.unwrap_result(msg_wrapper.call_pathId(msg)); + sender + .send(ServerMessage::BlockReached { + viper_method: method_name, + vir_label: label, + path_id: path_id, + }) + .unwrap(); + }, + "viper.silver.reporter.PathProcessedMessage" => { + let msg_wrapper = silver::reporter::PathProcessedMessage::with(env); + let method_name = + jni.get_string(jni.unwrap_result(msg_wrapper.call_methodName(msg))); + let label = + jni.get_string(jni.unwrap_result(msg_wrapper.call_label(msg))); + let path_id = jni.unwrap_result(msg_wrapper.call_pathId(msg)); + let result = + jni.get_string(jni.unwrap_result(msg_wrapper.call_verificationResultKind(msg))); + sender + .send(ServerMessage::PathProcessed { + viper_method: method_name, + vir_label: label, + path_id: path_id, + result: result, + }) + .unwrap(); + }, _ => (), } } diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 9adde618b8c..216c05afe17 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -111,6 +111,17 @@ async fn handle_stream( while let Some((program_name, server_msg)) = verification_messages.next().await { match server_msg { ServerMessage::Termination(result) => handle_termination_message(env_diagnostic, program_name, result, &mut prusti_errors, &mut overall_result), + ServerMessage::BlockReached { + viper_method, + vir_label, + path_id, + } => handle_block_processing_message(env_diagnostic, program_name, viper_method, vir_label, path_id, None), + ServerMessage::PathProcessed { + viper_method, + vir_label, + path_id, + result, + } => handle_block_processing_message(env_diagnostic, program_name, viper_method, vir_label, path_id, Some(result)), } } @@ -193,6 +204,44 @@ fn handle_termination_message( } } +// TODO: probably in some other util module +fn viper_method_to_rust_method(_viper_method: &String, program_name: &String) -> Option { + Some(program_name.clone()) +} + +// TODO +fn vir_label_to_pos(_vir_label: &String) -> Option { + Some(MultiSpan::from_span(DUMMY_SP.into())) +} + +fn handle_block_processing_message( + env_diagnostic: &EnvDiagnostic<'_>, + program_name: String, + viper_method: String, + vir_label: String, + path_id: i32, + result: Option, +) { + if config::report_viper_messages() && config::report_block_messages() { + let processed = result != None; + debug!("Received {}: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label}, path_id: {path_id} }}", + if processed {"path processed"} else {"block reached"}); + if let Some(method_name) = viper_method_to_rust_method(&viper_method, & program_name) { + if let Some(span) = vir_label_to_pos(&vir_label) { + PrustiError::message( + format!("{}{}", + if processed {"pathProcessedMessage"} else {"blockReachedMessage"}, + if processed {json!({"method": method_name, "path_id": path_id, "result": result.unwrap()})} + // FIXME: outputting vir_label only because it makes the messages different, otherwise the errors get merged. + // should be removed once backtranslation of labels is implemented so the resulting spans are different. + else {json!({"method": method_name, "path_id": path_id, "label": vir_label})}, + ), span.clone() + ).emit(env_diagnostic); + } else { error!("Could not map vir label {vir_label} to a position in verification of method {method_name} in {program_name}") } + } else { error!("Could not map viper method {viper_method} to a Rust method in verification of {program_name}") } + } +} + fn verify_requests_server( verification_requests: Vec<(String, VerificationRequest)>, server_address: String, diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs index 5cbdda94a2f..bff938cbf3e 100644 --- a/prusti-server/src/server_message.rs +++ b/prusti-server/src/server_message.rs @@ -30,4 +30,24 @@ pub enum ServerMessage { // triggers: String, // pos_id: u64, // }, + + /// Contains a path id, label and viper method name corresponding to a symbolic + /// execution path through the program being verified, the VIR label of the cfg + /// basic block in the program and name of the method in the generated viper file + BlockReached { + viper_method: String, + vir_label: String, + path_id: i32, + }, + + /// Contains a path id, label, viper method name and a result corresponding to a + /// symbolic execution path through the program being verified, the VIR label of + /// the cfg basic block in the program, name of the method in the generated + /// viper file, and the tentative verification result of the block. + PathProcessed { + viper_method: String, + vir_label: String, + path_id: i32, + result: String, + }, } \ No newline at end of file diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 17222b410f1..402b74a292f 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -66,6 +66,10 @@ impl ViperBackendConfig { verifier_args.push("--checkTimeout".to_string()); verifier_args.push(check_timeout.to_string()); } + + if config::report_block_messages() && config::report_viper_messages() { + verifier_args.push("--generateBlockMessages".to_string()); + } } VerificationBackend::Carbon => { verifier_args.extend(vec!["--disableAllocEncoding".to_string()]); diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 7651f3bfc0e..d4666c593bb 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -175,6 +175,7 @@ lazy_static::lazy_static! { settings.set_default("show_ide_info", false).unwrap(); settings.set_default("skip_verification", false).unwrap(); settings.set_default::>("verify_only_defpath", None).unwrap(); + settings.set_default("report_block_messages", false).unwrap(); // Get the list of all allowed flags. let mut allowed_keys = get_keys(&settings); @@ -797,6 +798,12 @@ fn read_smt_wrapper_dependent_option(name: &'static str) -> Option { value } +/// Whether to report the messages produced by the viper backend about CFG block being processed +/// (primarily for IDE display) +pub fn report_block_messages() -> bool { + read_setting("report_block_messages") +} + /// Whether the built-in quantifiers should be ignored when comparing bounds. pub fn smt_qi_ignore_builtin() -> bool { read_setting("smt_qi_ignore_builtin") diff --git a/prusti/Cargo.toml b/prusti/Cargo.toml index 040a5b6a080..b21e6db66cf 100644 --- a/prusti/Cargo.toml +++ b/prusti/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prusti" -version = "0.2.2" +version = "0.3.0" authors = ["Prusti Devs "] edition = "2021" diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 6834ed4f685..d2258af071e 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -52,7 +52,7 @@ pub fn verify<'tcx>( // which is constructed further inside `prusti_server`) let request = prusti_encoder::test_entrypoint( env.tcx(), - env.body, + env.body, // FIXME: currently needing env by value because of this line def_spec, ); let program = request.program; diff --git a/viper-sys/build.rs b/viper-sys/build.rs index 75438f39952..33f94022e55 100644 --- a/viper-sys/build.rs +++ b/viper-sys/build.rs @@ -171,6 +171,17 @@ fn main() { // method!("quantifier"), // method!("instantiations"), // ]), + java_class!("viper.silver.reporter.BlockReachedMessage", vec![ + method!("methodName"), + method!("label"), + method!("pathId"), + ]), + java_class!("viper.silver.reporter.PathProcessedMessage", vec![ + method!("methodName"), + method!("label"), + method!("pathId"), + method!("verificationResultKind"), + ]), java_class!("viper.silver.verifier.Verifier", vec![ method!("name"), method!("buildVersion"), From 99c3b2c1181f802392f9f60ba30b8a26c2addf88 Mon Sep 17 00:00:00 2001 From: trk Date: Tue, 18 Jun 2024 09:36:05 +0200 Subject: [PATCH 07/53] Attempt to fix the only defpath flag and fix not emitting fake error Every body owner function that is not passed to be verified explicitly acts as though it were pure and trusted to skip the encoding of their bodies. Additionally, VERIFY_ONLY_DEFPATH now takes a vector of String instead of just a string. --- prusti-encoder/src/lib.rs | 7 ++++--- prusti-interface/src/environment/diagnostic.rs | 1 + prusti-utils/src/config.rs | 4 ++-- prusti/src/callbacks.rs | 11 ++++++----- prusti/src/ide_helper/fake_error.rs | 6 +++--- prusti/src/verifier.rs | 1 + 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index ab7e228efca..b114bf7edcc 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -15,6 +15,7 @@ use prusti_interface::environment::EnvBody; use prusti_rustc_interface::{ middle::ty, hir, + hir::def_id::DefId, }; use task_encoder::TaskEncoder; @@ -24,13 +25,13 @@ pub fn test_entrypoint<'tcx>( tcx: ty::TyCtxt<'tcx>, body: EnvBody<'tcx>, def_spec: prusti_interface::specs::typed::DefSpecificationMap, + procedures: &Vec, ) -> request::RequestWithContext { crate::encoders::init_def_spec(def_spec); vir::init_vcx(vir::VirCtxt::new(tcx, body)); // TODO: this should be a "crate" encoder, which will deps.require all the methods in the crate - for def_id in tcx.hir().body_owners() { tracing::debug!("test_entrypoint item: {def_id:?}"); let kind = tcx.def_kind(def_id); @@ -47,8 +48,8 @@ pub fn test_entrypoint<'tcx>( let is_trusted = proc_spec.trusted.extract_inherit().unwrap_or_default(); (is_pure, is_trusted) }).unwrap_or_default(); - - if !(is_trusted && is_pure) { + + if procedures.contains(&def_id) && !(is_trusted && is_pure) { let substs = ty::GenericArgs::identity_for_item(tcx, def_id); let res = crate::encoders::MirImpureEnc::encode((def_id, substs, None)); assert!(res.is_ok()); diff --git a/prusti-interface/src/environment/diagnostic.rs b/prusti-interface/src/environment/diagnostic.rs index 8b1fe91cfd9..17eed26efb4 100644 --- a/prusti-interface/src/environment/diagnostic.rs +++ b/prusti-interface/src/environment/diagnostic.rs @@ -4,6 +4,7 @@ use prusti_rustc_interface::{ }; use std::cell::RefCell; +#[derive(Clone)] pub struct EnvDiagnostic<'tcx> { tcx: TyCtxt<'tcx>, warn_buffer: RefCell>, diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index d4666c593bb..20d3195ae2e 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -174,7 +174,7 @@ lazy_static::lazy_static! { // Flags specifically for Prusti-Assistant: settings.set_default("show_ide_info", false).unwrap(); settings.set_default("skip_verification", false).unwrap(); - settings.set_default::>("verify_only_defpath", None).unwrap(); + settings.set_default::>("verify_only_defpath", vec![]).unwrap(); settings.set_default("report_block_messages", false).unwrap(); // Get the list of all allowed flags. @@ -1066,6 +1066,6 @@ pub fn skip_verification() -> bool { /// Used for selective verification, can be passed a String containing /// the DefPath of the method to be verified -pub fn verify_only_defpath() -> Option { +pub fn verify_only_defpath() -> Vec { read_setting("verify_only_defpath") } \ No newline at end of file diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index b13cd3291d0..0908fd6a4f1 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -200,7 +200,8 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // collect and output Information used by IDE: if !config::no_verify() && !config::skip_verification() { - if let Some(target_def_path) = config::verify_only_defpath() { + let target_def_paths = config::verify_only_defpath(); + if !target_def_paths.is_empty() { // if we do selective verification, then definitely only // for methods of the primary package. Check needed because // of the fake_error, otherwise verification stops early @@ -208,16 +209,16 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { if is_primary_package { let procedures = annotated_procedures .into_iter() - .filter(|x| env.name.get_unique_item_name(*x) == target_def_path) + .filter(|x| target_def_paths.contains(&env.name.get_unique_item_name(*x))) .collect(); debug!("selected procedures: {:?}", procedures); let selective_task = VerificationTask { procedures, types }; // fake_error because otherwise a verification-success // (for a single method for example) will cause this result // to be cached by compiler at the moment + let env_diagnostic = env.diagnostic.clone(); verify(env, def_spec, selective_task); - // FIXME: problematic because env is moved above - // fake_error(&env); + fake_error(&env_diagnostic); } } else { let verification_task = VerificationTask { @@ -228,7 +229,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { } } else if config::skip_verification() && !config::no_verify() && is_primary_package { // add a fake error, reason explained in issue #1261 - fake_error(&env); + fake_error(&env.diagnostic); } }); diff --git a/prusti/src/ide_helper/fake_error.rs b/prusti/src/ide_helper/fake_error.rs index d33919cdaa3..8e608601e76 100644 --- a/prusti/src/ide_helper/fake_error.rs +++ b/prusti/src/ide_helper/fake_error.rs @@ -1,16 +1,16 @@ -use prusti_interface::environment::Environment; +use prusti_interface::environment::EnvDiagnostic; use prusti_rustc_interface::{errors::MultiSpan, span::DUMMY_SP}; /// This error will be thrown when skipping verification (which is done /// when we just collect information for prusti-assistant), because then /// a successful result will be cached and subsequent actual verifications succeed /// (see issue #1261) -pub fn fake_error(env: &Environment) { +pub fn fake_error(env_diagnostic: &EnvDiagnostic<'_>) { let sp = MultiSpan::from_span(DUMMY_SP); let message = String::from("[Prusti: FakeError]"); let help = None; let notes = []; - env.diagnostic + env_diagnostic .span_err_with_help_and_notes(sp, &message, &help, ¬es); } \ No newline at end of file diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index d2258af071e..3e3c51941a3 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -54,6 +54,7 @@ pub fn verify<'tcx>( env.tcx(), env.body, // FIXME: currently needing env by value because of this line def_spec, + &verification_task.procedures, ); let program = request.program; From e607cc0173a502a93b4a019bf0c36b51871c4f42 Mon Sep 17 00:00:00 2001 From: trk Date: Tue, 18 Jun 2024 10:28:29 +0200 Subject: [PATCH 08/53] Port call finder --- prusti/src/ide_helper/call_finder.rs | 124 +++++++++++++++++++++++++ prusti/src/ide_helper/compiler_info.rs | 21 +++++ prusti/src/ide_helper/mod.rs | 3 +- 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 prusti/src/ide_helper/call_finder.rs diff --git a/prusti/src/ide_helper/call_finder.rs b/prusti/src/ide_helper/call_finder.rs new file mode 100644 index 00000000000..971b2cb121c --- /dev/null +++ b/prusti/src/ide_helper/call_finder.rs @@ -0,0 +1,124 @@ +use prusti_interface::environment::{EnvQuery, Environment}; +use prusti_rustc_interface::{ + hir::{ + def_id::DefId, + intravisit::{self, Visitor}, + Expr, ExprKind, + }, + middle::{hir::map::Map, ty::TyCtxt}, + span::Span, +}; + +/// The hir-visitor to collect all the function calls +pub struct CallSpanFinder<'tcx> { + pub env_query: EnvQuery<'tcx>, + pub tcx: TyCtxt<'tcx>, + pub called_functions: Vec<(String, DefId, Span)>, +} + +impl<'tcx> CallSpanFinder<'tcx> { + pub fn new(env: &Environment<'tcx>) -> Self { + Self { + env_query: env.query, + called_functions: Vec::new(), + tcx: env.tcx(), + } + } + + pub fn resolve_expression(&self, expr: &'tcx Expr) -> Result<(DefId, DefId), ()> { + let maybe_method_def_id = self + .tcx + .typeck(expr.hir_id.owner.def_id) + .type_dependent_def_id(expr.hir_id); + if let Some(method_def_id) = maybe_method_def_id { + let owner_def_id = expr.hir_id.owner.def_id; + let tyck_res = self.tcx.typeck(owner_def_id); + let substs = tyck_res.node_args(expr.hir_id); + let (resolved_def_id, _subst) = + self.env_query + .resolve_method_call(owner_def_id, method_def_id, substs); + Ok((method_def_id, resolved_def_id)) + } else { + Err(()) + } + } +} + +impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { + 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_expr(&mut self, expr: &'tcx Expr) { + intravisit::walk_expr(self, expr); + match expr.kind { + ExprKind::Call(e1, _e2) => { + if let ExprKind::Path(ref qself) = e1.kind { + let tyck_res = self.tcx.typeck(e1.hir_id.owner.def_id); + let res = tyck_res.qpath_res(qself, e1.hir_id); + if let prusti_rustc_interface::hir::def::Res::Def(_, def_id) = res { + if def_id.as_local().is_none() { + let defpath = self.tcx.def_path_str(def_id); + self.called_functions.push((defpath, def_id, expr.span)); + } + } + } + } + ExprKind::MethodCall(_path, _e1, _e2, sp) => { + let resolve_res = self.resolve_expression(expr); + if let Ok((method_def_id, resolved_def_id)) = resolve_res { + let defpath_unresolved = self.tcx.def_path_str(method_def_id); + let defpath_resolved = self.tcx.def_path_str(resolved_def_id); + + if method_def_id.as_local().is_none() { + if defpath_unresolved == defpath_resolved { + self.called_functions + .push((defpath_resolved, resolved_def_id, sp)); + } else { + // in this case we want both + self.called_functions + .push((defpath_resolved, resolved_def_id, sp)); + self.called_functions + .push((defpath_unresolved, method_def_id, sp)); + } + } + } + } + ExprKind::Binary(..) | ExprKind::AssignOp(..) | ExprKind::Unary(..) => { + let resolve_res = self.resolve_expression(expr); + // this will already fail for builtin-operations + if let Ok((method_def_id, resolved_def_id)) = resolve_res { + let defpath_unresolved = self.tcx.def_path_str(method_def_id); + let defpath_resolved = self.tcx.def_path_str(resolved_def_id); + + if method_def_id.as_local().is_none() { + if defpath_unresolved == defpath_resolved { + self.called_functions.push(( + defpath_resolved, + resolved_def_id, + expr.span, + )); + } else { + // For binary operations this will be the operation + // from the standard libary and the "overriding" method + + self.called_functions.push(( + defpath_resolved, + resolved_def_id, + expr.span, + )); + self.called_functions.push(( + defpath_unresolved, + method_def_id, + expr.span, + )); + } + } + } + } + _ => {} + } + } +} diff --git a/prusti/src/ide_helper/compiler_info.rs b/prusti/src/ide_helper/compiler_info.rs index bf207de9690..c4627d5d043 100644 --- a/prusti/src/ide_helper/compiler_info.rs +++ b/prusti/src/ide_helper/compiler_info.rs @@ -1,3 +1,4 @@ +use super::call_finder; use prusti_interface::{environment::Environment, specs::typed}; use prusti_rustc_interface::{ hir::def_id::DefId, @@ -11,6 +12,7 @@ use serde::Serialize; #[derive(Serialize)] pub struct IdeInfo { procedure_defs: Vec, + function_calls: Vec, } impl IdeInfo { @@ -21,11 +23,20 @@ impl IdeInfo { ) -> Self { let procedure_defs = collect_procedures(env, procedures, def_spec); let source_map = env.tcx().sess.source_map(); + let function_calls = collect_fncalls(env) + .into_iter() + .map(|(name, defid, sp)| ProcDef { + name, + defid, + span: VscSpan::from_span(&sp, source_map), + }) + .collect::>(); // For declaring external specifications: // let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); Self { procedure_defs, + function_calls, } } } @@ -90,3 +101,13 @@ fn collect_procedures( procs } +/// collect all the function calls, so the extension can query external_spec +/// templates for it +fn collect_fncalls(env: &Environment<'_>) -> Vec<(String, DefId, Span)> { + let mut fnvisitor = call_finder::CallSpanFinder::new(env); + env.tcx() + .hir() + .visit_all_item_likes_in_crate(&mut fnvisitor); + + fnvisitor.called_functions +} diff --git a/prusti/src/ide_helper/mod.rs b/prusti/src/ide_helper/mod.rs index c9ac3792d15..0210498f7be 100644 --- a/prusti/src/ide_helper/mod.rs +++ b/prusti/src/ide_helper/mod.rs @@ -1,2 +1,3 @@ pub mod compiler_info; -pub mod fake_error; \ No newline at end of file +pub mod fake_error; +mod call_finder; \ No newline at end of file From d472c60c7402199f4e2a1b2cbb31bddbec34002b Mon Sep 17 00:00:00 2001 From: trk Date: Tue, 18 Jun 2024 12:45:30 +0200 Subject: [PATCH 09/53] Attempt porting QUERY_METHOD_SIGNATURE flag --- docs/dev-guide/src/config/flags.md | 5 + prusti-utils/src/config.rs | 8 + prusti/src/ide_helper/compiler_info.rs | 6 +- prusti/src/ide_helper/mod.rs | 3 +- prusti/src/ide_helper/query_signature.rs | 437 +++++++++++++++++++++++ 5 files changed, 456 insertions(+), 3 deletions(-) create mode 100644 prusti/src/ide_helper/query_signature.rs diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index f10edadb327..a3dda374123 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -55,6 +55,7 @@ | [`PRINT_DESUGARED_SPECS`](#print_desugared_specs) | `bool` | `false` | A | | [`PRINT_HASH`](#print_hash) | `bool` | `false` | A | | [`PRINT_TYPECKD_SPECS`](#print_typeckd_specs) | `bool` | `false` | A | +| [`QUERY_METHOD_SIGNATURE`](#query_method_signature) | `Option` | `None` | A | | [`QUIET`](#quiet) | `bool` | `false` | A* | | [`REPORT_VIPER_MESSAGES`](#report_viper_messages) | `bool` | `false` | A | | [`REPORT_BLOCK_MESSAGES`](#report_block_messages) | `bool` | `false` | A | @@ -356,6 +357,10 @@ When enabled, prints the hash of a verification request (the hash is used for ca When enabled, prints the type-checked specifications. +## `QUERY_METHOD_SIGNATURE` + +When set to a defpath, prusti will generate a template for an external specification for this method. The result is part of the CompilerInfo and will only be emitted if the `SHOW_IDE_INFO` flag is enabled too. + ## `QUIET` When enabled, user messages are not printed. Otherwise, messages output into `stderr`. diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 20d3195ae2e..3a960ee3ba4 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -175,6 +175,7 @@ lazy_static::lazy_static! { settings.set_default("show_ide_info", false).unwrap(); settings.set_default("skip_verification", false).unwrap(); settings.set_default::>("verify_only_defpath", vec![]).unwrap(); + settings.set_default::>("query_method_signature", None).unwrap(); settings.set_default("report_block_messages", false).unwrap(); // Get the list of all allowed flags. @@ -1068,4 +1069,11 @@ pub fn skip_verification() -> bool { /// the DefPath of the method to be verified pub fn verify_only_defpath() -> Vec { read_setting("verify_only_defpath") +} + +/// A flag that can be used to ask the compiler for the declaration / +/// signature of a method, used to automatically generate a skeleton +/// for an external specification +pub fn query_method_signature() -> Option { + read_setting("query_method_signature") } \ No newline at end of file diff --git a/prusti/src/ide_helper/compiler_info.rs b/prusti/src/ide_helper/compiler_info.rs index c4627d5d043..63371ab7c5a 100644 --- a/prusti/src/ide_helper/compiler_info.rs +++ b/prusti/src/ide_helper/compiler_info.rs @@ -1,4 +1,4 @@ -use super::call_finder; +use super::{call_finder, query_signature}; use prusti_interface::{environment::Environment, specs::typed}; use prusti_rustc_interface::{ hir::def_id::DefId, @@ -13,6 +13,7 @@ use serde::Serialize; pub struct IdeInfo { procedure_defs: Vec, function_calls: Vec, + queried_source: Option, } impl IdeInfo { @@ -33,10 +34,11 @@ impl IdeInfo { .collect::>(); // For declaring external specifications: - // let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); + let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); Self { procedure_defs, function_calls, + queried_source, } } } diff --git a/prusti/src/ide_helper/mod.rs b/prusti/src/ide_helper/mod.rs index 0210498f7be..617dc40c368 100644 --- a/prusti/src/ide_helper/mod.rs +++ b/prusti/src/ide_helper/mod.rs @@ -1,3 +1,4 @@ pub mod compiler_info; pub mod fake_error; -mod call_finder; \ No newline at end of file +mod call_finder; +mod query_signature; \ No newline at end of file diff --git a/prusti/src/ide_helper/query_signature.rs b/prusti/src/ide_helper/query_signature.rs new file mode 100644 index 00000000000..4affede8dcc --- /dev/null +++ b/prusti/src/ide_helper/query_signature.rs @@ -0,0 +1,437 @@ +use prusti_utils::config; +use std::{collections::HashMap, fmt}; + +use super::compiler_info::ProcDef; +use prusti_rustc_interface::{ + hir::{def::DefKind, def_id::DefId}, + // middle::ty::{self, Clause, DefIdTree, ImplSubject, PredicateKind, TyCtxt}, + middle::ty::{ImplSubject, ClauseKind, TyCtxt}, +}; + +/// Data structure used to generate an external specification template. +/// The main purpose of this struct is to strictly split the collection +/// of all this information from the displaying afterwards. +/// Allows users in prusti-assistant to query such a template for function calls. +#[derive(Debug)] +enum ExternSpecBlock { + StandaloneFn { + parent_chain: String, + function_sig: FunctionSignature, + }, + Trait { + name: String, + path: String, + generics: Vec, + bounds: HashMap>, + function_sig: FunctionSignature, + }, + Impl { + name: String, // in this case the name also includes + // the path + trait_name: Option, + generics: Vec, + bounds: HashMap>, + function_sig: FunctionSignature, + }, +} + +impl ExternSpecBlock { + fn from_defid(tcx: TyCtxt<'_>, defid: DefId) -> Option { + let def_kind = tcx.opt_def_kind(defid)?; + let signature = FunctionSignature::from_defid(tcx, defid)?; + match def_kind { + DefKind::Fn => { + let parent_chain = get_parent_chain(defid, tcx); + Some(ExternSpecBlock::StandaloneFn { + parent_chain, + function_sig: signature, + }) + } + DefKind::AssocFn => { + // this will be None for traits + match tcx.impl_of_method(defid) { + Some(impl_defid) => { + // function is part of impl block + let mut trait_name = None; + // let impl_subj = tcx.impl_subject(impl_defid); + let impl_subj = tcx.impl_subject(impl_defid).skip_binder(); + let self_ty = match impl_subj { + ImplSubject::Trait(tref) => { + trait_name = Some(format!("{}", tref.print_only_trait_name())); + tref.self_ty() + } + ImplSubject::Inherent(ty) => ty, + } + .to_string(); + let generics = generic_params(tcx, impl_defid); + let bounds = trait_bounds(tcx, impl_defid); + + Some(ExternSpecBlock::Impl { + name: self_ty, + trait_name, + generics, + bounds, + function_sig: signature, + }) + } + None => { + // function is a Trait (or something else?) + // are there other cases for AssocFns? + let parent = tcx.opt_parent(defid)?; + if tcx.is_trait(parent) { + // indeed a trait + let traitname = tcx.opt_item_name(parent)?.to_string(); + let parent_defpath = get_parent_chain(parent, tcx); + let trait_params = generic_params(tcx, parent); + let bounds = trait_bounds(tcx, parent); + + Some(ExternSpecBlock::Trait { + name: traitname, + path: parent_defpath, + generics: trait_params, + bounds, + function_sig: signature, + }) + } else { + None + } + } + } + } + _ => { + // another case to be handled? + None + } + } + } +} + +impl fmt::Display for ExternSpecBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExternSpecBlock::Impl { + name, + trait_name, + generics, + bounds, + function_sig, + } => { + let generics_str = generic_args_str(generics, false); + let where_block = bounds_where_block(bounds); + + write!(f, "#[extern_spec]\nimpl{generics_str} ")?; + if let Some(trait_name) = trait_name { + write!(f, "{trait_name} for ")?; + } + writeln!(f, "{name}{where_block}\n{{")?; + let fn_sig = indent(&function_sig.to_string()); + write!(f, "{fn_sig}\n}}") + } + ExternSpecBlock::Trait { + name, + path, + generics, + bounds, + function_sig, + } => { + let fn_sig = indent(&function_sig.to_string()); + let generics_str = generic_args_str(generics, false); + let where_block = bounds_where_block(bounds); + // do traits specify traitbounds too? + writeln!(f, "#[extern_spec({path})]")?; + writeln!(f, "trait {name}{generics_str}{where_block}\n{{")?; + writeln!(f, "{fn_sig}\n}}") + } + ExternSpecBlock::StandaloneFn { + parent_chain, + function_sig, + } => { + writeln!(f, "#[extern_spec({parent_chain})]")?; + write!(f, "{function_sig}") + } + } + } +} + +#[derive(Debug, Clone)] +struct GenericArg { + name: String, + _default_value: Option, +} + +impl fmt::Display for GenericArg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + // Trait bounds will always be in where blocks since this + // complicates things a lot otherwise. + // for now we ignore defaults since prusti doesnt accept them in + // some cases.. + // if self.default_value.is_some() { + // write!(f, "={}", self.default_value.as_ref().unwrap())?; + // } + } +} + +/// the string for the where clause. Given a list of genericArgs, this would +/// generate a string of the form: +/// ``` +/// where +/// T: bound1 + bound2, +/// S: anotherbound, +/// ``` +fn bounds_where_block(traitbounds: &HashMap>) -> String { + let bounds_vec = traitbounds + .iter() + .map(|(arg, bounds)| format!("\t{}: {}", arg, traitbounds_string(bounds))) + .collect::>(); + if !bounds_vec.is_empty() { + format!("\nwhere\n{}", bounds_vec.join(",\n")) + } else { + "".to_string() + } +} + +/// If a function or impl block has a list of generic arguments, this +/// will generate the string for it such as . +fn generic_args_str(arglist: &Vec, include_bounds: bool) -> String { + if !arglist.is_empty() { + let res = arglist + .iter() + .map(|genarg| { + if include_bounds { + genarg.to_string() + } else { + genarg.name.clone() + } + }) + .collect::>() + .join(", "); + format!("<{res}>") + } else { + "".to_string() + } +} + +/// example result: Sized + PartialEq + Eq +fn traitbounds_string(boundlist: &Vec) -> String { + if !boundlist.is_empty() { + let res = boundlist + .iter() + .map(|bound| bound.to_string()) + .collect::>() + .join(" + "); + res + } else { + "".to_string() + } +} + +#[derive(Debug, Clone)] +struct TraitBound { + name: String, + args: Vec, +} + +impl fmt::Display for TraitBound { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name)?; + if !self.args.is_empty() { + let args_str = self.args.join(", "); + write!(f, "<{args_str}>")?; + } + Ok(()) + } +} + +#[derive(Debug)] +struct FunctionSignature { + name: String, + generics: Vec, + bounds: HashMap>, + arguments: Vec<(String, String)>, // (name, type) + return_type: Option, +} + +impl FunctionSignature { + fn from_defid(tcx: TyCtxt<'_>, defid: DefId) -> Option { + let name = tcx.opt_item_name(defid)?.to_string(); + let sig = tcx.fn_sig(defid).skip_binder(); + let arg_types = sig.inputs().iter(); + let arg_names = tcx.fn_arg_names(defid); + let output = sig.output().skip_binder(); + let return_type = if output.is_unit() { + None + } else { + Some(output.to_string()) + }; + + let arguments: Vec<(String, String)> = arg_names + .iter() + .zip(arg_types) + .map(|(name, ty)| (name.to_string(), ty.skip_binder().to_string())) + .collect(); + + let generics = generic_params(tcx, defid); + let bounds = trait_bounds(tcx, defid); + Some(Self { + name, + generics, + bounds, + arguments, + return_type, + }) + } + + fn arguments_string(&self) -> String { + let args = self + .arguments + .iter() + .map(|(name, ty)| format!("{name}: {ty}")) + .collect::>() + .join(", "); + format!("({args})") + } +} + +impl fmt::Display for FunctionSignature { + // fn(args) -> output where + // bounds; + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let generics_str = generic_args_str(&self.generics, false); + let where_block = bounds_where_block(&self.bounds); + let args_str = self.arguments_string(); + + write!(f, "fn {}{}{}", self.name, generics_str, args_str)?; + if let Some(ret_ty) = self.return_type.clone() { + write!(f, " -> {ret_ty}")?; + } + write!(f, "{where_block};") + } +} + +fn generic_params(tcx: TyCtxt<'_>, defid: DefId) -> Vec { + let generics = tcx.generics_of(defid); + + generics + .params + .iter() + .filter_map(|param| { + let ident = param.name.to_string(); + if ident == "Self" { + None + } else { + // let substs = ty::subst::InternalSubsts::identity_for_item(tcx, defid); + let default_value = param + .default_value(tcx) + // FIXME: i'm not sure what subst really is but am assuming ty::generic_args::GenerigArg is its analog + // in this rust nightly version. + // the construct seems to be removed from documentation? https://doc.rust-lang.org/stable/nightly-rustc/rustc_middle/ty/subst/ + // .map(|val| val.skip_binder().subst(tcx, substs).to_string()); + .map(|val| val.skip_binder().to_string()); + Some(GenericArg { + name: ident, + _default_value: default_value, + }) + } + }) + .collect() +} + +fn trait_bounds(tcx: TyCtxt<'_>, defid: DefId) -> HashMap> { + let mut traitbounds: HashMap> = HashMap::new(); + let predicates = tcx.predicates_of(defid); + + for (predicate, _) in predicates.predicates { + let kind: ClauseKind = predicate.kind().skip_binder(); + // let kind: PredicateKind = predicate.kind().skip_binder(); + match kind { + // PredicateKind::Clause(Clause::Trait(t)) => { + ClauseKind::Trait(t) => { + let bound_traitref = t.trait_ref; + let trait_name = tcx.def_path_str(bound_traitref.def_id); + let self_ty = format!("{}", bound_traitref.self_ty()); + if self_ty == "Self" { + continue; + } + // let trait_args_opt = bound_traitref.substs.try_as_type_list(); + let trait_args = bound_traitref.args + // let trait_args = if let Some(typelist) = trait_args_opt { + // typelist + .iter() + .skip(1) // the first one is the self type + .map(|ty| format!("{ty}")) + .collect::>(); + // .collect::>() + // } else { + // vec![] + // }; + let bound = TraitBound { + name: trait_name, + args: trait_args, + }; + // add this bound to the given type. + if let Some(bounds) = traitbounds.get_mut(&self_ty) { + bounds.push(bound); + } else { + traitbounds.insert(self_ty, vec![bound]); + } + } + // PredicateKind::Clause(Clause::Projection(p)) => { + ClauseKind::Projection(p) => { + let item_id = p.projection_ty.def_id; + let self_ty = format!("{}", p.projection_ty.self_ty()); + let trait_defid: DefId = p.projection_ty.trait_def_id(tcx); + let trait_defpath = tcx.def_path_str(trait_defid); + + let item_name = tcx.item_name(item_id).to_string(); + + let projection_term = format!("{}={}", item_name, p.term); + let bounds = traitbounds.get_mut(&self_ty).unwrap(); + let mut last_bound = bounds.pop().unwrap(); + if last_bound.name == trait_defpath { + last_bound.args.push(projection_term); + } + bounds.push(last_bound); + } + _ => {} + } + } + traitbounds +} + +pub fn collect_queried_signature(tcx: TyCtxt<'_>, fncalls: &[ProcDef]) -> Option { + let def_path_str: String = config::query_method_signature()?; + let existing = fncalls + .iter() + .find(|procdef| procdef.name == def_path_str)?; + + // helpers: + let defid: DefId = existing.defid; + let extern_spec_block = ExternSpecBlock::from_defid(tcx, defid); + extern_spec_block.map(|x| x.to_string()) +} + +fn get_parent_chain(defid: DefId, tcx: TyCtxt<'_>) -> String { + // let mut parent_opt = tcx.opt_parent(defid); + // this (above) apparently doesn't work. E.g. for std::ops::Add + // it will return std::ops::arith which is problematic + let defpath_str = tcx.def_path_str(defid); + let mut defpath: Vec<&str> = defpath_str.split("::").collect(); + defpath.pop(); // drop the name itself + defpath.join("::") +} + +/// indent all lines that are not empty by one tab +fn indent(input: &String) -> String { + let mut res = String::from("\t"); + let len = input.len(); + for (i, c) in input.chars().enumerate() { + res.push(c); + if c == '\n' && i != len - 1 { + // insert a tab after every newline that is not terminating + // the string + res.push('\t'); + } + } + res +} From 1443c24361770ff9d5752305c289dbaa529eed65 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 5 Jul 2024 11:45:01 +0200 Subject: [PATCH 10/53] Improve contract span emission `SpanOfCallContracts` now has a vector as it's `call_spans` field so only one object is created per function definition rather than one per call. `CallSpanFinder` now also collects local functions for call contract span emission. Functions that are never called need not be emitted. --- Cargo.lock | 2 + prusti-encoder/Cargo.toml | 2 + prusti-encoder/src/lib.rs | 54 +++++++++++++++++- prusti-server/src/ide/encoding_info.rs | 50 +++++++++++++++- prusti-server/src/ide/vsc_span.rs | 2 +- prusti/src/callbacks.rs | 11 ++-- prusti/src/ide_helper/call_finder.rs | 79 +++++++++++++------------- prusti/src/ide_helper/compiler_info.rs | 67 ++++++++++++++++++---- prusti/src/verifier.rs | 42 +++++++++++++- 9 files changed, 251 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38fc05cb543..3464a355f09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1871,6 +1871,8 @@ dependencies = [ "mir-state-analysis", "prusti-interface", "prusti-rustc-interface", + "prusti-server", + "prusti-utils", "task-encoder", "tracing 0.1.0", "vir", diff --git a/prusti-encoder/Cargo.toml b/prusti-encoder/Cargo.toml index b79a39469a7..e1cf1189fe5 100644 --- a/prusti-encoder/Cargo.toml +++ b/prusti-encoder/Cargo.toml @@ -8,8 +8,10 @@ edition = "2021" doctest = false # we have no doc tests [dependencies] +prusti-utils = { path = "../prusti-utils" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } prusti-interface = { path = "../prusti-interface" } +prusti-server = { path = "../prusti-server" } mir-ssa-analysis = { path = "../mir-ssa-analysis" } mir-state-analysis = { path = "../mir-state-analysis" } task-encoder = { path = "../task-encoder" } diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index b114bf7edcc..670557f1cca 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -12,26 +12,34 @@ mod encoder_traits; pub mod request; use prusti_interface::environment::EnvBody; +use prusti_interface::environment::EnvQuery; use prusti_rustc_interface::{ middle::ty, hir, hir::def_id::DefId, + span::Span, + data_structures::fx::FxHashMap, }; +use prusti_utils::config; use task_encoder::TaskEncoder; +use prusti_server::ide::encoding_info::SpanOfCallContracts; use crate::encoders::lifted::ty_constructor::TyConstructorEnc; pub fn test_entrypoint<'tcx>( tcx: ty::TyCtxt<'tcx>, body: EnvBody<'tcx>, + query: EnvQuery<'tcx>, def_spec: prusti_interface::specs::typed::DefSpecificationMap, procedures: &Vec, + contract_spans_map: &mut FxHashMap, ) -> request::RequestWithContext { crate::encoders::init_def_spec(def_spec); vir::init_vcx(vir::VirCtxt::new(tcx, body)); // TODO: this should be a "crate" encoder, which will deps.require all the methods in the crate + let source_map = tcx.sess.source_map(); for def_id in tcx.hir().body_owners() { tracing::debug!("test_entrypoint item: {def_id:?}"); let kind = tcx.def_kind(def_id); @@ -46,9 +54,53 @@ pub fn test_entrypoint<'tcx>( let (is_pure, is_trusted) = crate::encoders::with_proc_spec(def_id, |proc_spec| { let is_pure = proc_spec.kind.is_pure().unwrap_or_default(); let is_trusted = proc_spec.trusted.extract_inherit().unwrap_or_default(); + + if config::show_ide_info() { + // TODO: only handles inherent spec items + contract_spans_map + .entry(def_id) + .and_modify(|contract_spans| { + let mut spans: Vec = Vec::new(); + // the `get` method has a comment about how it is a bad API, but does it matter + // if we know if the result is inkerent, inherited or refined here? + // if let Some((_, pre_def_ids)) = proc_spec.pres.get() { + if let Some(pre_def_ids) = proc_spec.pres.expect_empty_or_inherent() { + let mut pre_spans = pre_def_ids + .iter() + .map(|pre_def_id| query.get_def_span(pre_def_id)) + .collect::>(); + spans.append(&mut pre_spans); + } + if let Some(post_def_ids) = proc_spec.posts.expect_empty_or_inherent() { + let mut post_spans = post_def_ids + .iter() + .map(|pre_def_id| query.get_def_span(pre_def_id)) + .collect::>(); + spans.append(&mut post_spans); + } + if let Some(pledges) = proc_spec.pledges.expect_empty_or_inherent() { + let mut pledge_spans = pledges + .iter() + .map(|pledge| { + let rhs = query.get_def_span(pledge.rhs); + if let Some(lhs) = pledge.lhs { query.get_def_span(lhs).to(rhs) } + else { rhs } + }).collect::>(); + spans.append(&mut pledge_spans); + } + if let Some(Some(purity_def_id)) = proc_spec.purity.expect_empty_or_inherent() { + spans.push(query.get_def_span(purity_def_id)); + } + if let Some(Some(terminates_def_id)) = proc_spec.terminates.expect_empty_or_inherent() { + spans.push(query.get_def_span(terminates_def_id.to_def_id())); + } + contract_spans.set_contract_spans(spans, source_map); + }); + } + (is_pure, is_trusted) }).unwrap_or_default(); - + if procedures.contains(&def_id) && !(is_trusted && is_pure) { let substs = ty::GenericArgs::identity_for_item(tcx, def_id); let res = crate::encoders::MirImpureEnc::encode((def_id, substs, None)); diff --git a/prusti-server/src/ide/encoding_info.rs b/prusti-server/src/ide/encoding_info.rs index e49abec9032..96c6655fe34 100644 --- a/prusti-server/src/ide/encoding_info.rs +++ b/prusti-server/src/ide/encoding_info.rs @@ -1,9 +1,57 @@ +use prusti_rustc_interface::span::{source_map::SourceMap, Span}; use serde::Serialize; use super::vsc_span::VscSpan; +/// Represents the locations of specifications of a function call. +/// Generated for each encoded function call to be used by prusti-assistant. +#[derive(Serialize, Clone, Debug)] +pub struct SpanOfCallContracts { + /// the defpath of the method that is called + pub defpath: String, + /// the spans where this method is called + pub call_spans: Vec, + /// the spans of all the specifications of the called method + pub contracts_spans: Vec, +} + +impl SpanOfCallContracts { + pub fn new( + defpath: String, + call_spans: Vec, + contracts_spans: Vec, + source_map: &SourceMap + ) -> Self { + let call_spans = call_spans + .iter() + .map(|sp| VscSpan::from_span(sp, source_map)) + .collect::>(); + let contracts_spans = contracts_spans + .iter() + .map(|sp| VscSpan::from_span(sp, source_map)) + .collect::>(); + Self { + defpath, + call_spans, + contracts_spans, + } + } + + pub fn set_contract_spans(&mut self, spans: Vec, source_map: &SourceMap) { + self.contracts_spans = spans + .iter() + .map(|sp| VscSpan::from_span(sp, source_map)) + .collect::>(); + } + + pub fn push_call_span(&mut self, span: &Span, source_map: &SourceMap) { + let vsc_span = VscSpan::from_span(span, source_map); + self.call_spans.push(vsc_span); + } +} + #[derive(Serialize)] pub struct EncodingInfo { - pub call_contract_spans: String, + pub call_contract_spans: Vec, } impl EncodingInfo { diff --git a/prusti-server/src/ide/vsc_span.rs b/prusti-server/src/ide/vsc_span.rs index e452e74bec1..21428a688c5 100644 --- a/prusti-server/src/ide/vsc_span.rs +++ b/prusti-server/src/ide/vsc_span.rs @@ -2,7 +2,7 @@ use prusti_rustc_interface::span::{source_map::SourceMap, Span}; use serde::Serialize; /// a representation of spans that is more usable with VSCode. -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, Debug)] pub struct VscSpan { column_end: usize, column_start: usize, diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 0908fd6a4f1..6fedc6efc4f 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -12,7 +12,7 @@ use prusti_interface::{ }; use prusti_rustc_interface::{ borrowck::consumers, - data_structures::steal::Steal, + data_structures::{steal::Steal, fx::FxHashMap}, driver::Compilation, index::IndexVec, interface::{interface::Compiler, Config, Queries}, @@ -188,12 +188,15 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // that is already in `def_spec`? let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); + let mut call_spans_map = FxHashMap::default(); if config::show_ide_info() && !config::no_verify() { - let compiler_info = + let mut compiler_info = compiler_info::IdeInfo::collect(&env, &annotated_procedures, &def_spec); let out = serde_json::to_string(&compiler_info).unwrap(); PrustiError::message(format!("compilerInfo{out}"), DUMMY_SP.into()) .emit(&env.diagnostic); + // TODO: might only need local ones if we can assume that external calls have no contract spans + call_spans_map = compiler_info.get_call_spans_map(); } // as long as we have to throw a fake error we need to check this let is_primary_package = std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); @@ -217,7 +220,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // (for a single method for example) will cause this result // to be cached by compiler at the moment let env_diagnostic = env.diagnostic.clone(); - verify(env, def_spec, selective_task); + verify(env, def_spec, selective_task, call_spans_map); fake_error(&env_diagnostic); } } else { @@ -225,7 +228,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { procedures: annotated_procedures, types, }; - verify(env, def_spec, verification_task); + verify(env, def_spec, verification_task, call_spans_map); } } else if config::skip_verification() && !config::no_verify() && is_primary_package { // add a fake error, reason explained in issue #1261 diff --git a/prusti/src/ide_helper/call_finder.rs b/prusti/src/ide_helper/call_finder.rs index 971b2cb121c..f8870d35300 100644 --- a/prusti/src/ide_helper/call_finder.rs +++ b/prusti/src/ide_helper/call_finder.rs @@ -14,6 +14,7 @@ pub struct CallSpanFinder<'tcx> { pub env_query: EnvQuery<'tcx>, pub tcx: TyCtxt<'tcx>, pub called_functions: Vec<(String, DefId, Span)>, + pub called_functions_local: Vec<(String, DefId, Span)>, } impl<'tcx> CallSpanFinder<'tcx> { @@ -21,6 +22,7 @@ impl<'tcx> CallSpanFinder<'tcx> { Self { env_query: env.query, called_functions: Vec::new(), + called_functions_local: Vec::new(), tcx: env.tcx(), } } @@ -59,10 +61,11 @@ impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { let tyck_res = self.tcx.typeck(e1.hir_id.owner.def_id); let res = tyck_res.qpath_res(qself, e1.hir_id); if let prusti_rustc_interface::hir::def::Res::Def(_, def_id) = res { - if def_id.as_local().is_none() { - let defpath = self.tcx.def_path_str(def_id); - self.called_functions.push((defpath, def_id, expr.span)); - } + let defpath = self.tcx.def_path_str(def_id); + let called_functions = + if def_id.as_local().is_none() {&mut self.called_functions} + else {&mut self.called_functions_local}; + called_functions.push((defpath, def_id, expr.span)); } } } @@ -71,18 +74,18 @@ impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { if let Ok((method_def_id, resolved_def_id)) = resolve_res { let defpath_unresolved = self.tcx.def_path_str(method_def_id); let defpath_resolved = self.tcx.def_path_str(resolved_def_id); - - if method_def_id.as_local().is_none() { - if defpath_unresolved == defpath_resolved { - self.called_functions - .push((defpath_resolved, resolved_def_id, sp)); - } else { - // in this case we want both - self.called_functions - .push((defpath_resolved, resolved_def_id, sp)); - self.called_functions - .push((defpath_unresolved, method_def_id, sp)); - } + let called_functions = + if method_def_id.as_local().is_none() {&mut self.called_functions} + else {&mut self.called_functions_local}; + if defpath_unresolved == defpath_resolved { + called_functions + .push((defpath_resolved, resolved_def_id, sp)); + } else { + // in this case we want both + called_functions + .push((defpath_resolved, resolved_def_id, sp)); + called_functions + .push((defpath_unresolved, method_def_id, sp)); } } } @@ -92,29 +95,29 @@ impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { if let Ok((method_def_id, resolved_def_id)) = resolve_res { let defpath_unresolved = self.tcx.def_path_str(method_def_id); let defpath_resolved = self.tcx.def_path_str(resolved_def_id); + let called_functions = + if method_def_id.as_local().is_none() {&mut self.called_functions} + else {&mut self.called_functions_local}; + if defpath_unresolved == defpath_resolved { + called_functions.push(( + defpath_resolved, + resolved_def_id, + expr.span, + )); + } else { + // For binary operations this will be the operation + // from the standard libary and the "overriding" method - if method_def_id.as_local().is_none() { - if defpath_unresolved == defpath_resolved { - self.called_functions.push(( - defpath_resolved, - resolved_def_id, - expr.span, - )); - } else { - // For binary operations this will be the operation - // from the standard libary and the "overriding" method - - self.called_functions.push(( - defpath_resolved, - resolved_def_id, - expr.span, - )); - self.called_functions.push(( - defpath_unresolved, - method_def_id, - expr.span, - )); - } + called_functions.push(( + defpath_resolved, + resolved_def_id, + expr.span, + )); + called_functions.push(( + defpath_unresolved, + method_def_id, + expr.span, + )); } } } diff --git a/prusti/src/ide_helper/compiler_info.rs b/prusti/src/ide_helper/compiler_info.rs index 63371ab7c5a..430233ae2c8 100644 --- a/prusti/src/ide_helper/compiler_info.rs +++ b/prusti/src/ide_helper/compiler_info.rs @@ -3,8 +3,9 @@ use prusti_interface::{environment::Environment, specs::typed}; use prusti_rustc_interface::{ hir::def_id::DefId, span::{source_map::SourceMap, Span}, + data_structures::fx::FxHashMap, }; -use prusti_server::ide::vsc_span::VscSpan; +use prusti_server::ide::{vsc_span::VscSpan, encoding_info::SpanOfCallContracts}; use serde::Serialize; /// This struct will be passed to prusti-assistant containing information @@ -12,8 +13,14 @@ use serde::Serialize; #[derive(Serialize)] pub struct IdeInfo { procedure_defs: Vec, + // this only contains calls to external functions function_calls: Vec, + // this only contains calls to functions defined in this crate + #[serde(skip)] + function_calls_local: Vec, queried_source: Option, + #[serde(skip)] + contract_spans_map: Option>, } impl IdeInfo { @@ -24,29 +31,69 @@ impl IdeInfo { ) -> Self { let procedure_defs = collect_procedures(env, procedures, def_spec); let source_map = env.tcx().sess.source_map(); - let function_calls = collect_fncalls(env) + let (fn_calls, fn_calls_local) = collect_fncalls(env); + let mut contract_spans_map: FxHashMap = FxHashMap::default(); + let function_calls = fn_calls + .into_iter() + .map(|(name, defid, sp)| { + contract_spans_map.entry(defid) + .and_modify(|cs: &mut SpanOfCallContracts| cs.push_call_span(&sp, source_map)) + .or_insert(SpanOfCallContracts::new( + name.clone(), + vec![sp], + vec![], + source_map, + )); + ProcDef { + name, + defid, + span: VscSpan::from_span(&sp, source_map), + } + }) + .collect::>(); + let function_calls_local = fn_calls_local .into_iter() - .map(|(name, defid, sp)| ProcDef { - name, - defid, - span: VscSpan::from_span(&sp, source_map), + .map(|(name, defid, sp)| { + contract_spans_map.entry(defid) + .and_modify(|cs: &mut SpanOfCallContracts| cs.push_call_span(&sp, source_map)) + .or_insert(SpanOfCallContracts::new( + name.clone(), + vec![sp], + vec![], + source_map, + )); + ProcDef { + name, + defid, + span: VscSpan::from_span(&sp, source_map), + } }) - .collect::>(); + .collect::>(); // For declaring external specifications: let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); Self { procedure_defs, function_calls, + function_calls_local, queried_source, + contract_spans_map: Some(contract_spans_map), } } + + // pub fn get_calls(&self) -> Vec { + pub fn get_call_spans_map(&mut self) -> FxHashMap { + // let mut fncalls = self.function_calls.clone(); + // fncalls.append(&mut self.function_calls_local.clone()); + // fncalls + self.contract_spans_map.take().expect("Map has already been taken") + } } /// A struct that contains either a reference to a procedure that can be verified /// (for selective verification) or a function call (so a user can query /// external_spec blocks for it). The name contains the defpath. -#[derive(Serialize)] +#[derive(Serialize, Debug, Clone)] pub struct ProcDef { pub name: String, #[serde(skip)] @@ -105,11 +152,11 @@ fn collect_procedures( /// collect all the function calls, so the extension can query external_spec /// templates for it -fn collect_fncalls(env: &Environment<'_>) -> Vec<(String, DefId, Span)> { +fn collect_fncalls(env: &Environment<'_>) -> (Vec<(String, DefId, Span)>, Vec<(String, DefId, Span)>) { let mut fnvisitor = call_finder::CallSpanFinder::new(env); env.tcx() .hir() .visit_all_item_likes_in_crate(&mut fnvisitor); - fnvisitor.called_functions + (fnvisitor.called_functions, fnvisitor.called_functions_local) } diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 3e3c51941a3..09fd33cedfa 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -4,16 +4,25 @@ use log::{debug, warn}; use prusti_utils::{config, report::user}; use prusti_interface::{ data::{VerificationResult, VerificationTask}, - environment::Environment, + environment::{Environment, EnvDiagnostic}, specs::typed, + PrustiError, }; -use prusti_rustc_interface::errors::MultiSpan; +use prusti_rustc_interface::{ + errors::MultiSpan, + span::DUMMY_SP, + data_structures::fx::FxHashMap, + hir::def_id::DefId, +}; +use prusti_server::ide::encoding_info::{SpanOfCallContracts, EncodingInfo}; +use crate::ide_helper::compiler_info::ProcDef; #[tracing::instrument(name = "prusti::verify", level = "debug", skip(env))] pub fn verify<'tcx>( env: Environment<'tcx>, def_spec: typed::DefSpecificationMap, verification_task: VerificationTask<'tcx>, + mut contract_spans_map: FxHashMap, ) { if env.diagnostic.has_errors() { warn!("The compiler reported an error, so the program will not be verified."); @@ -52,10 +61,17 @@ pub fn verify<'tcx>( // which is constructed further inside `prusti_server`) let request = prusti_encoder::test_entrypoint( env.tcx(), - env.body, // FIXME: currently needing env by value because of this line + env.body, + env.query, def_spec, &verification_task.procedures, + &mut contract_spans_map, ); + + if config::show_ide_info() { + emit_contract_spans(&env.diagnostic, contract_spans_map); + } + let program = request.program; let result = prusti_server::verify_programs(&env.diagnostic, vec![program]); @@ -113,3 +129,23 @@ pub fn verify<'tcx>( //}; } } + +pub fn emit_contract_spans( + env_diagnostic: &EnvDiagnostic<'_>, + contract_spans_map: FxHashMap, +) { + let mut contract_spans: Vec = contract_spans_map + .into_values() + .collect(); + contract_spans.retain(|cs| !cs.contracts_spans.is_empty()); + // sort, so the order does not randomly change between runs + contract_spans + .sort_by(|a,b| a.defpath.cmp(&b.defpath)); + + let encoding_info = EncodingInfo { call_contract_spans: contract_spans }; + PrustiError::message( + format!("encodingInfo{}", encoding_info.to_json_string()), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); +} From cd45907708c829d3c7dfd8cf2c504c26ca40a3fb Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 12 Jul 2024 09:57:29 +0200 Subject: [PATCH 11/53] Accept defpaths argument as a single string --- docs/dev-guide/src/config/flags.md | 6 ++++-- prusti-utils/src/config.rs | 22 +++++++++++++++++++--- prusti/src/callbacks.rs | 4 ++-- viper/Cargo.toml | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index a3dda374123..7c19210eea7 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -375,9 +375,11 @@ When enabled for both server and client, certain supported Viper messages will b When enabled for both server and client, messages for individual basic blocks will be reported to the user. Does nothing if [`REPORT_VIPER_MESSAGES`](#report_viper_messages) is not enabled. Intended for usage with the Prusti Assistant (IDE). -## `VERIFY_ONLY_DEFPATH` +## `VERIFY_ONLY_DEFPATHS` -When set to the defpath of a local method, prusti will only verify the specified method. A fake error will be generated to avoid caching of a success. +When set to a list of defpaths of local methods, prusti will only verify the specified methods. A fake error will be generated to avoid caching of a success. + +> **Note:** This argument should be of JSON compatible form, e.g. `["","METHOD2>",...]`, with no white spaces. ## `SERVER_ADDRESS` diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 3a960ee3ba4..0ec22cb3472 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -174,7 +174,7 @@ lazy_static::lazy_static! { // Flags specifically for Prusti-Assistant: settings.set_default("show_ide_info", false).unwrap(); settings.set_default("skip_verification", false).unwrap(); - settings.set_default::>("verify_only_defpath", vec![]).unwrap(); + settings.set_default::>("verify_only_defpaths", None).unwrap(); settings.set_default::>("query_method_signature", None).unwrap(); settings.set_default("report_block_messages", false).unwrap(); @@ -1067,8 +1067,24 @@ pub fn skip_verification() -> bool { /// Used for selective verification, can be passed a String containing /// the DefPath of the method to be verified -pub fn verify_only_defpath() -> Vec { - read_setting("verify_only_defpath") +pub fn verify_only_defpaths() -> Vec { + if let Some(input) = read_setting::>("verify_only_defpaths") { + if input.starts_with('[') || input.ends_with(']') { + panic!("verify_only_defpaths: invalid format. Make sure to enclose the list in brackets (`[]`). Was `{input}` but expected form `[\"\",\"\",...]`") + } + let trimmed = &input[1..input.len()-1]; + let parts: Vec<&str> = trimmed.split(',').collect(); + parts.into_iter() + .filter(|s| !s.is_empty()) + .map(|s| + if s.len() >= 2 && s.starts_with('\"') && s.ends_with('\"') { + s[1..s.len()-1].to_string() + } else { + panic!("verify_only_defpaths: invalid format. Make sure to use double quotes (`\"`) for each element. Element was `{s}` but expected \"\"") + } + ) + .collect() + } else {vec![]} } /// A flag that can be used to ask the compiler for the declaration / diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 6fedc6efc4f..18e43c32aef 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -203,7 +203,8 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // collect and output Information used by IDE: if !config::no_verify() && !config::skip_verification() { - let target_def_paths = config::verify_only_defpath(); + let target_def_paths = config::verify_only_defpaths(); + debug!("Received def paths: {:?}. Package is primary: {}", target_def_paths, is_primary_package); if !target_def_paths.is_empty() { // if we do selective verification, then definitely only // for methods of the primary package. Check needed because @@ -214,7 +215,6 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { .into_iter() .filter(|x| target_def_paths.contains(&env.name.get_unique_item_name(*x))) .collect(); - debug!("selected procedures: {:?}", procedures); let selective_task = VerificationTask { procedures, types }; // fake_error because otherwise a verification-success // (for a single method for example) will cause this result diff --git a/viper/Cargo.toml b/viper/Cargo.toml index 0715e82be61..83aed1c9b09 100644 --- a/viper/Cargo.toml +++ b/viper/Cargo.toml @@ -18,7 +18,7 @@ bincode = "1.3.3" rustc-hash = "1.1.0" tokio = { version = "1.20", features = ["io-util", "net", "rt", "sync"] } futures = "0.3.21" -smt-log-analyzer = { path = "../smt-log-analyzer"} +smt-log-analyzer = { path = "../smt-log-analyzer" } tracing = { path = "../tracing" } [dev-dependencies] From 9f58eef5099356480bd32ccdefa1314347fabc22 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 12 Jul 2024 12:48:29 +0200 Subject: [PATCH 12/53] WIP: Restructure how verification requests are handled A main goal here is that the vir-viper translation happens on the same thread that encodes the program in the local verification case. This should happen so the allocated arena, which is thread local, can be used. Additionally, the JVM reliant bits are a bit more hidden from the general procedure. --- Cargo.lock | 4 +- prusti-server/src/backend.rs | 29 +- prusti-server/src/process_verification.rs | 331 ++++------------------ prusti-server/src/verification_request.rs | 236 ++++++++++++++- viper/src/ast_factory/macros.rs | 2 +- 5 files changed, 300 insertions(+), 302 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3464a355f09..042433ec506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -720,9 +720,9 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index c85db67fdde..98a3bcd741b 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -22,37 +22,18 @@ pub enum Backend<'a> { impl<'a> Backend<'a> { pub fn verify( &mut self, - program: vir::ProgramRef, + viper_program: viper::Program, sender: mpsc::Sender, ) -> VerificationResultKind { match self { - Backend::Viper(ref mut verifier, context, viper_arc) => { - let mut stopwatch = - Stopwatch::start("prusti-server backend", "construction of JVM objects"); + Backend::Viper(ref mut verifier, viper_thread, viper_arc) => { - let ast_utils = context.new_ast_utils(); + let mut stopwatch = Stopwatch::start("prusti-server backend", "viper verification"); + let ast_utils = viper_thread.new_ast_utils(); ast_utils.with_local_frame(16, || { - let ast_factory = context.new_ast_factory(); - - let viper_program = vir::with_vcx(|vcx| { - let program = vcx.get_program(program); - prusti_viper::program_to_viper(program, &ast_factory) - }); - - if config::dump_viper_program() { - stopwatch.start_next("dumping viper program"); - dump_viper_program( - &ast_utils, - viper_program, - &program.get_name_with_check_mode(), - ); - } - - stopwatch.start_next("viper verification"); - if config::report_viper_messages() { - verify_and_poll_msgs(verifier, context, viper_arc, viper_program, sender) + verify_and_poll_msgs(verifier, viper_thread, viper_arc, viper_program, sender) } else { verifier.verify(viper_program) } diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index c572711d064..18c9b123f40 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -4,30 +4,20 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{Backend, ServerMessage, VerificationRequest, ViperBackendConfig}; -use futures::{lock, stream::Stream}; +use crate::{ServerMessage, VerificationRequest, ServerRequest}; +use futures::{lock, stream::Stream, future::Either}; use log::{debug, info}; -use once_cell::sync::Lazy; use prusti_utils::{ config, - report::log::{report, to_legal_file_name}, - Stopwatch, }; use std::{ - fs::create_dir_all, - path::PathBuf, sync::{self, mpsc, Arc}, thread, }; use viper::{ - smt_manager::SmtManager, Cache, PersistentCache, VerificationBackend, VerificationContext, - VerificationResult, VerificationResultKind, Viper, + Cache, PersistentCache, }; - -enum ServerRequest { - Verification(VerificationRequest), - SaveCache, -} +use async_stream::stream; struct ThreadJoin { handle: Option>, @@ -47,6 +37,8 @@ pub struct VerificationRequestProcessing { // mtx_tx_verreq has to be dropped before thread_join #[allow(dead_code)] thread_join: ThreadJoin, + // the cache is used across different threads + mtx_cache_arc: Arc>, } impl Default for VerificationRequestProcessing { @@ -65,35 +57,66 @@ impl VerificationRequestProcessing { let (tx_verreq, rx_verreq) = mpsc::channel(); let mtx_rx_servermsg = lock::Mutex::new(rx_servermsg); let mtx_tx_verreq = sync::Mutex::new(tx_verreq); + let cache = PersistentCache::load_cache(config::cache_path()); + let mtx_cache_arc = Arc::new(sync::Mutex::new(cache)); - let handle = thread::spawn(move || verification_thread(rx_verreq, tx_servermsg)); + let handle = thread::spawn(move || verification_thread(rx_verreq, tx_servermsg, mtx_cache_arc)); Self { mtx_rx_servermsg, mtx_tx_verreq, thread_join: ThreadJoin { handle: Some(handle), }, + mtx_cache_arc, } } pub fn verify(&self, request: VerificationRequest) -> impl Stream + '_ { - self.mtx_tx_verreq - .lock() - .unwrap() - .send(ServerRequest::Verification(request)) - .unwrap(); - // return a stream that has as last non-None message the ServerMessage::Termination - futures::stream::unfold(false, move |done: bool| async move { - if done { - return None; - } - let msg = self.mtx_rx_servermsg.lock().await.recv().unwrap(); - let mut done = false; - if let ServerMessage::Termination(_) = msg { - done = true; + let hash = request.get_hash(); + info!( + "Verification request hash: {} - for program {}", + hash, + request.program.get_name(), + ); + + // Early return in case of cache hit + // let mut cache = PersistentCache::load_cache(config::cache_path()); + if config::enable_cache() { + if let Some(mut result) = (&mut *self.mtx_cache_arc.lock().unwrap()).get(hash) { + info!( + "Using cached result {:?} for program {}", + &result, + request.program.get_name() + ); + /*if config::dump_viper_program() { + ast_utils.with_local_frame(16, || { + let _ = build_or_dump_viper_program(); + }); + } + normalization_info.denormalize_result(&mut result);*/ + result.cached = true; + return Either::Left( + futures::stream::once(async {ServerMessage::Termination(result)}) + ); } - Some((msg, done)) - }) + }; + + request.send_request(&self.mtx_tx_verreq); + + // return a stream that has as last non-None message the ServerMessage::Termination + Either::Right( + futures::stream::unfold(false, move |done: bool| async move { + if done { + return None; + } + let msg = self.mtx_rx_servermsg.lock().await.recv().unwrap(); + let mut done = false; + if let ServerMessage::Termination(_) = msg { + done = true; + } + Some((msg, done)) + }) + ) } pub fn save_cache(&self) { @@ -108,256 +131,18 @@ impl VerificationRequestProcessing { fn verification_thread( rx_verreq: mpsc::Receiver, tx_servermsg: mpsc::Sender, + mtx_cache_arc: Arc>, ) { debug!("Verification thread started."); - // The vcx is thread local, so we need to initialize it anew for this thread - vir::init_vcx(vir::VirCtxt::new_without_tcx()); - let viper = Lazy::new(|| { - Arc::new(Viper::new_with_args( - &config::viper_home(), - config::extra_jvm_args(), - )) - }); - let viper_thread = Lazy::new(|| viper.attach_current_thread()); - let mut cache = PersistentCache::load_cache(config::cache_path()); while let Ok(request) = rx_verreq.recv() { match request { - ServerRequest::Verification(verification_request) => process_verification_request( - &viper, - &viper_thread, - &mut cache, + ServerRequest::Verification(verification_request) => verification_request.execute( + mtx_cache_arc, &tx_servermsg, - verification_request, ), - ServerRequest::SaveCache => cache.save(), + ServerRequest::SaveCache => mtx_cache_arc.lock().unwrap().save(), } } debug!("Verification thread finished."); } - -#[tracing::instrument(level = "debug", skip_all, fields(program = %request.program.get_name()))] -pub fn process_verification_request<'v, 't: 'v>( - viper_arc: &Lazy, impl Fn() -> Arc>, - verification_context: &'v Lazy, impl Fn() -> VerificationContext<'t>>, - cache: impl Cache, - sender: &mpsc::Sender, - mut request: VerificationRequest, -) { - // TODO: if I'm not mistaken, this currently triggers the creation of the JVM on every request, - // so this should be changed once there are actually backends that not use a JVM - /* - let ast_utils = verification_context.new_ast_utils(); - - // Only for testing: Check that the normalization is reversible. - if config::print_hash() { - debug_assert!({ - let mut program = request.program.clone(); - let normalization_info = NormalizationInfo::normalize_program(&mut program); - normalization_info.denormalize_program(&mut program); - program == request.program - }); - } - - // Normalize the request before reaching the cache. - let normalization_info = NormalizationInfo::normalize_program(&mut request.program);*/ - - let hash = request.get_hash(); - info!( - "Verification request hash: {} - for program {}", - hash, - request.program.get_name(), - ); - /* - let build_or_dump_viper_program = || { - let mut stopwatch = Stopwatch::start("prusti-server", "construction of JVM objects"); - let ast_factory = verification_context.new_ast_factory(); - - let viper_program = prusti_viper::program_to_viper(request.program, &ast_factory); - //let viper_program = request - // .program - // .to_viper(prusti_common::vir::LoweringContext::default(), &ast_factory); - if config::dump_viper_program() { - stopwatch.start_next("dumping viper program"); - dump_viper_program( - &ast_utils, - viper_program, - &request.program.get_name_with_check_mode(), - ); - } - - viper_program - }; - - // Only for testing: Print the hash and skip verification. - if config::print_hash() { - println!( - "Received verification request for: {}", - request.program.get_name() - ); - println!("Hash of the request is: {hash}"); - // Some tests need the dump to report a diff of the Viper programs. - if config::dump_viper_program() { - ast_utils.with_local_frame(16, || { - let _ = build_or_dump_viper_program(); - }); - } - // return viper::VerificationResult::Success; - sender - .send(ServerMessage::Termination( - VerificationResult::dummy_success(), - )) - .unwrap(); - return; - } -*/ - // Early return in case of cache hit - if config::enable_cache() { - if let Some(mut result) = cache.get(hash) { - info!( - "Using cached result {:?} for program {}", - &result, - request.program.get_name() - ); - /*if config::dump_viper_program() { - ast_utils.with_local_frame(16, || { - let _ = build_or_dump_viper_program(); - }); - } - normalization_info.denormalize_result(&mut result);*/ - result.cached = true; - sender.send(ServerMessage::Termination(result)).unwrap(); - return; - } - }; - - let mut stopwatch = Stopwatch::start("prusti-server", "verifier startup"); - - // Create a new verifier each time. - // Workaround for https://github.com/viperproject/prusti-dev/issues/744 - let mut backend = match request.backend_config.backend { - VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( - new_viper_verifier( - request.program.get_name(), - verification_context, - request.backend_config, - ), - verification_context, - Lazy::force(viper_arc), - ), - }; - - let mut result = VerificationResult { - item_name: request.program.get_name().to_string(), - kind: VerificationResultKind::Success, - cached: false, - time_ms: 0, - }; - stopwatch.start_next("backend verification"); - result.kind = backend.verify(request.program, sender.clone()); - result.time_ms = stopwatch.finish().as_millis(); - - // Don't cache Java exceptions, which might be due to misconfigured paths. - if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { - info!( - "Storing new cached result {:?} for program {}", - &result, - request.program.get_name(), - ); - cache.insert(hash, result.clone()); - } - - /*normalization_info.denormalize_result(&mut result.kind);*/ - sender.send(ServerMessage::Termination(result)).unwrap(); -} - -pub fn dump_viper_program( - ast_utils: &viper::AstUtils, - program: viper::Program, - program_name: &str, -) { - let namespace = "viper_program"; - let filename = format!("{program_name}.vpr"); - info!("Dumping Viper program to '{}/{}'", namespace, filename); - report(namespace, filename, ast_utils.pretty_print(program)); -} - -fn new_viper_verifier<'v, 't: 'v>( - program_name: &str, - verification_context: &'v viper::VerificationContext<'t>, - backend_config: ViperBackendConfig, -) -> viper::Verifier<'v> { - let mut verifier_args: Vec = backend_config.verifier_args; - let report_path: Option; - if config::dump_debug_info() { - let log_path = config::log_dir() - .join("viper_tmp") - .join(to_legal_file_name(program_name)); - create_dir_all(&log_path).unwrap(); - report_path = Some(log_path.join("report.csv")); - let log_dir_str = log_path.to_str().unwrap(); - match backend_config.backend { - VerificationBackend::Silicon => { - verifier_args.extend(vec![ - "--tempDirectory".to_string(), - log_dir_str.to_string(), - "--printMethodCFGs".to_string(), - //"--printTranslatedProgram".to_string(), - ]) - } - VerificationBackend::Carbon => verifier_args.extend(vec![ - "--boogieOpt".to_string(), - format!("/logPrefix {log_dir_str}"), - //"--print".to_string(), "./log/boogie_program/program.bpl".to_string(), - ]), - } - } else { - report_path = None; - if backend_config.backend == VerificationBackend::Silicon { - // TODO: unknown option? - // verifier_args.extend(vec!["--disableTempDirectory".to_string()]); - } - } - let (smt_solver, smt_manager) = if config::use_smt_wrapper() { - std::env::set_var("PRUSTI_ORIGINAL_SMT_SOLVER_PATH", config::smt_solver_path()); - let log_path = config::log_dir() - .join("smt") - .join(to_legal_file_name(program_name)); - create_dir_all(&log_path).unwrap(); - let smt_manager = SmtManager::new( - log_path, - config::preserve_smt_trace_files(), - config::write_smt_statistics(), - config::smt_qi_ignore_builtin(), - config::smt_qi_bound_global_kind(), - config::smt_qi_bound_trace(), - config::smt_qi_bound_trace_kind(), - config::smt_unique_triggers_bound(), - config::smt_unique_triggers_bound_total(), - ); - std::env::set_var( - "PRUSTI_SMT_SOLVER_MANAGER_PORT", - smt_manager.port().to_string(), - ); - if config::log_smt_wrapper_interaction() { - std::env::set_var("PRUSTI_LOG_SMT_INTERACTION", "true"); - } - (config::smt_solver_wrapper_path(), smt_manager) - } else { - (config::smt_solver_path(), SmtManager::default()) - }; - let boogie_path = config::boogie_path(); - if let Some(bound) = config::smt_qi_bound_global() { - // We need to set the environment variable to reach our Z3 wrapper. - std::env::set_var("PRUSTI_SMT_QI_BOUND_GLOBAL", bound.to_string()); - } - - verification_context.new_verifier( - backend_config.backend, - verifier_args, - report_path, - smt_solver, - boogie_path, - smt_manager, - ) -} diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 402b74a292f..fe428afe9f2 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -4,8 +4,85 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use prusti_utils::config; -use viper::{self, VerificationBackend}; +use log::{debug, info}; +use viper::{self, VerificationBackend, Program, AstUtils, Viper, VerificationContext, VerificationResultKind, smt_manager::SmtManager, VerificationResult, PersistentCache, Cache}; +use prusti_utils::{ + config, + Stopwatch, + report::log::{report, to_legal_file_name}, +}; +use crate::{ServerMessage, Backend}; +use once_cell::sync::Lazy; +use std::{ + sync::{self, mpsc, Arc}, + fs::create_dir_all, + path::PathBuf, +}; + +pub(crate) enum ServerRequest { + Verification(ServerVerificationRequest), + SaveCache, +} + +pub struct ServerVerificationRequest { + // hash of the original VerificationRequest to store in the cache + hash: u64, + kind: ServerVerificationRequestKind, +} + +pub enum ServerVerificationRequestKind { + // viper program, program name, backend config + JVMViperRequest(jni::objects::GlobalRef, Arc, String, ViperBackendConfig), +} + +impl ServerVerificationRequest { + pub fn execute<'v, 't: 'v>( + &self, + mtx_cache_arc: Arc>, + sender: &mpsc::Sender, + ) { + let mut stopwatch = Stopwatch::start("prusti-server", "verifier startup"); + let mut result = VerificationResult { + item_name: "".to_string(), + kind: VerificationResultKind::Success, + cached: false, + time_ms: 0, + }; + + match self.kind { + ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, viper_arc, program_name, backend_config) => { + let verification_context = viper_arc.attach_current_thread(); + let backend = match backend_config.backend { + VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( + new_viper_verifier( + &program_name, + &verification_context, + backend_config, + ), + &verification_context, + &viper_arc, + ), + }; + stopwatch.start_next("backend verification"); + result.item_name = program_name; + result.kind = backend.verify(viper_program_ref.as_obj(), sender.clone()); + result.time_ms = stopwatch.finish().as_millis(); + } + } + + // Don't cache Java exceptions, which might be due to misconfigured paths. + if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { + info!( + "Storing new cached result {:?}", + &result, + ); + (&mut *mtx_cache_arc.lock().unwrap()).insert(self.hash, result.clone()); + } + + /*normalization_info.denormalize_result(&mut result.kind);*/ + sender.send(ServerMessage::Termination(result)).unwrap(); + } +} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct VerificationRequest { @@ -17,6 +94,81 @@ impl<'vir> VerificationRequest { pub(crate) fn get_hash(&self) -> u64 { self.program.get_hash() } + + pub(crate) fn send_request( + &self, + mtx_tx_verreq: &sync::Mutex>, + ) { + let request = self.build_request(); + + mtx_tx_verreq + .lock() + .unwrap() + .send(ServerRequest::Verification(request)) + .unwrap(); + } + + fn build_request( + &self, + ) -> ServerVerificationRequest { + match self.backend_config.backend { + VerificationBackend::Carbon | VerificationBackend::Silicon => { + let mut stopwatch = + Stopwatch::start("prusti-server backend", "construction of JVM objects"); + + let viper_arc = Arc::new( + Viper::new_with_args(&config::viper_home(), config::extra_jvm_args()) + ); + let context = viper_arc.attach_current_thread(); + let ast_utils = context.new_ast_utils(); + + ast_utils.with_local_frame(16, || { + let ast_factory = context.new_ast_factory(); + + let viper_program = vir::with_vcx(|vcx| { + let program = vcx.get_program(self.program); + prusti_viper::program_to_viper(program, &ast_factory) + }); + + if config::dump_viper_program() { + stopwatch.start_next("dumping viper program"); + dump_viper_program( + &ast_utils, + viper_program, + &self.program.get_name_with_check_mode(), + ); + } + + let viper_program_ref = context + .env() + .new_global_ref(viper_program.to_jobject()) + .unwrap(); + + let kind = ServerVerificationRequestKind::JVMViperRequest( + viper_program_ref, + viper_arc, + self.program.get_name().to_string(), + self.backend_config, + ); + ServerVerificationRequest { + hash: self.get_hash(), + kind, + } + }) + }, + } + } +} + +pub fn dump_viper_program( + ast_utils: &viper::AstUtils, + program: viper::Program, + program_name: &str, +) { + let namespace = "viper_program"; + let filename = format!("{program_name}.vpr"); + info!("Dumping Viper program to '{}/{}'", namespace, filename); + report(namespace, filename, ast_utils.pretty_print(program)); } /// The configuration for the viper backend, (i.e. verifier). @@ -81,3 +233,83 @@ impl ViperBackendConfig { } } } + +fn new_viper_verifier<'v, 't: 'v>( + program_name: &str, + verification_context: &'v viper::VerificationContext<'t>, + backend_config: ViperBackendConfig, +) -> viper::Verifier<'v> { + let mut verifier_args: Vec = backend_config.verifier_args; + let report_path: Option; + if config::dump_debug_info() { + let log_path = config::log_dir() + .join("viper_tmp") + .join(to_legal_file_name(program_name)); + create_dir_all(&log_path).unwrap(); + report_path = Some(log_path.join("report.csv")); + let log_dir_str = log_path.to_str().unwrap(); + match backend_config.backend { + VerificationBackend::Silicon => { + verifier_args.extend(vec![ + "--tempDirectory".to_string(), + log_dir_str.to_string(), + "--printMethodCFGs".to_string(), + //"--printTranslatedProgram".to_string(), + ]) + } + VerificationBackend::Carbon => verifier_args.extend(vec![ + "--boogieOpt".to_string(), + format!("/logPrefix {log_dir_str}"), + //"--print".to_string(), "./log/boogie_program/program.bpl".to_string(), + ]), + } + } else { + report_path = None; + if backend_config.backend == VerificationBackend::Silicon { + // TODO: unknown option? + // verifier_args.extend(vec!["--disableTempDirectory".to_string()]); + } + } + let (smt_solver, smt_manager) = if config::use_smt_wrapper() { + std::env::set_var("PRUSTI_ORIGINAL_SMT_SOLVER_PATH", config::smt_solver_path()); + let log_path = config::log_dir() + .join("smt") + .join(to_legal_file_name(program_name)); + create_dir_all(&log_path).unwrap(); + let smt_manager = SmtManager::new( + log_path, + config::preserve_smt_trace_files(), + config::write_smt_statistics(), + config::smt_qi_ignore_builtin(), + config::smt_qi_bound_global_kind(), + config::smt_qi_bound_trace(), + config::smt_qi_bound_trace_kind(), + config::smt_unique_triggers_bound(), + config::smt_unique_triggers_bound_total(), + ); + std::env::set_var( + "PRUSTI_SMT_SOLVER_MANAGER_PORT", + smt_manager.port().to_string(), + ); + if config::log_smt_wrapper_interaction() { + std::env::set_var("PRUSTI_LOG_SMT_INTERACTION", "true"); + } + (config::smt_solver_wrapper_path(), smt_manager) + } else { + (config::smt_solver_path(), SmtManager::default()) + }; + let boogie_path = config::boogie_path(); + if let Some(bound) = config::smt_qi_bound_global() { + // We need to set the environment variable to reach our Z3 wrapper. + std::env::set_var("PRUSTI_SMT_QI_BOUND_GLOBAL", bound.to_string()); + } + + verification_context.new_verifier( + backend_config.backend, + verifier_args, + report_path, + smt_solver, + boogie_path, + smt_manager, + ) +} \ No newline at end of file diff --git a/viper/src/ast_factory/macros.rs b/viper/src/ast_factory/macros.rs index 51ebc5bcf51..2e6d513b992 100644 --- a/viper/src/ast_factory/macros.rs +++ b/viper/src/ast_factory/macros.rs @@ -14,7 +14,7 @@ macro_rules! jobject_wrapper { pub(crate) fn new(obj: JObject<'a>) -> Self { $name { obj } } - pub(crate) fn to_jobject(self) -> JObject<'a> { + pub fn to_jobject(self) -> JObject<'a> { self.obj } } From 1de40ff311bdef381c5761712e8f7313f247ace3 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 12 Jul 2024 13:38:12 +0200 Subject: [PATCH 13/53] Optimize selective encoding for the non-selective case, config bug fix --- prusti-encoder/src/lib.rs | 6 ++++-- prusti-interface/src/data.rs | 2 ++ prusti-server/src/lib.rs | 18 +----------------- prusti-server/src/verification_request.rs | 8 ++++++++ prusti-utils/src/config.rs | 2 +- prusti/src/callbacks.rs | 7 ++++++- prusti/src/verifier.rs | 2 +- 7 files changed, 23 insertions(+), 22 deletions(-) diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index 670557f1cca..2a4e5dd7304 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -31,7 +31,9 @@ pub fn test_entrypoint<'tcx>( body: EnvBody<'tcx>, query: EnvQuery<'tcx>, def_spec: prusti_interface::specs::typed::DefSpecificationMap, - procedures: &Vec, + // this is None if the verification is not selective - all procedures should be encoded. + // if the verification is selective, only the procedures in this vector should be encoded with body + procedures: Option<&Vec>, contract_spans_map: &mut FxHashMap, ) -> request::RequestWithContext { @@ -101,7 +103,7 @@ pub fn test_entrypoint<'tcx>( (is_pure, is_trusted) }).unwrap_or_default(); - if procedures.contains(&def_id) && !(is_trusted && is_pure) { + if procedures.map_or(true, |procs| procs.contains(&def_id)) && !(is_trusted && is_pure) { let substs = ty::GenericArgs::identity_for_item(tcx, def_id); let res = crate::encoders::MirImpureEnc::encode((def_id, substs, None)); assert!(res.is_ok()); diff --git a/prusti-interface/src/data.rs b/prusti-interface/src/data.rs index 11fdb92cce3..7b29ec2da95 100644 --- a/prusti-interface/src/data.rs +++ b/prusti-interface/src/data.rs @@ -19,6 +19,8 @@ pub struct VerificationTask<'tcx> { pub procedures: Vec, /// A list of types to verify. pub types: Vec>, + /// Whether or not the task is selective (see verify_only_defpaths) + pub selective: bool, } /// Verification result returned by a verifier. diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 216c05afe17..765740ede6f 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -14,7 +14,7 @@ use crate::{ }; use prusti_utils::{config, Stopwatch}; use prusti_interface::{ - data::{VerificationResult, VerificationTask}, + data::VerificationResult, environment::EnvDiagnostic, specs::typed, PrustiError, @@ -69,10 +69,6 @@ pub fn verify_programs( (program_name, request) }); - if config::show_ide_info() { - emit_contract_spans(env_diagnostic); - } - let mut stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); // runtime used either for client connecting to server sequentially // or to sequentially verify the requests -> single thread is sufficient @@ -271,15 +267,3 @@ fn verify_requests_local<'a>( }; verification_stream.flatten() } - -pub fn emit_contract_spans(env_diagnostic: &EnvDiagnostic<'_>) { - let encoding_info = EncodingInfo { - // call_contract_spans: self.encoder.spans_of_call_contracts.borrow().to_vec(), - call_contract_spans: "call contract spans not implemented".to_string(), - }; - PrustiError::message( - format!("encodingInfo{}", encoding_info.to_json_string()), - DUMMY_SP.into(), - ) - .emit(env_diagnostic); -} diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 402b74a292f..32473f9dad5 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -58,6 +58,14 @@ impl ViperBackendConfig { config::counterexample() ); + // if let Some(smt_qi_profile) = config::smt_qi_profile() { + // prover_args = format!("{prover_args} smt.qi.profile={smt_qi_profile}"); + // } + // if let Some(smt_qi_profile_freq) = config::smt_qi_profile_freq() { + // prover_args = + // format!("{prover_args} smt.qi.profile_freq={smt_qi_profile_freq}"); + // } + verifier_args.push(prover_args); verifier_args.extend(vec!["--logLevel".to_string(), "ERROR".to_string()]); diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 0ec22cb3472..ff2d3310671 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -1069,7 +1069,7 @@ pub fn skip_verification() -> bool { /// the DefPath of the method to be verified pub fn verify_only_defpaths() -> Vec { if let Some(input) = read_setting::>("verify_only_defpaths") { - if input.starts_with('[') || input.ends_with(']') { + if !input.starts_with('[') || !input.ends_with(']') { panic!("verify_only_defpaths: invalid format. Make sure to enclose the list in brackets (`[]`). Was `{input}` but expected form `[\"\",\"\",...]`") } let trimmed = &input[1..input.len()-1]; diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 18e43c32aef..673dec67c69 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -215,7 +215,11 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { .into_iter() .filter(|x| target_def_paths.contains(&env.name.get_unique_item_name(*x))) .collect(); - let selective_task = VerificationTask { procedures, types }; + let selective_task = VerificationTask { + procedures, + types, + selective: true + }; // fake_error because otherwise a verification-success // (for a single method for example) will cause this result // to be cached by compiler at the moment @@ -227,6 +231,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { let verification_task = VerificationTask { procedures: annotated_procedures, types, + selective: false, }; verify(env, def_spec, verification_task, call_spans_map); } diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 09fd33cedfa..0a97fbdba43 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -64,7 +64,7 @@ pub fn verify<'tcx>( env.body, env.query, def_spec, - &verification_task.procedures, + if verification_task.selective { Some(&verification_task.procedures) } else { None }, &mut contract_spans_map, ); From aaecb18852fabe084a656619dbbdd800916dc2ba Mon Sep 17 00:00:00 2001 From: Zack Grannan Date: Mon, 13 May 2024 14:28:57 -0700 Subject: [PATCH 14/53] Use cfg_if instead of if(cfg!()) (#49) --- vir/src/make.rs | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/vir/src/make.rs b/vir/src/make.rs index 7b03a901435..0dbedcb7527 100644 --- a/vir/src/make.rs +++ b/vir/src/make.rs @@ -282,8 +282,10 @@ impl<'tcx> VirCtxt<'tcx> { )) ) ); - if cfg!(debug_assertions) { - check_expr_bindings(&mut HashMap::new(), let_expr); + cfg_if! { + if #[cfg(debug_assertions)] { + check_expr_bindings(&mut HashMap::new(), let_expr); + } } let_expr } @@ -442,12 +444,14 @@ impl<'tcx> VirCtxt<'tcx> { // TODO: Typecheck pre and post conditions if let Some(body) = expr { assert!(body.ty() == ret); - if cfg!(debug_assertions) { - let mut m = HashMap::new(); - for arg in args { - m.insert(arg.name, arg.ty); + cfg_if! { + if #[cfg(debug_assertions)] { + let mut m = HashMap::new(); + for arg in args { + m.insert(arg.name, arg.ty); + } + check_expr_bindings(&mut m, body); } - check_expr_bindings(&mut m, body); } } self.alloc(FunctionGenData { @@ -665,15 +669,17 @@ impl<'tcx> VirCtxt<'tcx> { posts: &'vir [ExprGen<'vir, Curr, Next>], blocks: Option<&'vir [CfgBlockGen<'vir, Curr, Next>]>, // first one is the entrypoint ) -> MethodGen<'vir, Curr, Next> { - if cfg!(debug_assertions) { - if let Some(blocks) = blocks { - let mut m = HashMap::new(); - for arg in args { - m.insert(arg.name, arg.ty); - } - for block in blocks { - for stmt in block.stmts { - check_stmt_bindings(&mut m, stmt); + cfg_if! { + if #[cfg(debug_assertions)] { + if let Some(blocks) = blocks { + let mut m = HashMap::new(); + for arg in args { + m.insert(arg.name, arg.ty); + } + for block in blocks { + for stmt in block.stmts { + check_stmt_bindings(&mut m, stmt); + } } } } From bc26f7b00bb940fd5f4dea350b6357978c744415 Mon Sep 17 00:00:00 2001 From: Zack Grannan Date: Tue, 14 May 2024 04:32:33 -0700 Subject: [PATCH 15/53] Impure Generics (#45) * Impure generics * Fixed bug due to type parameters not being encoded * Remove fractional perm * mk_bool doesn't need extra typarams --- Cargo.lock | 1 + local-testing/generics/add.rs | 13 + local-testing/generics/container.rs | 20 + local-testing/generics/impure.rs | 7 + local-testing/generics/nested2.rs | 7 + local-testing/generics/pair2.rs | 35 ++ local-testing/generics/point.rs | 26 + prusti-encoder/Cargo.toml | 3 +- .../src/encoder_traits/function_enc.rs | 45 ++ .../src/encoder_traits/impure_function_enc.rs | 192 +++++++ prusti-encoder/src/encoder_traits/mod.rs | 2 + .../src/encoder_traits/pure_func_app_enc.rs | 54 +- .../src/encoder_traits/pure_function_enc.rs | 23 +- prusti-encoder/src/encoders/const.rs | 5 +- prusti-encoder/src/encoders/local_def.rs | 5 +- prusti-encoder/src/encoders/mir_impure.rs | 498 +++++++++-------- .../src/encoders/mir_poly_impure.rs | 63 +++ prusti-encoder/src/encoders/mir_pure.rs | 38 +- .../src/encoders/mir_pure_function.rs | 33 +- prusti-encoder/src/encoders/mod.rs | 19 +- .../src/encoders/mono/mir_impure.rs | 84 +++ .../src/encoders/mono/mir_pure_function.rs | 39 +- prusti-encoder/src/encoders/mono/mod.rs | 2 + .../src/encoders/mono/task_description.rs | 34 ++ prusti-encoder/src/encoders/type/domain.rs | 7 +- .../encoders/type/lifted/aggregate_cast.rs | 10 +- .../src/encoders/type/lifted/cast.rs | 215 ++++++-- .../src/encoders/type/lifted/casters.rs | 505 ++++++++++++++++++ .../type/lifted/func_app_ty_params.rs | 8 +- .../src/encoders/type/lifted/mod.rs | 2 +- .../src/encoders/type/lifted/rust_ty_cast.rs | 145 +++-- .../encoders/type/lifted/ty_constructor.rs | 8 +- .../src/encoders/type/most_generic_ty.rs | 27 +- prusti-encoder/src/encoders/type/predicate.rs | 10 +- .../src/encoders/type/rust_ty_predicates.rs | 12 +- prusti-encoder/src/lib.rs | 32 +- prusti-interface/src/environment/body.rs | 2 +- .../src/environment/mir_storage.rs | 4 +- vir/src/callable_idents.rs | 81 +-- vir/src/make.rs | 8 +- vir/src/viper_ident.rs | 1 + 41 files changed, 1793 insertions(+), 532 deletions(-) create mode 100644 local-testing/generics/add.rs create mode 100644 local-testing/generics/container.rs create mode 100644 local-testing/generics/impure.rs create mode 100644 local-testing/generics/nested2.rs create mode 100644 local-testing/generics/pair2.rs create mode 100644 local-testing/generics/point.rs create mode 100644 prusti-encoder/src/encoder_traits/function_enc.rs create mode 100644 prusti-encoder/src/encoder_traits/impure_function_enc.rs create mode 100644 prusti-encoder/src/encoders/mir_poly_impure.rs create mode 100644 prusti-encoder/src/encoders/mono/mir_impure.rs create mode 100644 prusti-encoder/src/encoders/mono/task_description.rs create mode 100644 prusti-encoder/src/encoders/type/lifted/casters.rs diff --git a/Cargo.lock b/Cargo.lock index 3464a355f09..4b46391f835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1867,6 +1867,7 @@ dependencies = [ name = "prusti-encoder" version = "0.1.0" dependencies = [ + "cfg-if", "mir-ssa-analysis", "mir-state-analysis", "prusti-interface", diff --git a/local-testing/generics/add.rs b/local-testing/generics/add.rs new file mode 100644 index 00000000000..2ef9220c9ad --- /dev/null +++ b/local-testing/generics/add.rs @@ -0,0 +1,13 @@ +use prusti_contracts::*; + +#[pure] +fn id(x: T) -> T { + x +} + +#[pure] +fn main(){ + let x = id(1); + let y = id(2); + let z = x + y; +} diff --git a/local-testing/generics/container.rs b/local-testing/generics/container.rs new file mode 100644 index 00000000000..ee7b5214f2e --- /dev/null +++ b/local-testing/generics/container.rs @@ -0,0 +1,20 @@ +use prusti_contracts::*; + +struct Container { + content: T, +} + +#[requires(container.content == 123)] +#[ensures(result.content == 124)] +fn increment_container(container: Container) -> Container { + Container { content: container.content + 1 } +} + +#[ensures(result > 123)] +fn client() -> i32 { + let num_container = Container { content: 123 }; + let incremented_container = increment_container(num_container); + incremented_container.content +} + +fn main(){} diff --git a/local-testing/generics/impure.rs b/local-testing/generics/impure.rs new file mode 100644 index 00000000000..091db207e9c --- /dev/null +++ b/local-testing/generics/impure.rs @@ -0,0 +1,7 @@ +use prusti_contracts::*; + +pub fn test(x: i8, y: T) -> T{ y } + +fn main(){ + let a = test(-127, 11); +} diff --git a/local-testing/generics/nested2.rs b/local-testing/generics/nested2.rs new file mode 100644 index 00000000000..1083fa0de81 --- /dev/null +++ b/local-testing/generics/nested2.rs @@ -0,0 +1,7 @@ +struct R(Option>); +fn main() { + match R(None).0 { + Some(_) => (), + _ => (), + } +} diff --git a/local-testing/generics/pair2.rs b/local-testing/generics/pair2.rs new file mode 100644 index 00000000000..1d8b5d775bb --- /dev/null +++ b/local-testing/generics/pair2.rs @@ -0,0 +1,35 @@ +use prusti_contracts::*; + +struct Pair { + first: T, + second: U, +} + +#[requires(pair.second == true)] +#[ensures(result.second == true)] +fn copy_pair(pair: Pair) -> Pair { + Pair { + first: pair.first, + second: pair.second + } +} + +fn fst(pair: Pair) -> T { + let unused = pair.second; + pair.first +} + +#[ensures(result == true)] +fn client() -> bool { + let initial_pair = Pair { + first: 42u32, + second: true + }; + let copied_pair = copy_pair(initial_pair); + copied_pair.second +} + +fn main() { + let pair = Pair { first: 1, second: 2 }; + fst(pair); +} diff --git a/local-testing/generics/point.rs b/local-testing/generics/point.rs new file mode 100644 index 00000000000..8507849d711 --- /dev/null +++ b/local-testing/generics/point.rs @@ -0,0 +1,26 @@ +use prusti_contracts::*; + +struct Point { + x: T, + y: T, +} + +impl Point { + #[pure] + fn new(x: T, y: T) -> Point { + Point { x, y } + } + + #[pure] + fn x(self) -> T { + self.x + } + + #[pure] + fn y(self) -> T { + self.y + } +} + +#[ensures(Point::new(1, 2).x() == 1)] +fn main() {} diff --git a/prusti-encoder/Cargo.toml b/prusti-encoder/Cargo.toml index e1cf1189fe5..9e713a490ea 100644 --- a/prusti-encoder/Cargo.toml +++ b/prusti-encoder/Cargo.toml @@ -9,6 +9,7 @@ doctest = false # we have no doc tests [dependencies] prusti-utils = { path = "../prusti-utils" } +cfg-if = "1.0.0" prusti-rustc-interface = { path = "../prusti-rustc-interface" } prusti-interface = { path = "../prusti-interface" } prusti-server = { path = "../prusti-server" } @@ -23,6 +24,6 @@ tracing = { path = "../tracing" } rustc_private = true [features] -default = ["mono_function_encoding"] +# default = ["mono_function_encoding"] vir_debug = ["vir/vir_debug"] mono_function_encoding = [] diff --git a/prusti-encoder/src/encoder_traits/function_enc.rs b/prusti-encoder/src/encoder_traits/function_enc.rs new file mode 100644 index 00000000000..1fedfbf363f --- /dev/null +++ b/prusti-encoder/src/encoder_traits/function_enc.rs @@ -0,0 +1,45 @@ +use task_encoder::TaskEncoder; +use prusti_rustc_interface::{ + middle::ty::GenericArgs, + span::def_id::DefId +}; + +/// Task encoders for Rust functions should implement this trait. +pub trait FunctionEnc + where + Self: 'static + Sized + TaskEncoder +{ + /// Obtains the function's [`DefId`] from the task key + fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId; + + /// Obtains the caller's [`DefId`] from the task key, if possible + fn get_caller_def_id(task_key: &Self::TaskKey<'_>) -> Option; + + /// Obtains type substitutions for the function. For polymorphic encoding, + /// this should be the identity substitution obtained from the DefId of the + /// function. For the monomorphic encoding, the substitutions at the call + /// site should be used. + fn get_substs<'tcx>( + vcx: &vir::VirCtxt<'tcx>, + substs_src: &Self::TaskKey<'tcx>, + ) -> &'tcx GenericArgs<'tcx>; +} + +/// Implementation for polymorphic encoding +impl TaskEncoder = DefId>> FunctionEnc for T { + fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId { + *task_key + } + + fn get_caller_def_id(_: &Self::TaskKey<'_>) -> Option { + None + } + + fn get_substs<'tcx>( + vcx: &vir::VirCtxt<'tcx>, + def_id: &Self::TaskKey<'tcx>, + ) -> &'tcx GenericArgs<'tcx> { + GenericArgs::identity_for_item(vcx.tcx(), *def_id) + } + +} diff --git a/prusti-encoder/src/encoder_traits/impure_function_enc.rs b/prusti-encoder/src/encoder_traits/impure_function_enc.rs new file mode 100644 index 00000000000..5a355143646 --- /dev/null +++ b/prusti-encoder/src/encoder_traits/impure_function_enc.rs @@ -0,0 +1,192 @@ +use prusti_rustc_interface::middle::mir; +use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use vir::{MethodIdent, UnknownArity, ViperIdent}; + +use crate::encoders::{ + lifted::func_def_ty_params::LiftedTyParamsEnc, ImpureEncVisitor, MirImpureEnc, MirLocalDefEnc, MirSpecEnc +}; + +use super::function_enc::FunctionEnc; + +#[derive(Clone, Debug)] +pub struct ImpureFunctionEncError; + +#[derive(Clone, Debug)] +pub struct ImpureFunctionEncOutputRef<'vir> { + pub method_ref: MethodIdent<'vir, UnknownArity<'vir>>, +} +impl<'vir> task_encoder::OutputRefAny for ImpureFunctionEncOutputRef<'vir> {} + +#[derive(Clone, Debug)] +pub struct ImpureFunctionEncOutput<'vir> { + pub method: vir::Method<'vir>, +} + +const ENCODE_REACH_BB: bool = false; + +pub trait ImpureFunctionEnc +where + Self: 'static + + Sized + + FunctionEnc + + for<'vir> TaskEncoder = ImpureFunctionEncOutputRef<'vir>>, +{ + /// Generates the identifier for the method; for a monomorphic encoding, + /// this should be a name including (mangled) type arguments + fn mk_method_ident<'vir, 'tcx>( + vcx: &'vir vir::VirCtxt<'tcx>, + task_key: &Self::TaskKey<'tcx>, + ) -> ViperIdent<'vir>; + + fn encode<'vir, 'tcx: 'vir>( + task_key: Self::TaskKey<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> ImpureFunctionEncOutput<'vir> { + let def_id = Self::get_def_id(&task_key); + let caller_def_id = Self::get_caller_def_id(&task_key); + let trusted = crate::encoders::with_proc_spec(def_id, |def_spec| { + def_spec.trusted.extract_inherit().unwrap_or_default() + }) + .unwrap_or_default(); + vir::with_vcx(|vcx| { + use mir::visit::Visitor; + let substs = Self::get_substs(vcx, &task_key); + let local_defs = deps + .require_local::((def_id, substs, caller_def_id)) + .unwrap(); + + // Argument count for the Viper method: + // - one (`Ref`) for the return place; + // - one (`Ref`) for each MIR argument. + // + // Note that the return place is modelled as an argument of the + // Viper method. This corresponds to an execution model where the + // method can return data to the caller without a copy--it directly + // modifies a place provided by the caller. + // + // TODO: type parameters + let arg_count = local_defs.arg_count + 1; + + let method_name = Self::mk_method_ident(vcx, &task_key); + let mut args = vec![&vir::TypeData::Ref; arg_count]; + let param_ty_decls = deps + .require_local::(substs) + .unwrap() + .iter() + .map(|g| g.decl()) + .collect::>(); + args.extend(param_ty_decls.iter().map(|decl| decl.ty)); + let args = UnknownArity::new(vcx.alloc_slice(&args)); + let method_ref = MethodIdent::new(method_name, args); + deps.emit_output_ref::(task_key, ImpureFunctionEncOutputRef { method_ref }); + + // Do not encode the method body if it is external, trusted or just + // a call stub. + let local_def_id = def_id.as_local().filter(|_| !trusted); + let blocks = if let Some(local_def_id) = local_def_id { + let body = vcx + .body_mut() + .get_impure_fn_body(local_def_id, substs, caller_def_id); + // let body = vcx.tcx().mir_promoted(local_def_id).0.borrow(); + + let fpcs_analysis = mir_state_analysis::run_free_pcs(&body, vcx.tcx()); + + //let ssa_analysis = SsaAnalysis::analyse(&body); + + let block_count = body.basic_blocks.len(); + + // Local count for the Viper method: + // - one for each basic block; + // - one (`Ref`) for each non-argument, non-return local. + let _local_count = block_count + 1 * (body.local_decls.len() - arg_count); + + let mut encoded_blocks = Vec::with_capacity( + // extra blocks: Start, End + 2 + block_count, + ); + let mut start_stmts = Vec::new(); + for local in (arg_count..body.local_decls.len()).map(mir::Local::from) { + let name_p = local_defs.locals[local].local.name; + start_stmts.push( + vcx.mk_local_decl_stmt(vir::vir_local_decl! { vcx; [name_p] : Ref }, None), + ) + } + if ENCODE_REACH_BB { + start_stmts.extend((0..block_count).map(|block| { + let name = vir::vir_format!(vcx, "_reach_bb{block}"); + vcx.mk_local_decl_stmt( + vir::vir_local_decl! { vcx; [name] : Bool }, + Some(vcx.mk_todo_expr("false")), + ) + })); + } + encoded_blocks.push(vcx.mk_cfg_block( + vcx.alloc(vir::CfgBlockLabelData::Start), + vcx.alloc_slice(&start_stmts), + vcx.mk_goto_stmt(vcx.alloc(vir::CfgBlockLabelData::BasicBlock(0))), + )); + + let mut visitor = ImpureEncVisitor { + monomorphize: MirImpureEnc::monomorphize(), + vcx, + deps, + def_id, + local_decls: &body.local_decls, + //ssa_analysis, + fpcs_analysis, + local_defs, + + tmp_ctr: 0, + + current_fpcs: None, + + current_stmts: None, + current_terminator: None, + encoded_blocks, + }; + visitor.visit_body(&body); + + visitor.encoded_blocks.push(vcx.mk_cfg_block( + vcx.alloc(vir::CfgBlockLabelData::End), + &[], + vcx.alloc(vir::TerminatorStmtData::Exit), + )); + Some(vcx.alloc_slice(&visitor.encoded_blocks)) + } else { + None + }; + + let spec = deps + .require_local::((def_id, substs, None, false)) + .unwrap(); + let (spec_pres, spec_posts) = (spec.pres, spec.posts); + + let mut pres = Vec::with_capacity(arg_count - 1); + let mut args = Vec::with_capacity(arg_count + substs.len()); + for arg_idx in 0..arg_count { + let name_p = local_defs.locals[arg_idx.into()].local.name; + args.push(vir::vir_local_decl! { vcx; [name_p] : Ref }); + if arg_idx != 0 { + pres.push(local_defs.locals[arg_idx.into()].impure_pred); + } + } + args.extend(param_ty_decls.iter()); + pres.extend(spec_pres); + + let mut posts = Vec::with_capacity(spec_posts.len() + 1); + posts.push(local_defs.locals[mir::RETURN_PLACE].impure_pred); + posts.extend(spec_posts); + + ImpureFunctionEncOutput { + method: vcx.mk_method( + method_ref, + vcx.alloc_slice(&args), + &[], + vcx.alloc_slice(&pres), + vcx.alloc_slice(&posts), + blocks, + ), + } + }) + } +} diff --git a/prusti-encoder/src/encoder_traits/mod.rs b/prusti-encoder/src/encoder_traits/mod.rs index 0cfaf0dee7b..331b104578a 100644 --- a/prusti-encoder/src/encoder_traits/mod.rs +++ b/prusti-encoder/src/encoder_traits/mod.rs @@ -1,2 +1,4 @@ pub mod pure_function_enc; pub mod pure_func_app_enc; +pub mod function_enc; +pub mod impure_function_enc; diff --git a/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs b/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs index dfa5339e721..04202f73937 100644 --- a/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs @@ -9,9 +9,8 @@ use task_encoder::TaskEncoderDependencies; use crate::encoders::{ lifted::{ - cast::{CastArgs, PureGenericCastEnc}, - func_app_ty_params::LiftedFuncAppTyParamsEnc, - }, PureFunctionEnc, + cast::{CastArgs, CastToEnc}, casters::CastTypePure, func_app_ty_params::LiftedFuncAppTyParamsEnc + }, FunctionCallTaskDescription, PureFunctionEnc }; /// Encoders (such as [`crate::encoders::MirPureEnc`], @@ -32,6 +31,9 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { /// when getting the type of the function. type LocalDeclsSrc: ?Sized + HasLocalDecls<'tcx>; + // Are we monomorphizing functions? + fn monomorphize(&self) -> bool; + /// Task encoder dependencies are required for encoding Viper casts between /// generic and concrete types. fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir>; @@ -60,40 +62,17 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { } } - /// Obtains the signature of the function. If we are monomoprhising function - /// calls, then the signature of the monomorphised function is returned. - /// Otherwise, the signature of the original function is returned (`substs` - /// and `param_env` are therefore ignored). - fn get_fn_sig( - &self, - def_id: DefId, - substs: &'tcx List>, - param_env: ty::ParamEnv<'tcx>, - ) -> Binder<'tcx, FnSig<'tcx>> { - let sig = self.vcx().tcx().fn_sig(def_id); - if cfg!(feature="mono_function_encoding") { - self.vcx().tcx().subst_and_normalize_erasing_regions( - substs, - param_env, - sig - ) - } else { - sig.instantiate_identity() - } - } - /// Encodes the arguments to the function. The first arguments are the lifted /// type parameters, followed by the actual arguments. Appropriate casts /// are inserted to convert from/to generic and concrete arguments as necessary. fn encode_fn_args( &mut self, - def_id: DefId, + sig: Binder<'tcx, FnSig<'tcx>>, substs: &'tcx List>, - param_env: ty::ParamEnv<'tcx>, args: &[mir::Operand<'tcx>], encode_operand_args: &Self::EncodeOperandArgs, ) -> Vec> { - let sig = self.get_fn_sig(def_id, substs, param_env); + let mono = self.monomorphize(); let fn_arg_tys = sig .inputs() .iter() @@ -102,7 +81,9 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { .collect::>(); let encoded_ty_args = self .deps() - .require_local::(substs) + .require_local::( + (mono, substs) + ) .unwrap(); // Initial arguments are lifted type parameters @@ -119,7 +100,7 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { let oper_ty = oper.ty(self.local_decls_src(), self.vcx().tcx()); let caster = self .deps() - .require_ref::(CastArgs { + .require_ref::>(CastArgs { expected: expected_ty, actual: oper_ty }) @@ -137,6 +118,7 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { fn encode_pure_func_app( &mut self, def_id: DefId, + sig: Binder<'tcx, FnSig<'tcx>>, substs: &'tcx List>, args: &Vec>, destination: &mir::Place<'tcx>, @@ -144,24 +126,18 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { encode_operand_args: &Self::EncodeOperandArgs, ) -> vir::ExprGen<'vir, Self::Curr, Self::Next> { let vcx = self.vcx(); - let param_env = vcx.tcx().param_env(caller_def_id); - let sig = self.get_fn_sig( - def_id, - substs, - param_env - ); let fn_result_ty = sig.output().skip_binder(); let pure_func = self .deps() - .require_ref::((def_id, substs, caller_def_id)) + .require_ref::(FunctionCallTaskDescription::new(def_id, substs, caller_def_id)) .unwrap() .function_ref; - let encoded_args = self.encode_fn_args(def_id, substs, param_env, args, encode_operand_args); + let encoded_args = self.encode_fn_args(sig, substs, args, encode_operand_args); let call = pure_func.apply(vcx, &encoded_args); let expected_ty = destination.ty(self.local_decls_src(), vcx.tcx()).ty; let result_cast = self .deps() - .require_ref::(CastArgs { + .require_ref::>(CastArgs { expected: expected_ty, actual: fn_result_ty, }) diff --git a/prusti-encoder/src/encoder_traits/pure_function_enc.rs b/prusti-encoder/src/encoder_traits/pure_function_enc.rs index da6e5acb470..11c8cffb082 100644 --- a/prusti-encoder/src/encoder_traits/pure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_function_enc.rs @@ -1,7 +1,6 @@ -use prusti_rustc_interface::{ - middle::{mir, ty::{GenericArgs, Ty}}, - span::def_id::DefId -}; +use prusti_rustc_interface:: + middle::{mir, ty::Ty} +; use task_encoder::{TaskEncoder, TaskEncoderDependencies}; use vir::{CallableIdent, ExprGen, FunctionIdent, Reify, UnknownArity, ViperIdent}; @@ -9,6 +8,8 @@ use crate::encoders::{ domain::DomainEnc, lifted::{func_def_ty_params::LiftedTyParamsEnc, ty::{EncodeGenericsAsLifted, LiftedTy, LiftedTyEnc}}, most_generic_ty::extract_type_params, GenericEnc, MirLocalDefEnc, MirPureEnc, MirPureEncTask, MirSpecEnc, PureKind }; +use super::function_enc::FunctionEnc; + #[derive(Clone, Debug)] pub struct MirFunctionEncOutputRef<'vir> { pub function_ref: FunctionIdent<'vir, UnknownArity<'vir>>, @@ -25,15 +26,10 @@ pub struct MirFunctionEncOutput<'vir> { /// functions; see [`MirMonoFunctionEnc`] and [`MirFunctionEnc`] pub trait PureFunctionEnc where - Self: 'static + Sized + for <'vir> TaskEncoder< + Self: 'static + Sized + FunctionEnc + for <'vir> TaskEncoder< OutputRef<'vir> = MirFunctionEncOutputRef<'vir> > { - /// Obtains the function's [`DefId`] from the task key - fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId; - - /// Obtains the caller's [`DefId`] from the task key, if possible - fn get_caller_def_id(task_key: &Self::TaskKey<'_>) -> Option; /// Generates the identifier for the function; for a monomorphic encoding, /// this should be a name including (mangled) type arguments @@ -42,13 +38,6 @@ where task_key: &Self::TaskKey<'tcx>, ) -> ViperIdent<'vir>; - /// Obtains type substitutions for the function. For polymorphic encoding, - /// this is the DefId of the function. For the monomorhpic encoding, the - /// substitutions at the call site should be used. - fn get_substs<'tcx>( - vcx: &vir::VirCtxt<'tcx>, - substs_src: &Self::TaskKey<'tcx>, - ) -> &'tcx GenericArgs<'tcx>; /// Adds an assertion connecting the type of an argument (or return) of the /// function with the appropriate type based on the param, e.g. in f task_encoder::OutputRefAny for ConstEncOutputRef<'vir> {} use crate::encoders::{MirPureEnc, mir_pure::PureKind, MirPureEncTask}; -use super::rust_ty_snapshots::RustTySnapshotsEnc; -use super::lifted::rust_ty_cast::RustTyGenericCastEnc; +use super::{lifted::{casters::CastTypePure, rust_ty_cast::RustTyCastersEnc}, rust_ty_snapshots::RustTySnapshotsEnc}; impl TaskEncoder for ConstEnc { task_encoder::encoder_cache!(ConstEnc); @@ -90,7 +89,7 @@ impl TaskEncoder for ConstEnc { .generic_snapshot .specifics .expect_structlike(); - let cast = deps.require_local::(str_ty).unwrap(); + let cast = deps.require_local::>(str_ty).unwrap(); vir::with_vcx(|vcx| { // first, we create a string snapshot let snap = str_snap.field_snaps_to_snap.apply(vcx, &[]); diff --git a/prusti-encoder/src/encoders/local_def.rs b/prusti-encoder/src/encoders/local_def.rs index bdbee603e97..08070db5b7a 100644 --- a/prusti-encoder/src/encoders/local_def.rs +++ b/prusti-encoder/src/encoders/local_def.rs @@ -68,9 +68,8 @@ impl TaskEncoder for MirLocalDefEnc { ) -> LocalDef<'vir> { let local = vcx.mk_local(name, &vir::TypeData::Ref); let local_ex = vcx.mk_local_ex_local(local); - let args = ty.ref_to_args(vcx, local_ex); - let impure_snap = ty.ref_to_snap(vcx, args); - let impure_pred = ty.ref_to_pred(vcx, args, None); + let impure_snap = ty.ref_to_snap(vcx, local_ex); + let impure_pred = ty.ref_to_pred(vcx, local_ex, None); LocalDef { local, local_ex, diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index 65a3f615ac8..8a8c9d11278 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -3,7 +3,11 @@ use mir_state_analysis::{ utils::Place, }; use prusti_rustc_interface::{ - middle::{mir, ty}, + abi, + middle::{ + mir, + ty::{GenericArgs, TyKind}, + }, span::def_id::DefId, }; //use mir_ssa_analysis::{ @@ -30,24 +34,50 @@ pub struct MirImpureEncOutput<'vir> { pub method: vir::Method<'vir>, } -use crate::{encoder_traits::pure_func_app_enc::PureFuncAppEnc, encoders::{ - lifted::{aggregate_cast::{AggregateSnapArgsCastEnc, AggregateSnapArgsCastEncTask}, func_def_ty_params::LiftedTyParamsEnc}, rust_ty_predicates::RustTyPredicatesEnc, ConstEnc, MirBuiltinEnc, MirLocalDefEnc, MirSpecEnc -}}; +use crate::{ + encoder_traits::{impure_function_enc::{ + ImpureFunctionEncOutput, ImpureFunctionEncOutputRef, + }, pure_func_app_enc::PureFuncAppEnc}, + encoders::{ + self, + lifted::{ + aggregate_cast::{ + AggregateSnapArgsCastEnc, + AggregateSnapArgsCastEncTask + }, + func_app_ty_params::LiftedFuncAppTyParamsEnc + }, + FunctionCallTaskDescription, MirBuiltinEnc + } +}; + +use super::{ + lifted::{ + cast::{CastArgs, CastToEnc}, + casters::CastTypeImpure, + rust_ty_cast::RustTyCastersEnc + }, + rust_ty_predicates::RustTyPredicatesEnc, + ConstEnc, + MirMonoImpureEnc, + MirPolyImpureEnc +}; const ENCODE_REACH_BB: bool = false; +impl MirImpureEnc { + pub fn monomorphize() -> bool { + cfg!(feature = "mono_function_encoding") + } +} + impl TaskEncoder for MirImpureEnc { task_encoder::encoder_cache!(MirImpureEnc); - // TODO: local def id (+ promoted, substs, etc) - type TaskDescription<'tcx> = ( - DefId, // ID of the function - ty::GenericArgsRef<'tcx>, // ? this should be the "signature", after applying the env/substs - Option, // ID of the caller function, if any - ); + type TaskDescription<'tcx> = FunctionCallTaskDescription<'tcx>; - type OutputRef<'vir> = MirImpureEncOutputRef<'vir>; - type OutputFullLocal<'vir> = MirImpureEncOutput<'vir>; + type OutputRef<'vir> = ImpureFunctionEncOutputRef<'vir>; + type OutputFullLocal<'vir> = ImpureFunctionEncOutput<'vir>; type EncodingError = MirImpureEncError; @@ -58,194 +88,57 @@ impl TaskEncoder for MirImpureEnc { fn do_encode_full<'tcx: 'vir, 'vir>( task_key: &Self::TaskKey<'tcx>, deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { - let (def_id, substs, caller_def_id) = *task_key; - - let trusted = crate::encoders::with_proc_spec(def_id, |def_spec| - def_spec.trusted.extract_inherit().unwrap_or_default() - ).unwrap_or_default(); - - vir::with_vcx(|vcx| { - use mir::visit::Visitor; - let local_defs = deps.require_local::( - *task_key, - ).unwrap(); - - // Argument count for the Viper method: - // - one (`Ref`) for the return place; - // - one (`Ref`) for each MIR argument. - // - // Note that the return place is modelled as an argument of the - // Viper method. This corresponds to an execution model where the - // method can return data to the caller without a copy--it directly - // modifies a place provided by the caller. - // - // TODO: type parameters - let arg_count = local_defs.arg_count + 1; - - let extra: String = substs.iter().map(|s| format!("_{s}")).collect(); - let caller = caller_def_id - .map(|id| format!("{}_{}", id.krate, id.index.index())) - .unwrap_or_default(); - let method_name = vir::vir_format_identifier!( - vcx, - "m_{}{extra}_CALLER_{caller}", - vcx.tcx().item_name(def_id) - ); - let mut args = vec![&vir::TypeData::Ref; arg_count]; - let param_ty_decls = deps.require_local::( - substs - ).unwrap().iter().map(|g| g.decl()).collect::>(); - args.extend(param_ty_decls.iter().map(|decl| decl.ty)); - let args = UnknownArity::new(vcx.alloc_slice(&args)); - let method_ref = MethodIdent::new(method_name, args); - deps.emit_output_ref::(*task_key, MirImpureEncOutputRef { method_ref }); - - // Do not encode the method body if it is external, trusted or just - // a call stub. - let local_def_id = def_id.as_local().filter(|_| !trusted && caller_def_id.is_none()); - let blocks = if let Some(local_def_id) = local_def_id { - let body = vcx.body_mut().get_impure_fn_body(local_def_id, substs, caller_def_id); - // let body = vcx.tcx().mir_promoted(local_def_id).0.borrow(); - - let fpcs_analysis = mir_state_analysis::run_free_pcs(&body, vcx.tcx()); - - //let ssa_analysis = SsaAnalysis::analyse(&body); - - let block_count = body.basic_blocks.len(); - - // Local count for the Viper method: - // - one for each basic block; - // - one (`Ref`) for each non-argument, non-return local. - let _local_count = block_count + 1 * (body.local_decls.len() - arg_count); - - let mut encoded_blocks = Vec::with_capacity( - // extra blocks: Start, End - 2 + block_count, - ); - let mut start_stmts = Vec::new(); - for local in (arg_count..body.local_decls.len()).map(mir::Local::from) { - let name_p = local_defs.locals[local].local.name; - start_stmts.push( - vcx.mk_local_decl_stmt(vir::vir_local_decl! { vcx; [name_p] : Ref }, None) - ) - } - if ENCODE_REACH_BB { - start_stmts.extend((0..block_count) - .map(|block| { - let name = vir::vir_format!(vcx, "_reach_bb{block}"); - vcx.mk_local_decl_stmt( - vir::vir_local_decl! { vcx; [name] : Bool }, - Some(vcx.mk_todo_expr("false")) - ) - })); - } - encoded_blocks.push( - vcx.mk_cfg_block( - vcx.alloc(vir::CfgBlockLabelData::Start), - vcx.alloc_slice(&start_stmts), - vcx.mk_goto_stmt(vcx.alloc(vir::CfgBlockLabelData::BasicBlock(0))) - ) - ); - - let mut visitor = EncVisitor { - vcx, - deps, - def_id, - local_decls: &body.local_decls, - //ssa_analysis, - fpcs_analysis, - local_defs, - - tmp_ctr: 0, - - current_fpcs: None, - - current_stmts: None, - current_terminator: None, - encoded_blocks, - }; - visitor.visit_body(&body); - - visitor.encoded_blocks.push( - vcx.mk_cfg_block( - vcx.alloc(vir::CfgBlockLabelData::End), - &[], - vcx.alloc(vir::TerminatorStmtData::Exit) - ) - ); - Some(vcx.alloc_slice(&visitor.encoded_blocks)) - } else { - None - }; - - let spec = deps.require_local::( - (def_id, substs, caller_def_id, false) - ).unwrap(); - let (spec_pres, spec_posts) = (spec.pres, spec.posts); - - let mut pres = Vec::with_capacity(arg_count - 1); - let mut args = Vec::with_capacity(arg_count + substs.len()); - for arg_idx in 0..arg_count { - let name_p = local_defs.locals[arg_idx.into()].local.name; - args.push(vir::vir_local_decl! { vcx; [name_p] : Ref }); - if arg_idx != 0 { - pres.push(local_defs.locals[arg_idx.into()].impure_pred); - } - } - args.extend(param_ty_decls.iter()); - pres.extend(spec_pres); - - let mut posts = Vec::with_capacity(spec_posts.len() + 1); - posts.push(local_defs.locals[mir::RETURN_PLACE].impure_pred); - posts.extend(spec_posts); - - Ok(( - MirImpureEncOutput { - method: vcx.mk_method( - method_ref, - vcx.alloc_slice(&args), - &[], - vcx.alloc_slice(&pres), - vcx.alloc_slice(&posts), - blocks, - ), - }, - (), - )) - }) + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + let monomorphize = Self::monomorphize(); + let output_ref = if monomorphize { + deps.require_ref::(*task_key).unwrap() + } else { + deps.require_ref::(task_key.def_id).unwrap() + }; + deps.emit_output_ref::(*task_key, output_ref); + let output: ImpureFunctionEncOutput<'_> = if monomorphize { + deps.require_local::(*task_key).unwrap() + } else { + deps.require_local::(task_key.def_id).unwrap() + }; + Ok((output, ())) } } -struct EncVisitor<'tcx, 'vir, 'enc> +pub struct ImpureEncVisitor<'tcx, 'vir, 'enc> where 'vir: 'enc { - vcx: &'vir vir::VirCtxt<'tcx>, - deps: &'enc mut TaskEncoderDependencies<'vir>, - def_id: DefId, - local_decls: &'enc mir::LocalDecls<'tcx>, + pub vcx: &'vir vir::VirCtxt<'tcx>, + // Are we monomorphizing functions? + pub monomorphize: bool, + pub deps: &'enc mut TaskEncoderDependencies<'vir>, + pub def_id: DefId, + pub local_decls: &'enc mir::LocalDecls<'tcx>, //ssa_analysis: SsaAnalysis, - fpcs_analysis: FreePcsAnalysis<'enc, 'tcx>, - local_defs: crate::encoders::local_def::MirLocalDefEncOutput<'vir>, + pub fpcs_analysis: FreePcsAnalysis<'enc, 'tcx>, + pub local_defs: crate::encoders::MirLocalDefEncOutput<'vir>, - tmp_ctr: usize, + pub tmp_ctr: usize, // for the current basic block - current_fpcs: Option>, + pub current_fpcs: Option>, - current_stmts: Option>>, - current_terminator: Option>, + pub current_stmts: Option>>, + pub current_terminator: Option>, - encoded_blocks: Vec>, // TODO: use IndexVec ? + pub encoded_blocks: Vec>, // TODO: use IndexVec ? } -impl<'tcx: 'vir, 'vir> PureFuncAppEnc<'tcx, 'vir> for EncVisitor<'tcx, 'vir, '_> { +impl<'tcx: 'vir, 'vir> PureFuncAppEnc<'tcx, 'vir> for ImpureEncVisitor<'tcx, 'vir, '_> { type EncodeOperandArgs = (); type Curr = !; type Next = !; @@ -269,9 +162,34 @@ impl<'tcx: 'vir, 'vir> PureFuncAppEnc<'tcx, 'vir> for EncVisitor<'tcx, 'vir, '_> ) -> vir::ExprGen<'vir, Self::Curr, Self::Next> { self.encode_operand_snap(operand) } + + fn monomorphize(&self) -> bool { + self.monomorphize + } } -impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { +struct EncodePlaceResult<'vir> { + expr: vir::Expr<'vir>, + + /// Statements to undo the impure casts that were made to access the place. + /// If the place was only accessed to take a snapshot or copy (rather than a + /// move), these statements should be applied in-order to restore + /// permissions to the root of the place. + undo_casts: Vec>, +} + +impl<'vir> EncodePlaceResult<'vir> { + fn new(expr: vir::Expr<'vir>) -> Self { + Self { expr, undo_casts: Vec::new() } + } + + fn map_expr(&mut self, f: impl FnOnce(vir::Expr<'vir>) -> vir::Expr<'vir>) -> &mut Self { + self.expr = f(self.expr); + self + } +} + +impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { fn stmt(&mut self, stmt: vir::Stmt<'vir>) { self.current_stmts .as_mut() @@ -361,7 +279,7 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { .generic_predicate .expect_pred_variant_opt(place_ty.variant_index); - let ref_p = self.encode_place(place); + let ref_p = self.encode_place(place).expr; let args = place_ty_out.ref_to_args(self.vcx, ref_p); let predicate = ref_to_pred.apply(self.vcx, &args, None); if matches!( @@ -379,11 +297,10 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { let place_ty_out = self.deps.require_ref::(place_ty.ty).unwrap(); - let ref_p = self.encode_place(place); - let args = place_ty_out.ref_to_args(self.vcx, ref_p); + let ref_p = self.encode_place(place).expr; self.stmt( self.vcx - .mk_exhale_stmt(place_ty_out.ref_to_pred(self.vcx, &args, None)), + .mk_exhale_stmt(place_ty_out.ref_to_pred(self.vcx, ref_p, None)), ); } unsupported_op => panic!("unsupported repack op: {unsupported_op:?}"), @@ -413,28 +330,34 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { self.current_fpcs = Some(current_fpcs); } + fn undo_impure_casts(&mut self, result: EncodePlaceResult<'vir>) { + result.undo_casts.iter().for_each(|stmt| self.stmt(stmt)); + } + fn encode_operand_snap(&mut self, operand: &mir::Operand<'tcx>) -> vir::Expr<'vir> { let ty = operand.ty(self.local_decls, self.vcx.tcx()); match operand { &mir::Operand::Move(source) => { let ty_out = self.deps.require_ref::(ty).unwrap(); - let place_exp = self.encode_place(Place::from(source)); - let args = ty_out.ref_to_args(self.vcx, place_exp); - let snap_val = ty_out.ref_to_snap(self.vcx, &args); + let result = self.encode_place(Place::from(source)); + let snap_val = ty_out.ref_to_snap(self.vcx, result.expr); let tmp_exp = self.new_tmp(ty_out.snapshot()).1; self.stmt(self.vcx.mk_pure_assign_stmt(tmp_exp, snap_val)); self.stmt( self.vcx - .mk_exhale_stmt(ty_out.ref_to_pred(self.vcx, &args, None)), + .mk_exhale_stmt(ty_out.ref_to_pred(self.vcx, result.expr, None)), ); tmp_exp } &mir::Operand::Copy(source) => { let ty_out = self.deps.require_ref::(ty).unwrap(); - let viper_ref = self.encode_place(Place::from(source)); - let args = ty_out.ref_to_args(self.vcx, viper_ref); - ty_out.ref_to_snap(self.vcx, args) + let result = self.encode_place(Place::from(source)); + let snap_val = ty_out.ref_to_snap(self.vcx, result.expr); + let tmp_exp = self.new_tmp(ty_out.snapshot()).1; + self.stmt(self.vcx.mk_pure_assign_stmt(tmp_exp, snap_val)); + self.undo_impure_casts(result); + tmp_exp } mir::Operand::Constant(box constant) => self .deps @@ -448,57 +371,113 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { operand: &mir::Operand<'tcx>, ) -> vir::Expr<'vir> { let ty = operand.ty(self.local_decls, self.vcx.tcx()); - let (snap_val, ty_out) = match operand { - &mir::Operand::Move(source) => return self.encode_place(Place::from(source)), + let (encode_place_result, ty_out) = match operand { + &mir::Operand::Move(source) => return self.encode_place(Place::from(source)).expr, &mir::Operand::Copy(source) => { let ty_out = self.deps.require_ref::(ty).unwrap(); - let source: vir::Expr<'vir> = - ty_out.ref_to_snap(self.vcx, &[self.encode_place(Place::from(source))]); - (source, ty_out) + let mut result = self.encode_place(Place::from(source)); + result.map_expr(|e| ty_out.ref_to_snap(self.vcx, e)); + (result, ty_out) } mir::Operand::Constant(box constant) => { let ty_out = self.deps.require_ref::(ty).unwrap(); let constant = self.deps.require_local::((constant.literal, 0, self.def_id)).unwrap(); - (constant, ty_out) + (EncodePlaceResult::new(constant), ty_out) } }; let tmp_exp: vir::Expr<'vir> = self.new_tmp(&vir::TypeData::Ref).1; - self.stmt(ty_out.apply_method_assign(self.vcx, tmp_exp, snap_val)); + self.stmt(ty_out.apply_method_assign(self.vcx, tmp_exp, encode_place_result.expr)); + self.undo_impure_casts(encode_place_result); tmp_exp } fn encode_place( &mut self, place: Place<'tcx>, - ) -> vir::Expr<'vir> { + ) -> EncodePlaceResult<'vir> { let mut place_ty = mir::tcx::PlaceTy::from_ty(self.local_decls[place.local].ty); - let mut expr = self.local_defs.locals[place.local].local_ex; + let mut result = EncodePlaceResult::new(self.local_defs.locals[place.local].local_ex); // TODO: factor this out (duplication with pure encoder)? for &elem in place.projection { - expr = self.encode_place_element(place_ty, elem, expr); + let (expr, unapply_cast_stmt) = self.encode_place_element(place_ty, elem, result.expr); + result.expr = expr; + if let Some(stmt) = unapply_cast_stmt { + result.undo_casts.push(stmt); + } place_ty = place_ty.projection_ty(self.vcx.tcx(), elem); } - expr + result } - fn encode_place_element(&mut self, place_ty: mir::tcx::PlaceTy<'tcx>, elem: mir::PlaceElem<'tcx>, expr: vir::Expr<'vir>) -> vir::Expr<'vir> { + // Returns a tuple (expr, unapply_cast), where `expr` is the encoded place element, + // and `unapply_cast` is a statement to undo the impure cast that was made to access + // it. + fn encode_place_element( + &mut self, + place_ty: mir::tcx::PlaceTy<'tcx>, + elem: mir::PlaceElem<'tcx>, + expr: vir::Expr<'vir> + ) -> (vir::Expr<'vir>, Option>) { match elem { - mir::ProjectionElem::Field(field_idx, _) => { + mir::ProjectionElem::Field(field_idx, ty) => { let e_ty = self.deps.require_ref::(place_ty.ty).unwrap(); let field_access = e_ty .generic_predicate .expect_variant_opt(place_ty.variant_index) .ref_to_field_refs; let projection_p = field_access[field_idx.as_usize()]; - projection_p.apply(self.vcx, [expr]) + let proj_app = projection_p.apply(self.vcx, [expr]); + let mut unapply_cast_stmt = None; + match place_ty.ty.kind() { + TyKind::Adt(def, _) => { + let variant = def.variant(place_ty.variant_index.unwrap_or( + abi::FIRST_VARIANT + )); + let generic_field_ty = variant.fields[field_idx].ty( + self.vcx.tcx(), + GenericArgs::identity_for_item(self.vcx.tcx(), def.did()) + ); + let cast_args = CastArgs { + expected: ty, + actual: generic_field_ty + }; + if let Some(cast) = self + .deps.require_ref::>(cast_args) + .unwrap().apply_cast_if_necessary(self.vcx, proj_app) { + self.stmt(cast); + unapply_cast_stmt = self + .deps + .require_ref::>(cast_args.reversed()) + .unwrap().apply_cast_if_necessary(self.vcx, proj_app); + } + } + TyKind::Tuple(_) => { + if let Some(cast_stmts) = self + .deps.require_local::>(ty) + .unwrap().cast_to_concrete_if_possible(self.vcx, proj_app) { + self.stmt(cast_stmts.apply_cast_stmt); + unapply_cast_stmt = Some(cast_stmts.unapply_cast_stmt); + } + } + _ => {} + } + (proj_app, unapply_cast_stmt) } // TODO: should all variants start at the same `Ref`? - mir::ProjectionElem::Downcast(..) => expr, + mir::ProjectionElem::Downcast(..) => (expr, None), mir::ProjectionElem::Deref => { assert!(place_ty.variant_index.is_none()); let e_ty = self.deps.require_ref::(place_ty.ty).unwrap(); let ref_field = e_ty.generic_predicate.expect_ref().ref_field; - self.vcx.mk_field_expr(expr, ref_field) + let expr = self.vcx.mk_field_expr(expr, ref_field); + let inner_ty = place_ty.ty.builtin_deref(true).unwrap().ty; + if let Some(cast_stmts) = self + .deps.require_local::>(inner_ty) + .unwrap().cast_to_concrete_if_possible(self.vcx, expr) { + self.stmt(cast_stmts.apply_cast_stmt); + return (expr, Some(cast_stmts.unapply_cast_stmt)); + } + (expr, None) } _ => todo!("Unsupported ProjectionElem {:?}", elem), } @@ -516,7 +495,7 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { } } -impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc> { +impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir, 'enc> { // fn visit_body(&mut self, body: &mir::Body<'tcx>) { // println!("visiting body!"); // } @@ -619,7 +598,7 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc //let ty_s = self.local_types[ssa_update.local].snapshot; // What are we assigning to? - let proj_ref = self.encode_place(Place::from(*dest)); + let proj_ref = self.encode_place(Place::from(*dest)).expr; let rvalue_ty = rvalue.ty(self.local_decls, self.vcx.tcx()); @@ -709,12 +688,12 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc let e_rvalue_ty = self.deps.require_ref::(rvalue_ty).unwrap(); let place_ty = place.ty(self.local_decls, self.vcx.tcx()); let ty = self.deps.require_ref::(place_ty.ty).unwrap(); - let place_expr = self.encode_place(Place::from(*place)); + let place_expr = self.encode_place(Place::from(*place)).expr; match ty.generic_predicate.get_enumlike().filter(|_| place_ty.variant_index.is_none()) { Some(el) => { let discr_expr = self.vcx.mk_field_expr(place_expr, el.as_ref().unwrap().discr); - self.vcx.mk_unfolding_expr(ty.ref_to_pred_app(self.vcx, &[place_expr], Some(self.vcx.mk_wildcard())), discr_expr) + self.vcx.mk_unfolding_expr(ty.ref_to_pred_app(self.vcx, place_expr, Some(self.vcx.mk_wildcard())), discr_expr) } None => { // mir::Rvalue::Discriminant documents "Returns zero for types without discriminant" @@ -845,18 +824,36 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc target, .. } => { - let (func_def_id, arg_tys) = self.get_def_id_and_caller_substs(func); + let (func_def_id, caller_substs) = self.get_def_id_and_caller_substs(func); let is_pure = crate::encoders::with_proc_spec(func_def_id, |spec| spec.kind.is_pure().unwrap_or_default() ).unwrap_or_default(); - let dest = self.encode_place(Place::from(*destination)); + let dest = self.encode_place(Place::from(*destination)).expr; let task = (func_def_id, self.def_id); + let sig = self.vcx().tcx().fn_sig(func_def_id); + let sig = if self.monomorphize { + let param_env = self.vcx().tcx().param_env(self.def_id); + self.vcx().tcx().subst_and_normalize_erasing_regions( + caller_substs, + param_env, + sig + ) + } else { + sig.instantiate_identity() + }; + let fn_arg_tys = sig + .inputs() + .iter() + .map(|i| i.skip_binder()) + .copied() + .collect::>(); if is_pure { let pure_func_app = self.encode_pure_func_app( func_def_id, - arg_tys, + sig, + caller_substs, args, destination, self.def_id, @@ -874,25 +871,64 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc } else { let func_out = self .deps - .require_ref::((task.0, arg_tys, Some(task.1))) + .require_ref::( + FunctionCallTaskDescription::new( + task.0, caller_substs, task.1 + ) + ) .unwrap(); - let method_in = args.iter().map(|op| self.encode_operand(op)); - let mut method_args = - std::iter::once(dest).chain(method_in).collect::>(); - for arg in arg_tys { - let arg_ty_enc = self - .deps - .require_ref::(arg.expect_ty()) + let method_in = args.iter().map(|arg| self.encode_operand(arg)).collect::>(); + + + for ((fn_arg_ty, arg), arg_ex) in fn_arg_tys.iter().zip(args.iter()).zip(method_in.iter()) { + let local_decls = self.local_decls_src(); + let tcx = self.vcx().tcx(); + let arg_ty = arg.ty(local_decls, tcx); + let caster = self.deps() + .require_ref::>(CastArgs { + expected: *fn_arg_ty, + actual: arg_ty + }) .unwrap(); - method_args.extend(arg_ty_enc.ty.arg_exprs(self.vcx)) + // In this context, `apply_cast_if_necessary` returns + // the impure operation to perform the cast + if let Some(stmt) = caster.apply_cast_if_necessary(self.vcx(), arg_ex) { + self.stmt(stmt); + } } + let mut method_args = + std::iter::once(dest).chain(method_in).collect::>(); + let mono = self.monomorphize; + let encoded_ty_args = self + .deps() + .require_local::( + (mono, caller_substs) + ) + .unwrap() + .iter() + .map(|ty| ty.expr(self.vcx())); + + method_args.extend(encoded_ty_args); + self.stmt( self.vcx .alloc(func_out.method_ref.apply(self.vcx, &method_args)), ); + let expected_ty = destination.ty(self.local_decls_src(), self.vcx.tcx()).ty; + let fn_result_ty = sig.output().skip_binder(); + let result_cast = self + .deps() + .require_ref::>(CastArgs { + expected: expected_ty, + actual: fn_result_ty, + }) + .unwrap(); + if let Some(stmt) = result_cast.apply_cast_if_necessary(self.vcx, dest) { + self.stmt(stmt); + } } target diff --git a/prusti-encoder/src/encoders/mir_poly_impure.rs b/prusti-encoder/src/encoders/mir_poly_impure.rs new file mode 100644 index 00000000000..bc5028e3d74 --- /dev/null +++ b/prusti-encoder/src/encoders/mir_poly_impure.rs @@ -0,0 +1,63 @@ +use prusti_rustc_interface::span::def_id::DefId; +use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use vir::{MethodIdent, UnknownArity}; + +/// Encodes a Rust function as a Viper method using the polymorphic encoding of generics. +pub struct MirPolyImpureEnc; + +#[derive(Clone, Debug)] +pub struct MirImpureEncOutputRef<'vir> { + pub method_ref: MethodIdent<'vir, UnknownArity<'vir>>, +} +impl<'vir> task_encoder::OutputRefAny for MirImpureEncOutputRef<'vir> {} + +#[derive(Clone, Debug)] +pub struct MirImpureEncOutput<'vir> { + pub method: vir::Method<'vir>, +} + +use crate::encoder_traits::impure_function_enc::{ + ImpureFunctionEnc, ImpureFunctionEncError, ImpureFunctionEncOutput, ImpureFunctionEncOutputRef, +}; + +impl ImpureFunctionEnc for MirPolyImpureEnc { + fn mk_method_ident<'vir, 'tcx>( + vcx: &'vir vir::VirCtxt<'tcx>, + def_id: &Self::TaskKey<'tcx>, + ) -> vir::ViperIdent<'vir> { + vir::vir_format_identifier!(vcx, "m_{}", vcx.tcx().def_path_str(*def_id)) + } +} + +impl TaskEncoder for MirPolyImpureEnc { + task_encoder::encoder_cache!(MirPolyImpureEnc); + + type TaskDescription<'tcx> = DefId; + + type TaskKey<'tcx> = DefId; + + type OutputRef<'vir> = ImpureFunctionEncOutputRef<'vir>; + type OutputFullLocal<'vir> = ImpureFunctionEncOutput<'vir>; + + type EncodingError = ImpureFunctionEncError; + + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { + *task + } + + fn do_encode_full<'tcx: 'vir, 'vir>( + def_id: &Self::TaskKey<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + Ok((::encode(*def_id, deps), ())) + } +} diff --git a/prusti-encoder/src/encoders/mir_pure.rs b/prusti-encoder/src/encoders/mir_pure.rs index 4613344741f..40242cdec70 100644 --- a/prusti-encoder/src/encoders/mir_pure.rs +++ b/prusti-encoder/src/encoders/mir_pure.rs @@ -12,10 +12,17 @@ use task_encoder::{ use vir::add_debug_note; use std::collections::HashMap; // TODO: replace uses of `PredicateEnc` with `SnapshotEnc` -use crate::{encoder_traits::pure_func_app_enc::PureFuncAppEnc, encoders::{lifted::cast::{CastArgs, PureGenericCastEnc}, ConstEnc, MirBuiltinEnc, ViperTupleEnc}}; +use crate::encoders::{lifted::cast::{CastArgs, CastToEnc}, ConstEnc, MirBuiltinEnc, ViperTupleEnc}; use super::{ - lifted::{aggregate_cast::{AggregateSnapArgsCastEnc, AggregateSnapArgsCastEncTask}, rust_ty_cast::RustTyGenericCastEnc}, rust_ty_predicates::RustTyPredicatesEnc, rust_ty_snapshots::RustTySnapshotsEnc + lifted::{ + aggregate_cast::{AggregateSnapArgsCastEnc, AggregateSnapArgsCastEncTask}, + casters::CastTypePure, + rust_ty_cast::RustTyCastersEnc, + }, + rust_ty_predicates::RustTyPredicatesEnc, + rust_ty_snapshots::RustTySnapshotsEnc }; +use crate::encoder_traits::pure_func_app_enc::PureFuncAppEnc; pub struct MirPureEnc; @@ -108,7 +115,7 @@ impl TaskEncoder for MirPureEnc { PureKind::Constant(promoted) => vcx.body_mut().get_promoted_constant_body(def_id, promoted) }; - let expr_inner = Enc::new(vcx, task_key.0, def_id, &body, deps).encode_body(); + let expr_inner = Enc::new(vcx, cfg!(feature="mono_function_encoding"), task_key.0, def_id, &body, deps).encode_body(); // We wrap the expression with an additional lazy that will perform // some sanity checks. These requirements cannot be expressed using @@ -169,6 +176,7 @@ impl<'vir> Update<'vir> { struct Enc<'tcx, 'vir: 'enc, 'enc> { + monomorphize: bool, vcx: &'vir vir::VirCtxt<'tcx>, encoding_depth: usize, def_id: DefId, @@ -208,12 +216,17 @@ impl <'tcx: 'vir, 'vir, 'enc> PureFuncAppEnc<'tcx, 'vir> for Enc<'tcx, 'vir, 'en ) -> vir::ExprGen<'vir, Self::Curr, Self::Next> { self.encode_operand(args, operand) } + + fn monomorphize(&self) -> bool { + self.monomorphize + } } impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> { fn new( vcx: &'vir vir::VirCtxt<'tcx>, + monomorphize: bool, encoding_depth: usize, def_id: DefId, body: &'enc mir::Body<'tcx>, @@ -222,6 +235,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> assert!(!body.basic_blocks.is_cfg_cyclic(), "MIR pure encoding does not support loops"); let rev_doms = rev_doms::ReverseDominators::new(&body.basic_blocks); Self { + monomorphize, vcx, encoding_depth, def_id, @@ -480,8 +494,20 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> def_spec.kind.is_pure().unwrap_or_default() ).unwrap_or_default(); if is_pure { + let sig = self.vcx().tcx().fn_sig(def_id); + let sig = if self.monomorphize { + let param_env = self.vcx().tcx().param_env(self.def_id); + self.vcx().tcx().subst_and_normalize_erasing_regions( + arg_tys, + param_env, + sig + ) + } else { + sig.instantiate_identity() + }; self.encode_pure_func_app( def_id, + sig, arg_tys, args, destination, @@ -555,7 +581,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> .field_snaps_to_snap; let (snap, place_ref) = self.encode_place_with_ref(curr_ver, place); let place_ty = place.ty(self.body, self.vcx.tcx()).ty; - let cast = self.deps.require_local::(place_ty).unwrap(); + let cast = self.deps.require_local::>(place_ty).unwrap(); // The snapshot of the referenced value should be encoded as a generic `Param` let snap = cast.cast_to_generic_if_necessary(self.vcx, snap); if kind.mutability().is_mut() { @@ -758,7 +784,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> let place_ty = place_ty.projection_ty(self.vcx.tcx(), elem); // Since the `expr` is the target of a reference, it is encoded as a `Param`. // If it is not a type parameter, we cast it to its concrete Snapshot. - let cast = self.deps.require_local::(place_ty.ty).unwrap(); + let cast = self.deps.require_local::>(place_ty.ty).unwrap(); let expr = cast.cast_to_concrete_if_possible(self.vcx, expr); (expr, place_ref) } @@ -792,7 +818,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> expected: ty, actual: generic_field_ty }; - self.deps.require_ref::(cast_args) + self.deps.require_ref::>(cast_args) .unwrap().apply_cast_if_necessary(self.vcx, proj_app) } else { diff --git a/prusti-encoder/src/encoders/mir_pure_function.rs b/prusti-encoder/src/encoders/mir_pure_function.rs index 2c584197e36..e035adba750 100644 --- a/prusti-encoder/src/encoders/mir_pure_function.rs +++ b/prusti-encoder/src/encoders/mir_pure_function.rs @@ -1,14 +1,13 @@ -use prusti_rustc_interface::{ - middle::ty::{self, GenericArgs}, - span::def_id::DefId, -}; +use prusti_rustc_interface::span::def_id::DefId; use task_encoder::{TaskEncoder, TaskEncoderDependencies}; use crate::encoder_traits::pure_function_enc::{ - PureFunctionEnc, MirFunctionEncOutput, MirFunctionEncOutputRef, + MirFunctionEncOutput, MirFunctionEncOutputRef, PureFunctionEnc }; +use super::mono::task_description::FunctionCallTaskDescription; + pub struct MirFunctionEnc; #[derive(Clone, Debug)] @@ -17,37 +16,19 @@ pub enum MirFunctionEncError { } impl PureFunctionEnc for MirFunctionEnc { - fn get_substs<'tcx>( - vcx: &vir::VirCtxt<'tcx>, - def_id: &Self::TaskKey<'tcx>, - ) -> &'tcx GenericArgs<'tcx> { - GenericArgs::identity_for_item(vcx.tcx(), *def_id) - } fn mk_function_ident<'vir, 'tcx>( vcx: &'vir vir::VirCtxt<'tcx>, def_id: &Self::TaskKey<'tcx>, ) -> vir::ViperIdent<'vir> { - vir::vir_format_identifier!(vcx, "f_{}", vcx.tcx().item_name(*def_id)) - } - - fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId { - *task_key - } - - fn get_caller_def_id(_task_key: &Self::TaskKey<'_>) -> Option { - None + vir::vir_format_identifier!(vcx, "f_{}", vcx.tcx().def_path_str(*def_id)) } } impl TaskEncoder for MirFunctionEnc { task_encoder::encoder_cache!(MirFunctionEnc); - type TaskDescription<'vir> = ( - DefId, // ID of the function - ty::GenericArgsRef<'vir>, // ? this should be the "signature", after applying the env/substs - DefId, // Caller DefID - ); + type TaskDescription<'tcx> = FunctionCallTaskDescription<'tcx>; type OutputRef<'vir> = MirFunctionEncOutputRef<'vir>; type OutputFullLocal<'vir> = MirFunctionEncOutput<'vir>; @@ -56,7 +37,7 @@ impl TaskEncoder for MirFunctionEnc { type EncodingError = MirFunctionEncError; fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { - task.0 + task.def_id } fn do_encode_full<'tcx: 'vir, 'vir>( diff --git a/prusti-encoder/src/encoders/mod.rs b/prusti-encoder/src/encoders/mod.rs index 0dd5b2a14e4..a4e80bc1a42 100644 --- a/prusti-encoder/src/encoders/mod.rs +++ b/prusti-encoder/src/encoders/mod.rs @@ -1,7 +1,8 @@ mod generic; mod mir_builtin; -mod mir_impure; mod mir_pure; +mod mir_poly_impure; +mod mir_impure; mod spec; mod mir_pure_function; mod pure; @@ -10,12 +11,16 @@ mod r#type; mod r#const; mod mono; -#[cfg(feature = "mono_function_encoding")] -pub type PureFunctionEnc = mono::mir_pure_function::MirMonoFunctionEnc; +cfg_if::cfg_if! { + if #[cfg(feature = "mono_function_encoding")] { + pub use mono::mir_pure_function::MirMonoFunctionEnc as PureFunctionEnc; + } else { + pub use mir_pure_function::MirFunctionEnc as PureFunctionEnc; + } +} -#[cfg(not(feature = "mono_function_encoding"))] -pub type PureFunctionEnc = mir_pure_function::MirFunctionEnc; +pub use mono::task_description::*; pub use pure::*; pub use pure::spec::MirSpecEnc; pub use local_def::*; @@ -25,7 +30,9 @@ pub use mir_builtin::{ MirBuiltinEnc, MirBuiltinEncTask, }; -pub use mir_impure::MirImpureEnc; +pub use mir_poly_impure::MirPolyImpureEnc; +pub use mono::mir_impure::MirMonoImpureEnc; +pub use mir_impure::{ImpureEncVisitor, MirImpureEnc}; pub use mir_pure::{ PureKind, MirPureEnc, diff --git a/prusti-encoder/src/encoders/mono/mir_impure.rs b/prusti-encoder/src/encoders/mono/mir_impure.rs new file mode 100644 index 00000000000..84e681c6b1d --- /dev/null +++ b/prusti-encoder/src/encoders/mono/mir_impure.rs @@ -0,0 +1,84 @@ +use prusti_rustc_interface::{middle::ty::GenericArgs, span::def_id::DefId}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use vir::{MethodIdent, UnknownArity}; +/// Encodes a Rust function as a Viper method using the monomorphic encoding of generics. +pub struct MirMonoImpureEnc; + +#[derive(Clone, Debug)] +pub struct MirImpureEncOutputRef<'vir> { + pub method_ref: MethodIdent<'vir, UnknownArity<'vir>>, +} +impl<'vir> task_encoder::OutputRefAny for MirImpureEncOutputRef<'vir> {} + +#[derive(Clone, Debug)] +pub struct MirImpureEncOutput<'vir> { + pub method: vir::Method<'vir>, +} + +use crate::{ + encoder_traits::{ + function_enc::FunctionEnc, + impure_function_enc::{ + ImpureFunctionEnc, ImpureFunctionEncError, ImpureFunctionEncOutput, + ImpureFunctionEncOutputRef, + }, + }, + encoders::FunctionCallTaskDescription, +}; + +impl FunctionEnc for MirMonoImpureEnc { + fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId { + task_key.def_id + } + + fn get_caller_def_id(task_key: &Self::TaskKey<'_>) -> Option { + Some(task_key.caller_def_id) + } + + fn get_substs<'tcx>( + _vcx: &vir::VirCtxt<'tcx>, + task_key: &Self::TaskKey<'tcx>, + ) -> &'tcx GenericArgs<'tcx> { + task_key.substs + } +} + +impl ImpureFunctionEnc for MirMonoImpureEnc { + fn mk_method_ident<'vir, 'tcx>( + vcx: &'vir vir::VirCtxt<'tcx>, + task_key: &Self::TaskKey<'tcx>, + ) -> vir::ViperIdent<'vir> { + task_key.vir_method_ident(vcx) + } +} + +impl TaskEncoder for MirMonoImpureEnc { + task_encoder::encoder_cache!(MirMonoImpureEnc); + + type TaskDescription<'tcx> = FunctionCallTaskDescription<'tcx>; + + type OutputRef<'vir> = ImpureFunctionEncOutputRef<'vir>; + type OutputFullLocal<'vir> = ImpureFunctionEncOutput<'vir>; + + type EncodingError = ImpureFunctionEncError; + + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { + *task + } + + fn do_encode_full<'tcx: 'vir, 'vir>( + task_key: &Self::TaskKey<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + Ok((::encode(*task_key, deps), ())) + } +} diff --git a/prusti-encoder/src/encoders/mono/mir_pure_function.rs b/prusti-encoder/src/encoders/mono/mir_pure_function.rs index 2ef0350ed23..36530879fdd 100644 --- a/prusti-encoder/src/encoders/mono/mir_pure_function.rs +++ b/prusti-encoder/src/encoders/mono/mir_pure_function.rs @@ -1,44 +1,35 @@ use prusti_rustc_interface::{middle::ty::GenericArgs, span::def_id::DefId}; -use std::fmt::Write; - use task_encoder::{TaskEncoder, TaskEncoderDependencies}; use crate::{ - encoder_traits::pure_function_enc::{PureFunctionEnc, MirFunctionEncOutput, MirFunctionEncOutputRef}, + encoder_traits::{function_enc::FunctionEnc, pure_function_enc::{MirFunctionEncOutput, MirFunctionEncOutputRef, PureFunctionEnc}}, encoders::mir_pure_function::{MirFunctionEnc, MirFunctionEncError}, }; -impl PureFunctionEnc for MirMonoFunctionEnc { +impl FunctionEnc for MirMonoFunctionEnc { + fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId { + task_key.def_id + } + + fn get_caller_def_id(task_key: &Self::TaskKey<'_>) -> Option { + Some(task_key.caller_def_id) + } + fn get_substs<'tcx>( _vcx: &vir::VirCtxt<'tcx>, task_key: &Self::TaskKey<'tcx>, ) -> &'tcx GenericArgs<'tcx> { - task_key.1 + task_key.substs } +} + +impl PureFunctionEnc for MirMonoFunctionEnc { fn mk_function_ident<'vir, 'tcx>( vcx: &'vir vir::VirCtxt<'tcx>, task_key: &Self::TaskKey<'tcx>, ) -> vir::ViperIdent<'vir> { - let (def_id, substs, caller_def_id) = task_key; - let mut extra = String::new(); - for s in substs.iter() { - write!(extra, "_{s}").unwrap(); - } - let (krate, index) = (caller_def_id.krate, caller_def_id.index.index()); - vir::vir_format_identifier!( - vcx, - "f_{}{extra}_CALLER_{krate}_{index}", - vcx.tcx().item_name(*def_id) - ) - } - - fn get_def_id(task_key: &Self::TaskKey<'_>) -> DefId { - task_key.0 - } - - fn get_caller_def_id(task_key: &Self::TaskKey<'_>) -> Option { - Some(task_key.2) + task_key.vir_function_ident(vcx) } } diff --git a/prusti-encoder/src/encoders/mono/mod.rs b/prusti-encoder/src/encoders/mono/mod.rs index 2304bd90def..3c794918c27 100644 --- a/prusti-encoder/src/encoders/mono/mod.rs +++ b/prusti-encoder/src/encoders/mono/mod.rs @@ -1 +1,3 @@ pub mod mir_pure_function; +pub mod task_description; +pub mod mir_impure; diff --git a/prusti-encoder/src/encoders/mono/task_description.rs b/prusti-encoder/src/encoders/mono/task_description.rs new file mode 100644 index 00000000000..ca3b22c2a7e --- /dev/null +++ b/prusti-encoder/src/encoders/mono/task_description.rs @@ -0,0 +1,34 @@ +use std::fmt::Write; +use prusti_rustc_interface::{ + span::def_id::DefId, + middle::ty +}; +use vir::VirCtxt; + +#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] +pub struct FunctionCallTaskDescription<'tcx> { + pub def_id: DefId, + // Substitutions at the call site + pub substs: ty::GenericArgsRef<'tcx>, + pub caller_def_id: DefId, +} + +impl <'tcx> FunctionCallTaskDescription<'tcx> { + pub fn new(def_id: DefId, substs: ty::GenericArgsRef<'tcx>, caller_def_id: DefId) -> Self { + Self { def_id, substs, caller_def_id } + } + + pub fn vir_function_ident<'vir>(&self, vcx: &'vir VirCtxt<'tcx>) -> vir::ViperIdent<'vir> { + vir::vir_format_identifier!(vcx, "f_{}", self.get_mangled_name(vcx)) + } + pub fn vir_method_ident<'vir>(&self, vcx: &'vir VirCtxt<'tcx>) -> vir::ViperIdent<'vir> { + vir::vir_format_identifier!(vcx, "m_{}", self.get_mangled_name(vcx)) + } + + fn get_mangled_name(&self, vcx: &VirCtxt<'tcx>) -> String { + let mut name = vcx.tcx().def_path_str_with_args(self.def_id, self.substs); + write!(name, "_CALLER_{}_{}", self.caller_def_id.krate, self.caller_def_id.index.index()).unwrap(); + name + } + +} diff --git a/prusti-encoder/src/encoders/type/domain.rs b/prusti-encoder/src/encoders/type/domain.rs index bdfc1312e63..532258334e4 100644 --- a/prusti-encoder/src/encoders/type/domain.rs +++ b/prusti-encoder/src/encoders/type/domain.rs @@ -103,7 +103,12 @@ impl<'vir> task_encoder::OutputRefAny for DomainEncOutputRef<'vir> {} use crate::encoders::{generic::GenericEncOutputRef, GenericEnc}; use super::{ - lifted::{cast_functions::CastFunctionsEnc, ty::{EncodeGenericsAsParamTy, LiftedTy, LiftedTyEnc}, ty_constructor::{TyConstructorEnc, TyConstructorEncOutputRef}}, most_generic_ty::{extract_type_params, MostGenericTy}, rust_ty_snapshots::RustTySnapshotsEnc + lifted::{ + ty::{EncodeGenericsAsParamTy, LiftedTy, LiftedTyEnc}, + ty_constructor::{TyConstructorEnc, TyConstructorEncOutputRef} + }, + most_generic_ty::{extract_type_params, MostGenericTy}, + rust_ty_snapshots::RustTySnapshotsEnc }; pub fn all_outputs<'vir>() -> Vec> { diff --git a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs index 6d64f32e17f..4054c19365c 100644 --- a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs @@ -5,9 +5,9 @@ use prusti_rustc_interface::{ }; use task_encoder::TaskEncoder; -use crate::encoders::lifted::cast::{CastArgs, PureGenericCastEnc}; +use crate::encoders::lifted::cast::{CastArgs, CastToEnc}; -use super::{cast::PureCast, rust_ty_cast::RustTyGenericCastEnc}; +use super::{cast::PureCast, casters::CastTypePure, rust_ty_cast::RustTyCastersEnc}; /// Casts arguments to the snapshot constructor for an aggregate type (e.g. /// Tuples, ADTs) to appropriate (generic or concrete) Viper representations, @@ -101,8 +101,8 @@ impl TaskEncoder for AggregateSnapArgsCastEnc { .iter() .map(|ty| { let cast_functions = - deps.require_local::(*ty).unwrap(); - cast_functions.to_generic_cast() + deps.require_local::>(*ty).unwrap(); + cast_functions.to_generic_cast().map(|c| c.map_applicator(|f| f.as_unknown_arity())) }) .collect::>(), AggregateType::Adt { @@ -119,7 +119,7 @@ impl TaskEncoder for AggregateSnapArgsCastEnc { .zip(task_key.tys.iter()) .map(|(v_field, actual_ty)| { let cast = deps - .require_ref::(CastArgs { + .require_ref::>(CastArgs { expected: v_field.ty(vcx.tcx(), identity_substs), actual: *actual_ty, }) diff --git a/prusti-encoder/src/encoders/type/lifted/cast.rs b/prusti-encoder/src/encoders/type/lifted/cast.rs index b698c46b8a8..5afe520261b 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast.rs @@ -1,10 +1,14 @@ use prusti_rustc_interface::middle::ty; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; -use vir::VirCtxt; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, TaskEncoderError}; +use vir::{FunctionIdent, MethodIdent, StmtGen, UnknownArity, VirCtxt}; use super::{ - cast_functions::CastFunctionsOutputRef, generic::LiftedGeneric, - rust_ty_cast::RustTyGenericCastEnc, ty::LiftedTy, + casters::{ + CastType, CastTypeImpure, CastTypePure, Casters, CastersEncOutputRef, + }, + generic::LiftedGeneric, + rust_ty_cast::{RustTyCastersEnc, RustTyGenericCastEncOutput}, + ty::LiftedTy, }; #[derive(Copy, Hash, PartialEq, Eq, Clone, Debug)] @@ -15,35 +19,56 @@ pub struct CastArgs<'tcx> { pub actual: ty::Ty<'tcx>, } -/// Holds the necessary information to cast a snapshot to a generic or concrete +impl<'tcx> CastArgs<'tcx> { + pub fn reversed(&self) -> CastArgs<'tcx> { + CastArgs { + expected: self.actual, + actual: self.expected, + } + } +} + +/// Holds the necessary information to cast to a generic or concrete /// version. #[derive(Copy, Clone)] -pub struct PureCast<'vir> { - /// The function that performs the cast. The first argument is the expression to - /// cast, followed by the type arguments. - cast_function: vir::FunctionIdent<'vir, vir::UnknownArity<'vir>>, +pub struct Cast<'vir, T> { + /// Either a function or method identifier that can be applied to perform + /// the cast + cast_applicator: T, + /// Type arguments that will be passed to the cast applicator ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], } -impl<'vir> PureCast<'vir> { +pub type PureCast<'vir> = Cast<'vir, FunctionIdent<'vir, UnknownArity<'vir>>>; + +impl<'vir, T> Cast<'vir, T> { pub fn new( - cast_function: vir::FunctionIdent<'vir, vir::UnknownArity<'vir>>, + cast_applicator: T, ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], - ) -> PureCast<'vir> { - PureCast { - cast_function, + ) -> Cast<'vir, T> { + Cast { + cast_applicator, ty_args, } } + pub fn map_applicator(self, f: impl FnOnce(T) -> U) -> Cast<'vir, U> { + Cast { + cast_applicator: f(self.cast_applicator), + ty_args: self.ty_args, + } + } +} + +impl<'vir> Cast<'vir, FunctionIdent<'vir, UnknownArity<'vir>>> { /// Returns the result of the cast pub fn apply( &self, vcx: &'vir VirCtxt, expr: vir::ExprGen<'vir, Curr, Next>, ) -> vir::ExprGen<'vir, Curr, Next> { - self.cast_function.apply( + self.cast_applicator.apply( vcx, &std::iter::once(expr) .chain(self.ty_args.iter().map(|t| t.expr(vcx))) @@ -53,23 +78,23 @@ impl<'vir> PureCast<'vir> { } #[derive(Clone)] -pub enum PureGenericCastOutputRef<'vir> { +pub enum GenericCastOutputRef<'vir, T> { NoCast, - Cast(PureCast<'vir>), + Cast(Cast<'vir, T>), } -impl<'vir> PureGenericCastOutputRef<'vir> { +impl<'vir> GenericCastOutputRef<'vir, FunctionIdent<'vir, UnknownArity<'vir>>> { pub fn apply_cast_if_necessary( &self, vcx: &'vir VirCtxt<'_>, expr: vir::ExprGen<'vir, Curr, Next>, ) -> vir::ExprGen<'vir, Curr, Next> { match self { - PureGenericCastOutputRef::NoCast => expr, - PureGenericCastOutputRef::Cast(PureCast { - cast_function, + GenericCastOutputRef::NoCast => expr, + GenericCastOutputRef::Cast(Cast { + cast_applicator, ty_args, - }) => cast_function.apply( + }) => cast_applicator.apply( vcx, &std::iter::once(expr) .chain(ty_args.iter().map(|t| t.expr(vcx))) @@ -77,16 +102,43 @@ impl<'vir> PureGenericCastOutputRef<'vir> { ), } } +} + +impl<'vir> GenericCastOutputRef<'vir, MethodIdent<'vir, UnknownArity<'vir>>> { + pub fn apply_cast_if_necessary( + &self, + vcx: &'vir VirCtxt<'_>, + expr: vir::ExprGen<'vir, Curr, Next>, + ) -> Option> { + match self { + GenericCastOutputRef::NoCast => None, + GenericCastOutputRef::Cast(Cast { + cast_applicator, + ty_args, + }) => Some( + vcx.alloc( + cast_applicator.apply( + vcx, + &std::iter::once(expr) + .chain(ty_args.iter().map(|t| t.expr(vcx))) + .collect::>(), + ), + ), + ), + } + } +} - pub fn cast_function(&self) -> Option> { +impl<'vir, T: Copy> GenericCastOutputRef<'vir, T> { + pub fn cast_function(&self) -> Option> { match self { - PureGenericCastOutputRef::NoCast => None, - PureGenericCastOutputRef::Cast(f) => Some(*f), + GenericCastOutputRef::NoCast => None, + GenericCastOutputRef::Cast(f) => Some(*f), } } } -impl<'vir> task_encoder::OutputRefAny for PureGenericCastOutputRef<'vir> {} +impl<'vir, T> task_encoder::OutputRefAny for GenericCastOutputRef<'vir, T> {} /// Returns necessary data to support casting the generic Viper representation /// of a Rust expression to its concrete type, or vice versa, for function @@ -98,12 +150,59 @@ impl<'vir> task_encoder::OutputRefAny for PureGenericCastOutputRef<'vir> {} /// the type and the argument is concrete, it returns a function to cast the /// concrete expression to its generic version. Otherwise, no cast is necessary /// and it returns [`PureGenericCastOutputRef::NoCast`]. -pub struct PureGenericCastEnc; +/// +/// The type parameter `T` is used to choose whether a pure or impure cast +/// should be encoded, it should be instantiated with either [`CastTypePure`] or +/// [`CastTypeImpure`]. +pub struct CastToEnc(std::marker::PhantomData); + +impl CastToEnc +where + RustTyCastersEnc: for<'tcx, 'vir> TaskEncoder< + TaskDescription<'tcx> = ty::Ty<'tcx>, + OutputFullLocal<'vir> = RustTyGenericCastEncOutput<'vir, Casters<'vir, T>>, + >, + TaskEncoderError>: Sized, +{ + fn encode_cast<'tcx: 'vir, 'vir>( + task_key: CastArgs<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> GenericCastOutputRef<'vir, T::CastApplicator<'vir>> { + let expected_is_param = matches!(task_key.expected.kind(), ty::Param(_)); + let actual_is_param = matches!(task_key.actual.kind(), ty::Param(_)); + if expected_is_param == actual_is_param { + GenericCastOutputRef::NoCast + } else if actual_is_param { + // expected is concrete type, `actual` should be concretized + let generic_cast = deps + .require_local::>(task_key.expected) + .unwrap(); + if let CastersEncOutputRef::Casters { make_concrete, .. } = generic_cast.cast { + GenericCastOutputRef::Cast(Cast::new( + T::to_concrete_applicator(make_concrete), + generic_cast.ty_args, + )) + } else { + unreachable!() + } + } else { + // expected is generic type, `actual` should be be made generic + let generic_cast = deps + .require_local::>(task_key.actual) + .unwrap(); + if let CastersEncOutputRef::Casters { make_generic, .. } = generic_cast.cast { + GenericCastOutputRef::Cast(Cast::new(T::to_generic_applicator(make_generic), &[])) + } else { + unreachable!() + } + } + } +} -impl TaskEncoder for PureGenericCastEnc { - task_encoder::encoder_cache!(PureGenericCastEnc); +impl TaskEncoder for CastToEnc { + task_encoder::encoder_cache!(CastToEnc); type TaskDescription<'tcx> = CastArgs<'tcx>; - type OutputRef<'vir> = PureGenericCastOutputRef<'vir>; + type OutputRef<'vir> = GenericCastOutputRef<'vir, FunctionIdent<'vir, UnknownArity<'vir>>>; type OutputFullLocal<'vir> = (); type EncodingError = (); @@ -124,31 +223,37 @@ impl TaskEncoder for PureGenericCastEnc { Option>, ), > { - let expected_is_param = matches!(task_key.expected.kind(), ty::Param(_)); - let actual_is_param = matches!(task_key.actual.kind(), ty::Param(_)); - let output_ref = if expected_is_param == actual_is_param { - PureGenericCastOutputRef::NoCast - } else if actual_is_param { - // expected is concrete type, `actual` should be concretized - let generic_cast = deps - .require_local::(task_key.expected) - .unwrap(); - if let CastFunctionsOutputRef::CastFunctions { make_concrete, .. } = generic_cast.cast { - PureGenericCastOutputRef::Cast(PureCast::new(make_concrete, generic_cast.ty_args)) - } else { - unreachable!() - } - } else { - // expected is generic type, `actual` should be be made generic - let generic_cast = deps - .require_local::(task_key.actual) - .unwrap(); - if let CastFunctionsOutputRef::CastFunctions { make_generic, .. } = generic_cast.cast { - PureGenericCastOutputRef::Cast(PureCast::new(make_generic.as_unknown_arity(), &[])) - } else { - unreachable!() - } - }; + let output_ref = Self::encode_cast(*task_key, deps); + deps.emit_output_ref::(*task_key, output_ref); + Ok(((), ())) + } +} + +impl TaskEncoder for CastToEnc { + task_encoder::encoder_cache!(CastToEnc); + type TaskDescription<'tcx> = CastArgs<'tcx>; + type OutputRef<'vir> = GenericCastOutputRef<'vir, MethodIdent<'vir, UnknownArity<'vir>>>; + type OutputFullLocal<'vir> = (); + type EncodingError = (); + + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { + *task + } + + fn do_encode_full<'tcx: 'vir, 'vir>( + task_key: &Self::TaskKey<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + let output_ref = Self::encode_cast(*task_key, deps); deps.emit_output_ref::(*task_key, output_ref); Ok(((), ())) } diff --git a/prusti-encoder/src/encoders/type/lifted/casters.rs b/prusti-encoder/src/encoders/type/lifted/casters.rs new file mode 100644 index 00000000000..187ab4500e0 --- /dev/null +++ b/prusti-encoder/src/encoders/type/lifted/casters.rs @@ -0,0 +1,505 @@ +use std::marker::PhantomData; + +use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use vir::{Arity, CallableIdent, FunctionIdent, MethodIdent, TypeData, UnaryArity, UnknownArity}; + +use crate::encoders::{ + domain::DomainEnc, lifted::ty_constructor::TyConstructorEnc, most_generic_ty::MostGenericTy, + GenericEnc, PredicateEnc, +}; + +use super::{ + generic::{LiftedGeneric, LiftedGenericEnc}, + ty::LiftedTy, +}; + +pub struct CastTypePure; + +impl CastTypePure { + pub fn cast_to_generic_if_necessary<'vir, Curr, Next>( + casters: &Casters<'vir, Self>, + vcx: &'vir vir::VirCtxt<'_>, + snap: vir::ExprGen<'vir, Curr, Next>, + ) -> vir::ExprGen<'vir, Curr, Next> { + match casters { + CastFunctionsOutputRef::AlreadyGeneric => snap, + CastFunctionsOutputRef::Casters { make_generic, .. } => make_generic.apply(vcx, [snap]), + } + } +} + +impl CastType for CastTypePure { + type CastOutput<'vir, Curr: 'vir, Next: 'vir> = vir::ExprGen<'vir, Curr, Next>; + type ToGeneric<'vir> = MakeGenericCastFunction<'vir>; + type ToConcrete<'vir> = MakeConcreteCastFunction<'vir>; + type CastApplicator<'vir> = vir::FunctionIdent<'vir, UnknownArity<'vir>>; + + fn cast_to_concrete_if_possible<'vir, Curr, Next>( + casters: &Casters<'vir, Self>, + vcx: &'vir vir::VirCtxt<'_>, + snap: vir::ExprGen<'vir, Curr, Next>, + ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], + ) -> Self::CastOutput<'vir, Curr, Next> { + match casters { + CastFunctionsOutputRef::AlreadyGeneric => snap, + CastFunctionsOutputRef::Casters { make_concrete, .. } => make_concrete.apply( + vcx, + &std::iter::once(snap) + .chain(ty_args.iter().map(|t| t.expr(vcx))) + .collect::>(), + ), + } + } + + fn to_concrete_applicator(to_concrete: Self::ToConcrete<'_>) -> Self::CastApplicator<'_> { + to_concrete + } + + fn to_generic_applicator(to_generic: Self::ToGeneric<'_>) -> Self::CastApplicator<'_> { + to_generic.as_unknown_arity() + } +} + +pub struct CastTypeImpure; + +pub struct ImpureCastStmts<'vir, Curr, Next> { + pub apply_cast_stmt: vir::StmtGen<'vir, Curr, Next>, + pub unapply_cast_stmt: vir::StmtGen<'vir, Curr, Next>, +} + +impl<'vir, Curr, Next> ImpureCastStmts<'vir, Curr, Next> { + fn new( + apply_cast_stmt: vir::StmtGen<'vir, Curr, Next>, + unapply_cast_stmt: vir::StmtGen<'vir, Curr, Next>, + ) -> Self { + ImpureCastStmts { + apply_cast_stmt, + unapply_cast_stmt, + } + } +} + +impl CastType for CastTypeImpure { + type CastOutput<'vir, Curr: 'vir, Next: 'vir> = Option>; + + type ToGeneric<'vir> = vir::MethodIdent<'vir, UnknownArity<'vir>>; + + type ToConcrete<'vir> = vir::MethodIdent<'vir, UnknownArity<'vir>>; + + type CastApplicator<'vir> = vir::MethodIdent<'vir, UnknownArity<'vir>>; + + fn cast_to_concrete_if_possible<'vir, Curr, Next>( + casters: &CastersEncOutputRef, Self::ToConcrete<'vir>>, + vcx: &'vir vir::VirCtxt<'_>, + snap: vir::ExprGen<'vir, Curr, Next>, + ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], + ) -> Self::CastOutput<'vir, Curr, Next> { + match casters { + CastersEncOutputRef::AlreadyGeneric => None, + CastersEncOutputRef::Casters { + make_concrete, + make_generic, + } => { + let args = vcx.alloc_slice( + &std::iter::once(snap) + .chain(ty_args.iter().map(|t| t.expr(vcx))) + .collect::>(), + ); + Some(ImpureCastStmts::new( + vcx.alloc(make_concrete.apply(vcx, args)), + vcx.alloc(make_generic.apply(vcx, args)), + )) + } + } + } + + fn to_concrete_applicator(to_concrete: Self::ToConcrete<'_>) -> Self::CastApplicator<'_> { + to_concrete + } + + fn to_generic_applicator(to_generic: Self::ToGeneric<'_>) -> Self::CastApplicator<'_> { + to_generic + } +} +pub trait CastType +where + Self: Sized, +{ + /// The shape of an applied cast, either an expression (for a pure cast) + /// or a statement (for an impure cast) + type CastOutput<'vir, Curr: 'vir, Next: 'vir>; + + /// The type of the VIR construct (either a function or method identifier) + /// that can be applied to perform a cast from the concrete to the generic + /// version + type ToGeneric<'vir>; + + /// The type of the VIR construct (either a function or method identifier) + /// that can be applied to perform a cast from the generic to the concrete + /// version + type ToConcrete<'vir>; + + /// The type of the VIR construct (either a function or method identifier) + /// that can be applied to perform a cast in either direction. Effectively + /// this is type that subsumes both`ToGeneric` and `ToConcrete`. + type CastApplicator<'vir>; + + fn to_concrete_applicator(to_concrete: Self::ToConcrete<'_>) -> Self::CastApplicator<'_>; + + fn to_generic_applicator(to_generic: Self::ToGeneric<'_>) -> Self::CastApplicator<'_>; + + fn cast_to_concrete_if_possible<'vir, Curr, Next>( + casters: &Casters<'vir, Self>, + vcx: &'vir vir::VirCtxt<'_>, + snap: vir::ExprGen<'vir, Curr, Next>, + ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], + ) -> Self::CastOutput<'vir, Curr, Next>; +} + +#[allow(type_alias_bounds)] +pub type Casters<'vir, T: CastType> = CastersEncOutputRef, T::ToConcrete<'vir>>; + +#[derive(Clone)] +pub enum CastersEncOutputRef { + Casters { make_generic: G, make_concrete: C }, + AlreadyGeneric, +} + +impl CastersEncOutputRef { + pub fn expect_casters(&self) -> (G, C) { + match self { + CastersEncOutputRef::AlreadyGeneric => panic!(), + CastersEncOutputRef::Casters { + make_generic, + make_concrete, + } => (*make_generic, *make_concrete), + } + } +} + +pub type CastFunctionsOutputRef<'vir> = + CastersEncOutputRef, MakeConcreteCastFunction<'vir>>; + +pub type CastMethodsOutputRef<'vir> = CastersEncOutputRef< + MethodIdent<'vir, UnknownArity<'vir>>, + MethodIdent<'vir, UnknownArity<'vir>>, +>; + +impl CastersEncOutputRef { + pub fn generic_option(&self) -> Option { + match self { + CastersEncOutputRef::AlreadyGeneric => None, + CastersEncOutputRef::Casters { make_generic, .. } => Some(*make_generic), + } + } +} + +pub type MakeGenericCastFunction<'vir> = FunctionIdent<'vir, UnaryArity<'vir>>; +pub type MakeConcreteCastFunction<'vir> = FunctionIdent<'vir, UnknownArity<'vir>>; + +/// Takes as input the most generic version (c.f. [`MostGenericTy`]) of a Rust +/// type, and generates functions to convert the generic Viper representation of +/// a Rust expression with that type to its concrete representation, and +/// vice-versa. If the provided type is generic, it does nothing, returning +/// [`CastFunctionsOutputRef::AlreadyGeneric`]. +pub struct CastersEnc(PhantomData); + +impl task_encoder::OutputRefAny for CastersEncOutputRef {} + +/// The list of cast functions, if any +type GenericCastOutput<'vir> = &'vir [vir::Function<'vir>]; + +impl TaskEncoder for CastersEnc { + task_encoder::encoder_cache!(CastersEnc); + + type TaskDescription<'vir> = MostGenericTy<'vir>; + type OutputRef<'vir> = CastFunctionsOutputRef<'vir>; + type OutputFullLocal<'vir> = GenericCastOutput<'vir>; + type EncodingError = (); + + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { + *task + } + + fn do_encode_full<'tcx: 'vir, 'vir>( + ty: &Self::TaskKey<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + if ty.is_generic() { + deps.emit_output_ref::(*ty, CastFunctionsOutputRef::AlreadyGeneric); + return Ok((&[], ())); + } + vir::with_vcx(|vcx| { + let domain_ref = deps.require_ref::(*ty).unwrap(); + let generic_ref = deps.require_ref::(()).unwrap(); + let self_ty = domain_ref.domain.apply(vcx, []); + let base_name = &domain_ref.base_name; + let ty_constructor = deps + .require_ref::(*ty) + .unwrap() + .ty_constructor; + + let make_generic_arg_tys = [self_ty]; + let make_generic_ident = FunctionIdent::new( + vir::vir_format_identifier!(vcx, "make_generic_s_{base_name}"), + UnaryArity::new(vcx.alloc(make_generic_arg_tys)), + generic_ref.param_snapshot, + ); + + let make_concrete_ty_params = ty + .generics() + .into_iter() + .map(|g| deps.require_ref::(*g).unwrap()) + .collect::>(); + + let make_concrete_arg_tys = std::iter::once(generic_ref.param_snapshot) + .chain(make_concrete_ty_params.iter().map(|t| t.ty())) + .collect::>(); + + let make_concrete_ident = FunctionIdent::new( + vir::vir_format_identifier!(vcx, "make_concrete_s_{base_name}"), + UnknownArity::new(vcx.alloc(make_concrete_arg_tys)), + self_ty, + ); + + deps.emit_output_ref::( + *ty, + CastFunctionsOutputRef::Casters { + make_generic: make_generic_ident, + make_concrete: make_concrete_ident, + }, + ); + let make_generic_arg = vcx.mk_local_decl("self", self_ty); + let make_generic_expr = vcx.mk_local_ex(make_generic_arg.name, make_generic_arg.ty); + + let make_generic_arg_decls = vcx.alloc_slice(&[make_generic_arg]); + + let make_concrete_ty_param_exprs = make_concrete_ty_params + .iter() + .map(|t| t.expr(vcx)) + .collect::>(); + + let make_generic_result = vcx.mk_result(generic_ref.param_snapshot); + + // Type parameters obtained from the snapshot-encoded value of the type, + let ty_params_from_snap = ty + .generics() + .iter() + .enumerate() + .map(|(idx, _)| domain_ref.ty_param_from_snap(vcx, idx, make_generic_expr)) + .collect::>(); + + // Asserts that the type of `param` is equal to the ty constructor + // applied to type arguments `args` + let mk_type_spec = |param, args| { + let lifted_param_snap_ty = generic_ref.param_type_function.apply(vcx, [param]); + vcx.mk_eq_expr(lifted_param_snap_ty, ty_constructor.apply(vcx, args)) + }; + + let make_generic = vcx.mk_function( + make_generic_ident.name_str(), + make_generic_arg_decls, + generic_ref.param_snapshot, + &[], + vcx.alloc_slice(&[ + mk_type_spec(make_generic_result, &ty_params_from_snap), + vcx.mk_eq_expr( + make_concrete_ident.apply( + vcx, + &std::iter::once(make_generic_result) + .chain(ty_params_from_snap.iter().copied()) + .collect::>(), + ), + make_generic_expr, + ), + ]), + None, + ); + + let make_concrete_snap_arg_decl = vcx.mk_local_decl("snap", generic_ref.param_snapshot); + let make_concrete_arg_decls = vcx.alloc_slice( + &std::iter::once(make_concrete_snap_arg_decl) + .chain(make_concrete_ty_params.iter().map(|t| t.decl())) + .collect::>(), + ); + + let make_concrete_pre = mk_type_spec( + vcx.mk_local_ex( + make_concrete_snap_arg_decl.name, + make_concrete_snap_arg_decl.ty, + ), + &make_concrete_ty_param_exprs, + ); + + let make_concrete_post = vcx.mk_eq_expr( + make_generic_ident.apply(vcx, [vcx.mk_result(self_ty)]), + vcx.mk_local_ex( + make_concrete_snap_arg_decl.name, + make_concrete_snap_arg_decl.ty, + ), + ); + + let make_concrete = vcx.mk_function( + make_concrete_ident.name_str(), + make_concrete_arg_decls, + self_ty, + vcx.alloc_slice(&[make_concrete_pre]), + vcx.alloc_slice(&[make_concrete_post]), + None, + ); + + Ok((vcx.alloc_slice(&[make_generic, make_concrete]), ())) + }) + } +} + +impl TaskEncoder for CastersEnc { + task_encoder::encoder_cache!(CastersEnc); + + type TaskDescription<'vir> = MostGenericTy<'vir>; + type OutputRef<'vir> = CastMethodsOutputRef<'vir>; + type OutputFullLocal<'vir> = &'vir [vir::Method<'vir>]; + type EncodingError = (); + + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { + *task + } + + fn do_encode_full<'tcx: 'vir, 'vir>( + ty: &Self::TaskKey<'tcx>, + deps: &mut TaskEncoderDependencies<'vir>, + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + if ty.is_generic() { + deps.emit_output_ref::(*ty, CastMethodsOutputRef::AlreadyGeneric); + return Ok((&[], ())); + } + vir::with_vcx(|vcx| { + let predicate_ref = deps.require_ref::(*ty).unwrap(); + let generic_ref = deps.require_ref::(()).unwrap(); + let base_name = predicate_ref.ref_to_pred.name(); + let ty_constructor = deps.require_ref::(*ty).unwrap(); + + let arg_tys = vcx.alloc_slice( + &std::iter::once(&TypeData::Ref) + .chain(ty_constructor.arity().args().iter().copied()) + .collect::>(), + ); + + let make_generic_ident = MethodIdent::new( + vir::vir_format_identifier!(vcx, "make_generic_{base_name}"), + UnknownArity::new(arg_tys), + ); + + let make_concrete_ident = MethodIdent::new( + vir::vir_format_identifier!(vcx, "make_concrete_{base_name}"), + UnknownArity::new(arg_tys), + ); + + deps.emit_output_ref::( + *ty, + CastMethodsOutputRef::Casters { + make_generic: make_generic_ident, + make_concrete: make_concrete_ident, + }, + ); + let (make_generic_pure, _) = deps + .require_ref::>(*ty) + .unwrap() + .expect_casters(); + let self_decl = vcx.mk_local_decl("self", &TypeData::Ref); + let self_expr = vcx.mk_local_ex(self_decl.name, self_decl.ty); + let arg_ty_decls = ty_constructor + .arity() + .args() + .iter() + .enumerate() + .map(|(idx, ty)| vcx.mk_local_decl(vcx.alloc_str(&format!("T{}", idx)), ty)) + .collect::>(); + let arg_ty_exprs = arg_ty_decls + .iter() + .map(|decl| vcx.mk_local_ex(decl.name, decl.ty)) + .collect::>(); + let decls = vcx.alloc_slice( + &[self_decl] + .into_iter() + .chain(arg_ty_decls) + .collect::>(), + ); + + let concrete_predicate_args = &std::iter::once(self_expr) + .chain(arg_ty_exprs.iter().copied()) + .collect::>(); + + let concrete_predicate = + predicate_ref + .ref_to_pred + .apply(vcx, concrete_predicate_args, None); + + let concrete_snap = predicate_ref + .ref_to_snap + .apply(vcx, concrete_predicate_args); + + let concrete_predicate = vcx.mk_predicate_app_expr(concrete_predicate); + + let lifted_ty_expr = ty_constructor.ty_constructor.apply(vcx, &arg_ty_exprs); + + let generic_predicate = + generic_ref + .ref_to_pred + .apply(vcx, [self_expr, lifted_ty_expr], None); + + let generic_snap = generic_ref + .ref_to_snap + .apply(vcx, [self_expr, lifted_ty_expr]); + + let generic_predicate = vcx.mk_predicate_app_expr(generic_predicate); + + let make_generic_same_snap = vcx.mk_eq_expr( + vcx.mk_old_expr(make_generic_pure.apply(vcx, [concrete_snap])), + generic_snap, + ); + + let make_concrete_same_snap = vcx.mk_eq_expr( + vcx.mk_old_expr(generic_snap), + make_generic_pure.apply(vcx, [concrete_snap]), + ); + + let make_generic = vcx.mk_method( + make_generic_ident, + decls, + &[], + vcx.alloc_slice(&[concrete_predicate]), + vcx.alloc_slice(&[generic_predicate, make_generic_same_snap]), + None, + ); + + let make_concrete = vcx.mk_method( + make_concrete_ident, + decls, + &[], + vcx.alloc_slice(&[generic_predicate]), + vcx.alloc_slice(&[concrete_predicate, make_concrete_same_snap]), + None, + ); + Ok((vcx.alloc_slice(&[make_generic, make_concrete]), ())) + }) + } +} diff --git a/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs b/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs index d9b6da8f375..22267a67135 100644 --- a/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs +++ b/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs @@ -17,7 +17,8 @@ pub struct LiftedFuncAppTyParamsEnc; impl TaskEncoder for LiftedFuncAppTyParamsEnc { task_encoder::encoder_cache!(LiftedFuncAppTyParamsEnc); - type TaskDescription<'tcx> = GenericArgsRef<'tcx>; + // 1st: true iff we are monomorphizing + type TaskDescription<'tcx> = (bool, GenericArgsRef<'tcx>); type OutputFullLocal<'vir> = &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>]; @@ -42,9 +43,10 @@ impl TaskEncoder for LiftedFuncAppTyParamsEnc { > { deps.emit_output_ref::(*task_key, ()); vir::with_vcx(|vcx| { - let tys = task_key.iter().filter_map(|arg| arg.as_type()); + let (monomorphize, substs) = task_key; + let tys = substs.iter().filter_map(|arg| arg.as_type()); - let ty_args: Vec<_> = if cfg!(feature = "mono_function_encoding") { + let ty_args: Vec<_> = if *monomorphize { unique(tys.flat_map(extract_ty_params)).collect() } else { tys.collect() diff --git a/prusti-encoder/src/encoders/type/lifted/mod.rs b/prusti-encoder/src/encoders/type/lifted/mod.rs index 40ec50e9181..1577cbbccfe 100644 --- a/prusti-encoder/src/encoders/type/lifted/mod.rs +++ b/prusti-encoder/src/encoders/type/lifted/mod.rs @@ -1,6 +1,6 @@ pub mod aggregate_cast; pub mod cast; -pub mod cast_functions; +pub mod casters; pub mod func_app_ty_params; pub mod func_def_ty_params; pub mod generic; diff --git a/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs b/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs index d6743166e07..4c099910e27 100644 --- a/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs @@ -1,59 +1,108 @@ +use std::marker::PhantomData; + use prusti_rustc_interface::middle::ty; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, TaskEncoderError}; use vir::with_vcx; -use crate::encoders::most_generic_ty::extract_type_params; +use crate::encoders::most_generic_ty::{extract_type_params, MostGenericTy}; -use super::{cast::PureCast, cast_functions::{CastFunctionsEnc, CastFunctionsOutputRef}, generic::LiftedGeneric, ty::{EncodeGenericsAsLifted, LiftedTy, LiftedTyEnc}}; +use super::{ + cast::Cast, + casters::{ + CastFunctionsOutputRef, CastMethodsOutputRef, CastType, CastTypeImpure, CastTypePure, + Casters, CastersEnc, ImpureCastStmts, MakeGenericCastFunction, + }, + generic::LiftedGeneric, + ty::{EncodeGenericsAsLifted, LiftedTy, LiftedTyEnc}, +}; /// Generates Viper functions to cast between generic and non-generic Viper -/// representations of a Rust value. See [`CastFunctionsEnc`] for more details. -pub struct RustTyGenericCastEnc; +/// representations of a Rust value. See [`CastersEnc`] for more details. The +/// type parameter `T` indicates the cast type, it should be either +/// [`CastTypePure`] or [`CastTypeImpure`]. +pub struct RustTyCastersEnc(PhantomData); #[derive(Clone)] -pub struct RustTyGenericCastEncOutput<'vir> { - pub cast: CastFunctionsOutputRef<'vir>, +pub struct RustTyGenericCastEncOutput<'vir, T> { + pub cast: T, // Type arguments required by the cast function pub ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], } -impl<'vir> RustTyGenericCastEncOutput<'vir> { +impl<'vir> RustTyGenericCastEncOutput<'vir, CastFunctionsOutputRef<'vir>> { /// Returns the data to facilitate a cast from the concrete representation to /// the generic representation, if the input type wasn't already a generic. - pub fn to_generic_cast(&self) -> Option> { - self.cast - .generic_option() - .map(|f| PureCast::new(f.as_unknown_arity(), &[])) + pub fn to_generic_cast(&self) -> Option>> { + self.cast.generic_option().map(|f| Cast::new(f, &[])) + } +} + +impl<'vir> RustTyGenericCastEncOutput<'vir, CastMethodsOutputRef<'vir>> { + pub fn cast_to_concrete_if_possible<'tcx, Curr, Next>( + &self, + vcx: &'vir vir::VirCtxt<'tcx>, + snap: vir::ExprGen<'vir, Curr, Next>, + ) -> Option> { + CastTypeImpure::cast_to_concrete_if_possible(&self.cast, vcx, snap, self.ty_args) } +} - /// See [`CastFunctionsOutputRef::cast_to_concrete_if_possible`]. +impl<'vir> RustTyGenericCastEncOutput<'vir, CastFunctionsOutputRef<'vir>> { pub fn cast_to_concrete_if_possible<'tcx, Curr, Next>( &self, vcx: &'vir vir::VirCtxt<'tcx>, snap: vir::ExprGen<'vir, Curr, Next>, ) -> vir::ExprGen<'vir, Curr, Next> { - self.cast - .cast_to_concrete_if_possible(vcx, snap, self.ty_args) + CastTypePure::cast_to_concrete_if_possible(&self.cast, vcx, snap, self.ty_args) } - /// See `GenericCastOutputRef::cast_to_generic_if_necessary`. pub fn cast_to_generic_if_necessary<'tcx, Curr, Next>( &self, vcx: &'vir vir::VirCtxt<'tcx>, snap: vir::ExprGen<'vir, Curr, Next>, ) -> vir::ExprGen<'vir, Curr, Next> { - self.cast.cast_to_generic_if_necessary(vcx, snap) + CastTypePure::cast_to_generic_if_necessary(&self.cast, vcx, snap) } } -impl<'vir> task_encoder::OutputRefAny for RustTyGenericCastEncOutput<'vir> {} +impl<'vir, T> task_encoder::OutputRefAny for RustTyGenericCastEncOutput<'vir, T> {} -impl TaskEncoder for RustTyGenericCastEnc { - task_encoder::encoder_cache!(RustTyGenericCastEnc); +impl RustTyCastersEnc +where + CastersEnc: for<'vir, 'tcx> TaskEncoder< + TaskDescription<'tcx> = MostGenericTy<'tcx>, + OutputRef<'vir> = Casters<'vir, T>, + >, + TaskEncoderError>: Sized, +{ + fn encode<'tcx: 'vir, 'vir>( + task_key: &ty::Ty<'tcx>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir>, + ) -> RustTyGenericCastEncOutput<'vir, Casters<'vir, T>> { + with_vcx(|vcx| { + let (generic_ty, args) = extract_type_params(vcx.tcx(), *task_key); + let cast = deps.require_ref::>(generic_ty).unwrap(); + let ty_args = args + .iter() + .map(|a| { + deps.require_local::>(*a) + .unwrap() + }) + .collect::>(); + RustTyGenericCastEncOutput { + cast, + ty_args: vcx.alloc_slice(&ty_args), + } + }) + } +} + +impl TaskEncoder for RustTyCastersEnc { + task_encoder::encoder_cache!(RustTyCastersEnc); type TaskDescription<'vir> = ty::Ty<'vir>; - type OutputFullLocal<'vir> = RustTyGenericCastEncOutput<'vir>; + type OutputFullLocal<'vir> = RustTyGenericCastEncOutput<'vir, CastFunctionsOutputRef<'vir>>; type TaskKey<'tcx> = Self::TaskDescription<'tcx>; @@ -76,24 +125,40 @@ impl TaskEncoder for RustTyGenericCastEnc { Option>, ), > { - with_vcx(|vcx| { - deps.emit_output_ref::(*task_key, ()); - let (generic_ty, args) = extract_type_params(vcx.tcx(), *task_key); - let cast = deps.require_ref::(generic_ty).unwrap(); - let ty_args = args - .iter() - .map(|a| { - deps.require_local::>(*a) - .unwrap() - }) - .collect::>(); - Ok(( - RustTyGenericCastEncOutput { - cast, - ty_args: vcx.alloc_slice(&ty_args), - }, - (), - )) - }) + deps.emit_output_ref::(*task_key, ()); + Ok((Self::encode(task_key, deps), ())) + } +} + +impl TaskEncoder for RustTyCastersEnc { + task_encoder::encoder_cache!(RustTyCastersEnc); + + type TaskDescription<'vir> = ty::Ty<'vir>; + + type OutputFullLocal<'vir> = RustTyGenericCastEncOutput<'vir, CastMethodsOutputRef<'vir>>; + + type TaskKey<'tcx> = Self::TaskDescription<'tcx>; + + type EncodingError = (); + + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { + *task + } + + fn do_encode_full<'tcx: 'vir, 'vir>( + task_key: &Self::TaskKey<'tcx>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir>, + ) -> Result< + ( + Self::OutputFullLocal<'vir>, + Self::OutputFullDependency<'vir>, + ), + ( + Self::EncodingError, + Option>, + ), + > { + deps.emit_output_ref::(*task_key, ()); + Ok((Self::encode(task_key, deps), ())) } } diff --git a/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs b/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs index c05b477c1cf..e47254e421f 100644 --- a/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs +++ b/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs @@ -20,6 +20,12 @@ pub struct TyConstructorEncOutputRef<'vir> { pub ty_param_accessors: &'vir [vir::FunctionIdent<'vir, UnaryArity<'vir>>], } +impl <'vir> TyConstructorEncOutputRef<'vir> { + pub fn arity(&self) -> UnknownArity<'vir> { + *self.ty_constructor.arity() + } +} + impl<'vir> OutputRefAny for TyConstructorEncOutputRef<'vir> {} #[derive(Clone)] @@ -41,8 +47,6 @@ impl TaskEncoder for TyConstructorEnc { type OutputFullLocal<'vir> = TyConstructorEncOutput<'vir>; - type OutputFullDependency<'vir> = (); - type EncodingError = (); fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { diff --git a/prusti-encoder/src/encoders/type/most_generic_ty.rs b/prusti-encoder/src/encoders/type/most_generic_ty.rs index b6102a5e38d..498b23e2267 100644 --- a/prusti-encoder/src/encoders/type/most_generic_ty.rs +++ b/prusti-encoder/src/encoders/type/most_generic_ty.rs @@ -12,21 +12,24 @@ use vir::{DomainParamData, NullaryArityAny}; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct MostGenericTy<'tcx>(ty::Ty<'tcx>); -impl <'tcx: 'vir, 'vir> MostGenericTy<'tcx> { - pub fn get_vir_domain_ident(&self, vcx: &'vir vir::VirCtxt<'tcx>) -> - vir::DomainIdent<'vir, NullaryArityAny<'vir, DomainParamData<'vir>>> { +impl<'tcx: 'vir, 'vir> MostGenericTy<'tcx> { + pub fn get_vir_domain_ident( + &self, + vcx: &'vir vir::VirCtxt<'tcx>, + ) -> vir::DomainIdent<'vir, NullaryArityAny<'vir, DomainParamData<'vir>>> { let base_name = self.get_vir_base_name(vcx); vir::DomainIdent::nullary(vir::vir_format_identifier!(vcx, "s_{base_name}")) } } impl<'tcx> MostGenericTy<'tcx> { - pub fn get_vir_base_name(&self, vcx: &vir::VirCtxt<'tcx>) -> String { match self.kind() { TyKind::Bool => String::from("Bool"), + TyKind::Char => String::from("Char"), TyKind::Int(kind) => format!("Int_{}", kind.name_str()), TyKind::Uint(kind) => format!("UInt_{}", kind.name_str()), + TyKind::Float(kind) => format!("Float_{}", kind.name_str()), TyKind::Str => String::from("String"), TyKind::Adt(adt, _) => vcx.tcx().item_name(adt.did()).to_ident_string(), TyKind::Tuple(params) => format!("{}_Tuple", params.len()), @@ -43,7 +46,6 @@ impl<'tcx> MostGenericTy<'tcx> { } } - pub fn is_generic(&self) -> bool { matches!(self.kind(), TyKind::Param(_)) } @@ -82,12 +84,13 @@ impl<'tcx> MostGenericTy<'tcx> { TyKind::Array(orig, _) => vec![as_param_ty(*orig)], TyKind::Slice(orig) => vec![as_param_ty(*orig)], TyKind::Ref(_, orig, _) => vec![as_param_ty(*orig)], - TyKind::Bool => Vec::new(), - TyKind::Int(_) => Vec::new(), - TyKind::Uint(_) => Vec::new(), - TyKind::Str => Vec::new(), - TyKind::Param(_) => Vec::new(), - TyKind::Never => Vec::new(), + TyKind::Bool + | TyKind::Char + | TyKind::Float(_) + | TyKind::Int(_) + | TyKind::Never + | TyKind::Param(_) + | TyKind::Uint(_) => Vec::new(), other => todo!("generics for {:?}", other), } } @@ -146,7 +149,7 @@ pub fn extract_type_params<'tcx>( (MostGenericTy(ty), vec![orig]) } TyKind::Param(_) => (MostGenericTy(to_placeholder(tcx, None)), Vec::new()), - TyKind::Bool | TyKind::Int(_) | TyKind::Uint(_) | TyKind::Str | TyKind::Never => { + TyKind::Bool | TyKind::Char | TyKind::Int(_) | TyKind::Uint(_) | TyKind::Float(_) | TyKind::Never | TyKind::Str => { (MostGenericTy(ty), Vec::new()) } _ => todo!("extract_type_params for {:?}", ty), diff --git a/prusti-encoder/src/encoders/type/predicate.rs b/prusti-encoder/src/encoders/type/predicate.rs index 2c2b4034eef..e8eefe5644b 100644 --- a/prusti-encoder/src/encoders/type/predicate.rs +++ b/prusti-encoder/src/encoders/type/predicate.rs @@ -80,6 +80,11 @@ pub struct PredicateEncOutputRef<'vir> { impl<'vir> task_encoder::OutputRefAny for PredicateEncOutputRef<'vir> {} impl<'vir> PredicateEncOutputRef<'vir> { + + /// Constructs arguments for [`PredicateEncOutputRef::ref_to_pred`] and + /// [`PredicateEncOutputRef::ref_to_snap`]. Takes as input a Ref representing + /// the self, and the encoded Rust type (see [`LiftedTy`]). The arguments to the + /// function are the type arguments of the lifted type. pub fn ref_to_args<'tcx>( &self, vcx: &'vir vir::VirCtxt<'tcx>, @@ -603,10 +608,9 @@ impl<'vir, 'tcx> PredicateEncValues<'vir, 'tcx> { .enumerate() .map(|(idx, f_ty)| { let self_field = field_fns[idx].apply(self.vcx, [self.self_ex]); - let args = f_ty.ref_to_args(self.vcx, self_field); FieldApp { - self_field_pred: f_ty.ref_to_pred(self.vcx, args, None), - self_field_snap: f_ty.ref_to_snap(self.vcx, args), + self_field_pred: f_ty.ref_to_pred(self.vcx, self_field, None), + self_field_snap: f_ty.ref_to_snap(self.vcx, self_field), } }) .collect() diff --git a/prusti-encoder/src/encoders/type/rust_ty_predicates.rs b/prusti-encoder/src/encoders/type/rust_ty_predicates.rs index 239e98a9120..d83831719ac 100644 --- a/prusti-encoder/src/encoders/type/rust_ty_predicates.rs +++ b/prusti-encoder/src/encoders/type/rust_ty_predicates.rs @@ -44,27 +44,27 @@ impl<'vir> RustTyPredicatesEncOutputRef<'vir> { pub fn ref_to_pred<'tcx>( &self, vcx: &'vir vir::VirCtxt<'tcx>, - args: &[vir::Expr<'vir>], + self_ref: vir::Expr<'vir>, perm: Option>, ) -> vir::Expr<'vir> { - vcx.mk_predicate_app_expr(self.generic_predicate.ref_to_pred.apply(vcx, args, perm)) + vcx.mk_predicate_app_expr(self.ref_to_pred_app(vcx, self_ref, perm)) } pub fn ref_to_pred_app<'tcx>( &self, vcx: &'vir vir::VirCtxt<'tcx>, - args: &[vir::Expr<'vir>], + self_ref: vir::Expr<'vir>, perm: Option>, ) -> vir::PredicateApp<'vir> { - self.generic_predicate.ref_to_pred.apply(vcx, args, perm) + self.generic_predicate.ref_to_pred.apply(vcx, self.ref_to_args(vcx, self_ref), perm) } pub fn ref_to_snap<'tcx>( &self, vcx: &'vir vir::VirCtxt<'tcx>, - args: &[vir::Expr<'vir>], + self_ref: vir::Expr<'vir>, ) -> vir::Expr<'vir> { - let expr = self.generic_predicate.ref_to_snap.apply(vcx, args); + let expr = self.generic_predicate.ref_to_snap.apply(vcx, self.ref_to_args(vcx, self_ref)); assert!(expr.ty() == self.snapshot()); expr } diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index 2a4e5dd7304..010c33c1b54 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -24,7 +24,10 @@ use prusti_utils::config; use task_encoder::TaskEncoder; use prusti_server::ide::encoding_info::SpanOfCallContracts; -use crate::encoders::lifted::ty_constructor::TyConstructorEnc; +use crate::encoders::{lifted::{ + casters::{CastTypeImpure, CastTypePure, CastersEnc}, + ty_constructor::TyConstructorEnc +}, MirPolyImpureEnc}; pub fn test_entrypoint<'tcx>( tcx: ty::TyCtxt<'tcx>, @@ -104,8 +107,7 @@ pub fn test_entrypoint<'tcx>( }).unwrap_or_default(); if procedures.map_or(true, |procs| procs.contains(&def_id)) && !(is_trusted && is_pure) { - let substs = ty::GenericArgs::identity_for_item(tcx, def_id); - let res = crate::encoders::MirImpureEnc::encode((def_id, substs, None)); + let res = MirPolyImpureEnc::encode(def_id); assert!(res.is_ok()); } } @@ -128,8 +130,16 @@ pub fn test_entrypoint<'tcx>( let mut program_functions = vec![]; let mut program_methods = vec![]; + // We output results from both monomorphic and polymorphic encoding of + // functions, because even when Prusti is configured to use the monomorphic + // it will still use `MirPolyImpureEnc` directly sometimes (see usages + // earlier in this file). header(&mut viper_code, "methods"); - for output in crate::encoders::MirImpureEnc::all_outputs() { + for output in crate::encoders::MirMonoImpureEnc::all_outputs() { + viper_code.push_str(&format!("{:?}\n", output.method)); + program_methods.push(output.method); + } + for output in crate::encoders::MirPolyImpureEnc::all_outputs() { viper_code.push_str(&format!("{:?}\n", output.method)); program_methods.push(output.method); } @@ -154,14 +164,22 @@ pub fn test_entrypoint<'tcx>( program_domains.push(output.param_snapshot); } - header(&mut viper_code, "generic casts"); - for output in crate::encoders::lifted::cast_functions::CastFunctionsEnc::all_outputs() { - for cast_function in output { + header(&mut viper_code, "pure generic casts"); + for cast_functions in CastersEnc::::all_outputs() { + for cast_function in cast_functions { viper_code.push_str(&format!("{:?}\n", cast_function)); program_functions.push(cast_function); } } + header(&mut viper_code, "impure generic casts"); + for cast_methods in CastersEnc:::: all_outputs() { + for cast_method in cast_methods { + viper_code.push_str(&format!("{:?}\n", cast_method)); + program_methods.push(cast_method); + } + } + header(&mut viper_code, "snapshots"); for output in crate::encoders::DomainEnc_all_outputs() { viper_code.push_str(&format!("{:?}\n", output)); diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index 05d2811eaac..56a1c08c096 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -196,7 +196,7 @@ impl<'tcx> EnvBody<'tcx> { /// Get the MIR body of a local impure function, monomorphised /// with the given type substitutions. pub fn get_impure_fn_body(&self, def_id: LocalDefId, substs: GenericArgsRef<'tcx>, caller_def_id: Option) -> MirBody<'tcx> { - if let Some(body) = self.get_monomorphised(def_id.to_def_id(), substs, None) { + if let Some(body) = self.get_monomorphised(def_id.to_def_id(), substs, caller_def_id) { return body; } let body = self.get_impure_fn_body_identity(def_id); diff --git a/prusti-interface/src/environment/mir_storage.rs b/prusti-interface/src/environment/mir_storage.rs index 39b2897e0fc..728474e9377 100644 --- a/prusti-interface/src/environment/mir_storage.rs +++ b/prusti-interface/src/environment/mir_storage.rs @@ -50,7 +50,9 @@ pub(crate) unsafe fn retrieve_mir_body<'tcx>( ) -> BodyWithBorrowckFacts<'tcx> { let body_with_facts: BodyWithBorrowckFacts<'static> = SHARED_STATE_WITH_FACTS.with(|state| { let mut map = state.borrow_mut(); - map.remove(&def_id).unwrap() + map.remove(&def_id).unwrap_or_else(|| { + panic!("No MIR body found for {:?}", def_id); + }) }); // SAFETY: See the module level comment. unsafe { std::mem::transmute(body_with_facts) } diff --git a/vir/src/callable_idents.rs b/vir/src/callable_idents.rs index eb51a30d3de..6c9b2c3caa9 100644 --- a/vir/src/callable_idents.rs +++ b/vir/src/callable_idents.rs @@ -2,6 +2,7 @@ use crate::{ debug_info::DebugInfo, viper_ident::ViperIdent, with_vcx, DomainParamData, ExprGen, LocalDecl, MethodCallGenData, PredicateAppGen, PredicateAppGenData, StmtGenData, Type, TypeData, VirCtxt }; use sealed::sealed; +use std::{backtrace::Backtrace, fmt::Debug}; pub trait CallableIdent<'vir, A: Arity<'vir>, ResultTy> { fn new(name: ViperIdent<'vir>, args: A, result_ty: ResultTy) -> Self; @@ -270,14 +271,50 @@ impl<'vir, const N: usize> FunctionIdent<'vir, KnownArity<'vir, N>> { vcx: &'vir VirCtxt<'tcx>, args: [ExprGen<'vir, Curr, Next>; N], ) -> ExprGen<'vir, Curr, Next> { - if self.1.types_match(&args) { - vcx.mk_func_app(self.name().to_str(), &args, self.result_ty()) + self.check_and_apply(vcx, &args) + } +} + +impl<'vir, A: Arity<'vir, Arg = Type<'vir>> + Debug> FunctionIdent<'vir, A> { + fn check_and_apply<'tcx, Curr: 'vir, Next: 'vir>( + &self, + vcx: &'vir VirCtxt<'tcx>, + args: &[ExprGen<'vir, Curr, Next>], + ) -> ExprGen<'vir, Curr, Next> { + if self.1.types_match(args) { + vcx.mk_func_app(self.name().to_str(), args, self.result_ty()) + } else { + panic!( + "Function {} could not be applied. Expected: {:?}, Actual Exprs: {:?}, Actual Types: {:?}, debug info: {}", + self.name(), + self.arity(), + args, + args.iter().map(|a| a.ty()).collect::>(), + self.debug_info() + ); + } + } +} + +impl<'vir, A: Arity<'vir, Arg = Type<'vir>> + Debug> PredicateIdent<'vir, A> { + fn check_and_apply<'tcx, Curr: 'vir, Next: 'vir>( + &self, + vcx: &'vir VirCtxt<'tcx>, + args: &[ExprGen<'vir, Curr, Next>], + perm: Option>, + ) -> PredicateAppGen<'vir, Curr, Next> { + if self.1.types_match(args) { + vcx.alloc(PredicateAppGenData { + target: self.name().to_str(), + args: vcx.alloc_slice(args), + perm, + }) } else { panic!( - "Function {} could not be applied. Expected: {:?}, Actual: {:?}, debug info: {}", + "Predicate {} could not be applied. Expected arg types: {:?}, Actual arg types: {:?}, Debug info: {}", self.name(), self.arity(), - args.map(|a| a.ty()), + args.iter().map(|a| a.ty()).collect::>(), self.debug_info() ); } @@ -291,12 +328,7 @@ impl<'vir, const N: usize> PredicateIdent<'vir, KnownArity<'vir, N>> { args: [ExprGen<'vir, Curr, Next>; N], perm: Option>, ) -> PredicateAppGen<'vir, Curr, Next> { - assert!(self.1.types_match(&args)); - vcx.alloc(PredicateAppGenData { - target: self.name().to_str(), - args: vcx.alloc_slice(&args), - perm, - }) + self.check_and_apply(vcx, &args, perm) } } impl<'vir, const N: usize> MethodIdent<'vir, KnownArity<'vir, N>> { @@ -328,18 +360,7 @@ impl<'vir> FunctionIdent<'vir, UnknownArity<'vir>> { vcx: &'vir VirCtxt<'tcx>, args: &[ExprGen<'vir, Curr, Next>], ) -> ExprGen<'vir, Curr, Next> { - if self.1.types_match(args) { - vcx.mk_func_app(self.name().to_str(), args, self.result_ty()) - } else { - panic!( - "Function {} could not be applied. Expected: {:?}, Actual Exprs: {:?}, Actual Types: {:?}, debug info: {}", - self.name(), - self.arity(), - args, - args.iter().map(|a| a.ty()).collect::>(), - self.debug_info() - ); - } + self.check_and_apply(vcx, args) } } @@ -350,21 +371,7 @@ impl<'vir> PredicateIdent<'vir, UnknownArity<'vir>> { args: &[ExprGen<'vir, Curr, Next>], perm: Option>, ) -> PredicateAppGen<'vir, Curr, Next> { - if self.1.types_match(args) { - vcx.alloc(PredicateAppGenData { - target: self.name().to_str(), - args: vcx.alloc_slice(args), - perm, - }) - } else { - panic!( - "Predicate {} could not be applied. Expected arg types: {:?}, Actual arg types: {:?}, Debug info: {}", - self.name(), - self.arity(), - args.iter().map(|a| a.ty()).collect::>(), - self.debug_info() - ); - } + self.check_and_apply(vcx, args, perm) } } impl<'vir> MethodIdent<'vir, UnknownArity<'vir>> { diff --git a/vir/src/make.rs b/vir/src/make.rs index 0dbedcb7527..698ae99376f 100644 --- a/vir/src/make.rs +++ b/vir/src/make.rs @@ -640,7 +640,7 @@ impl<'tcx> VirCtxt<'tcx> { }) } - pub fn mk_method<'vir, Curr, Next, A: Arity<'vir> + CheckTypes<'vir>>( + pub fn mk_method<'vir, Curr, Next, A: Arity<'vir> + CheckTypes<'vir> + Debug>( &'vir self, ident: MethodIdent<'vir, A>, args: &'vir [LocalDecl<'vir>], @@ -649,7 +649,11 @@ impl<'tcx> VirCtxt<'tcx> { posts: &'vir [ExprGen<'vir, Curr, Next>], blocks: Option<&'vir [CfgBlockGen<'vir, Curr, Next>]>, // first one is the entrypoint ) -> MethodGen<'vir, Curr, Next> { - assert!(ident.arity().types_match(args)); + assert!(ident.arity().types_match(args), + "Method {} could not be created. Identifier arity: {:?}, Method decls: {args:?}", + ident.name_str(), + ident.arity() + ); self.mk_method_unchecked( ident.name().to_str(), args, diff --git a/vir/src/viper_ident.rs b/vir/src/viper_ident.rs index 342470fda6b..60b888bb7fe 100644 --- a/vir/src/viper_ident.rs +++ b/vir/src/viper_ident.rs @@ -37,6 +37,7 @@ fn sanitize_char(c: char) -> Option { '>' => Some("$gt$".to_string()), ' ' => Some("$space$".to_string()), ',' => Some("$comma$".to_string()), + ':' => Some("$colon$".to_string()), _ => None, } } From ca5edb3608127cb40b1786505bb9236392505320 Mon Sep 17 00:00:00 2001 From: Aurel Date: Tue, 14 May 2024 17:36:40 +0200 Subject: [PATCH 16/53] Support cycles in encoders (#47) * domain fields don't need a full encoding of their type yet * type alias for do_encode_full result * parameterise TaskEncoderDependencies by the owning encoder * remove some dependency unwraps * remove 'tcx lifetime, use 'vir * check for cycles when requesting dependencies or emitting output ref * add try operators for some emit output refs --- .../src/encoder_traits/function_enc.rs | 16 +- .../src/encoder_traits/impure_function_enc.rs | 34 +-- .../src/encoder_traits/pure_func_app_enc.rs | 30 +- .../src/encoder_traits/pure_function_enc.rs | 30 +- prusti-encoder/src/encoders/const.rs | 34 +-- prusti-encoder/src/encoders/generic.rs | 25 +- prusti-encoder/src/encoders/local_def.rs | 29 +- prusti-encoder/src/encoders/mir_builtin.rs | 59 ++-- prusti-encoder/src/encoders/mir_impure.rs | 93 +++--- .../src/encoders/mir_poly_impure.rs | 22 +- prusti-encoder/src/encoders/mir_pure.rs | 79 +++-- .../src/encoders/mir_pure_function.rs | 19 +- .../src/encoders/mono/mir_impure.rs | 22 +- .../src/encoders/mono/mir_pure_function.rs | 19 +- prusti-encoder/src/encoders/pure/spec.rs | 30 +- prusti-encoder/src/encoders/spec.rs | 17 +- prusti-encoder/src/encoders/type/domain.rs | 108 ++++--- .../encoders/type/lifted/aggregate_cast.rs | 21 +- .../src/encoders/type/lifted/cast.rs | 51 ++-- .../encoders/type/lifted/cast_functions.rs | 32 +- .../src/encoders/type/lifted/casters.rs | 44 +-- .../type/lifted/func_app_ty_params.rs | 21 +- .../type/lifted/func_def_ty_params.rs | 21 +- .../src/encoders/type/lifted/generic.rs | 25 +- .../src/encoders/type/lifted/rust_ty_cast.rs | 51 ++-- prusti-encoder/src/encoders/type/lifted/ty.rs | 46 +-- .../encoders/type/lifted/ty_constructor.rs | 25 +- prusti-encoder/src/encoders/type/predicate.rs | 44 ++- .../src/encoders/type/rust_ty_predicates.rs | 30 +- .../src/encoders/type/rust_ty_snapshots.rs | 30 +- prusti-encoder/src/encoders/type/snapshot.rs | 27 +- .../src/encoders/type/viper_tuple.rs | 19 +- prusti-encoder/src/lib.rs | 2 +- task-encoder/src/lib.rs | 277 ++++++++++++------ 34 files changed, 634 insertions(+), 798 deletions(-) diff --git a/prusti-encoder/src/encoder_traits/function_enc.rs b/prusti-encoder/src/encoder_traits/function_enc.rs index 1fedfbf363f..1ae34f70b09 100644 --- a/prusti-encoder/src/encoder_traits/function_enc.rs +++ b/prusti-encoder/src/encoder_traits/function_enc.rs @@ -19,10 +19,10 @@ pub trait FunctionEnc /// this should be the identity substitution obtained from the DefId of the /// function. For the monomorphic encoding, the substitutions at the call /// site should be used. - fn get_substs<'tcx>( - vcx: &vir::VirCtxt<'tcx>, - substs_src: &Self::TaskKey<'tcx>, - ) -> &'tcx GenericArgs<'tcx>; + fn get_substs<'vir>( + vcx: &vir::VirCtxt<'vir>, + substs_src: &Self::TaskKey<'vir>, + ) -> &'vir GenericArgs<'vir>; } /// Implementation for polymorphic encoding @@ -35,10 +35,10 @@ impl TaskEncoder = DefId>> FunctionEnc for None } - fn get_substs<'tcx>( - vcx: &vir::VirCtxt<'tcx>, - def_id: &Self::TaskKey<'tcx>, - ) -> &'tcx GenericArgs<'tcx> { + fn get_substs<'vir>( + vcx: &vir::VirCtxt<'vir>, + def_id: &Self::TaskKey<'vir>, + ) -> &'vir GenericArgs<'vir> { GenericArgs::identity_for_item(vcx.tcx(), *def_id) } diff --git a/prusti-encoder/src/encoder_traits/impure_function_enc.rs b/prusti-encoder/src/encoder_traits/impure_function_enc.rs index 5a355143646..0eff53dce5c 100644 --- a/prusti-encoder/src/encoder_traits/impure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/impure_function_enc.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::middle::mir; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{EncodeFullError, TaskEncoder, TaskEncoderDependencies}; use vir::{MethodIdent, UnknownArity, ViperIdent}; use crate::encoders::{ @@ -33,15 +33,18 @@ where { /// Generates the identifier for the method; for a monomorphic encoding, /// this should be a name including (mangled) type arguments - fn mk_method_ident<'vir, 'tcx>( - vcx: &'vir vir::VirCtxt<'tcx>, - task_key: &Self::TaskKey<'tcx>, + fn mk_method_ident<'vir>( + vcx: &'vir vir::VirCtxt<'vir>, + task_key: &Self::TaskKey<'vir>, ) -> ViperIdent<'vir>; - fn encode<'vir, 'tcx: 'vir>( - task_key: Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> ImpureFunctionEncOutput<'vir> { + fn encode<'vir>( + task_key: Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> Result< + ImpureFunctionEncOutput<'vir>, + EncodeFullError<'vir, Self>, + > { let def_id = Self::get_def_id(&task_key); let caller_def_id = Self::get_caller_def_id(&task_key); let trusted = crate::encoders::with_proc_spec(def_id, |def_spec| { @@ -52,8 +55,7 @@ where use mir::visit::Visitor; let substs = Self::get_substs(vcx, &task_key); let local_defs = deps - .require_local::((def_id, substs, caller_def_id)) - .unwrap(); + .require_local::((def_id, substs, caller_def_id))?; // Argument count for the Viper method: // - one (`Ref`) for the return place; @@ -70,15 +72,14 @@ where let method_name = Self::mk_method_ident(vcx, &task_key); let mut args = vec![&vir::TypeData::Ref; arg_count]; let param_ty_decls = deps - .require_local::(substs) - .unwrap() + .require_local::(substs)? .iter() .map(|g| g.decl()) .collect::>(); args.extend(param_ty_decls.iter().map(|decl| decl.ty)); let args = UnknownArity::new(vcx.alloc_slice(&args)); let method_ref = MethodIdent::new(method_name, args); - deps.emit_output_ref::(task_key, ImpureFunctionEncOutputRef { method_ref }); + deps.emit_output_ref(task_key, ImpureFunctionEncOutputRef { method_ref })?; // Do not encode the method body if it is external, trusted or just // a call stub. @@ -157,8 +158,7 @@ where }; let spec = deps - .require_local::((def_id, substs, None, false)) - .unwrap(); + .require_local::((def_id, substs, None, false))?; let (spec_pres, spec_posts) = (spec.pres, spec.posts); let mut pres = Vec::with_capacity(arg_count - 1); @@ -177,7 +177,7 @@ where posts.push(local_defs.locals[mir::RETURN_PLACE].impure_pred); posts.extend(spec_posts); - ImpureFunctionEncOutput { + Ok(ImpureFunctionEncOutput { method: vcx.mk_method( method_ref, vcx.alloc_slice(&args), @@ -186,7 +186,7 @@ where vcx.alloc_slice(&posts), blocks, ), - } + }) }) } } diff --git a/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs b/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs index 04202f73937..739eda044b0 100644 --- a/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_func_app_enc.rs @@ -5,7 +5,7 @@ use prusti_rustc_interface::{ }, span::def_id::DefId, }; -use task_encoder::TaskEncoderDependencies; +use task_encoder::{TaskEncoder, TaskEncoderDependencies}; use crate::encoders::{ lifted::{ @@ -16,7 +16,7 @@ use crate::encoders::{ /// Encoders (such as [`crate::encoders::MirPureEnc`], /// [`crate::encoders::MirImpureEnc`]) implement this trait to encode /// applications of Rust functions annotated as pure. -pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { +pub trait PureFuncAppEnc<'vir, E: TaskEncoder + 'vir + ?Sized> { /// Extra arguments required for the encoder to encode an argument to the /// function (in mir this is an `Operand`) type EncodeOperandArgs; @@ -29,32 +29,32 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { /// The type of the data source that can provide local declarations; this is used /// when getting the type of the function. - type LocalDeclsSrc: ?Sized + HasLocalDecls<'tcx>; + type LocalDeclsSrc: ?Sized + HasLocalDecls<'vir>; // Are we monomorphizing functions? fn monomorphize(&self) -> bool; /// Task encoder dependencies are required for encoding Viper casts between /// generic and concrete types. - fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir>; + fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir, E>; /// The data source that can provide local declarations, necesary for determining /// the function type fn local_decls_src(&self) -> &Self::LocalDeclsSrc; - fn vcx(&self) -> &'vir vir::VirCtxt<'tcx>; + fn vcx(&self) -> &'vir vir::VirCtxt<'vir>; /// Encodes an operand (an argument to a function) as a pure Viper expression. fn encode_operand( &mut self, args: &Self::EncodeOperandArgs, - operand: &mir::Operand<'tcx>, + operand: &mir::Operand<'vir>, ) -> vir::ExprGen<'vir, Self::Curr, Self::Next>; /// Obtains the function's definition ID and the substitutions made at the callsite fn get_def_id_and_caller_substs( &self, - func: &mir::Operand<'tcx>, - ) -> (DefId, &'tcx List>) { + func: &mir::Operand<'vir>, + ) -> (DefId, &'vir List>) { let func_ty = func.ty(self.local_decls_src(), self.vcx().tcx()); match func_ty.kind() { &ty::TyKind::FnDef(def_id, arg_tys) => (def_id, arg_tys), @@ -67,9 +67,9 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { /// are inserted to convert from/to generic and concrete arguments as necessary. fn encode_fn_args( &mut self, - sig: Binder<'tcx, FnSig<'tcx>>, - substs: &'tcx List>, - args: &[mir::Operand<'tcx>], + sig: Binder<'vir, FnSig<'vir>>, + substs: &'vir List>, + args: &[mir::Operand<'vir>], encode_operand_args: &Self::EncodeOperandArgs, ) -> Vec> { let mono = self.monomorphize(); @@ -118,10 +118,10 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { fn encode_pure_func_app( &mut self, def_id: DefId, - sig: Binder<'tcx, FnSig<'tcx>>, - substs: &'tcx List>, - args: &Vec>, - destination: &mir::Place<'tcx>, + sig: Binder<'vir, FnSig<'vir>>, + substs: &'vir List>, + args: &Vec>, + destination: &mir::Place<'vir>, caller_def_id: DefId, encode_operand_args: &Self::EncodeOperandArgs, ) -> vir::ExprGen<'vir, Self::Curr, Self::Next> { diff --git a/prusti-encoder/src/encoder_traits/pure_function_enc.rs b/prusti-encoder/src/encoder_traits/pure_function_enc.rs index 11c8cffb082..6a7269653a7 100644 --- a/prusti-encoder/src/encoder_traits/pure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_function_enc.rs @@ -1,6 +1,7 @@ -use prusti_rustc_interface:: - middle::{mir, ty::Ty} -; +use prusti_rustc_interface::{ + middle::{mir, ty::{GenericArgs, Ty}}, + span::def_id::DefId, +}; use task_encoder::{TaskEncoder, TaskEncoderDependencies}; use vir::{CallableIdent, ExprGen, FunctionIdent, Reify, UnknownArity, ViperIdent}; @@ -33,21 +34,20 @@ where /// Generates the identifier for the function; for a monomorphic encoding, /// this should be a name including (mangled) type arguments - fn mk_function_ident<'vir, 'tcx>( - vcx: &'vir vir::VirCtxt<'tcx>, - task_key: &Self::TaskKey<'tcx>, + fn mk_function_ident<'vir>( + vcx: &'vir vir::VirCtxt<'vir>, + task_key: &Self::TaskKey<'vir>, ) -> ViperIdent<'vir>; - /// Adds an assertion connecting the type of an argument (or return) of the /// function with the appropriate type based on the param, e.g. in f(u: U) -> T, this would be called to require that the type of `u` be /// `U` - fn mk_type_assertion<'vir, 'tcx: 'vir, Curr, Next>( - vcx: &'vir vir::VirCtxt<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, + fn mk_type_assertion<'vir, Curr, Next>( + vcx: &'vir vir::VirCtxt<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, arg: ExprGen<'vir, Curr, Next>, // Snapshot encoded argument - ty: Ty<'tcx>, + ty: Ty<'vir>, ) -> Option> { let lifted_ty = deps .require_local::>(ty) @@ -77,9 +77,9 @@ where } } - fn encode<'vir, 'tcx: 'vir>( - task_key: Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, + fn encode<'vir>( + task_key: Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, ) -> MirFunctionEncOutput<'vir> { let def_id = Self::get_def_id(&task_key); let caller_def_id = Self::get_caller_def_id(&task_key); @@ -106,7 +106,7 @@ where let ident_args = UnknownArity::new(vcx.alloc_slice(&ident_args)); let return_type = local_defs.locals[mir::RETURN_PLACE].ty; let function_ref = FunctionIdent::new(function_ident, ident_args, return_type.snapshot); - deps.emit_output_ref::(task_key, MirFunctionEncOutputRef { function_ref }); + deps.emit_output_ref(task_key, MirFunctionEncOutputRef { function_ref }); let spec = deps .require_local::((def_id, substs, None, true)) diff --git a/prusti-encoder/src/encoders/const.rs b/prusti-encoder/src/encoders/const.rs index 25733deb340..6043fb0ad08 100644 --- a/prusti-encoder/src/encoders/const.rs +++ b/prusti-encoder/src/encoders/const.rs @@ -6,6 +6,7 @@ use rustc_middle::mir::interpret::{ConstValue, Scalar, GlobalAlloc}; use task_encoder::{ TaskEncoder, TaskEncoderDependencies, + EncodeFullResult, }; use vir::{CallableIdent, Arity}; @@ -25,8 +26,8 @@ use super::{lifted::{casters::CastTypePure, rust_ty_cast::RustTyCastersEnc}, rus impl TaskEncoder for ConstEnc { task_encoder::encoder_cache!(ConstEnc); - type TaskDescription<'tcx> = ( - mir::ConstantKind<'tcx>, + type TaskDescription<'vir> = ( + mir::ConstantKind<'vir>, usize, // current encoding depth DefId, // DefId of the current function ); @@ -37,21 +38,15 @@ impl TaskEncoder for ConstEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; let (const_, encoding_depth, def_id) = *task_key; let res = match const_ { mir::ConstantKind::Val(val, ty) => { - let kind = deps.require_local::(ty).unwrap().generic_snapshot.specifics; + let kind = deps.require_local::(ty)?.generic_snapshot.specifics; match val { ConstValue::Scalar(Scalar::Int(int)) => { let prim = kind.expect_primitive(); @@ -84,12 +79,11 @@ impl TaskEncoder for ConstEnc { let ref_ty = kind.expect_structlike(); let str_ty = ty.peel_refs(); let str_snap = deps - .require_local::(str_ty) - .unwrap() + .require_local::(str_ty)? .generic_snapshot .specifics .expect_structlike(); - let cast = deps.require_local::>(str_ty).unwrap(); + let cast = deps.require_local::>(str_ty)?; vir::with_vcx(|vcx| { // first, we create a string snapshot let snap = str_snap.field_snaps_to_snap.apply(vcx, &[]); @@ -112,10 +106,10 @@ impl TaskEncoder for ConstEnc { kind: PureKind::Constant(uneval.promoted.unwrap()), caller_def_id: Some(def_id) }; - let expr = deps.require_local::(task).unwrap().expr; + let expr = deps.require_local::(task)?.expr; use vir::Reify; - expr.reify(vcx, (uneval.def, &[])) - }), + Ok(expr.reify(vcx, (uneval.def, &[]))) + })?, mir::ConstantKind::Ty(_) => todo!("ConstantKind::Ty"), }; Ok((res, ())) diff --git a/prusti-encoder/src/encoders/generic.rs b/prusti-encoder/src/encoders/generic.rs index 19361bb2a4a..ad562a82095 100644 --- a/prusti-encoder/src/encoders/generic.rs +++ b/prusti-encoder/src/encoders/generic.rs @@ -1,4 +1,4 @@ -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{ BinaryArity, CallableIdent, DomainIdent, DomainParamData, FunctionIdent, KnownArityAny, NullaryArity, PredicateIdent, TypeData, UnaryArity, ViperIdent, @@ -39,7 +39,7 @@ const SNAPSHOT_PARAM_DOMAIN: TypeData<'static> = TypeData::Domain("s_Param", &[] impl TaskEncoder for GenericEnc { task_encoder::encoder_cache!(GenericEnc); - type TaskDescription<'tcx> = (); // ? + type TaskDescription<'vir> = (); // ? type OutputRef<'vir> = GenericEncOutputRef<'vir>; type OutputFullLocal<'vir> = GenericEncOutput<'vir>; @@ -51,19 +51,10 @@ impl TaskEncoder for GenericEnc { } #[allow(non_snake_case)] - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { let ref_to_pred = PredicateIdent::new(ViperIdent::new("p_Param"), BinaryArity::new(&[&TypeData::Ref, &TYP_DOMAIN])); let type_domain_ident = DomainIdent::nullary(ViperIdent::new("Type")); @@ -98,10 +89,10 @@ impl TaskEncoder for GenericEnc { }; #[allow(clippy::unit_arg)] - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, output_ref - ); + )?; let typ = FunctionIdent::new( ViperIdent::new("typ"), diff --git a/prusti-encoder/src/encoders/local_def.rs b/prusti-encoder/src/encoders/local_def.rs index 08070db5b7a..de10e2379bc 100644 --- a/prusti-encoder/src/encoders/local_def.rs +++ b/prusti-encoder/src/encoders/local_def.rs @@ -4,7 +4,7 @@ use prusti_rustc_interface::{ span::def_id::DefId }; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use crate::encoders::{ rust_ty_predicates::{RustTyPredicatesEnc, RustTyPredicatesEncOutputRef}, @@ -31,9 +31,9 @@ pub struct LocalDef<'vir> { impl TaskEncoder for MirLocalDefEnc { task_encoder::encoder_cache!(MirLocalDefEnc); - type TaskDescription<'tcx> = ( + type TaskDescription<'vir> = ( DefId, // ID of the function - ty::GenericArgsRef<'tcx>, // ? this should be the "signature", after applying the env/substs + ty::GenericArgsRef<'vir>, // ? this should be the "signature", after applying the env/substs Option, // ID of the caller function, if any ); @@ -45,24 +45,15 @@ impl TaskEncoder for MirLocalDefEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { let (def_id, substs, caller_def_id) = *task_key; - deps.emit_output_ref::(*task_key, ()); + deps.emit_output_ref(*task_key, ())?; - fn mk_local_def<'vir, 'tcx>( - vcx: &'vir vir::VirCtxt<'tcx>, + fn mk_local_def<'vir>( + vcx: &'vir vir::VirCtxt<'vir>, name: &'vir str, ty: RustTyPredicatesEncOutputRef<'vir>, ) -> LocalDef<'vir> { diff --git a/prusti-encoder/src/encoders/mir_builtin.rs b/prusti-encoder/src/encoders/mir_builtin.rs index e7a87fd05d4..ef1d664939e 100644 --- a/prusti-encoder/src/encoders/mir_builtin.rs +++ b/prusti-encoder/src/encoders/mir_builtin.rs @@ -5,6 +5,7 @@ use prusti_rustc_interface::{ use task_encoder::{ TaskEncoder, TaskEncoderDependencies, + EncodeFullResult, }; use vir::{UnknownArity, FunctionIdent, CallableIdent}; @@ -51,16 +52,10 @@ impl TaskEncoder for MirBuiltinEnc { task.clone() } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { vir::with_vcx(|vcx| { match *task_key { MirBuiltinEncTask::UnOp(res_ty, op, operand_ty) => { @@ -92,12 +87,12 @@ fn int_name<'tcx>(ty: ty::Ty<'tcx>) -> &'static str { } impl MirBuiltinEnc { - fn handle_un_op<'vir, 'tcx>( - vcx: &'vir vir::VirCtxt<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - key: ::TaskKey<'tcx>, + fn handle_un_op<'vir>( + vcx: &'vir vir::VirCtxt<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + key: ::TaskKey<'vir>, op: mir::UnOp, - ty: ty::Ty<'tcx>, + ty: ty::Ty<'vir>, ) -> vir::Function<'vir> { let e_ty = deps .require_local::(ty) @@ -107,7 +102,7 @@ impl MirBuiltinEnc { let name = vir::vir_format_identifier!(vcx, "mir_unop_{op:?}_{}", int_name(ty)); let arity = UnknownArity::new(vcx.alloc_slice(&[e_ty.snapshot])); let function = FunctionIdent::new(name, arity, e_ty.snapshot); - deps.emit_output_ref::(key, MirBuiltinEncOutputRef { + deps.emit_output_ref(key, MirBuiltinEncOutputRef { function, }); @@ -140,14 +135,14 @@ impl MirBuiltinEnc { ) } - fn handle_bin_op<'vir, 'tcx>( - vcx: &'vir vir::VirCtxt<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - key: ::TaskKey<'tcx>, - res_ty: ty::Ty<'tcx>, + fn handle_bin_op<'vir>( + vcx: &'vir vir::VirCtxt<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + key: ::TaskKey<'vir>, + res_ty: ty::Ty<'vir>, op: mir::BinOp, - l_ty: ty::Ty<'tcx>, - r_ty: ty::Ty<'tcx>, + l_ty: ty::Ty<'vir>, + r_ty: ty::Ty<'vir>, ) -> vir::Function<'vir> { use mir::BinOp::*; let e_l_ty = deps @@ -169,7 +164,7 @@ impl MirBuiltinEnc { let name = vir::vir_format_identifier!(vcx, "mir_binop_{op:?}_{}_{}", int_name(l_ty), int_name(r_ty)); let arity = UnknownArity::new(vcx.alloc_slice(&[e_l_ty.snapshot, e_r_ty.snapshot])); let function = FunctionIdent::new(name, arity, e_res_ty.snapshot); - deps.emit_output_ref::(key, MirBuiltinEncOutputRef { + deps.emit_output_ref(key, MirBuiltinEncOutputRef { function, }); let lhs = prim_l_ty.snap_to_prim.apply(vcx, @@ -268,14 +263,14 @@ impl MirBuiltinEnc { ) } - fn handle_checked_bin_op<'vir, 'tcx>( - vcx: &'vir vir::VirCtxt<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - key: ::TaskKey<'tcx>, - res_ty: ty::Ty<'tcx>, + fn handle_checked_bin_op<'vir>( + vcx: &'vir vir::VirCtxt<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + key: ::TaskKey<'vir>, + res_ty: ty::Ty<'vir>, op: mir::BinOp, - l_ty: ty::Ty<'tcx>, - r_ty: ty::Ty<'tcx>, + l_ty: ty::Ty<'vir>, + r_ty: ty::Ty<'vir>, ) -> vir::Function<'vir> { // `op` can only be `Add`, `Sub` or `Mul` assert!(matches!( @@ -303,7 +298,7 @@ impl MirBuiltinEnc { .unwrap() .generic_snapshot; let function = FunctionIdent::new(name, arity, e_res_ty.snapshot); - deps.emit_output_ref::(key, MirBuiltinEncOutputRef { function }); + deps.emit_output_ref(key, MirBuiltinEncOutputRef { function }); let e_res_ty = deps .require_local::(res_ty) diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index 8a8c9d11278..dcb9fa8b796 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -13,7 +13,7 @@ use prusti_rustc_interface::{ //use mir_ssa_analysis::{ // SsaAnalysis, //}; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{MethodIdent, UnknownArity}; pub struct MirImpureEnc; @@ -74,7 +74,7 @@ impl MirImpureEnc { impl TaskEncoder for MirImpureEnc { task_encoder::encoder_cache!(MirImpureEnc); - type TaskDescription<'tcx> = FunctionCallTaskDescription<'tcx>; + type TaskDescription<'vir> = FunctionCallTaskDescription<'vir>; type OutputRef<'vir> = ImpureFunctionEncOutputRef<'vir>; type OutputFullLocal<'vir> = ImpureFunctionEncOutput<'vir>; @@ -85,52 +85,43 @@ impl TaskEncoder for MirImpureEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { let monomorphize = Self::monomorphize(); let output_ref = if monomorphize { - deps.require_ref::(*task_key).unwrap() + deps.require_ref::(*task_key)? } else { - deps.require_ref::(task_key.def_id).unwrap() + deps.require_ref::(task_key.def_id)? }; - deps.emit_output_ref::(*task_key, output_ref); + deps.emit_output_ref(*task_key, output_ref); let output: ImpureFunctionEncOutput<'_> = if monomorphize { - deps.require_local::(*task_key).unwrap() + deps.require_local::(*task_key)? } else { - deps.require_local::(task_key.def_id).unwrap() + deps.require_local::(task_key.def_id)? }; Ok((output, ())) } } -pub struct ImpureEncVisitor<'tcx, 'vir, 'enc> +pub struct ImpureEncVisitor<'vir, 'enc, E: TaskEncoder> where 'vir: 'enc { - pub vcx: &'vir vir::VirCtxt<'tcx>, + pub vcx: &'vir vir::VirCtxt<'vir>, // Are we monomorphizing functions? pub monomorphize: bool, - pub deps: &'enc mut TaskEncoderDependencies<'vir>, + pub deps: &'enc mut TaskEncoderDependencies<'vir, E>, pub def_id: DefId, - pub local_decls: &'enc mir::LocalDecls<'tcx>, + pub local_decls: &'enc mir::LocalDecls<'vir>, //ssa_analysis: SsaAnalysis, - pub fpcs_analysis: FreePcsAnalysis<'enc, 'tcx>, + pub fpcs_analysis: FreePcsAnalysis<'enc, 'vir>, pub local_defs: crate::encoders::MirLocalDefEncOutput<'vir>, pub tmp_ctr: usize, // for the current basic block - pub current_fpcs: Option>, + pub current_fpcs: Option>, pub current_stmts: Option>>, pub current_terminator: Option>, @@ -138,16 +129,16 @@ pub struct ImpureEncVisitor<'tcx, 'vir, 'enc> pub encoded_blocks: Vec>, // TODO: use IndexVec ? } -impl<'tcx: 'vir, 'vir> PureFuncAppEnc<'tcx, 'vir> for ImpureEncVisitor<'tcx, 'vir, '_> { +impl<'vir, E: TaskEncoder> PureFuncAppEnc<'vir, E> for ImpureEncVisitor<'vir, '_, E> { type EncodeOperandArgs = (); type Curr = !; type Next = !; - type LocalDeclsSrc = mir::LocalDecls<'tcx>; - fn vcx(&self) -> &'vir vir::VirCtxt<'tcx> { + type LocalDeclsSrc = mir::LocalDecls<'vir>; + fn vcx(&self) -> &'vir vir::VirCtxt<'vir> { self.vcx } - fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir> { + fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir, E> { self.deps } @@ -158,7 +149,7 @@ impl<'tcx: 'vir, 'vir> PureFuncAppEnc<'tcx, 'vir> for ImpureEncVisitor<'tcx, 'vi fn encode_operand( &mut self, _args: &Self::EncodeOperandArgs, - operand: &mir::Operand<'tcx>, + operand: &mir::Operand<'vir>, ) -> vir::ExprGen<'vir, Self::Curr, Self::Next> { self.encode_operand_snap(operand) } @@ -189,7 +180,7 @@ impl<'vir> EncodePlaceResult<'vir> { } } -impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { +impl<'vir, 'enc, E: TaskEncoder> ImpureEncVisitor<'vir, 'enc, E> { fn stmt(&mut self, stmt: vir::Stmt<'vir>) { self.current_stmts .as_mut() @@ -261,7 +252,7 @@ impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { fn fpcs_repacks( &mut self, - repacks: &[RepackOp<'tcx>], + repacks: &[RepackOp<'vir>], ) { for &repack_op in repacks { match repack_op { @@ -334,7 +325,7 @@ impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { result.undo_casts.iter().for_each(|stmt| self.stmt(stmt)); } - fn encode_operand_snap(&mut self, operand: &mir::Operand<'tcx>) -> vir::Expr<'vir> { + fn encode_operand_snap(&mut self, operand: &mir::Operand<'vir>) -> vir::Expr<'vir> { let ty = operand.ty(self.local_decls, self.vcx.tcx()); match operand { &mir::Operand::Move(source) => { @@ -368,7 +359,7 @@ impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { fn encode_operand( &mut self, - operand: &mir::Operand<'tcx>, + operand: &mir::Operand<'vir>, ) -> vir::Expr<'vir> { let ty = operand.ty(self.local_decls, self.vcx.tcx()); let (encode_place_result, ty_out) = match operand { @@ -393,7 +384,7 @@ impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { fn encode_place( &mut self, - place: Place<'tcx>, + place: Place<'vir>, ) -> EncodePlaceResult<'vir> { let mut place_ty = mir::tcx::PlaceTy::from_ty(self.local_decls[place.local].ty); let mut result = EncodePlaceResult::new(self.local_defs.locals[place.local].local_ex); @@ -414,8 +405,8 @@ impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { // it. fn encode_place_element( &mut self, - place_ty: mir::tcx::PlaceTy<'tcx>, - elem: mir::PlaceElem<'tcx>, + place_ty: mir::tcx::PlaceTy<'vir>, + elem: mir::PlaceElem<'vir>, expr: vir::Expr<'vir> ) -> (vir::Expr<'vir>, Option>) { match elem { @@ -495,14 +486,14 @@ impl<'tcx, 'vir, 'enc> ImpureEncVisitor<'tcx, 'vir, 'enc> { } } -impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir, 'enc> { +impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor<'vir, 'enc, E> { // fn visit_body(&mut self, body: &mir::Body<'tcx>) { // println!("visiting body!"); // } fn visit_basic_block_data( &mut self, block: mir::BasicBlock, - data: &mir::BasicBlockData<'tcx>, + data: &mir::BasicBlockData<'vir>, ) { self.current_fpcs = Some(self.fpcs_analysis.get_all_for_bb(block)); @@ -563,7 +554,7 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir fn visit_statement( &mut self, - statement: &mir::Statement<'tcx>, + statement: &mir::Statement<'vir>, location: mir::Location, ) { // TODO: these should not be ignored, but should havoc the local instead @@ -606,12 +597,12 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir let expr = match rvalue { mir::Rvalue::Use(op) => self.encode_operand_snap(op), - //mir::Rvalue::Repeat(Operand<'tcx>, Const<'tcx>) => {} - //mir::Rvalue::Ref(Region<'tcx>, BorrowKind, Place<'tcx>) => {} + //mir::Rvalue::Repeat(Operand<'vir>, Const<'vir>) => {} + //mir::Rvalue::Ref(Region<'vir>, BorrowKind, Place<'vir>) => {} //mir::Rvalue::ThreadLocalRef(DefId) => {} - //mir::Rvalue::AddressOf(Mutability, Place<'tcx>) => {} - //mir::Rvalue::Len(Place<'tcx>) => {} - //mir::Rvalue::Cast(CastKind, Operand<'tcx>, Ty<'tcx>) => {} + //mir::Rvalue::AddressOf(Mutability, Place<'vir>) => {} + //mir::Rvalue::Len(Place<'vir>) => {} + //mir::Rvalue::Cast(CastKind, Operand<'vir>, Ty<'vir>) => {} rv@mir::Rvalue::BinaryOp(op, box (l, r)) | rv@mir::Rvalue::CheckedBinaryOp(op, box (l, r)) => { @@ -632,7 +623,7 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir ]) } - //mir::Rvalue::NullaryOp(NullOp, Ty<'tcx>) => {} + //mir::Rvalue::NullaryOp(NullOp, Ty<'vir>) => {} mir::Rvalue::UnaryOp(unop, operand) => { let operand_ty = operand.ty(self.local_decls, self.vcx.tcx()); @@ -703,9 +694,9 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir } } - //mir::Rvalue::Discriminant(Place<'tcx>) => {} - //mir::Rvalue::ShallowInitBox(Operand<'tcx>, Ty<'tcx>) => {} - //mir::Rvalue::CopyForDeref(Place<'tcx>) => {} + //mir::Rvalue::Discriminant(Place<'vir>) => {} + //mir::Rvalue::ShallowInitBox(Operand<'vir>, Ty<'vir>) => {} + //mir::Rvalue::CopyForDeref(Place<'vir>) => {} other => { tracing::error!("unsupported rvalue {other:?}"); self.vcx.mk_todo_expr(vir::vir_format!(self.vcx, "rvalue {rvalue:?}")) @@ -743,7 +734,7 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for ImpureEncVisitor<'tcx, 'vir fn visit_terminator( &mut self, - terminator: &mir::Terminator<'tcx>, + terminator: &mir::Terminator<'vir>, location: mir::Location, ) { self.stmt(self.vcx.mk_comment_stmt( diff --git a/prusti-encoder/src/encoders/mir_poly_impure.rs b/prusti-encoder/src/encoders/mir_poly_impure.rs index bc5028e3d74..8bd2f075518 100644 --- a/prusti-encoder/src/encoders/mir_poly_impure.rs +++ b/prusti-encoder/src/encoders/mir_poly_impure.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::span::def_id::DefId; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{EncodeFullResult, TaskEncoder, TaskEncoderDependencies}; use vir::{MethodIdent, UnknownArity}; /// Encodes a Rust function as a Viper method using the polymorphic encoding of generics. @@ -45,19 +45,11 @@ impl TaskEncoder for MirPolyImpureEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - def_id: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - Ok((::encode(*def_id, deps), ())) + fn do_encode_full<'vir>( + def_id: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + ::encode(*def_id, deps) + .map(|r| (r, ())) } } diff --git a/prusti-encoder/src/encoders/mir_pure.rs b/prusti-encoder/src/encoders/mir_pure.rs index 40242cdec70..cebf1c40f04 100644 --- a/prusti-encoder/src/encoders/mir_pure.rs +++ b/prusti-encoder/src/encoders/mir_pure.rs @@ -8,6 +8,7 @@ use prusti_rustc_interface::{ use task_encoder::{ TaskEncoder, TaskEncoderDependencies, + EncodeFullResult, }; use vir::add_debug_note; use std::collections::HashMap; @@ -52,27 +53,27 @@ pub enum PureKind { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct MirPureEncTask<'tcx> { +pub struct MirPureEncTask<'vir> { // TODO: depth of encoding should be in the lazy context rather than here; // can we integrate the lazy context into the identifier system? pub encoding_depth: usize, pub kind: PureKind, pub parent_def_id: DefId, // ID of the function - pub param_env: ty::ParamEnv<'tcx>, // param environment at the usage site - pub substs: ty::GenericArgsRef<'tcx>, // type substitutions at the usage site + pub param_env: ty::ParamEnv<'vir>, // param environment at the usage site + pub substs: ty::GenericArgsRef<'vir>, // type substitutions at the usage site pub caller_def_id: Option, // ID of the caller function, if any } impl TaskEncoder for MirPureEnc { task_encoder::encoder_cache!(MirPureEnc); - type TaskDescription<'tcx> = MirPureEncTask<'tcx>; + type TaskDescription<'vir> = MirPureEncTask<'vir>; - type TaskKey<'tcx> = ( + type TaskKey<'vir> = ( usize, // encoding depth PureKind, // encoding a pure function? DefId, // ID of the function - ty::GenericArgsRef<'tcx>, // ? this should be the "signature", after applying the env/substs + ty::GenericArgsRef<'vir>, // ? this should be the "signature", after applying the env/substs Option, // Caller/Use DefID ); @@ -80,7 +81,7 @@ impl TaskEncoder for MirPureEnc { type EncodingError = MirPureEncError; - fn task_to_key<'tcx>(task: &Self::TaskDescription<'tcx>) -> Self::TaskKey<'tcx> { + fn task_to_key<'vir>(task: &Self::TaskDescription<'vir>) -> Self::TaskKey<'vir> { ( // TODO task.encoding_depth, @@ -91,17 +92,11 @@ impl TaskEncoder for MirPureEnc { ) } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; let (_, kind, def_id, substs, caller_def_id) = *task_key; @@ -174,22 +169,21 @@ impl<'vir> Update<'vir> { } } -struct Enc<'tcx, 'vir: 'enc, 'enc> -{ +struct Enc<'vir: 'enc, 'enc> { monomorphize: bool, - vcx: &'vir vir::VirCtxt<'tcx>, + vcx: &'vir vir::VirCtxt<'vir>, encoding_depth: usize, def_id: DefId, - body: &'enc mir::Body<'tcx>, + body: &'enc mir::Body<'vir>, rev_doms: rev_doms::ReverseDominators, - deps: &'enc mut TaskEncoderDependencies<'vir>, + deps: &'enc mut TaskEncoderDependencies<'vir, MirPureEnc>, visited: IndexVec, version_ctr: IndexVec, phi_ctr: usize, } -impl <'tcx: 'vir, 'vir, 'enc> PureFuncAppEnc<'tcx, 'vir> for Enc<'tcx, 'vir, 'enc> { - fn vcx(&self) -> &'vir vir::VirCtxt<'tcx> { +impl <'vir, 'enc> PureFuncAppEnc<'vir, MirPureEnc> for Enc<'vir, 'enc> { + fn vcx(&self) -> &'vir vir::VirCtxt<'vir> { self.vcx } @@ -199,9 +193,9 @@ impl <'tcx: 'vir, 'vir, 'enc> PureFuncAppEnc<'tcx, 'vir> for Enc<'tcx, 'vir, 'en type Next = vir::ExprKind<'vir>; - type LocalDeclsSrc = Body<'tcx>; + type LocalDeclsSrc = Body<'vir>; - fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir> { + fn deps(&mut self) -> &mut TaskEncoderDependencies<'vir, MirPureEnc> { self.deps } @@ -212,7 +206,7 @@ impl <'tcx: 'vir, 'vir, 'enc> PureFuncAppEnc<'tcx, 'vir> for Enc<'tcx, 'vir, 'en fn encode_operand( &mut self, args: &Self::EncodeOperandArgs, - operand: &mir::Operand<'tcx>, + operand: &mir::Operand<'vir>, ) -> vir::ExprGen<'vir, Self::Curr, Self::Next> { self.encode_operand(args, operand) } @@ -222,15 +216,14 @@ impl <'tcx: 'vir, 'vir, 'enc> PureFuncAppEnc<'tcx, 'vir> for Enc<'tcx, 'vir, 'en } } -impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> -{ +impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { fn new( - vcx: &'vir vir::VirCtxt<'tcx>, + vcx: &'vir vir::VirCtxt<'vir>, monomorphize: bool, encoding_depth: usize, def_id: DefId, - body: &'enc mir::Body<'tcx>, - deps: &'enc mut TaskEncoderDependencies<'vir>, + body: &'enc mir::Body<'vir>, + deps: &'enc mut TaskEncoderDependencies<'vir, MirPureEnc>, ) -> Self { assert!(!body.basic_blocks.is_cfg_cyclic(), "MIR pure encoding does not support loops"); let rev_doms = rev_doms::ReverseDominators::new(&body.basic_blocks); @@ -537,7 +530,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> fn encode_stmt( &mut self, curr_ver: &HashMap, - stmt: &mir::Statement<'tcx>, + stmt: &mir::Statement<'vir>, ) -> Update<'vir> { let mut update = Update::new(); match &stmt.kind { @@ -563,7 +556,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> fn encode_rvalue( &mut self, curr_ver: &HashMap, - rvalue: &mir::Rvalue<'tcx>, + rvalue: &mir::Rvalue<'vir>, ) -> ExprRet<'vir> { let rvalue_ty = rvalue.ty(self.body, self.vcx.tcx()); match rvalue { @@ -718,7 +711,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> fn encode_operand( &mut self, curr_ver: &HashMap, - operand: &mir::Operand<'tcx>, + operand: &mir::Operand<'vir>, ) -> ExprRet<'vir> { match operand { mir::Operand::Copy(place) @@ -731,7 +724,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> fn encode_place( &mut self, curr_ver: &HashMap, - place: &mir::Place<'tcx>, + place: &mir::Place<'vir>, ) -> ExprRet<'vir> { self.encode_place_with_ref(curr_ver, place).0 } @@ -739,7 +732,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> fn encode_place_with_ref( &mut self, curr_ver: &HashMap, - place: &mir::Place<'tcx>, + place: &mir::Place<'vir>, ) -> (ExprRet<'vir>, Option>) { // TODO: remove (debug) assert!(curr_ver.contains_key(&place.local)); @@ -759,8 +752,8 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> fn encode_place_element( &mut self, - place_ty: mir::tcx::PlaceTy<'tcx>, - elem: mir::PlaceElem<'tcx>, + place_ty: mir::tcx::PlaceTy<'vir>, + elem: mir::PlaceElem<'vir>, expr: ExprRet<'vir>, place_ref: Option>, ) -> (ExprRet<'vir>, Option>) { @@ -836,7 +829,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, 'vir, 'enc> } } - fn encode_prusti_builtin(&mut self, curr_ver: &HashMap, def_id: DefId, arg_tys: ty::GenericArgsRef<'tcx>, args: &Vec>) -> ExprRet<'vir> { + fn encode_prusti_builtin(&mut self, curr_ver: &HashMap, def_id: DefId, arg_tys: ty::GenericArgsRef<'vir>, args: &Vec>) -> ExprRet<'vir> { #[derive(Debug)] enum PrustiBuiltin { Forall, @@ -985,7 +978,7 @@ mod rev_doms { pub end: mir::BasicBlock, } impl ReverseDominators { - pub fn new<'a, 'tcx>(blocks: &'a mir::BasicBlocks<'tcx>) -> Self { + pub fn new<'a, 'vir>(blocks: &'a mir::BasicBlocks<'vir>) -> Self { let no_succ_blocks = blocks.iter_enumerated().filter(|(_, data)| data.terminator().successors().next().is_none() ).map(|(bb, _)| bb).collect(); @@ -1006,7 +999,7 @@ mod rev_doms { /// A wrapper around `mir::BasicBlocks` which reverses the direction of the /// edges. Implements `ControlFlowGraph` such that we can call `dominators`. - struct RevBasicBlocks<'a, 'tcx>(&'a mir::BasicBlocks<'tcx>, Vec); + struct RevBasicBlocks<'a, 'vir>(&'a mir::BasicBlocks<'vir>, Vec); impl DirectedGraph for RevBasicBlocks<'_, '_> { type Node = mir::BasicBlock; } diff --git a/prusti-encoder/src/encoders/mir_pure_function.rs b/prusti-encoder/src/encoders/mir_pure_function.rs index e035adba750..db2074ef718 100644 --- a/prusti-encoder/src/encoders/mir_pure_function.rs +++ b/prusti-encoder/src/encoders/mir_pure_function.rs @@ -1,6 +1,6 @@ use prusti_rustc_interface::span::def_id::DefId; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use crate::encoder_traits::pure_function_enc::{ MirFunctionEncOutput, MirFunctionEncOutputRef, PureFunctionEnc @@ -40,19 +40,10 @@ impl TaskEncoder for MirFunctionEnc { task.def_id } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { Ok((::encode(*task_key, deps), ())) } } diff --git a/prusti-encoder/src/encoders/mono/mir_impure.rs b/prusti-encoder/src/encoders/mono/mir_impure.rs index 84e681c6b1d..b5975a02ec5 100644 --- a/prusti-encoder/src/encoders/mono/mir_impure.rs +++ b/prusti-encoder/src/encoders/mono/mir_impure.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::{middle::ty::GenericArgs, span::def_id::DefId}; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{MethodIdent, UnknownArity}; /// Encodes a Rust function as a Viper method using the monomorphic encoding of generics. pub struct MirMonoImpureEnc; @@ -66,19 +66,11 @@ impl TaskEncoder for MirMonoImpureEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - Ok((::encode(*task_key, deps), ())) + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + ::encode(*task_key, deps) + .map(|r| (r, ())) } } diff --git a/prusti-encoder/src/encoders/mono/mir_pure_function.rs b/prusti-encoder/src/encoders/mono/mir_pure_function.rs index 36530879fdd..e984a882fa5 100644 --- a/prusti-encoder/src/encoders/mono/mir_pure_function.rs +++ b/prusti-encoder/src/encoders/mono/mir_pure_function.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::{middle::ty::GenericArgs, span::def_id::DefId}; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use crate::{ encoder_traits::{function_enc::FunctionEnc, pure_function_enc::{MirFunctionEncOutput, MirFunctionEncOutputRef, PureFunctionEnc}}, @@ -59,19 +59,10 @@ impl TaskEncoder for MirMonoFunctionEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { Ok((::encode(*task_key, deps), ())) } } diff --git a/prusti-encoder/src/encoders/pure/spec.rs b/prusti-encoder/src/encoders/pure/spec.rs index 35626836d85..77c4af7ba67 100644 --- a/prusti-encoder/src/encoders/pure/spec.rs +++ b/prusti-encoder/src/encoders/pure/spec.rs @@ -3,7 +3,7 @@ use prusti_rustc_interface::{ span::def_id::DefId, }; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::Reify; use crate::encoders::{mir_pure::PureKind, rust_ty_predicates::RustTyPredicatesEnc, MirPureEnc}; @@ -35,32 +35,21 @@ impl TaskEncoder for MirSpecEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { let (def_id, substs, caller_def_id, pure) = *task_key; - deps.emit_output_ref::(*task_key, ()); + deps.emit_output_ref(*task_key, ())?; let local_defs = deps .require_local::(( def_id, substs, caller_def_id, - )) - .unwrap(); + ))?; let specs = deps - .require_local::(crate::encoders::SpecEncTask { def_id }) - .unwrap(); + .require_local::(crate::encoders::SpecEncTask { def_id })?; vir::with_vcx(|vcx| { let local_iter = (1..=local_defs.arg_count).map(mir::Local::from); @@ -88,8 +77,7 @@ impl TaskEncoder for MirSpecEnc { }; let to_bool = deps - .require_ref::(vcx.tcx().types.bool) - .unwrap() + .require_ref::(vcx.tcx().types.bool)? .generic_predicate .expect_prim() .snap_to_prim; diff --git a/prusti-encoder/src/encoders/spec.rs b/prusti-encoder/src/encoders/spec.rs index e0418dc38c0..6d2431da65b 100644 --- a/prusti-encoder/src/encoders/spec.rs +++ b/prusti-encoder/src/encoders/spec.rs @@ -6,6 +6,7 @@ use prusti_interface::specs::typed::{DefSpecificationMap, ProcedureSpecification use task_encoder::{ TaskEncoder, TaskEncoderDependencies, + EncodeFullResult, }; pub struct SpecEnc; @@ -75,17 +76,11 @@ impl TaskEncoder for SpecEnc { ) } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { - deps.emit_output_ref::(task_key.clone(), ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(task_key.clone(), ())?; vir::with_vcx(|vcx| { with_def_spec(|def_spec| { let specs = def_spec.get_proc_spec(&task_key.0); diff --git a/prusti-encoder/src/encoders/type/domain.rs b/prusti-encoder/src/encoders/type/domain.rs index 532258334e4..56d166a0531 100644 --- a/prusti-encoder/src/encoders/type/domain.rs +++ b/prusti-encoder/src/encoders/type/domain.rs @@ -5,8 +5,7 @@ use prusti_rustc_interface::{ }; use rustc_middle::ty::ParamTy; use task_encoder::{ - TaskEncoder, - TaskEncoderDependencies, + EncodeFullError, EncodeFullResult, TaskEncoder, TaskEncoderDependencies }; use vir::{ BinaryArity, CallableIdent, DomainParamData, FunctionIdent, NullaryArityAny, ToKnownArity, UnaryArity, UnknownArity @@ -135,16 +134,10 @@ impl TaskEncoder for DomainEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { vir::with_vcx(|vcx| { let base_name = task_key.get_vir_base_name(vcx); match task_key.kind() { @@ -157,7 +150,7 @@ impl TaskEncoder for DomainEnc { }; let mut enc = DomainEncData::new(vcx, task_key, vec![], deps); - enc.deps.emit_output_ref::(*task_key, enc.output_ref(base_name)); + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; let specifics = enc.mk_prim_specifics( task_key.ty(), prim_type @@ -172,18 +165,18 @@ impl TaskEncoder for DomainEnc { .map(|ty| deps.require_local::>(ty).unwrap().expect_generic()) .collect(); let mut enc = DomainEncData::new(vcx, task_key, generics, deps); - enc.deps.emit_output_ref::(*task_key, enc.output_ref(base_name)); + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; match adt.adt_kind() { ty::AdtKind::Struct => { let fields = if !adt.is_box() { let variant = adt.non_enum_variant(); - enc.mk_field_tys(variant, params) + enc.mk_field_tys(variant, params)? } else { // Box special case (this should be replaced by an // extern spec in the future) vec![ FieldTy { - ty: enc.deps.require_ref::(()).unwrap().param_snapshot, + ty: enc.deps.require_ref::(())?.param_snapshot, rust_ty_data: None } ] @@ -194,9 +187,9 @@ impl TaskEncoder for DomainEnc { ty::AdtKind::Enum => { let variants: Vec<_> = adt.discriminants(vcx.tcx()).map(|(v, d)| { let variant = adt.variant(v); - let field_tys = enc.mk_field_tys(variant, params); - (variant.name, v, field_tys, d) - }).collect(); + let field_tys = enc.mk_field_tys(variant, params)?; + Ok((variant.name, v, field_tys, d)) + }).collect::, _>>()?; let variants = if variants.is_empty() { None } else { @@ -206,8 +199,7 @@ impl TaskEncoder for DomainEnc { .any(|v| matches!(v.discr, ty::VariantDiscr::Explicit(_))); let discr_ty = adt.repr().discr_type().to_ty(vcx.tcx()); let discr_ty = enc.deps - .require_local::(discr_ty) - .unwrap() + .require_local::(discr_ty)? .generic_snapshot; Some(VariantData { discr_ty: discr_ty.snapshot, @@ -228,28 +220,30 @@ impl TaskEncoder for DomainEnc { .map(|ty| deps.require_local::>(ty).unwrap().expect_generic()) .collect(); let mut enc = DomainEncData::new(vcx, task_key, generics, deps); - enc.deps.emit_output_ref::(*task_key, enc.output_ref(base_name)); - let field_tys = params.iter().map(|ty| FieldTy::from_ty(vcx, enc.deps, ty)).collect(); + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; + let field_tys = params.iter() + .map(|ty| FieldTy::from_ty(vcx, enc.deps, ty)) + .collect::, _>>()?; let specifics = enc.mk_struct_specifics(field_tys); Ok((Some(enc.finalize(task_key)), specifics)) } TyKind::Never => { let mut enc = DomainEncData::new(vcx, task_key, vec![], deps); - enc.deps.emit_output_ref::(*task_key, enc.output_ref(base_name)); + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; let specifics = enc.mk_enum_specifics(None); Ok((Some(enc.finalize(task_key)), specifics)) } &TyKind::Ref(_, inner, _) => { - let generics = vec![deps.require_local::>(inner).unwrap().expect_generic()]; + let generics = vec![deps.require_local::>(inner)?.expect_generic()]; let mut enc = DomainEncData::new(vcx, task_key, generics, deps); - enc.deps.emit_output_ref::(*task_key, enc.output_ref(base_name)); - let field_tys = vec![FieldTy::from_ty(vcx, enc.deps, inner)]; + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; + let field_tys = vec![FieldTy::from_ty(vcx, enc.deps, inner)?]; let specifics = enc.mk_struct_specifics(field_tys); Ok((Some(enc.finalize(task_key)), specifics)) } &TyKind::Param(_) => { - let out = deps.require_ref::(()).unwrap(); - deps.emit_output_ref::( + let out = deps.require_ref::(())?; + deps.emit_output_ref( *task_key, DomainEncOutputRef { base_name, @@ -257,13 +251,13 @@ impl TaskEncoder for DomainEnc { ty_param_accessors: &[], typeof_function: out.param_type_function, }, - ); + )?; Ok((None, DomainEncSpecifics::Param)) } &TyKind::Str => { let mut enc = DomainEncData::new(vcx, task_key, vec![], deps); let base_name = String::from("String"); - enc.deps.emit_output_ref::(*task_key, enc.output_ref(base_name)); + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; let specifics = enc.mk_struct_specifics(vec![]); Ok((Some(enc.finalize(task_key)), specifics)) } @@ -273,16 +267,16 @@ impl TaskEncoder for DomainEnc { } } -pub struct VariantData<'vir, 'tcx> { +pub struct VariantData<'vir> { discr_ty: vir::Type<'vir>, discr_prim: DomainDataPrim<'vir>, /// Do any of the variants have an explicit discriminant value? has_explicit: bool, - variants: Vec<(symbol::Symbol, abi::VariantIdx, Vec>, ty::util::Discr<'tcx>)>, + variants: Vec<(symbol::Symbol, abi::VariantIdx, Vec>, ty::util::Discr<'vir>)>, } -struct DomainEncData<'vir, 'tcx, 'enc> { - vcx: &'vir vir::VirCtxt<'tcx>, +struct DomainEncData<'vir, 'enc> { + vcx: &'vir vir::VirCtxt<'vir>, domain: vir::DomainIdent<'vir, NullaryArityAny<'vir, DomainParamData<'vir>>>, generics: Vec<(ParamTy, vir::FunctionIdent<'vir, UnaryArity<'vir>>)>, typeof_function: vir::FunctionIdent<'vir, UnaryArity<'vir>>, @@ -292,15 +286,15 @@ struct DomainEncData<'vir, 'tcx, 'enc> { axioms: Vec>, functions: Vec>, generic_enc: GenericEncOutputRef<'vir>, - deps: &'enc mut TaskEncoderDependencies<'vir>, + deps: &'enc mut TaskEncoderDependencies<'vir, DomainEnc>, } -impl<'vir, 'tcx: 'vir, 'enc> DomainEncData<'vir, 'tcx, 'enc> { +impl<'vir, 'enc> DomainEncData<'vir, 'enc> { // Creation fn new( - vcx: &'vir vir::VirCtxt<'tcx>, - ty: &MostGenericTy<'tcx>, + vcx: &'vir vir::VirCtxt<'vir>, + ty: &MostGenericTy<'vir>, generics: Vec, - deps: &'enc mut TaskEncoderDependencies<'vir>, + deps: &'enc mut TaskEncoderDependencies<'vir, DomainEnc>, ) -> Self { let domain = ty.get_vir_domain_ident(vcx); let self_ty = domain.apply(vcx, []); @@ -350,20 +344,23 @@ impl<'vir, 'tcx: 'vir, 'enc> DomainEncData<'vir, 'tcx, 'enc> { pub fn mk_field_tys( &mut self, variant: &ty::VariantDef, - params: ty::GenericArgsRef<'tcx>, - ) -> Vec> { + params: ty::GenericArgsRef<'vir>, + ) -> Result< + Vec>, + EncodeFullError<'vir, DomainEnc>, + > { variant .fields .iter() .map(|f| f.ty(self.vcx.tcx(), params)) .map(|ty| FieldTy::from_ty(self.vcx, self.deps, ty)) - .collect() + .collect::, _>>() } // Creating specifics pub fn mk_prim_specifics( &mut self, - ty: ty::Ty<'tcx>, + ty: ty::Ty<'vir>, prim_type: vir::Type<'vir>, ) -> DomainEncSpecifics<'vir> { let prim_type_args = vec![FieldTy { @@ -398,7 +395,7 @@ impl<'vir, 'tcx: 'vir, 'enc> DomainEncData<'vir, 'tcx, 'enc> { } pub fn mk_enum_specifics( &mut self, - data: Option>, + data: Option>, ) -> DomainEncSpecifics<'vir> { let specifics = data.map(|data| { let discr_vals: Vec<_> = data.variants.iter().map(|(_, _, _, discr)| data.discr_prim.expr_from_bits(discr.ty, discr.val)).collect(); @@ -665,7 +662,7 @@ impl<'vir, 'tcx: 'vir, 'enc> DomainEncData<'vir, 'tcx, 'enc> { ), } } - fn finalize(mut self, ty: &MostGenericTy<'tcx>) -> vir::Domain<'vir> { + fn finalize(mut self, ty: &MostGenericTy<'vir>) -> vir::Domain<'vir> { // If this type has generics, assert a bijectivity axiom on the type // constructor: For any value of type T, with type parameters T1, ..., @@ -733,7 +730,7 @@ impl<'vir> DomainEncSpecifics<'vir> { } } impl<'vir> DomainDataPrim<'vir> { - pub fn expr_from_bits<'tcx>(&self, ty: ty::Ty<'tcx>, value: u128) -> vir::Expr<'vir> { + pub fn expr_from_bits(&self, ty: ty::Ty<'vir>, value: u128) -> vir::Expr<'vir> { match *self.prim_type { vir::TypeData::Bool => vir::with_vcx(|vcx| vcx.mk_const_expr(vir::ConstData::Bool(value != 0))), vir::TypeData::Int => { @@ -763,7 +760,7 @@ impl<'vir> DomainDataPrim<'vir> { ref k => unreachable!("{k:?}"), } } - fn bounds<'tcx>(&self, ty: ty::Ty<'tcx>) -> Option<(vir::Expr<'vir>, vir::Expr<'vir>)> { + fn bounds(&self, ty: ty::Ty<'vir>) -> Option<(vir::Expr<'vir>, vir::Expr<'vir>)> { match *self.prim_type { vir::TypeData::Bool => None, ref int@vir::TypeData::Int { .. } => { @@ -797,17 +794,18 @@ struct LiftedRustTyData<'vir> { } impl <'vir> FieldTy<'vir> { - fn from_ty<'tcx: 'vir>(vcx: &'vir vir::VirCtxt<'tcx>, deps: &mut TaskEncoderDependencies, ty: ty::Ty<'tcx>) -> FieldTy<'vir> { - let vir_ty = deps.require_local::(ty) - .unwrap() + fn from_ty(vcx: &'vir vir::VirCtxt<'vir>, deps: &mut TaskEncoderDependencies<'vir, DomainEnc>, ty: ty::Ty<'vir>) -> Result< + FieldTy<'vir>, + EncodeFullError<'vir, DomainEnc>, + > { + let vir_ty = deps.require_ref::(ty)? .generic_snapshot .snapshot; let typeof_function = deps.require_ref::( extract_type_params(vcx.tcx(), ty).0 - ).unwrap().typeof_function; - let lifted_ty = deps.require_local::>(ty) - .unwrap(); - FieldTy {ty: vir_ty, rust_ty_data: Some(LiftedRustTyData {lifted_ty, typeof_function})} + )?.typeof_function; + let lifted_ty = deps.require_local::>(ty)?; + Ok(FieldTy { ty: vir_ty, rust_ty_data: Some(LiftedRustTyData { lifted_ty, typeof_function }) }) } } diff --git a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs index 4054c19365c..710b09d8be5 100644 --- a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs @@ -3,7 +3,7 @@ use prusti_rustc_interface::{ middle::{mir, ty::{GenericArgs, Ty}}, span::def_id::DefId, }; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, EncodeFullResult}; use crate::encoders::lifted::cast::{CastArgs, CastToEnc}; @@ -79,20 +79,11 @@ impl TaskEncoder for AggregateSnapArgsCastEnc { task.clone() } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(task_key.clone(), ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(task_key.clone(), ())?; vir::with_vcx(|vcx| { let cast_functions: Vec>> = match task_key.aggregate_type { diff --git a/prusti-encoder/src/encoders/type/lifted/cast.rs b/prusti-encoder/src/encoders/type/lifted/cast.rs index 5afe520261b..81d5e0943a0 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::middle::ty; -use task_encoder::{TaskEncoder, TaskEncoderDependencies, TaskEncoderError}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, TaskEncoderError, EncodeFullResult}; use vir::{FunctionIdent, MethodIdent, StmtGen, UnknownArity, VirCtxt}; use super::{ @@ -158,15 +158,16 @@ pub struct CastToEnc(std::marker::PhantomData); impl CastToEnc where - RustTyCastersEnc: for<'tcx, 'vir> TaskEncoder< - TaskDescription<'tcx> = ty::Ty<'tcx>, + Self: TaskEncoder, + RustTyCastersEnc: for<'vir> TaskEncoder< + TaskDescription<'vir> = ty::Ty<'vir>, OutputFullLocal<'vir> = RustTyGenericCastEncOutput<'vir, Casters<'vir, T>>, >, TaskEncoderError>: Sized, { - fn encode_cast<'tcx: 'vir, 'vir>( - task_key: CastArgs<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, + fn encode_cast<'vir>( + task_key: CastArgs<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, ) -> GenericCastOutputRef<'vir, T::CastApplicator<'vir>> { let expected_is_param = matches!(task_key.expected.kind(), ty::Param(_)); let actual_is_param = matches!(task_key.actual.kind(), ty::Param(_)); @@ -210,21 +211,12 @@ impl TaskEncoder for CastToEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { let output_ref = Self::encode_cast(*task_key, deps); - deps.emit_output_ref::(*task_key, output_ref); + deps.emit_output_ref(*task_key, output_ref)?; Ok(((), ())) } } @@ -240,21 +232,12 @@ impl TaskEncoder for CastToEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { let output_ref = Self::encode_cast(*task_key, deps); - deps.emit_output_ref::(*task_key, output_ref); + deps.emit_output_ref(*task_key, output_ref)?; Ok(((), ())) } } diff --git a/prusti-encoder/src/encoders/type/lifted/cast_functions.rs b/prusti-encoder/src/encoders/type/lifted/cast_functions.rs index 6449249fe53..866f7106d7e 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast_functions.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast_functions.rs @@ -1,4 +1,4 @@ -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{CallableIdent, FunctionIdent, UnaryArity, UnknownArity}; use crate::encoders::{ @@ -100,31 +100,21 @@ impl TaskEncoder for CastFunctionsEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - ty: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + ty: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { if ty.is_generic() { - deps.emit_output_ref::(*ty, CastFunctionsOutputRef::AlreadyGeneric); + deps.emit_output_ref(*ty, CastFunctionsOutputRef::AlreadyGeneric)?; return Ok((&[], ())); } vir::with_vcx(|vcx| { - let domain_ref = deps.require_ref::(*ty).unwrap(); - let generic_ref = deps.require_ref::(()).unwrap(); + let domain_ref = deps.require_ref::(*ty)?; + let generic_ref = deps.require_ref::(())?; let self_ty = domain_ref.domain.apply(vcx, []); let base_name = &domain_ref.base_name; let ty_constructor = deps - .require_ref::(*ty) - .unwrap() + .require_ref::(*ty)? .ty_constructor; let make_generic_arg_tys = [self_ty]; @@ -149,13 +139,13 @@ impl TaskEncoder for CastFunctionsEnc { self_ty, ); - deps.emit_output_ref::( + deps.emit_output_ref( *ty, CastFunctionsOutputRef::CastFunctions { make_generic: make_generic_ident, make_concrete: make_concrete_ident, }, - ); + )?; let make_generic_arg = vcx.mk_local_decl("self", self_ty); let make_generic_expr = vcx.mk_local_ex(make_generic_arg.name, make_generic_arg.ty); diff --git a/prusti-encoder/src/encoders/type/lifted/casters.rs b/prusti-encoder/src/encoders/type/lifted/casters.rs index 187ab4500e0..a57b36e36ba 100644 --- a/prusti-encoder/src/encoders/type/lifted/casters.rs +++ b/prusti-encoder/src/encoders/type/lifted/casters.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{Arity, CallableIdent, FunctionIdent, MethodIdent, TypeData, UnaryArity, UnknownArity}; use crate::encoders::{ @@ -221,21 +221,12 @@ impl TaskEncoder for CastersEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - ty: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + ty: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { if ty.is_generic() { - deps.emit_output_ref::(*ty, CastFunctionsOutputRef::AlreadyGeneric); + deps.emit_output_ref(*ty, CastFunctionsOutputRef::AlreadyGeneric); return Ok((&[], ())); } vir::with_vcx(|vcx| { @@ -271,7 +262,7 @@ impl TaskEncoder for CastersEnc { self_ty, ); - deps.emit_output_ref::( + deps.emit_output_ref( *ty, CastFunctionsOutputRef::Casters { make_generic: make_generic_ident, @@ -374,21 +365,12 @@ impl TaskEncoder for CastersEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - ty: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + ty: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { if ty.is_generic() { - deps.emit_output_ref::(*ty, CastMethodsOutputRef::AlreadyGeneric); + deps.emit_output_ref(*ty, CastMethodsOutputRef::AlreadyGeneric); return Ok((&[], ())); } vir::with_vcx(|vcx| { @@ -413,7 +395,7 @@ impl TaskEncoder for CastersEnc { UnknownArity::new(arg_tys), ); - deps.emit_output_ref::( + deps.emit_output_ref( *ty, CastMethodsOutputRef::Casters { make_generic: make_generic_ident, diff --git a/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs b/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs index 22267a67135..b5e380bcd29 100644 --- a/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs +++ b/prusti-encoder/src/encoders/type/lifted/func_app_ty_params.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use prusti_rustc_interface::middle::ty::{GenericArgsRef, Ty, TyKind}; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, EncodeFullResult}; use super::{ generic::LiftedGeneric, @@ -28,20 +28,11 @@ impl TaskEncoder for LiftedFuncAppTyParamsEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; vir::with_vcx(|vcx| { let (monomorphize, substs) = task_key; let tys = substs.iter().filter_map(|arg| arg.as_type()); diff --git a/prusti-encoder/src/encoders/type/lifted/func_def_ty_params.rs b/prusti-encoder/src/encoders/type/lifted/func_def_ty_params.rs index 0f9041c4e18..1cbb437fd3e 100644 --- a/prusti-encoder/src/encoders/type/lifted/func_def_ty_params.rs +++ b/prusti-encoder/src/encoders/type/lifted/func_def_ty_params.rs @@ -1,6 +1,6 @@ use prusti_rustc_interface::middle::ty::{self, ParamTy, Ty, TyKind}; use std::collections::HashSet; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, EncodeFullResult}; use super::generic::{LiftedGeneric, LiftedGenericEnc}; @@ -25,20 +25,11 @@ impl TaskEncoder for LiftedTyParamsEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; vir::with_vcx(|vcx| { let ty_args = task_key .iter() diff --git a/prusti-encoder/src/encoders/type/lifted/generic.rs b/prusti-encoder/src/encoders/type/lifted/generic.rs index 0dbaabeaceb..41c3af077e3 100644 --- a/prusti-encoder/src/encoders/type/lifted/generic.rs +++ b/prusti-encoder/src/encoders/type/lifted/generic.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::middle::ty; -use task_encoder::{OutputRefAny, TaskEncoder}; +use task_encoder::{OutputRefAny, TaskEncoder, EncodeFullResult}; use vir::with_vcx; use crate::encoders::GenericEnc; @@ -44,25 +44,16 @@ impl TaskEncoder for LiftedGenericEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { with_vcx(|vcx| { let output_ref = vcx.mk_local_decl( - task_key.name.as_str(), - deps.require_ref::(()).unwrap().type_snapshot, + vcx.alloc_str(task_key.name.as_str()), + deps.require_ref::(())?.type_snapshot, ); - deps.emit_output_ref::(*task_key, LiftedGeneric(output_ref)); + deps.emit_output_ref(*task_key, LiftedGeneric(output_ref))?; Ok(((), ())) }) } diff --git a/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs b/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs index 4c099910e27..7164279ba67 100644 --- a/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use prusti_rustc_interface::middle::ty; -use task_encoder::{TaskEncoder, TaskEncoderError}; +use task_encoder::{TaskEncoder, TaskEncoderError, EncodeFullResult}; use vir::with_vcx; use crate::encoders::most_generic_ty::{extract_type_params, MostGenericTy}; @@ -69,15 +69,16 @@ impl<'vir, T> task_encoder::OutputRefAny for RustTyGenericCastEncOutput<'vir, T> impl RustTyCastersEnc where - CastersEnc: for<'vir, 'tcx> TaskEncoder< - TaskDescription<'tcx> = MostGenericTy<'tcx>, + Self: TaskEncoder, + CastersEnc: for<'vir> TaskEncoder< + TaskDescription<'vir> = MostGenericTy<'vir>, OutputRef<'vir> = Casters<'vir, T>, >, TaskEncoderError>: Sized, { - fn encode<'tcx: 'vir, 'vir>( - task_key: &ty::Ty<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, + fn encode<'vir>( + task_key: &ty::Ty<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, ) -> RustTyGenericCastEncOutput<'vir, Casters<'vir, T>> { with_vcx(|vcx| { let (generic_ty, args) = extract_type_params(vcx.tcx(), *task_key); @@ -112,20 +113,11 @@ impl TaskEncoder for RustTyCastersEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; Ok((Self::encode(task_key, deps), ())) } } @@ -145,20 +137,11 @@ impl TaskEncoder for RustTyCastersEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; Ok((Self::encode(task_key, deps), ())) } } diff --git a/prusti-encoder/src/encoders/type/lifted/ty.rs b/prusti-encoder/src/encoders/type/lifted/ty.rs index 86913dc7b29..3be3475ca8d 100644 --- a/prusti-encoder/src/encoders/type/lifted/ty.rs +++ b/prusti-encoder/src/encoders/type/lifted/ty.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use prusti_rustc_interface::middle::ty::{self, ParamTy, TyKind}; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, EncodeFullResult}; use vir::{with_vcx, FunctionIdent, UnknownArity}; use crate::encoders::{ @@ -124,24 +124,14 @@ impl TaskEncoder for LiftedTyEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; with_vcx(|vcx| { let result = deps - .require_local::>(*task_key) - .unwrap(); + .require_local::>(*task_key)?; let result = result.map(vcx, &mut |g| { deps.require_ref::(g).unwrap() }); @@ -167,28 +157,18 @@ impl TaskEncoder for LiftedTyEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; with_vcx(|vcx| { if let TyKind::Param(p) = task_key.kind() { return Ok((LiftedTy::Generic(*p), ())); } let (ty_constructor, args) = extract_type_params(vcx.tcx(), *task_key); let ty_constructor = deps - .require_ref::(ty_constructor) - .unwrap() + .require_ref::(ty_constructor)? .ty_constructor; let args = args .into_iter() diff --git a/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs b/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs index e47254e421f..70b6bea875b 100644 --- a/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs +++ b/prusti-encoder/src/encoders/type/lifted/ty_constructor.rs @@ -1,4 +1,4 @@ -use task_encoder::{OutputRefAny, TaskEncoder}; +use task_encoder::{OutputRefAny, TaskEncoder, EncodeFullResult}; use vir::{ vir_format_identifier, CallableIdent, FunctionIdent, UnaryArity, UnknownArity }; @@ -53,20 +53,11 @@ impl TaskEncoder for TyConstructorEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - let generic_ref = deps.require_ref::(()).unwrap(); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + let generic_ref = deps.require_ref::(())?; let mut functions = vec![]; let mut axioms = vec![]; vir::with_vcx(|vcx| { @@ -111,13 +102,13 @@ impl TaskEncoder for TyConstructorEnc { ) }) .collect::>(); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, TyConstructorEncOutputRef { ty_constructor: type_function_ident, ty_param_accessors: vcx.alloc_slice(&ty_accessor_functions), }, - ); + )?; let axiom_qvars = vcx.alloc_slice(&ty_arg_decls); let axiom_triggers = vcx.alloc_slice( diff --git a/prusti-encoder/src/encoders/type/predicate.rs b/prusti-encoder/src/encoders/type/predicate.rs index e8eefe5644b..ac0a526a0f1 100644 --- a/prusti-encoder/src/encoders/type/predicate.rs +++ b/prusti-encoder/src/encoders/type/predicate.rs @@ -2,7 +2,7 @@ use prusti_rustc_interface::{ abi, middle::ty::{self, TyKind}, }; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{ add_debug_note, CallableIdent, FunctionIdent, MethodIdent, NullaryArity, PredicateIdent, TypeData, UnaryArity, UnknownArity, VirCtxt, @@ -198,21 +198,12 @@ impl TaskEncoder for PredicateEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { - let snap = deps.require_local::(*task_key).unwrap(); - let generic_output_ref = deps.require_ref::(()).unwrap(); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + let snap = deps.require_local::(*task_key)?; + let generic_output_ref = deps.require_ref::(())?; let mut enc = vir::with_vcx(|vcx| { PredicateEncValues::new(vcx, &snap.base_name, snap.snapshot, snap.generics) }); @@ -228,7 +219,7 @@ impl TaskEncoder for PredicateEnc { ])), ) }); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, PredicateEncOutputRef { ref_to_pred: generic_output_ref.ref_to_pred.as_unknown_arity(), @@ -240,7 +231,7 @@ impl TaskEncoder for PredicateEnc { generics: &[], }, ); - let dep = deps.require_local::(()).unwrap(); + let dep = deps.require_local::(())?; vir::with_vcx(|vcx| { let method_assign = mk_method_assign( vcx, @@ -265,13 +256,13 @@ impl TaskEncoder for PredicateEnc { } TyKind::Bool | TyKind::Char | TyKind::Int(_) | TyKind::Uint(_) | TyKind::Float(_) => { let specifics = PredicateEncData::Primitive(snap.specifics.expect_primitive()); - deps.emit_output_ref::(*task_key, enc.output_ref(specifics)); + deps.emit_output_ref(*task_key, enc.output_ref(specifics)); Ok((enc.mk_prim(&snap.base_name), ())) } TyKind::Tuple(tys) => { let snap_data = snap.specifics.expect_structlike(); let specifics = enc.mk_struct_ref(None, snap_data); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::StructLike(specifics)), ); @@ -289,7 +280,7 @@ impl TaskEncoder for PredicateEnc { ty::AdtKind::Struct => { let snap_data = snap.specifics.expect_structlike(); let specifics = enc.mk_struct_ref(None, snap_data); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::StructLike(specifics)), ); @@ -316,7 +307,7 @@ impl TaskEncoder for PredicateEnc { } ty::AdtKind::Enum => { let specifics = enc.mk_enum_ref(snap.specifics.expect_enumlike()); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::EnumLike(specifics)), ); @@ -348,7 +339,7 @@ impl TaskEncoder for PredicateEnc { TyKind::Never => { let specifics = enc.mk_enum_ref(snap.specifics.expect_enumlike()); assert!(specifics.is_none()); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::EnumLike(None)), ); @@ -358,22 +349,21 @@ impl TaskEncoder for PredicateEnc { &TyKind::Ref(_, inner, m) => { let snap_data = snap.specifics.expect_structlike(); let specifics = enc.mk_ref_ref(snap_data, m.is_mut()); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::Ref(specifics)), ); let lifted_ty = deps.require_local::>(inner).unwrap(); let inner = deps - .require_ref::(inner) - .unwrap() + .require_ref::(inner)? .generic_predicate; Ok((enc.mk_ref(inner, lifted_ty, specifics), ())) } TyKind::Str => { let specifics = enc.mk_struct_ref(None, snap.specifics.expect_structlike()); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::StructLike(specifics)), ); diff --git a/prusti-encoder/src/encoders/type/rust_ty_predicates.rs b/prusti-encoder/src/encoders/type/rust_ty_predicates.rs index d83831719ac..2f4c44eba39 100644 --- a/prusti-encoder/src/encoders/type/rust_ty_predicates.rs +++ b/prusti-encoder/src/encoders/type/rust_ty_predicates.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::middle::ty::{self}; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{with_vcx, Type, TypeData}; use crate::encoders::{PredicateEnc, PredicateEncOutputRef}; @@ -89,34 +89,24 @@ impl TaskEncoder for RustTyPredicatesEnc { type OutputRef<'vir> = RustTyPredicatesEncOutputRef<'vir>; type OutputFullLocal<'vir> = (); - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { with_vcx(|vcx| { let (generic_ty, args) = extract_type_params(vcx.tcx(), *task_key); - let generic_predicate = deps.require_ref::(generic_ty).unwrap(); + let generic_predicate = deps.require_ref::(generic_ty)?; let ty = deps - .require_local::>(*task_key) - .unwrap(); - deps.emit_output_ref::( + .require_local::>(*task_key)?; + deps.emit_output_ref( *task_key, RustTyPredicatesEncOutputRef { generic_predicate, ty, }, - ); + )?; for arg in args { - deps.require_ref::(arg).unwrap(); + deps.require_ref::(arg)?; } Ok(((), ())) }) diff --git a/prusti-encoder/src/encoders/type/rust_ty_snapshots.rs b/prusti-encoder/src/encoders/type/rust_ty_snapshots.rs index ae8ae0518ab..60a50c6a909 100644 --- a/prusti-encoder/src/encoders/type/rust_ty_snapshots.rs +++ b/prusti-encoder/src/encoders/type/rust_ty_snapshots.rs @@ -1,5 +1,5 @@ use prusti_rustc_interface::middle::ty; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, EncodeFullResult}; use vir::with_vcx; use crate::encoders::SnapshotEnc; @@ -36,32 +36,22 @@ impl TaskEncoder for RustTySnapshotsEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut task_encoder::TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut task_encoder::TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { with_vcx(|vcx| { let (generic_ty, args) = extract_type_params(vcx.tcx(), *task_key); - let generic_snapshot = deps.require_ref::(generic_ty).unwrap(); - deps.emit_output_ref::( + let generic_snapshot = deps.require_ref::(generic_ty)?; + deps.emit_output_ref( *task_key, RustTySnapshotsEncOutputRef { generic_snapshot }, - ); + )?; for arg in args { - deps.require_ref::(arg).unwrap(); + deps.require_ref::(arg)?; } let generic_snapshot = deps - .require_local::(generic_ty) - .unwrap(); + .require_local::(generic_ty)?; Ok((RustTySnapshotsEncOutput { generic_snapshot }, ())) }) } diff --git a/prusti-encoder/src/encoders/type/snapshot.rs b/prusti-encoder/src/encoders/type/snapshot.rs index 6265f72b86d..e150f9abc71 100644 --- a/prusti-encoder/src/encoders/type/snapshot.rs +++ b/prusti-encoder/src/encoders/type/snapshot.rs @@ -1,4 +1,4 @@ -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use super::{domain::{DomainEnc, DomainEncSpecifics}, lifted::generic::{LiftedGeneric, LiftedGenericEnc}, most_generic_ty::MostGenericTy}; @@ -33,29 +33,20 @@ impl TaskEncoder for SnapshotEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - ty: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result< - ( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), - ( - Self::EncodingError, - Option>, - ), - > { + fn do_encode_full<'vir>( + ty: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { vir::with_vcx(|vcx| { - let out = deps.require_ref::(*ty).unwrap(); + let out = deps.require_ref::(*ty)?; let snapshot = out.domain.apply(vcx, []); - deps.emit_output_ref::( + deps.emit_output_ref( *ty, SnapshotEncOutputRef { snapshot, }, - ); - let specifics = deps.require_dep::(*ty).unwrap(); + )?; + let specifics = deps.require_dep::(*ty)?; let generics = vcx.alloc_slice( &ty.generics() .into_iter() diff --git a/prusti-encoder/src/encoders/type/viper_tuple.rs b/prusti-encoder/src/encoders/type/viper_tuple.rs index 63598a91335..82ad8f83d32 100644 --- a/prusti-encoder/src/encoders/type/viper_tuple.rs +++ b/prusti-encoder/src/encoders/type/viper_tuple.rs @@ -1,6 +1,7 @@ use task_encoder::{ TaskEncoder, TaskEncoderDependencies, + EncodeFullResult, }; use super::{domain::{DomainDataStruct, DomainEnc}, most_generic_ty::MostGenericTy}; @@ -47,21 +48,15 @@ impl TaskEncoder for ViperTupleEnc { *task } - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )> { - deps.emit_output_ref::(*task_key, ()); + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + deps.emit_output_ref(*task_key, ())?; if *task_key == 1 { Ok((ViperTupleEncOutput { tuple: None }, ())) } else { - let ret = deps.require_dep::(MostGenericTy::tuple(*task_key)).unwrap(); + let ret = deps.require_dep::(MostGenericTy::tuple(*task_key))?; Ok((ViperTupleEncOutput { tuple: Some(ret.expect_structlike()) }, ())) } } diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index 010c33c1b54..9d2de32256b 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -107,7 +107,7 @@ pub fn test_entrypoint<'tcx>( }).unwrap_or_default(); if procedures.map_or(true, |procs| procs.contains(&def_id)) && !(is_trusted && is_pure) { - let res = MirPolyImpureEnc::encode(def_id); + let res = MirPolyImpureEnc::encode(def_id, false); assert!(res.is_ok()); } } diff --git a/task-encoder/src/lib.rs b/task-encoder/src/lib.rs index c2fab0eba50..1e39303851c 100644 --- a/task-encoder/src/lib.rs +++ b/task-encoder/src/lib.rs @@ -1,7 +1,7 @@ #![feature(associated_type_defaults)] use hashlink::LinkedHashMap; -use std::cell::RefCell; +use std::{cell::RefCell, marker::PhantomData}; pub trait OutputRefAny {} impl OutputRefAny for () {} @@ -23,7 +23,7 @@ pub enum TaskEncoderCacheState<'vir, E: TaskEncoder + 'vir + ?Sized> { /// TODO: can still collect errors? Encoded { output_ref: ::OutputRef<'vir>, - deps: TaskEncoderDependencies<'vir>, + deps: TaskEncoderDependencies<'vir, E>, output_local: ::OutputFullLocal<'vir>, output_dep: ::OutputFullDependency<'vir>, }, @@ -41,7 +41,7 @@ pub enum TaskEncoderCacheState<'vir, E: TaskEncoder + 'vir + ?Sized> { /// to encode its signature, to be included in dependents' programs. ErrorEncode { output_ref: ::OutputRef<'vir>, - deps: TaskEncoderDependencies<'vir>, + deps: TaskEncoderDependencies<'vir, E>, error: TaskEncoderError, output_dep: Option<::OutputFullDependency<'vir>>, }, @@ -49,11 +49,11 @@ pub enum TaskEncoderCacheState<'vir, E: TaskEncoder + 'vir + ?Sized> { /// Cache for a task encoder. See `TaskEncoderCacheState` for a description of /// the possible values in the encoding process. -pub type Cache<'tcx, 'vir, E> = LinkedHashMap< - ::TaskKey<'tcx>, +pub type Cache<'vir, E> = LinkedHashMap< + ::TaskKey<'vir>, TaskEncoderCacheState<'vir, E>, >; -pub type CacheRef<'tcx, 'vir, E> = RefCell>; +pub type CacheRef<'vir, E> = RefCell>; pub type CacheStatic = LinkedHashMap< ::TaskKey<'static>, @@ -77,6 +77,43 @@ impl<'vir, E: TaskEncoder> TaskEncoderOutput<'vir, E> { } } */ + +/// The result of the actual encoder implementation (`do_encode_full`). +pub type EncodeFullResult<'vir, E: TaskEncoder + 'vir + ?Sized> = Result<( + E::OutputFullLocal<'vir>, + E::OutputFullDependency<'vir>, +), EncodeFullError<'vir, E>>; + +/// An unsuccessful result occurring in `do_encode_full`. +pub enum EncodeFullError<'vir, E: TaskEncoder + 'vir + ?Sized> { + /// Indicates that the current task has already been encoded. This can + /// occur when there are cyclic dependencies between multiple encoders. + /// This error is specifically returned when one encoder depends on + /// another encoder (using e.g. `TaskEncoderDependencies::require_ref`), + /// that latter encoder then depending on the former again, causing the + /// former encoder to complete its full encoding in the inner invocation. + /// The outer invocation remains on the stack, but will be aborted early + /// as soon as the control flow returns to it. + AlreadyEncoded, + + /// An actual error occurred during encoding. + EncodingError(::EncodingError, Option>), + + DependencyError, +} + +// Manual implementation, since neither `E` nor `E::OutputFullDependency` are +// required to be `Debug`. +impl<'vir, E: TaskEncoder + 'vir + ?Sized> std::fmt::Debug for EncodeFullError<'vir, E> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::AlreadyEncoded => write!(f, "AlreadyEncoded"), + Self::EncodingError(err, _output_dep) => f.debug_tuple("EncodingError").field(err)/*.field(output_dep)*/.finish(), + Self::DependencyError => write!(f, "DependencyError"), + } + } +} + pub enum TaskEncoderError { EnqueueingError(::EnqueueingError), EncodingError(::EncodingError), @@ -109,64 +146,102 @@ impl Clone for TaskEncoderError { } } -#[derive(Default)] -pub struct TaskEncoderDependencies<'a> { - pub deps_local: Vec<&'a dyn OutputRefAny>, - pub deps_dep: Vec<&'a dyn OutputRefAny>, +pub struct TaskEncoderDependencies<'vir, E: TaskEncoder + 'vir + ?Sized> { + _marker: PhantomData, + task_key: Option>, + pub deps_local: Vec<&'vir dyn OutputRefAny>, + pub deps_dep: Vec<&'vir dyn OutputRefAny>, } -impl<'a> TaskEncoderDependencies<'a> { - pub fn require_ref<'vir, 'tcx: 'vir, E: TaskEncoder>( +impl<'vir, E: TaskEncoder + 'vir + ?Sized> TaskEncoderDependencies<'vir, E> { + fn check_cycle(&self) -> Result<(), EncodeFullError<'vir, E>> { + if let Some(task_key) = self.task_key.as_ref() { + if E::with_cache(move |cache| matches!( + cache.borrow().get(task_key), + Some(TaskEncoderCacheState::Encoded { .. } + | TaskEncoderCacheState::ErrorEncode { .. } + | TaskEncoderCacheState::ErrorEnqueue { .. }), + )) { + return Err(EncodeFullError::AlreadyEncoded); + } + } + Ok(()) + } + + pub fn require_ref( &mut self, - task: ::TaskDescription<'tcx>, + task: ::TaskDescription<'vir>, ) -> Result< - ::OutputRef<'vir>, - TaskEncoderError, + ::OutputRef<'vir>, + EncodeFullError<'vir, E>, > { - E::encode_ref(task) + EOther::encode_ref(task) + .map_err(|_| EncodeFullError::DependencyError) + .and_then(|result| { + self.check_cycle()?; + Ok(result) + }) } - pub fn require_local<'vir, 'tcx: 'vir, E: TaskEncoder + 'vir>( + pub fn require_local( &mut self, - task: ::TaskDescription<'tcx>, + task: ::TaskDescription<'vir>, ) -> Result< - ::OutputFullLocal<'vir>, - TaskEncoderError, + ::OutputFullLocal<'vir>, + EncodeFullError<'vir, E>, > { - E::encode(task).map(|(_output_ref, output_local, _output_dep)| output_local) + EOther::encode(task, true) + .map(Option::unwrap) + .map(|(_output_ref, output_local, _output_dep)| output_local) + .map_err(|_| EncodeFullError::DependencyError) + .and_then(|result| { + self.check_cycle()?; + Ok(result) + }) } - pub fn require_dep<'vir, 'tcx: 'vir, E: TaskEncoder + 'vir>( + pub fn require_dep( &mut self, - task: ::TaskDescription<'tcx>, + task: ::TaskDescription<'vir>, ) -> Result< - ::OutputFullDependency<'vir>, - TaskEncoderError, + ::OutputFullDependency<'vir>, + EncodeFullError<'vir, E>, > { - E::encode(task).map(|(_output_ref, _output_local, output_dep)| output_dep) + EOther::encode(task, true) + .map(Option::unwrap) + .map(|(_output_ref, _output_local, output_dep)| output_dep) + .map_err(|_| EncodeFullError::DependencyError) + .and_then(|result| { + self.check_cycle()?; + Ok(result) + }) } - pub fn emit_output_ref<'vir, 'tcx: 'vir, E: TaskEncoder + 'vir>( + pub fn emit_output_ref( &mut self, - task_key: E::TaskKey<'tcx>, + task_key: E::TaskKey<'vir>, output_ref: E::OutputRef<'vir>, - ) { + ) -> Result<(), EncodeFullError<'vir, E>> { + assert!(self.task_key.replace(task_key.clone()).is_none(), "output ref already set for task key {task_key:?}"); + self.check_cycle()?; assert!(E::with_cache(move |cache| matches!(cache.borrow_mut().insert( task_key, TaskEncoderCacheState::Started { output_ref }, - ), Some(TaskEncoderCacheState::Enqueued)))); + ), Some(TaskEncoderCacheState::Enqueued + | TaskEncoderCacheState::Started { .. })))); + Ok(()) } } pub trait TaskEncoder { /// Description of a task to be performed. Should be easily obtained by /// clients of this encoder. - type TaskDescription<'tcx>: std::hash::Hash + Eq + Clone + std::fmt::Debug; + type TaskDescription<'vir>: std::hash::Hash + Eq + Clone + std::fmt::Debug; /// Cache key for a task to be performed. May differ from `TaskDescription`, /// for example if the description should be normalised or some non-trivial /// resolution needs to happen. In other words, multiple descriptions may /// lead to the same key and hence the same output. - type TaskKey<'tcx>: std::hash::Hash + Eq + Clone + std::fmt::Debug = Self::TaskDescription<'tcx>; + type TaskKey<'vir>: std::hash::Hash + Eq + Clone + std::fmt::Debug = Self::TaskDescription<'vir>; /// A reference to an encoded item. Should be non-unit for tasks which can /// be "referred" to from other parts of a program, as opposed to tasks @@ -177,20 +252,22 @@ pub trait TaskEncoder { /// Fully encoded output for this task. When encoding items which can be /// dependencies (such as methods), this output should only be emitted in /// one Viper program. - type OutputFullLocal<'vir>: Clone; + type OutputFullLocal<'vir>: Clone + where Self: 'vir; /// Fully encoded output for this task for dependents. When encoding items /// which can be dependencies (such as methods), this output should be /// emitted in each Viper program that depends on this task. - type OutputFullDependency<'vir>: Clone = (); + type OutputFullDependency<'vir>: Clone = () + where Self: 'vir; type EnqueueingError: Clone + std::fmt::Debug = (); type EncodingError: Clone + std::fmt::Debug; /// Enters the given function with a reference to the cache for this /// encoder. - fn with_cache<'tcx: 'vir, 'vir, F, R>(f: F) -> R - where Self: 'vir, F: FnOnce(&'vir CacheRef<'tcx, 'vir, Self>) -> R; + fn with_cache<'vir, F, R>(f: F) -> R + where Self: 'vir, F: FnOnce(&'vir CacheRef<'vir, Self>) -> R; //fn get_all_outputs() -> Self::CacheRef<'vir> { // todo!() @@ -215,7 +292,7 @@ pub trait TaskEncoder { ).is_none())); } - fn encode_ref<'tcx: 'vir, 'vir>(task: Self::TaskDescription<'tcx>) -> Result< + fn encode_ref<'vir>(task: Self::TaskDescription<'vir>) -> Result< Self::OutputRef<'vir>, TaskEncoderError, > @@ -234,22 +311,13 @@ pub trait TaskEncoder { return Ok(output_ref); } - // is the task enqueued already? - let task_key_clone = task_key.clone(); - if Self::with_cache(move |cache| cache.borrow().contains_key(&task_key_clone)) { - // Cyclic dependency error because: - // 1. An ouput ref was requested for the task, - // 2. the task was already enqueued, and - // 3. there is not an output ref available. - // - // This would happen if the current encoder directly or indirectly - // requested the encoding for a task it is already working on, - // before it called the `emit_output_ref` method. - return Err(TaskEncoderError::CyclicError); - } - - // otherwise, we need to start the encoding - Self::encode(task)?; + // Otherwise, we need to start the encoding. Note that this is done + // even if the encoding was started previously, i.e. if the cache + // contains a `Enqueued` entry for this task. This can happen if the + // same task was (recursively) requested from the same encoder, before + // its first invocation reached a call to `emit_output_ref`. + // TODO: we should still make sure that *some* progress is done, because an actual cyclic dependency could cause a stack overflow? + Self::encode(task, false)?; let task_key_clone = task_key.clone(); if let Some(output_ref) = Self::with_cache(move |cache| match cache.borrow().get(&task_key_clone) { @@ -264,11 +332,11 @@ pub trait TaskEncoder { panic!("output ref not found after encoding") // TODO: error? } - fn encode<'tcx: 'vir, 'vir>(task: Self::TaskDescription<'tcx>) -> Result<( + fn encode<'vir>(task: Self::TaskDescription<'vir>, need_output: bool) -> Result, Self::OutputFullLocal<'vir>, Self::OutputFullDependency<'vir>, - ), TaskEncoderError> + )>, TaskEncoderError> where Self: 'vir { let task_key = Self::task_to_key(&task); @@ -285,13 +353,17 @@ pub trait TaskEncoder { output_local, output_dep, .. - } => Some(Ok(( - output_ref.clone(), - output_local.clone(), - output_dep.clone(), - ))), - TaskEncoderCacheState::Enqueued | TaskEncoderCacheState::Started { .. } => - panic!("Encoding already started or enqueued"), + } => if need_output { + Some(Ok(Some(( + output_ref.clone(), + output_local.clone(), + output_dep.clone(), + )))) + } else { + Some(Ok(None)) + } + // TODO: should we return Some(Ok(None)) for `Started`, if `!need_output` ? + TaskEncoderCacheState::Enqueued | TaskEncoderCacheState::Started { .. } => None, }, None => { // enqueue @@ -304,29 +376,68 @@ pub trait TaskEncoder { return in_cache; } - let mut deps = TaskEncoderDependencies::default(); + let mut deps = TaskEncoderDependencies { + _marker: PhantomData, + task_key: None, + deps_local: vec![], + deps_dep: vec![], + }; let encode_result = Self::do_encode_full(&task_key, &mut deps); let output_ref = Self::with_cache(|cache| match cache.borrow().get(&task_key) { - Some(TaskEncoderCacheState::Started { output_ref }) => output_ref.clone(), + Some(TaskEncoderCacheState::Started { output_ref } + | TaskEncoderCacheState::Encoded { output_ref, .. }) => output_ref.clone(), _ => panic!("encoder did not provide output ref for task {task_key:?}"), }); match encode_result { Ok((output_local, output_dep)) => { - Self::with_cache(|cache| cache.borrow_mut().insert(task_key, TaskEncoderCacheState::Encoded { - output_ref: output_ref.clone(), - deps, - output_local: output_local.clone(), - output_dep: output_dep.clone(), - })); - Ok(( + if need_output { + Self::with_cache(|cache| cache.borrow_mut().insert(task_key, TaskEncoderCacheState::Encoded { + output_ref: output_ref.clone(), + deps, + output_local: output_local.clone(), + output_dep: output_dep.clone(), + })); + Ok(Some(( + output_ref, + output_local, + output_dep, + ))) + } else { + Self::with_cache(|cache| cache.borrow_mut().insert(task_key, TaskEncoderCacheState::Encoded { + output_ref: output_ref, + deps, + output_local: output_local, + output_dep: output_dep, + })); + Ok(None) + } + } + Err(EncodeFullError::AlreadyEncoded) => Self::with_cache(|cache| match cache.borrow().get(&task_key).unwrap() { + TaskEncoderCacheState::Encoded { output_ref, output_local, output_dep, - )) - } - Err((err, maybe_output_dep)) => { + .. + } => if need_output { + Ok(Some(( + // TODO: does it even make sense for an encoder to request the full encoding + // when a cycle can occur? + output_ref.clone(), + output_local.clone(), + output_dep.clone(), + ))) + } else { + Ok(None) + }, + TaskEncoderCacheState::ErrorEnqueue { error } + | TaskEncoderCacheState::ErrorEncode { error, .. } => Err(error.clone()), + TaskEncoderCacheState::Started { .. } + | TaskEncoderCacheState::Enqueued => panic!("encoder did not finish for task {task_key:?}"), + }), + Err(EncodeFullError::DependencyError) => todo!(), + Err(EncodeFullError::EncodingError(err, maybe_output_dep)) => { Self::with_cache(|cache| cache.borrow_mut().insert(task_key, TaskEncoderCacheState::ErrorEncode { output_ref: output_ref.clone(), deps, @@ -449,16 +560,10 @@ pub trait TaskEncoder { /// Given a task description, create a reference to the output. fn task_to_output_ref<'vir>(task: &Self::TaskDescription<'vir>) -> Self::OutputRef<'vir>; */ - fn do_encode_full<'tcx: 'vir, 'vir>( - task_key: &Self::TaskKey<'tcx>, - deps: &mut TaskEncoderDependencies<'vir>, - ) -> Result<( - Self::OutputFullLocal<'vir>, - Self::OutputFullDependency<'vir>, - ), ( - Self::EncodingError, - Option>, - )>; + fn do_encode_full<'vir>( + task_key: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self>; fn all_outputs<'vir>() -> Vec> where Self: 'vir @@ -489,8 +594,8 @@ pub trait TaskEncoder { #[macro_export] macro_rules! encoder_cache { ($encoder: ty) => { - fn with_cache<'tcx: 'vir, 'vir, F, R>(f: F) -> R - where F: FnOnce(&'vir $crate::CacheRef<'tcx, 'vir, $encoder>) -> R, + fn with_cache<'vir, F, R>(f: F) -> R + where F: FnOnce(&'vir $crate::CacheRef<'vir, $encoder>) -> R, { ::std::thread_local! { static CACHE: $crate::CacheStaticRef<$encoder> = ::std::cell::RefCell::new(Default::default()); From f8527ccf9c0f10857457c2274cb94c402fee6388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Tue, 18 Jun 2024 14:48:11 +0200 Subject: [PATCH 17/53] VIR span manager, backtranslate postcondition errors --- prusti-encoder/src/encoders/pure/spec.rs | 39 ++++--- prusti-encoder/src/lib.rs | 11 +- prusti-viper/src/lib.rs | 80 +++++++++++--- prusti/src/verifier.rs | 26 ++++- vir-proc-macro/src/reify_kind.rs | 14 ++- vir-proc-macro/src/serde.rs | 4 +- vir/src/context.rs | 11 +- vir/src/debug.rs | 3 + vir/src/gendata.rs | 9 +- vir/src/lib.rs | 2 + vir/src/make.rs | 3 +- vir/src/reify.rs | 6 +- vir/src/spans.rs | 131 +++++++++++++++++++++++ 13 files changed, 290 insertions(+), 49 deletions(-) create mode 100644 vir/src/spans.rs diff --git a/prusti-encoder/src/encoders/pure/spec.rs b/prusti-encoder/src/encoders/pure/spec.rs index 77c4af7ba67..62fdde766d9 100644 --- a/prusti-encoder/src/encoders/pure/spec.rs +++ b/prusti-encoder/src/encoders/pure/spec.rs @@ -1,3 +1,4 @@ +use prusti_interface::PrustiError; use prusti_rustc_interface::{ middle::{mir, ty}, span::def_id::DefId, @@ -119,22 +120,28 @@ impl TaskEncoder for MirSpecEnc { .posts .iter() .map(|spec_def_id| { - let expr = deps - .require_local::( - crate::encoders::MirPureEncTask { - encoding_depth: 0, - kind: PureKind::Spec, - parent_def_id: *spec_def_id, - param_env: vcx.tcx().param_env(spec_def_id), - substs, - // TODO: should this be `def_id` or `caller_def_id` - caller_def_id: Some(def_id), - }, - ) - .unwrap() - .expr; - let expr = expr.reify(vcx, (*spec_def_id, post_args)); - to_bool.apply(vcx, [expr]) + let span = vcx.tcx().def_span(spec_def_id); + vcx.with_span(span, |vcx| { + vcx.handle_error("postcondition.violated:assertion.false", move || { + Some(vec![PrustiError::verification("postcondition might not hold", span.into())]) + }); + let expr = deps + .require_local::( + crate::encoders::MirPureEncTask { + encoding_depth: 0, + kind: PureKind::Spec, + parent_def_id: *spec_def_id, + param_env: vcx.tcx().param_env(spec_def_id), + substs, + // TODO: should this be `def_id` or `caller_def_id` + caller_def_id: Some(def_id), + }, + ) + .unwrap() + .expr; + let expr = expr.reify(vcx, (*spec_def_id, post_args)); + to_bool.apply(vcx, [expr]) + }) }) .collect::>>(); let data = MirSpecEncOutput { diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index 9d2de32256b..a8692a62e3b 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -11,8 +11,8 @@ mod encoders; mod encoder_traits; pub mod request; -use prusti_interface::environment::EnvBody; -use prusti_interface::environment::EnvQuery; + +use prusti_interface::{environment::{EnvBody, EnvQuery}, PrustiError}; use prusti_rustc_interface::{ middle::ty, hir, @@ -238,3 +238,10 @@ pub fn test_entrypoint<'tcx>( program: program.to_ref(), } } + +pub fn backtranslate_error(error_kind: &str, offending_pos_id: usize) -> Option> { + vir::with_vcx(|vcx| vcx.backtranslate( + error_kind, + offending_pos_id, + )) +} diff --git a/prusti-viper/src/lib.rs b/prusti-viper/src/lib.rs index 695fc5d095b..86557d9fea8 100644 --- a/prusti-viper/src/lib.rs +++ b/prusti-viper/src/lib.rs @@ -1,5 +1,5 @@ use rustc_hash::FxHashMap; -use viper::{self, AstFactory}; +use viper::{self, AstFactory, Position}; pub fn program_to_viper<'vir, 'v>(program: vir::Program<'vir>, ast: &'vir AstFactory<'v>) -> viper::Program<'vir> { let mut domains: FxHashMap<_, _> = Default::default(); @@ -46,6 +46,23 @@ pub trait ToViper<'vir, 'v> { type Output; fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output; + + fn to_viper_with_pos(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { + self.to_viper(ctx) + } + + fn to_viper_with_span(&self, ctx: &ToViperContext<'vir, 'v>, span: Option<&'vir vir::VirSpan<'vir>>) -> Self::Output { + if let Some(span) = span { + // TODO: virtual_position seems more appropriate (no need to store + // columns and lines which we don't use anyway), but it is not + // a HasIdentifier implementation; should be changed in silver + // let pos = ctx.ast.virtual_position(format!("{}", span.id)); + let pos = ctx.ast.identifier_position(0, 0, format!("{}", span.id)); + self.to_viper_with_pos(ctx, pos) + } else { + self.to_viper(ctx) + } + } } /// Conversion of one VIR node into a vector of Viper AST nodes. @@ -112,6 +129,26 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::BinOp<'vir> { vir::BinOpKind::Implies => ctx.ast.implies(lhs, rhs), } } + fn to_viper_with_pos(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + let lhs = self.lhs.to_viper(ctx); + let rhs = self.rhs.to_viper(ctx); + match self.kind { + vir::BinOpKind::CmpEq => ctx.ast.eq_cmp_with_pos(lhs, rhs, pos), + vir::BinOpKind::CmpNe => ctx.ast.ne_cmp_with_pos(lhs, rhs, pos), + vir::BinOpKind::CmpGt => ctx.ast.gt_cmp_with_pos(lhs, rhs, pos), + vir::BinOpKind::CmpLt => ctx.ast.lt_cmp_with_pos(lhs, rhs, pos), + vir::BinOpKind::CmpGe => ctx.ast.ge_cmp_with_pos(lhs, rhs, pos), + vir::BinOpKind::CmpLe => ctx.ast.le_cmp_with_pos(lhs, rhs, pos), + vir::BinOpKind::And => ctx.ast.and_with_pos(lhs, rhs, pos), + vir::BinOpKind::Or => ctx.ast.or_with_pos(lhs, rhs, pos), + vir::BinOpKind::Add => ctx.ast.add_with_pos(lhs, rhs, pos), + vir::BinOpKind::Sub => ctx.ast.sub_with_pos(lhs, rhs, pos), + vir::BinOpKind::Mul => ctx.ast.mul_with_pos(lhs, rhs, pos), + vir::BinOpKind::Div => ctx.ast.div_with_pos(lhs, rhs, pos), + vir::BinOpKind::Mod => ctx.ast.mul_with_pos(lhs, rhs, pos), + vir::BinOpKind::Implies => ctx.ast.implies_with_pos(lhs, rhs, pos), + } + } } impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::CfgBlock<'vir> { @@ -203,27 +240,27 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::Expr<'vir> { type Output = viper::Expr<'v>; fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { match self.kind { - vir::ExprKindData::AccField(v) => v.to_viper(ctx), - vir::ExprKindData::BinOp(v) => v.to_viper(ctx), - vir::ExprKindData::Const(v) => v.to_viper(ctx), + vir::ExprKindData::AccField(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::BinOp(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::Const(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Field(recv, field) => ctx.ast.field_access( recv.to_viper(ctx), field.to_viper(ctx), // TODO: position ), - vir::ExprKindData::Forall(v) => v.to_viper(ctx), - vir::ExprKindData::FuncApp(v) => v.to_viper(ctx), - vir::ExprKindData::Let(v) => v.to_viper(ctx), - vir::ExprKindData::Local(v) => v.to_viper(ctx), + vir::ExprKindData::Forall(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::FuncApp(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::Let(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::Local(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Old(v) => ctx.ast.old(v.to_viper(ctx)), // TODO: position - vir::ExprKindData::PredicateApp(v) => v.to_viper(ctx), + vir::ExprKindData::PredicateApp(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Result(ty) => ctx.ast.result_with_pos( ty.to_viper(ctx), ctx.ast.no_position(), // TODO: position ), - vir::ExprKindData::Ternary(v) => v.to_viper(ctx), - vir::ExprKindData::Unfolding(v) => v.to_viper(ctx), - vir::ExprKindData::UnOp(v) => v.to_viper(ctx), + vir::ExprKindData::Ternary(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::Unfolding(v) => v.to_viper_with_span(ctx, self.span), + vir::ExprKindData::UnOp(v) => v.to_viper_with_span(ctx, self.span), //vir::ExprKindData::Lazy(&'vir str, Box Fn(&'vir crate::VirCtxt<'a>, Curr) -> Next + 'vir>), //vir::ExprKindData::Todo(&'vir str) => unreachable!(), @@ -276,6 +313,25 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::FuncApp<'vir> { ) } } + fn to_viper_with_pos(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + if let Some((domain, _)) = ctx.domain_functions.get(self.target) { + ctx.ast.domain_func_app2( + self.target, + &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), + &[], + self.result_ty.to_viper(ctx), + domain.name, + pos, + ) + } else { + ctx.ast.func_app( + self.target, + &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), + self.result_ty.to_viper(ctx), + pos, + ) + } + } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Function<'vir> { diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 0a97fbdba43..0ccc26b5a42 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -74,9 +74,30 @@ pub fn verify<'tcx>( let program = request.program; - let result = prusti_server::verify_programs(&env.diagnostic, vec![program]); + let mut results = prusti_server::verify_programs(vec![program]); + assert_eq!(results.len(), 1); // TODO: eventually verify separate methods as separate programs again? + + let result = results.pop().unwrap().1; println!("verification result: {result:?}"); - if !matches!(result, VerificationResult::Success) { + + let success = match result { + viper::VerificationResult::Success => true, + viper::VerificationResult::JavaException(_e) => false, + viper::VerificationResult::ConsistencyErrors(_e) => false, + viper::VerificationResult::Failure(errors) => { + errors + .into_iter() + .flat_map(|error| prusti_encoder::backtranslate_error( + &error.full_id, + error.offending_pos_id.unwrap().parse::().unwrap(), + ) + .expect("verification error could not be backtranslated") + .into_iter()) + .for_each(|prusti_error| prusti_error.emit(&env.diagnostic)); + false + } + }; + if !success { // TODO: This will be unnecessary if diagnostic errors are emitted // earlier, it's useful for now to ensure that Prusti returns an // error code when verification fails @@ -87,7 +108,6 @@ pub fn verify<'tcx>( &[], ); } - // TODO: backtranslate verification results //let verification_result = // if verification_task.procedures.is_empty() && verification_task.types.is_empty() { diff --git a/vir-proc-macro/src/reify_kind.rs b/vir-proc-macro/src/reify_kind.rs index 734ed17bbc1..0b358bf9e17 100644 --- a/vir-proc-macro/src/reify_kind.rs +++ b/vir-proc-macro/src/reify_kind.rs @@ -33,7 +33,12 @@ pub(crate) enum ReifyKind { /// Deser: allocate value into arena. ReifyOption, - // TODO: `PassOption`? + /// Option of a non-reifiable type, e.g. `Option>`. + /// + /// Reify: passthrough. + /// Ser: serialise value as owned data, if present. + /// Deser: allocate value into arena. + PassOption, /// Reifiable owned type. /// @@ -127,8 +132,11 @@ impl ReifyKind { arguments: syn::PathArguments::AngleBracketed(..), } if ident == "Option")) { assert!(!is_ref, "invalid flag on Option: is_ref"); - assert!(!is_reify_pass, "non-reifiable Options not yet implemented"); - return ReifyKind::ReifyOption; + if is_reify_pass { + return ReifyKind::PassOption; + } else { + return ReifyKind::ReifyOption; + } } if is_reify_pass { diff --git a/vir-proc-macro/src/serde.rs b/vir-proc-macro/src/serde.rs index 0661aca5922..5744bd41727 100644 --- a/vir-proc-macro/src/serde.rs +++ b/vir-proc-macro/src/serde.rs @@ -68,7 +68,7 @@ pub fn derive_serde(input: TokenStream) -> TokenStream { vcx.alloc_slice(&vec_of_refs) }); }, - ReifyKind::ReifyOption => quote! { + ReifyKind::ReifyOption | ReifyKind::PassOption => quote! { let opt: Option<_> = seq_val; let #field_ident_f = crate::with_vcx(|vcx| opt.map(|val| vcx.alloc(val))); }, @@ -182,7 +182,7 @@ pub fn derive_serde(input: TokenStream) -> TokenStream { vcx.alloc_slice(&vec_of_refs) }) }, - ReifyKind::ReifyOption => quote! { + ReifyKind::ReifyOption | ReifyKind::PassOption => quote! { let opt: Option<_> = seq_val; crate::with_vcx(|vcx| opt.map(|val| vcx.alloc(val))) }, diff --git a/vir/src/context.rs b/vir/src/context.rs index 663b717fc5a..fd2a76d9d58 100644 --- a/vir/src/context.rs +++ b/vir/src/context.rs @@ -15,10 +15,9 @@ pub struct VirCtxt<'tcx> { /// unnecessary cloning. pub arena: bumpalo::Bump, - /// The stack of spans during the encoding process. (TODO) - pub span_stack: Vec, - // TODO: span stack - // TODO: error positions? + /// Rust source code spans used during the encoding process, to be able to + /// map Viper errors back to their origin. + pub spans: RefCell>, /// The compiler's typing context. This allows convenient access to most /// of the compiler's APIs. Is only present when running through `rustc`, @@ -32,7 +31,7 @@ impl<'tcx> VirCtxt<'tcx> { pub fn new(tcx: ty::TyCtxt<'tcx>, body: EnvBody<'tcx>) -> Self { Self { arena: bumpalo::Bump::new(), - span_stack: vec![], + spans: RefCell::new(Default::default()), tcx: Some(tcx), body: Some(RefCell::new(body)), } @@ -41,7 +40,7 @@ impl<'tcx> VirCtxt<'tcx> { pub fn new_without_tcx() -> Self { Self { arena: bumpalo::Bump::new(), - span_stack: vec![], + spans: RefCell::new(Default::default()), tcx: None, body: None, } diff --git a/vir/src/debug.rs b/vir/src/debug.rs index 3ebe9322a2a..c87025259fc 100644 --- a/vir/src/debug.rs +++ b/vir/src/debug.rs @@ -132,6 +132,9 @@ impl<'vir> Debug for DomainFunctionData<'vir> { impl<'vir, Curr, Next> Debug for ExprGenData<'vir, Curr, Next> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { // TODO: Position, etc + if let Some(span) = self.span { + write!(f, "/*p:{}*/", span.id)?; + } self.kind.fmt(f) } } diff --git a/vir/src/gendata.rs b/vir/src/gendata.rs index a1470cf67b7..af8cf3cf48b 100644 --- a/vir/src/gendata.rs +++ b/vir/src/gendata.rs @@ -4,6 +4,7 @@ use crate::data::*; use crate::debug_info::DebugInfo; use crate::genrefs::*; use crate::refs::*; +use crate::spans::VirSpan; use crate::with_vcx; use vir_proc_macro::*; @@ -117,14 +118,16 @@ impl GenRow for fn(A) -> B { pub struct ExprGenData<'vir, Curr: 'vir, Next: 'vir> { pub kind: ExprKindGen<'vir, Curr, Next>, #[vir(reify_pass)] pub debug_info: DebugInfo<'vir>, + #[vir(reify_pass)] pub span: Option<&'vir VirSpan<'vir>>, } impl <'vir, Curr: 'vir, Next: 'vir> ExprGenData<'vir, Curr, Next> { pub fn new(kind: ExprKindGen<'vir, Curr, Next>) -> Self { - Self { + with_vcx(|vcx| Self { kind, - debug_info: with_vcx(DebugInfo::new), - } + debug_info: DebugInfo::new(vcx), + span: vcx.top_span(), + }) } } diff --git a/vir/src/lib.rs b/vir/src/lib.rs index 664ef4b0f9e..50b82d12d3b 100644 --- a/vir/src/lib.rs +++ b/vir/src/lib.rs @@ -13,6 +13,7 @@ mod make; mod refs; mod reify; mod serde; +mod spans; mod callable_idents; mod viper_ident; @@ -22,6 +23,7 @@ pub use gendata::*; pub use genrefs::*; pub use refs::*; pub use reify::*; +pub use spans::VirSpan; pub use callable_idents::*; pub use viper_ident::*; diff --git a/vir/src/make.rs b/vir/src/make.rs index 698ae99376f..a046c313398 100644 --- a/vir/src/make.rs +++ b/vir/src/make.rs @@ -9,7 +9,8 @@ macro_rules! const_expr { ($expr_kind:expr) => { &ExprGenData { kind: $expr_kind, - debug_info: DEBUGINFO_NONE + debug_info: DEBUGINFO_NONE, + span: None, } }; } diff --git a/vir/src/reify.rs b/vir/src/reify.rs index 8c8b0a14d6e..e4dbb84e20d 100644 --- a/vir/src/reify.rs +++ b/vir/src/reify.rs @@ -19,7 +19,11 @@ impl<'vir, Curr: Copy, NextA, NextB> Reify<'vir, Curr> for ExprGen<'vir, Curr, ExprKindGen<'vir, NextA, NextB>> { type Next = ExprGen<'vir, NextA, NextB>; fn reify<'tcx>(&self, vcx: &'vir VirCtxt<'tcx>, lctx: Curr) -> Self::Next { - vcx.alloc(ExprGenData { kind: self.kind.reify(vcx, lctx), debug_info: self.debug_info }) + vcx.alloc(ExprGenData { + kind: self.kind.reify(vcx, lctx), + debug_info: self.debug_info, + span: self.span, + }) } } diff --git a/vir/src/spans.rs b/vir/src/spans.rs new file mode 100644 index 00000000000..c530f300ca6 --- /dev/null +++ b/vir/src/spans.rs @@ -0,0 +1,131 @@ +use std::collections::HashMap; +use prusti_interface::PrustiError; +use prusti_rustc_interface::span::Span; +use crate::VirCtxt; + +pub struct VirSpanHandler<'vir> { + error_kind: &'static str, + handler: Box Option> + 'vir>, + next: Option>>, +} + +#[derive(Hash)] +pub struct VirSpan<'vir> { + pub id: usize, + span: Span, + parent: Option<&'vir VirSpan<'vir>>, +} + +unsafe impl<'vir> Send for VirSpan<'vir> {} +unsafe impl<'vir> Sync for VirSpan<'vir> {} + +impl serde::Serialize for VirSpan<'_> { + fn serialize(&self, ser: S) -> Result + where S: serde::ser::Serializer + { + ser.serialize_u64(self.id as u64) + } +} +impl<'de> serde::Deserialize<'de> for VirSpan<'_> { + fn deserialize(deser: D) -> Result + where D: serde::de::Deserializer<'de> + { + let id = u64::deserialize(deser)? as usize; + Ok(VirSpan { + id, + span: Default::default(), + parent: None, + }) + } +} + +/// The span manager. Maintains a vector of all allocated spans, as well as +/// the stack, used when allocating AST nodes. +#[derive(Default)] +pub struct VirSpanManager<'vir> { + /// Vector of all allocated spans. The `id` field of a `VirSpan` is an + /// index into this vector. The same `id` can thus be used as the position + /// ID given to Viper over JNI, and when backtranslating error positions + /// can be used to index into this vector again, to find any error + /// transformers. + all: Vec<&'vir crate::spans::VirSpan<'vir>>, + + /// Stack of "current" spans. This is maintained such that an encoder can + /// walk down the MIR primitives recursively, adding their stacks onto the + /// stack as it works. At the same time, these spans will be linked with + /// their parent, i.e. the preceding span in the stack. This parent link + /// can be used during error backtranslation to find the closest ancestor + /// to an offending node with an error transformer. + // TODO: it might be good to insert sentinel nodes into the stack whenever + // crossing an encoder context (e.g. when a different encoder is used as + // part of a `deps.require_*` call) to avoid linking unrelated spans + // together + stack: Vec<&'vir crate::spans::VirSpan<'vir>>, + + handlers: HashMap>, +} + +impl<'tcx> VirCtxt<'tcx> { + /// Execute the given function with the given span (temporarily) added to + /// the span stack. + pub fn with_span(&'tcx self, span: Span, f: impl FnOnce(&'tcx Self) -> T) -> T { + let mut manager = self.spans.borrow_mut(); + let span = self.alloc(VirSpan { + id: manager.all.len(), + span, + parent: manager.stack.last().copied(), + }); + manager.all.push(span); + manager.stack.push(span); + let len_before = manager.stack.len(); + drop(manager); + let res = f(self); + let mut manager = self.spans.borrow_mut(); + debug_assert_eq!(manager.stack.len(), len_before); + manager.stack.pop().unwrap(); + res + } + + /// Add an error handler to the span currently on top of the stack. + /// `error_kind` is the machine-readable identifier of an error, as + /// defined by Viper. The handler function should construct one or more + /// `PrustiError`s to report the error with the correct span etc. + pub fn handle_error( + &'tcx self, + error_kind: &'static str, + handler: impl Fn() -> Option> + 'tcx, + ) { + let top_span_id = self.top_span().unwrap().id; + let mut manager = self.spans.borrow_mut(); + let previous = manager.handlers.remove(&top_span_id); + manager.handlers.insert(top_span_id, VirSpanHandler { + error_kind, + handler: Box::new(handler), + next: previous.map(Box::new), + }); + } + + // TODO: eventually, this should not be an Option + pub fn top_span(&'tcx self) -> Option<&'tcx VirSpan<'tcx>> { + self.spans.borrow().stack.last().copied() + } + + /// Attempt to backtranslate the given error at the given position. + pub fn backtranslate(&'tcx self, error_kind: &str, pos: usize) -> Option> { + let manager = self.spans.borrow(); + let mut span_opt = manager.all.get(pos); + while let Some(span) = span_opt { + let mut handler_opt = manager.handlers.get(&span.id); + while let Some(handler) = handler_opt { + if handler.error_kind == error_kind { + if let Some(errors) = (handler.handler)() { + return Some(errors); + } + } + handler_opt = handler.next.as_deref(); + } + span_opt = span.parent.as_ref(); + } + None + } +} From c51377236ebcbc0960bc3318a66e4bcf86bc2356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Tue, 18 Jun 2024 18:08:12 +0200 Subject: [PATCH 18/53] add spans to statements, backtranslate precondition errors --- prusti-encoder/src/encoders/mir_impure.rs | 16 +- .../src/encoders/type/lifted/cast.rs | 8 +- .../src/encoders/type/lifted/casters.rs | 8 +- .../src/encoders/type/rust_ty_predicates.rs | 4 +- prusti-viper/src/lib.rs | 461 +++++++++--------- viper/src/ast_factory/expression.rs | 2 +- vir/src/callable_idents.rs | 14 +- vir/src/data.rs | 1 + vir/src/debug.rs | 10 +- vir/src/gendata.rs | 19 +- vir/src/genrefs.rs | 1 + vir/src/make.rs | 52 +- vir/src/refs.rs | 1 + 13 files changed, 333 insertions(+), 264 deletions(-) diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index dcb9fa8b796..c0858c990e1 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -2,6 +2,7 @@ use mir_state_analysis::{ free_pcs::{CapabilityKind, FreePcsAnalysis, FreePcsBasicBlock, FreePcsLocation, RepackOp}, utils::Place, }; +use prusti_interface::PrustiError; use prusti_rustc_interface::{ abi, middle::{ @@ -741,6 +742,7 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< // TODO: also add bb and location for better debugging? vir::vir_format!(self.vcx, "{:?}", terminator.kind), )); + let span = terminator.source_info.span; self.fpcs_repacks_location(location, |loc| &loc.repacks_start); // TODO: move this to after getting operands, before assignment @@ -873,7 +875,7 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< let method_in = args.iter().map(|arg| self.encode_operand(arg)).collect::>(); - for ((fn_arg_ty, arg), arg_ex) in fn_arg_tys.iter().zip(args.iter()).zip(method_in.iter()) { + for ((fn_arg_ty, arg), arg_ex) in fn_arg_tys.iter().zip(args.iter()).zip(method_in.iter()) { let local_decls = self.local_decls_src(); let tcx = self.vcx().tcx(); let arg_ty = arg.ty(local_decls, tcx); @@ -904,10 +906,14 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< method_args.extend(encoded_ty_args); - self.stmt( - self.vcx - .alloc(func_out.method_ref.apply(self.vcx, &method_args)), - ); + self.vcx().with_span(span, |vcx| { + vcx.handle_error("call.precondition:assertion.false", move || { + Some(vec![PrustiError::verification("precondition might not hold", span.into())]) + }); + self.stmt(self.vcx.alloc(vir::StmtGenData::new( + self.vcx.alloc(func_out.method_ref.apply(self.vcx, &method_args)), + ))); + }); let expected_ty = destination.ty(self.local_decls_src(), self.vcx.tcx()).ty; let fn_result_ty = sig.output().skip_binder(); let result_cast = self diff --git a/prusti-encoder/src/encoders/type/lifted/cast.rs b/prusti-encoder/src/encoders/type/lifted/cast.rs index 81d5e0943a0..eb680ae8e0f 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast.rs @@ -116,14 +116,14 @@ impl<'vir> GenericCastOutputRef<'vir, MethodIdent<'vir, UnknownArity<'vir>>> { cast_applicator, ty_args, }) => Some( - vcx.alloc( - cast_applicator.apply( + vcx.alloc(vir::StmtGenData::new( + vcx.alloc(cast_applicator.apply( vcx, &std::iter::once(expr) .chain(ty_args.iter().map(|t| t.expr(vcx))) .collect::>(), - ), - ), + )), + )), ), } } diff --git a/prusti-encoder/src/encoders/type/lifted/casters.rs b/prusti-encoder/src/encoders/type/lifted/casters.rs index a57b36e36ba..ea724c76347 100644 --- a/prusti-encoder/src/encoders/type/lifted/casters.rs +++ b/prusti-encoder/src/encoders/type/lifted/casters.rs @@ -106,8 +106,12 @@ impl CastType for CastTypeImpure { .collect::>(), ); Some(ImpureCastStmts::new( - vcx.alloc(make_concrete.apply(vcx, args)), - vcx.alloc(make_generic.apply(vcx, args)), + vcx.alloc(vir::StmtGenData::new( + vcx.alloc(make_concrete.apply(vcx, args)), + )), + vcx.alloc(vir::StmtGenData::new( + vcx.alloc(make_generic.apply(vcx, args)), + )), )) } } diff --git a/prusti-encoder/src/encoders/type/rust_ty_predicates.rs b/prusti-encoder/src/encoders/type/rust_ty_predicates.rs index 2f4c44eba39..50f70ddb698 100644 --- a/prusti-encoder/src/encoders/type/rust_ty_predicates.rs +++ b/prusti-encoder/src/encoders/type/rust_ty_predicates.rs @@ -34,7 +34,9 @@ impl<'vir> RustTyPredicatesEncOutputRef<'vir> { let mut args = vec![self_ref]; args.extend(self.ty.arg_exprs(vcx)); args.push(self_new_snap); - vcx.alloc(self.generic_predicate.method_assign.apply(vcx, &args)) + vcx.alloc(vir::StmtData::new( + vcx.alloc(self.generic_predicate.method_assign.apply(vcx, &args)), + )) } pub fn snapshot(&self) -> Type<'vir> { diff --git a/prusti-viper/src/lib.rs b/prusti-viper/src/lib.rs index 86557d9fea8..607d312c6f7 100644 --- a/prusti-viper/src/lib.rs +++ b/prusti-viper/src/lib.rs @@ -1,6 +1,7 @@ use rustc_hash::FxHashMap; use viper::{self, AstFactory, Position}; +/// Convert the given VIR program into a Viper program (i.e., Java object). pub fn program_to_viper<'vir, 'v>(program: vir::Program<'vir>, ast: &'vir AstFactory<'v>) -> viper::Program<'vir> { let mut domains: FxHashMap<_, _> = Default::default(); let mut domain_functions: FxHashMap<_, _> = Default::default(); @@ -20,7 +21,7 @@ pub fn program_to_viper<'vir, 'v>(program: vir::Program<'vir>, ast: &'vir AstFac domain_functions, domain_axioms, }; - program.to_viper(&ctx) + program.to_viper_no_pos(&ctx) } /// Context for conversion of VIR nodes to Viper AST. We need to keep track of @@ -40,38 +41,51 @@ pub struct ToViperContext<'vir, 'v> { domain_axioms: FxHashMap<&'vir str, (vir::Domain<'vir>, vir::DomainAxiom<'vir>)>, } -/// Conversion of one VIR node into one Viper AST node. -pub trait ToViper<'vir, 'v> { - /// Type of the created Viper AST node. - type Output; - - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output; - - fn to_viper_with_pos(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { - self.to_viper(ctx) - } - - fn to_viper_with_span(&self, ctx: &ToViperContext<'vir, 'v>, span: Option<&'vir vir::VirSpan<'vir>>) -> Self::Output { +impl<'vir, 'v> ToViperContext<'vir, 'v> { + /// If a span is given, convert it to a Viper position. Otherwise, return + /// a "no position". + // TODO: This signature is chosen to accommodate optional spans in + // expressions and statements. When a span is *always* set,then this + // should be changed. + fn span_to_pos(&self, span: Option<&'vir vir::VirSpan<'vir>>) -> Position { if let Some(span) = span { // TODO: virtual_position seems more appropriate (no need to store // columns and lines which we don't use anyway), but it is not // a HasIdentifier implementation; should be changed in silver - // let pos = ctx.ast.virtual_position(format!("{}", span.id)); - let pos = ctx.ast.identifier_position(0, 0, format!("{}", span.id)); - self.to_viper_with_pos(ctx, pos) + // self.ast.virtual_position(format!("{}", span.id)) + self.ast.identifier_position(0, 0, format!("{}", span.id)) } else { - self.to_viper(ctx) + self.ast.no_position() } } } +/// Conversion of one VIR node into one Viper AST node. +/// +/// **About spans and positions** +/// Regarding the three conversion methods in this trait. The method that +/// should be implemented for all VIR types is `to_viper`, not the other two, +/// which are just convenience wrappers. +/// In many cases, the `pos` received in `to_viper` should be ignored: it only +/// exists for cases where `Self` should have a position, but it is stored in +/// the parent. For example, `ExprGenData` contains a span, but allocating +/// the Java object is dispatched to another type, such as `TernaryData`. The +/// latter does not contain its own span, so the span (converted to a position) +/// must be passed to the ternary through the `pos` argument. +pub trait ToViper<'vir, 'v> { + /// Type of the created Viper AST node. + type Output; + + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output; +} + /// Conversion of one VIR node into a vector of Viper AST nodes. pub trait ToViperVec<'vir, 'v> { /// Type of a single created Viper AST node. type Output; /// Extend the given vector with the converted contents of `self`. - fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>); + fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>, pos: Position); /// Indicate how many elements there are in `self`. Does not need to be /// provided, nor does it need to be accurate; this is only used to set a @@ -81,57 +95,67 @@ pub trait ToViperVec<'vir, 'v> { } /// Helper method to allocate a vector and extend it with `self`. - fn to_viper_vec(&self, ctx: &ToViperContext<'vir, 'v>) -> Vec { + fn to_viper_vec(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Vec { let mut result = match self.size_hint() { Some(hint) => Vec::with_capacity(hint), None => Vec::new(), }; - self.to_viper_extend(&mut result, ctx); + self.to_viper_extend(&mut result, ctx, pos); result } } +trait ToViperPosHelper<'vir, 'v> { + type Output; + fn to_viper_no_pos(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output; + fn to_viper_with_span(&self, ctx: &ToViperContext<'vir, 'v>, span: Option<&'vir vir::VirSpan<'vir>>) -> Self::Output; +} +impl<'vir, 'v, T: ToViper<'vir, 'v>> ToViperPosHelper<'vir, 'v> for T { + type Output = >::Output; + fn to_viper_no_pos(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + self.to_viper(ctx, ctx.ast.no_position()) + } + fn to_viper_with_span(&self, ctx: &ToViperContext<'vir, 'v>, span: Option<&'vir vir::VirSpan<'vir>>) -> Self::Output { + self.to_viper(ctx, ctx.span_to_pos(span)) + } +} + +trait ToViperVecPosHelper<'vir, 'v> { + type Output; + fn to_viper_extend_no_pos(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>); + fn to_viper_vec_no_pos(&self, ctx: &ToViperContext<'vir, 'v>) -> Vec; +} +impl<'vir, 'v, T: ToViperVec<'vir, 'v>> ToViperVecPosHelper<'vir, 'v> for T { + type Output = >::Output; + fn to_viper_extend_no_pos(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>) { + self.to_viper_extend(vec, ctx, ctx.ast.no_position()); + } + fn to_viper_vec_no_pos(&self, ctx: &ToViperContext<'vir, 'v>) -> Vec { + self.to_viper_vec(ctx, ctx.ast.no_position()) + } +} + impl<'vir, 'v> ToViper<'vir, 'v> for vir::AccField<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - ctx.ast.field_access_predicate( - ctx.ast.field_access( - self.recv.to_viper(ctx), - self.field.to_viper(ctx), - // TODO: position + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + ctx.ast.field_access_predicate_with_pos( + ctx.ast.field_access_with_pos( + self.recv.to_viper_no_pos(ctx), + self.field.to_viper_no_pos(ctx), + pos, ), - self.perm.map(|v| v.to_viper(ctx)).unwrap_or_else(|| ctx.ast.full_perm()), - // TODO: position + self.perm.map(|v| v.to_viper_no_pos(ctx)).unwrap_or_else(|| ctx.ast.full_perm()), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::BinOp<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - let lhs = self.lhs.to_viper(ctx); - let rhs = self.rhs.to_viper(ctx); - // TODO: position - match self.kind { - vir::BinOpKind::CmpEq => ctx.ast.eq_cmp(lhs, rhs), - vir::BinOpKind::CmpNe => ctx.ast.ne_cmp(lhs, rhs), - vir::BinOpKind::CmpGt => ctx.ast.gt_cmp(lhs, rhs), - vir::BinOpKind::CmpLt => ctx.ast.lt_cmp(lhs, rhs), - vir::BinOpKind::CmpGe => ctx.ast.ge_cmp(lhs, rhs), - vir::BinOpKind::CmpLe => ctx.ast.le_cmp(lhs, rhs), - vir::BinOpKind::And => ctx.ast.and(lhs, rhs), - vir::BinOpKind::Or => ctx.ast.or(lhs, rhs), - vir::BinOpKind::Add => ctx.ast.add(lhs, rhs), - vir::BinOpKind::Sub => ctx.ast.sub(lhs, rhs), - vir::BinOpKind::Mul => ctx.ast.mul(lhs, rhs), - vir::BinOpKind::Div => ctx.ast.div(lhs, rhs), - vir::BinOpKind::Mod => ctx.ast.mul(lhs, rhs), - vir::BinOpKind::Implies => ctx.ast.implies(lhs, rhs), - } - } - fn to_viper_with_pos(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { - let lhs = self.lhs.to_viper(ctx); - let rhs = self.rhs.to_viper(ctx); + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + let lhs = self.lhs.to_viper_no_pos(ctx); + let rhs = self.rhs.to_viper_no_pos(ctx); match self.kind { vir::BinOpKind::CmpEq => ctx.ast.eq_cmp_with_pos(lhs, rhs, pos), vir::BinOpKind::CmpNe => ctx.ast.ne_cmp_with_pos(lhs, rhs, pos), @@ -156,16 +180,18 @@ impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::CfgBlock<'vir> { fn size_hint(&self) -> Option { Some(1 + self.stmts.len() + self.terminator.size_hint().unwrap_or(1)) } - fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>) { - vec.push(self.label.to_viper(ctx)); - vec.extend(self.stmts.iter().map(|v| v.to_viper(ctx))); - self.terminator.to_viper_extend(vec, ctx); + fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>, _pos: Position) { + vec.push(self.label.to_viper_no_pos(ctx)); // TODO: pass own position to label? + vec.extend(self.stmts.iter().map(|v| v.to_viper_no_pos(ctx))); + self.terminator.to_viper_extend_no_pos(vec, ctx); } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::CfgBlockLabel<'vir> { type Output = viper::Stmt<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + // `pos` coming from the parent `Stmt` should be used, but the node + // created her cannot be created with a position + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.label( &self.name(), &[], // TODO: invariants @@ -175,38 +201,38 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::CfgBlockLabel<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::Const<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - // TODO: position + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { match self { - vir::ConstData::Bool(true) => ctx.ast.true_lit(), - vir::ConstData::Bool(false) => ctx.ast.false_lit(), - vir::ConstData::Int(v) if *v < (i64::MAX as u128) => ctx.ast.int_lit(*v as i64), - vir::ConstData::Int(v) => ctx.ast.int_lit_from_ref(v), + vir::ConstData::Bool(true) => ctx.ast.true_lit_with_pos(pos), + vir::ConstData::Bool(false) => ctx.ast.false_lit_with_pos(pos), + vir::ConstData::Int(v) if *v < (i64::MAX as u128) => ctx.ast.int_lit_with_pos(*v as i64, pos), + vir::ConstData::Int(v) => ctx.ast.int_lit_from_ref_with_pos(v, pos), vir::ConstData::Wildcard => ctx.ast.wildcard_perm(), - vir::ConstData::Null => ctx.ast.null_lit(), + vir::ConstData::Null => ctx.ast.null_lit_with_pos(pos), } } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Domain<'vir> { type Output = viper::Domain<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.domain( self.name, - &self.functions.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.axioms.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.typarams.iter().map(|v| v.to_viper(ctx)).collect::>(), + &self.functions.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.axioms.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.typarams.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::DomainAxiom<'vir> { type Output = viper::NamedDomainAxiom<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { let (domain, _) = ctx.domain_axioms.get(self.name).expect("no domain for domain axiom"); ctx.ast.named_domain_axiom( self.name, - self.expr.to_viper(ctx), + self.expr.to_viper_no_pos(ctx), domain.name, ) } @@ -214,15 +240,15 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::DomainAxiom<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::DomainFunction<'vir> { type Output = viper::DomainFunc<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { let (domain, _) = ctx.domain_functions.get(self.name.to_str()).expect("no domain for domain function"); ctx.ast.domain_func( self.name.to_str(), &self.args.iter().enumerate().map(|(idx, v)| ctx.ast.local_var_decl( &format!("arg{idx}"), - v.to_viper(ctx), + v.to_viper_no_pos(ctx), )).collect::>(), - self.ret.to_viper(ctx), + self.ret.to_viper_no_pos(ctx), self.unique, domain.name, ) @@ -231,32 +257,32 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::DomainFunction<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::DomainParam<'vir> { type Output = viper::Type<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.type_var(self.name) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Expr<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { match self.kind { vir::ExprKindData::AccField(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::BinOp(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Const(v) => v.to_viper_with_span(ctx, self.span), - vir::ExprKindData::Field(recv, field) => ctx.ast.field_access( - recv.to_viper(ctx), - field.to_viper(ctx), - // TODO: position + vir::ExprKindData::Field(recv, field) => ctx.ast.field_access_with_pos( + recv.to_viper_no_pos(ctx), + field.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), vir::ExprKindData::Forall(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::FuncApp(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Let(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Local(v) => v.to_viper_with_span(ctx, self.span), - vir::ExprKindData::Old(v) => ctx.ast.old(v.to_viper(ctx)), // TODO: position + vir::ExprKindData::Old(v) => ctx.ast.old(v.to_viper_no_pos(ctx)), // TODO: position vir::ExprKindData::PredicateApp(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Result(ty) => ctx.ast.result_with_pos( - ty.to_viper(ctx), - ctx.ast.no_position(), // TODO: position + ty.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), vir::ExprKindData::Ternary(v) => v.to_viper_with_span(ctx, self.span), vir::ExprKindData::Unfolding(v) => v.to_viper_with_span(ctx, self.span), @@ -272,62 +298,45 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::Expr<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::Field<'vir> { type Output = viper::Field<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.field( self.name, - self.ty.to_viper(ctx), + self.ty.to_viper_no_pos(ctx), ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Forall<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - ctx.ast.forall( - &self.qvars.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.triggers.iter().map(|v| v.to_viper(ctx)).collect::>(), - self.body.to_viper(ctx), - // TODO: position + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + ctx.ast.forall_with_pos( + &self.qvars.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.triggers.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + self.body.to_viper_no_pos(ctx), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::FuncApp<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { if let Some((domain, _)) = ctx.domain_functions.get(self.target) { ctx.ast.domain_func_app2( self.target, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), &[], - self.result_ty.to_viper(ctx), - domain.name, - ctx.ast.no_position(), // TODO: position - ) - } else { - ctx.ast.func_app( - self.target, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - self.result_ty.to_viper(ctx), - ctx.ast.no_position(), // TODO: position - ) - } - } - fn to_viper_with_pos(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { - if let Some((domain, _)) = ctx.domain_functions.get(self.target) { - ctx.ast.domain_func_app2( - self.target, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - &[], - self.result_ty.to_viper(ctx), + self.result_ty.to_viper_no_pos(ctx), domain.name, pos, ) } else { ctx.ast.func_app( self.target, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - self.result_ty.to_viper(ctx), + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + self.result_ty.to_viper_no_pos(ctx), pos, ) } @@ -336,15 +345,15 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::FuncApp<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::Function<'vir> { type Output = viper::Function<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.function( self.name, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - self.ret.to_viper(ctx), - &self.pres.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.posts.iter().map(|v| v.to_viper(ctx)).collect::>(), - ctx.ast.no_position(), // TODO: position - self.expr.map(|v| v.to_viper(ctx)), + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + self.ret.to_viper_no_pos(ctx), + &self.pres.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.posts.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + ctx.ast.no_position(), // TODO: position (each function should have its own) + self.expr.map(|v| v.to_viper_no_pos(ctx)), ) } } @@ -358,30 +367,32 @@ impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::GotoIf<'vir> { Some(1) } } - fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>) { + // `pos` coming from the parent `Stmt` should be used, but the nodes + // created her cannot be created with positions + fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>, _pos: Position) { if self.targets.is_empty() { self.otherwise_statements.iter() - .for_each(|v| vec.push(v.to_viper(ctx))); + .for_each(|v| vec.push(v.to_viper_no_pos(ctx))); vec.push(ctx.ast.goto(&self.otherwise.name())); return; } - let value = self.value.to_viper(ctx); + let value = self.value.to_viper_no_pos(ctx); vec.push(self.targets.iter() .rfold({ let mut vec_otherwise = Vec::with_capacity(1 + self.otherwise_statements.len()); self.otherwise_statements.iter() - .for_each(|v| vec_otherwise.push(v.to_viper(ctx))); + .for_each(|v| vec_otherwise.push(v.to_viper_no_pos(ctx))); vec_otherwise.push(ctx.ast.goto(&self.otherwise.name())); ctx.ast.seqn(&vec_otherwise, &[]) }, |else_, target| { let mut vec_then = Vec::with_capacity(1 + target.statements.len()); target.statements.iter() - .for_each(|v| vec_then.push(v.to_viper(ctx))); + .for_each(|v| vec_then.push(v.to_viper_no_pos(ctx))); vec_then.push(ctx.ast.goto(&target.label.name())); ctx.ast.if_stmt( ctx.ast.eq_cmp( value, - target.value.to_viper(ctx), + target.value.to_viper_no_pos(ctx), ), ctx.ast.seqn(&vec_then, &[]), else_, @@ -392,50 +403,51 @@ impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::GotoIf<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::Let<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - ctx.ast.let_expr( + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + ctx.ast.let_expr_with_pos( ctx.ast.local_var_decl( self.name, - self.val.ty().to_viper(ctx), + self.val.ty().to_viper_no_pos(ctx), ), - self.val.to_viper(ctx), - self.expr.to_viper(ctx), - // TODO: position + self.val.to_viper_no_pos(ctx), + self.expr.to_viper_no_pos(ctx), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::LocalData<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { ctx.ast.local_var( self.name, - self.ty.to_viper(ctx), - // TODO: Use a real position here - ctx.ast.no_position() + self.ty.to_viper_no_pos(ctx), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::LocalDeclData<'vir> { type Output = viper::LocalVarDecl<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.local_var_decl( self.name, - self.ty.to_viper(ctx), + self.ty.to_viper_no_pos(ctx), ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Method<'vir> { type Output = viper::Method<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.method( self.name, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.rets.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.pres.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.posts.iter().map(|v| v.to_viper(ctx)).collect::>(), + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.rets.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.pres.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.posts.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), self.body.map(|body| { let size_hint = body.blocks.iter().flat_map(|b| b.size_hint()).sum(); let mut result = if size_hint > 0 { @@ -451,11 +463,11 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::Method<'vir> { &[], ).into()); b.stmts.iter() - .for_each(|s| match s { - vir::StmtGenData::LocalDecl(decl, _) => declarations.push(decl.to_viper(ctx).into()), + .for_each(|s| match s.kind { + vir::StmtKindGenData::LocalDecl(decl, _) => declarations.push(decl.to_viper_no_pos(ctx).into()), _ => (), }); - b.to_viper_extend(&mut result, ctx); + b.to_viper_extend_no_pos(&mut result, ctx); }); ctx.ast.seqn( &result, @@ -468,98 +480,100 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::Method<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::MethodCall<'vir> { type Output = viper::Stmt<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + // `pos` coming from the parent `Stmt` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { ctx.ast.method_call_with_pos( self.method, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - &self.targets.iter().map(|v| v.to_viper(ctx)).collect::>(), - ctx.ast.no_position(), // TODO: position + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + &self.targets.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::PredicateApp<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - ctx.ast.predicate_access_predicate( - ctx.ast.predicate_access( - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - self.target + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + ctx.ast.predicate_access_predicate_with_pos( + ctx.ast.predicate_access_with_pos( + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + self.target, + pos, ), - self.perm.map(|v| v.to_viper(ctx)).unwrap_or_else(|| ctx.ast.full_perm()), - // TODO: position + self.perm.map(|v| v.to_viper_no_pos(ctx)).unwrap_or_else(|| ctx.ast.full_perm()), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Predicate<'vir> { type Output = viper::Predicate<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.predicate( self.name, - &self.args.iter().map(|v| v.to_viper(ctx)).collect::>(), - self.expr.map(|v| v.to_viper(ctx)), + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + self.expr.map(|v| v.to_viper_no_pos(ctx)), ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Program<'vir> { type Output = viper::Program<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.program( - &self.domains.iter().map(|v| v.to_viper(&ctx)).collect::>(), - &self.fields.iter().map(|v| v.to_viper(&ctx)).collect::>(), - &self.functions.iter().map(|v| v.to_viper(&ctx)).collect::>(), - &self.predicates.iter().map(|v| v.to_viper(&ctx)).collect::>(), - &self.methods.iter().map(|v| v.to_viper(&ctx)).collect::>(), + &self.domains.iter().map(|v| v.to_viper_no_pos(&ctx)).collect::>(), + &self.fields.iter().map(|v| v.to_viper_no_pos(&ctx)).collect::>(), + &self.functions.iter().map(|v| v.to_viper_no_pos(&ctx)).collect::>(), + &self.predicates.iter().map(|v| v.to_viper_no_pos(&ctx)).collect::>(), + &self.methods.iter().map(|v| v.to_viper_no_pos(&ctx)).collect::>(), ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::PureAssign<'vir> { type Output = viper::Stmt<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.local_var_assign( // TODO: this won't work, maybe abstract assign? - self.lhs.to_viper(ctx), - self.rhs.to_viper(ctx), + self.lhs.to_viper_no_pos(ctx), + self.rhs.to_viper_no_pos(ctx), ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Stmt<'vir> { type Output = viper::Stmt<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - match self { - vir::StmtGenData::Comment(v) => ctx.ast.comment(v), - vir::StmtGenData::Exhale(v) => ctx.ast.exhale( - v.to_viper(ctx), - ctx.ast.no_position(), // TODO: position + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { + match self.kind { + vir::StmtKindGenData::Comment(v) => ctx.ast.comment(v), + vir::StmtKindGenData::Exhale(v) => ctx.ast.exhale( + v.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), - vir::StmtGenData::Fold(pred) => ctx.ast.fold( - pred.to_viper(ctx), - // TODO: position + vir::StmtKindGenData::Fold(pred) => ctx.ast.fold_with_pos( + pred.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), - vir::StmtGenData::Inhale(v) => ctx.ast.inhale( - v.to_viper(ctx), - ctx.ast.no_position(), // TODO: position + vir::StmtKindGenData::Inhale(v) => ctx.ast.inhale( + v.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), - vir::StmtGenData::LocalDecl(decl, Some(expr)) => ctx.ast.local_var_assign( - ctx.ast.local_var( + vir::StmtKindGenData::LocalDecl(decl, Some(expr)) => ctx.ast.local_var_assign( + ctx.ast.local_var_with_pos( decl.name, - decl.ty.to_viper(ctx), - ctx.ast.no_position(), - // TODO: position + decl.ty.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), - expr.to_viper(ctx), - // TODO: position + expr.to_viper_no_pos(ctx), + // TODO: position? ), - vir::StmtGenData::LocalDecl(decl, None) => ctx.ast.comment(&format!("var {}", decl.name)), - vir::StmtGenData::MethodCall(v) => v.to_viper(ctx), - vir::StmtGenData::PureAssign(v) => v.to_viper(ctx), - vir::StmtGenData::Unfold(pred) => ctx.ast.unfold( - pred.to_viper(ctx), - // TODO: position + vir::StmtKindGenData::LocalDecl(decl, None) => ctx.ast.comment(&format!("var {}", decl.name)), + vir::StmtKindGenData::MethodCall(v) => v.to_viper_with_span(ctx, self.span), + vir::StmtKindGenData::PureAssign(v) => v.to_viper_with_span(ctx, self.span), + vir::StmtKindGenData::Unfold(pred) => ctx.ast.unfold_with_pos( + pred.to_viper_no_pos(ctx), + ctx.span_to_pos(self.span), ), //vir::StmtGenData::Dummy(#[reify_copy] &'vir str), _ => unimplemented!(), @@ -576,21 +590,22 @@ impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::TerminatorStmt<'vir> { Some(1) } } - fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>) { + // `pos` coming from the parent `TerminatorStmt` is used + fn to_viper_extend(&self, vec: &mut Vec, ctx: &ToViperContext<'vir, 'v>, pos: Position) { match self { vir::TerminatorStmtGenData::AssumeFalse => vec.push(ctx.ast.inhale( - ctx.ast.false_lit(), // TODO: position - ctx.ast.no_position(), // TODO: position + ctx.ast.false_lit_with_pos(pos), + pos, )), vir::TerminatorStmtGenData::Goto(label) => vec.push(ctx.ast.goto(&label.name())), - vir::TerminatorStmtGenData::GotoIf(v) => v.to_viper_extend(vec, ctx), + vir::TerminatorStmtGenData::GotoIf(v) => v.to_viper_extend_no_pos(vec, ctx), vir::TerminatorStmtGenData::Exit => vec.push(ctx.ast.comment("return")), vir::TerminatorStmtGenData::Dummy(v) => vec.push(ctx.ast.seqn( &[ ctx.ast.comment(v), ctx.ast.inhale( - ctx.ast.false_lit(), // TODO: position - ctx.ast.no_position(), // TODO: position + ctx.ast.false_lit_with_pos(pos), + pos, ), ], &[], @@ -601,29 +616,30 @@ impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::TerminatorStmt<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::Ternary<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - ctx.ast.cond_exp( - self.cond.to_viper(ctx), - self.then.to_viper(ctx), - self.else_.to_viper(ctx), - // TODO: position + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + ctx.ast.cond_exp_with_pos( + self.cond.to_viper_no_pos(ctx), + self.then.to_viper_no_pos(ctx), + self.else_.to_viper_no_pos(ctx), + pos, ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Trigger<'vir> { type Output = viper::Trigger<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { ctx.ast.trigger( - &self.exprs.iter().map(|v| v.to_viper(ctx)).collect::>(), - // TODO: position + &self.exprs.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + // TODO: position (each trigger should have its own) ) } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Type<'vir> { type Output = viper::Type<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, _pos: Position) -> Self::Output { match self { vir::TypeData::Int => ctx.ast.int_type(), vir::TypeData::Bool => ctx.ast.bool_type(), @@ -634,7 +650,7 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::Type<'vir> { name, &domain.typarams.iter() .zip(params.iter()) - .map(|(domain_param, actual)| (ctx.ast.type_var(domain_param.name), actual.to_viper(ctx))) + .map(|(domain_param, actual)| (ctx.ast.type_var(domain_param.name), actual.to_viper_no_pos(ctx))) .collect::>(), &domain.typarams.iter() .map(|v| ctx.ast.type_var(v.name)) @@ -652,23 +668,24 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::Type<'vir> { impl<'vir, 'v> ToViper<'vir, 'v> for vir::UnOp<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - let expr = self.expr.to_viper(ctx); - // TODO: position + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + let expr = self.expr.to_viper_no_pos(ctx); match self.kind { - vir::UnOpKind::Neg => ctx.ast.minus(expr), - vir::UnOpKind::Not => ctx.ast.not(expr), + vir::UnOpKind::Neg => ctx.ast.minus_with_pos(expr, pos), + vir::UnOpKind::Not => ctx.ast.not_with_pos(expr, pos), } } } impl<'vir, 'v> ToViper<'vir, 'v> for vir::Unfolding<'vir> { type Output = viper::Expr<'v>; - fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>) -> Self::Output { - // TODO: position - ctx.ast.unfolding( - self.target.to_viper(ctx), - self.expr.to_viper(ctx), + // `pos` coming from the parent `Expr` is used + fn to_viper(&self, ctx: &ToViperContext<'vir, 'v>, pos: Position) -> Self::Output { + ctx.ast.unfolding_with_pos( + self.target.to_viper_no_pos(ctx), + self.expr.to_viper_no_pos(ctx), + pos, ) } } diff --git a/viper/src/ast_factory/expression.rs b/viper/src/ast_factory/expression.rs index 6d73f415582..81e858232f9 100644 --- a/viper/src/ast_factory/expression.rs +++ b/viper/src/ast_factory/expression.rs @@ -1254,7 +1254,7 @@ impl<'a> AstFactory<'a> { ) } - pub fn local_var(&self, name: &str, var_type: Type, position: Position<'a>) -> Expr<'a> { + pub fn local_var(&self, name: &str, var_type: Type, position: Position) -> Expr<'a> { self.local_var_with_pos(name, var_type, position) } diff --git a/vir/src/callable_idents.rs b/vir/src/callable_idents.rs index 6c9b2c3caa9..cb00618ccf5 100644 --- a/vir/src/callable_idents.rs +++ b/vir/src/callable_idents.rs @@ -1,6 +1,10 @@ use crate::{ - debug_info::DebugInfo, viper_ident::ViperIdent, with_vcx, DomainParamData, ExprGen, LocalDecl, MethodCallGenData, PredicateAppGen, PredicateAppGenData, StmtGenData, Type, TypeData, VirCtxt + debug_info::DebugInfo, viper_ident::ViperIdent, with_vcx, VirCtxt, }; +use crate::data::*; +use crate::refs::*; +use crate::gendata::*; +use crate::genrefs::*; use sealed::sealed; use std::{backtrace::Backtrace, fmt::Debug}; @@ -336,9 +340,9 @@ impl<'vir, const N: usize> MethodIdent<'vir, KnownArity<'vir, N>> { &self, vcx: &'vir VirCtxt<'tcx>, args: [ExprGen<'vir, Curr, Next>; N], - ) -> StmtGenData<'vir, Curr, Next> { + ) -> StmtKindGenData<'vir, Curr, Next> { assert!(self.1.types_match(&args)); - StmtGenData::MethodCall(vcx.alloc(MethodCallGenData { + StmtKindGenData::MethodCall(vcx.alloc(MethodCallGenData { targets: &[], method: self.name().to_str(), args: vcx.alloc_slice(&args), @@ -379,7 +383,7 @@ impl<'vir> MethodIdent<'vir, UnknownArity<'vir>> { &self, vcx: &'vir VirCtxt<'tcx>, args: &[ExprGen<'vir, Curr, Next>], - ) -> StmtGenData<'vir, Curr, Next> { + ) -> StmtKindGenData<'vir, Curr, Next> { if !self.1.types_match(args) { panic!( "Method {} could not be applied. Expected arg types: {:?}, Actual arg types: {:?}, Debug info: {}", @@ -389,7 +393,7 @@ impl<'vir> MethodIdent<'vir, UnknownArity<'vir>> { self.debug_info() ); } - StmtGenData::MethodCall(vcx.alloc(MethodCallGenData { + StmtKindGenData::MethodCall(vcx.alloc(MethodCallGenData { targets: &[], method: self.name().to_str(), args: vcx.alloc_slice(args), diff --git a/vir/src/data.rs b/vir/src/data.rs index ec06e982966..b584cc2a028 100644 --- a/vir/src/data.rs +++ b/vir/src/data.rs @@ -203,6 +203,7 @@ pub type PredicateData<'vir> = crate::gendata::PredicateGenData<'vir, !, !>; pub type ProgramData<'vir> = crate::gendata::ProgramGenData<'vir, !, !>; pub type PureAssignData<'vir> = crate::gendata::PureAssignGenData<'vir, !, !>; pub type StmtData<'vir> = crate::gendata::StmtGenData<'vir, !, !>; +pub type StmtKindData<'vir> = crate::gendata::StmtKindGenData<'vir, !, !>; pub type TerminatorStmtData<'vir> = crate::gendata::TerminatorStmtGenData<'vir, !, !>; pub type TernaryData<'vir> = crate::gendata::TernaryGenData<'vir, !, !>; pub type TriggerData<'vir> = crate::gendata::TriggerGenData<'vir, !, !>; diff --git a/vir/src/debug.rs b/vir/src/debug.rs index c87025259fc..8f5c14acc35 100644 --- a/vir/src/debug.rs +++ b/vir/src/debug.rs @@ -131,7 +131,6 @@ impl<'vir> Debug for DomainFunctionData<'vir> { impl<'vir, Curr, Next> Debug for ExprGenData<'vir, Curr, Next> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - // TODO: Position, etc if let Some(span) = self.span { write!(f, "/*p:{}*/", span.id)?; } @@ -303,6 +302,15 @@ impl<'vir, Curr, Next> Debug for PredicateAppGenData<'vir, Curr, Next> { } impl<'vir, Curr, Next> Debug for StmtGenData<'vir, Curr, Next> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + if let Some(span) = self.span { + write!(f, "/*p:{}*/", span.id)?; + } + self.kind.fmt(f) + } +} + +impl<'vir, Curr, Next> Debug for StmtKindGenData<'vir, Curr, Next> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::LocalDecl(decl, expr) => { diff --git a/vir/src/gendata.rs b/vir/src/gendata.rs index af8cf3cf48b..ee4dcd9ec3c 100644 --- a/vir/src/gendata.rs +++ b/vir/src/gendata.rs @@ -277,7 +277,24 @@ pub struct MethodCallGenData<'vir, Curr, Next> { } #[derive(VirHash, VirReify, VirSerde)] -pub enum StmtGenData<'vir, Curr, Next> { +pub struct StmtGenData<'vir, Curr, Next> { + pub kind: StmtKindGen<'vir, Curr, Next>, + // #[vir(reify_pass)] pub debug_info: DebugInfo<'vir>, + #[vir(reify_pass)] pub span: Option<&'vir VirSpan<'vir>>, +} + +impl <'vir, Curr: 'vir, Next: 'vir> StmtGenData<'vir, Curr, Next> { + pub fn new(kind: StmtKindGen<'vir, Curr, Next>) -> Self { + with_vcx(|vcx| Self { + kind, + // debug_info: DebugInfo::new(vcx), + span: vcx.top_span(), + }) + } +} + +#[derive(VirHash, VirReify, VirSerde)] +pub enum StmtKindGenData<'vir, Curr, Next> { LocalDecl( #[vir(reify_pass, is_ref)] LocalDecl<'vir>, Option>, diff --git a/vir/src/genrefs.rs b/vir/src/genrefs.rs index 29f0351dee0..bd8f4c03544 100644 --- a/vir/src/genrefs.rs +++ b/vir/src/genrefs.rs @@ -21,6 +21,7 @@ pub type PredicateAppGen<'vir, Curr, Next> = &'vir crate::gendata::PredicateAppG pub type ProgramGen<'vir, Curr, Next> = &'vir crate::gendata::ProgramGenData<'vir, Curr, Next>; pub type PureAssignGen<'vir, Curr, Next> = &'vir crate::gendata::PureAssignGenData<'vir, Curr, Next>; pub type StmtGen<'vir, Curr, Next> = &'vir crate::gendata::StmtGenData<'vir, Curr, Next>; +pub type StmtKindGen<'vir, Curr, Next> = &'vir crate::gendata::StmtKindGenData<'vir, Curr, Next>; pub type TerminatorStmtGen<'vir, Curr, Next> = &'vir crate::gendata::TerminatorStmtGenData<'vir, Curr, Next>; pub type TernaryGen<'vir, Curr, Next> = &'vir crate::gendata::TernaryGenData<'vir, Curr, Next>; pub type TriggerGen<'vir, Curr, Next> = &'vir crate::gendata::TriggerGenData<'vir, Curr, Next>; diff --git a/vir/src/make.rs b/vir/src/make.rs index a046c313398..6271eaee655 100644 --- a/vir/src/make.rs +++ b/vir/src/make.rs @@ -43,25 +43,25 @@ cfg_if! { m: &mut HashMap<&'vir str, Type<'vir>>, e: StmtGen<'vir, Curr, Next> ) { - match e { - StmtGenData::LocalDecl(local, e) => { + match e.kind { + StmtKindGenData::LocalDecl(local, e) => { if let Some(e) = e { check_expr_bindings(m, e); } m.insert(local.name, local.ty); } - StmtGenData::PureAssign(p) => { + StmtKindGenData::PureAssign(p) => { check_expr_bindings(m, p.lhs); check_expr_bindings(m, p.rhs); } - StmtGenData::Inhale(e) | - StmtGenData::Exhale(e) => { + StmtKindGenData::Inhale(e) | + StmtKindGenData::Exhale(e) => { check_expr_bindings(m, e); } - StmtGenData::Unfold(app) | StmtGenData::Fold(app) => { + StmtKindGenData::Unfold(app) | StmtKindGenData::Fold(app) => { check_predicate_app_bindings(m, app); } - StmtGenData::MethodCall(MethodCallGenData { + StmtKindGenData::MethodCall(MethodCallGenData { args, .. }) => { @@ -69,8 +69,8 @@ cfg_if! { check_expr_bindings(m, *arg); } } - StmtGenData::Comment(_) => {}, - StmtGenData::Dummy(_) => todo!(), + StmtKindGenData::Comment(_) => {}, + StmtKindGenData::Dummy(_) => todo!(), } } fn check_expr_bindings<'vir, Curr, Next>( @@ -520,21 +520,27 @@ impl<'tcx> VirCtxt<'tcx> { &'vir self, expr: ExprGen<'vir, Curr, Next> ) -> StmtGen<'vir, Curr, Next> { - self.alloc(StmtGenData::Exhale(expr)) + self.alloc(StmtGenData::new( + self.alloc(StmtKindGenData::Exhale(expr)), + )) } pub fn mk_unfold_stmt<'vir, Curr, Next>( &'vir self, pred_app: PredicateAppGen<'vir, Curr, Next> ) -> StmtGen<'vir, Curr, Next> { - self.alloc(StmtGenData::Unfold(pred_app)) + self.alloc(StmtGenData::new( + self.alloc(StmtKindGenData::Unfold(pred_app)), + )) } pub fn mk_fold_stmt<'vir, Curr, Next>( &'vir self, pred_app: PredicateAppGen<'vir, Curr, Next> ) -> StmtGen<'vir, Curr, Next> { - self.alloc(StmtGenData::Fold(pred_app)) + self.alloc(StmtGenData::new( + self.alloc(StmtKindGenData::Fold(pred_app)), + )) } pub fn mk_pure_assign_stmt<'vir, Curr, Next>( @@ -543,14 +549,14 @@ impl<'tcx> VirCtxt<'tcx> { rhs: ExprGen<'vir, Curr, Next> ) -> StmtGen<'vir, Curr, Next> { assert_eq!(lhs.ty(),rhs.ty()); - self.alloc( - StmtGenData::PureAssign( + self.alloc(StmtGenData::new( + self.alloc(StmtKindGenData::PureAssign( self.alloc(PureAssignGenData { lhs, - rhs - }) - ) - ) + rhs, + }), + )), + )) } pub fn mk_local_decl_stmt<'vir, Curr, Next>( @@ -558,7 +564,9 @@ impl<'tcx> VirCtxt<'tcx> { local: LocalDecl<'vir>, expr: Option> ) -> StmtGen<'vir, Curr, Next> { - self.alloc(StmtGenData::LocalDecl(local, expr)) + self.alloc(StmtGenData::new( + self.alloc(StmtKindGenData::LocalDecl(local, expr)), + )) } pub fn mk_assume_false_stmt<'vir, Curr, Next>( @@ -591,9 +599,9 @@ impl<'tcx> VirCtxt<'tcx> { &'vir self, msg: &'vir str ) -> StmtGen<'vir, Curr, Next> { - self.alloc( - StmtGenData::Comment(msg) - ) + self.alloc(StmtGenData::new( + self.alloc(StmtKindGenData::Comment(msg)), + )) } pub fn mk_goto_if_stmt<'vir, Curr, Next>( diff --git a/vir/src/refs.rs b/vir/src/refs.rs index 89f2d245ead..f308555ba8d 100644 --- a/vir/src/refs.rs +++ b/vir/src/refs.rs @@ -25,6 +25,7 @@ pub type PredicateApp<'vir> = &'vir crate::data::PredicateAppData<'vir>; pub type Program<'vir> = &'vir crate::data::ProgramData<'vir>; pub type PureAssign<'vir> = &'vir crate::data::PureAssignData<'vir>; pub type Stmt<'vir> = &'vir crate::data::StmtData<'vir>; +pub type StmtKind<'vir> = &'vir crate::data::StmtKindData<'vir>; pub type TerminatorStmt<'vir> = &'vir crate::data::TerminatorStmtData<'vir>; pub type Ternary<'vir> = &'vir crate::data::TernaryData<'vir>; pub type Trigger<'vir> = &'vir crate::data::TriggerData<'vir>; From e9772677e75f48bf745077e1b10a6e918cc304d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Sat, 22 Jun 2024 16:51:52 +0200 Subject: [PATCH 19/53] fix VIR translation errors --- prusti-viper/src/lib.rs | 4 ++-- viper/src/ast_factory/expression.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prusti-viper/src/lib.rs b/prusti-viper/src/lib.rs index 607d312c6f7..d2ab81bb285 100644 --- a/prusti-viper/src/lib.rs +++ b/prusti-viper/src/lib.rs @@ -169,7 +169,7 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::BinOp<'vir> { vir::BinOpKind::Sub => ctx.ast.sub_with_pos(lhs, rhs, pos), vir::BinOpKind::Mul => ctx.ast.mul_with_pos(lhs, rhs, pos), vir::BinOpKind::Div => ctx.ast.div_with_pos(lhs, rhs, pos), - vir::BinOpKind::Mod => ctx.ast.mul_with_pos(lhs, rhs, pos), + vir::BinOpKind::Mod => ctx.ast.mod_with_pos(lhs, rhs, pos), vir::BinOpKind::Implies => ctx.ast.implies_with_pos(lhs, rhs, pos), } } @@ -603,7 +603,7 @@ impl<'vir, 'v> ToViperVec<'vir, 'v> for vir::TerminatorStmt<'vir> { vir::TerminatorStmtGenData::Dummy(v) => vec.push(ctx.ast.seqn( &[ ctx.ast.comment(v), - ctx.ast.inhale( + ctx.ast.assert( ctx.ast.false_lit_with_pos(pos), pos, ), diff --git a/viper/src/ast_factory/expression.rs b/viper/src/ast_factory/expression.rs index 81e858232f9..5806840e792 100644 --- a/viper/src/ast_factory/expression.rs +++ b/viper/src/ast_factory/expression.rs @@ -151,7 +151,7 @@ impl<'a> AstFactory<'a> { self.div_with_pos(left, right, self.no_position()) } - pub fn module_with_pos(&self, left: Expr, right: Expr, pos: Position) -> Expr<'a> { + pub fn mod_with_pos(&self, left: Expr, right: Expr, pos: Position) -> Expr<'a> { build_ast_node_with_pos!( self, Expr, @@ -162,8 +162,8 @@ impl<'a> AstFactory<'a> { ) } - pub fn module(&self, left: Expr, right: Expr) -> Expr<'a> { - self.module_with_pos(left, right, self.no_position()) + pub fn mod_(&self, left: Expr, right: Expr) -> Expr<'a> { + self.mod_with_pos(left, right, self.no_position()) } pub fn lt_cmp_with_pos(&self, left: Expr, right: Expr, pos: Position) -> Expr<'a> { From 04086177ad378fbd74c2a6616fc538b54f2d3377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Sat, 22 Jun 2024 16:59:37 +0200 Subject: [PATCH 20/53] cast tuple components in pure contexts --- prusti-encoder/src/encoders/mir_pure.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prusti-encoder/src/encoders/mir_pure.rs b/prusti-encoder/src/encoders/mir_pure.rs index cebf1c40f04..3d2dec42071 100644 --- a/prusti-encoder/src/encoders/mir_pure.rs +++ b/prusti-encoder/src/encoders/mir_pure.rs @@ -814,6 +814,10 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { self.deps.require_ref::>(cast_args) .unwrap().apply_cast_if_necessary(self.vcx, proj_app) + } else if let TyKind::Tuple(_) = tykind { + self + .deps.require_local::>(ty) + .unwrap().cast_to_concrete_if_possible(self.vcx, proj_app) } else { proj_app }; From 9a994652bc2100d211e317772474c7a23bd434a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Sun, 7 Jul 2024 17:05:52 -0700 Subject: [PATCH 21/53] update Viper toolchain, to avoid error filtering (see Silicon#735) --- viper-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper-toolchain b/viper-toolchain index 519e6c04257..c86e8032dca 100644 --- a/viper-toolchain +++ b/viper-toolchain @@ -1 +1 @@ -v-2023-07-18-0828 +v-2024-03-27-0722 From f3cf4efe0e95c7c881f3634d707f71690cfc116a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Sun, 7 Jul 2024 17:07:23 -0700 Subject: [PATCH 22/53] small generic fixes --- prusti-encoder/src/encoders/type/lifted/cast.rs | 5 ++++- prusti-encoder/src/encoders/type/most_generic_ty.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/prusti-encoder/src/encoders/type/lifted/cast.rs b/prusti-encoder/src/encoders/type/lifted/cast.rs index eb680ae8e0f..ee82d45baca 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast.rs @@ -192,7 +192,10 @@ where .require_local::>(task_key.actual) .unwrap(); if let CastersEncOutputRef::Casters { make_generic, .. } = generic_cast.cast { - GenericCastOutputRef::Cast(Cast::new(T::to_generic_applicator(make_generic), &[])) + GenericCastOutputRef::Cast(Cast::new( + T::to_generic_applicator(make_generic), + generic_cast.ty_args, + )) } else { unreachable!() } diff --git a/prusti-encoder/src/encoders/type/most_generic_ty.rs b/prusti-encoder/src/encoders/type/most_generic_ty.rs index 498b23e2267..8dd10b4c1e1 100644 --- a/prusti-encoder/src/encoders/type/most_generic_ty.rs +++ b/prusti-encoder/src/encoders/type/most_generic_ty.rs @@ -90,7 +90,8 @@ impl<'tcx> MostGenericTy<'tcx> { | TyKind::Int(_) | TyKind::Never | TyKind::Param(_) - | TyKind::Uint(_) => Vec::new(), + | TyKind::Uint(_) + | TyKind::Str => Vec::new(), other => todo!("generics for {:?}", other), } } From f53725a314daea20653f4b325af6117ccd4fe830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Sun, 7 Jul 2024 17:10:29 -0700 Subject: [PATCH 23/53] handle assertion failures, add precondition precision --- prusti-encoder/src/encoders/mir_impure.rs | 34 +++++++++++++++++------ prusti-encoder/src/encoders/pure/spec.rs | 7 +++-- prusti-encoder/src/lib.rs | 7 ++++- prusti/src/verifier.rs | 1 + vir/src/spans.rs | 18 ++++++++---- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index c0858c990e1..f0bc696ab22 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -189,11 +189,6 @@ impl<'vir, 'enc, E: TaskEncoder> ImpureEncVisitor<'vir, 'enc, E> { .push(stmt); } - fn unreachable(&mut self) -> vir::TerminatorStmt<'vir> { - self.stmt(self.vcx.mk_exhale_stmt(self.vcx.mk_bool::())); - self.vcx.mk_assume_false_stmt() - } - /* fn project_fields( &mut self, @@ -907,8 +902,12 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< method_args.extend(encoded_ty_args); self.vcx().with_span(span, |vcx| { - vcx.handle_error("call.precondition:assertion.false", move || { - Some(vec![PrustiError::verification("precondition might not hold", span.into())]) + vcx.handle_error("call.precondition:assertion.false", move |reason_span_opt| { + let mut error = PrustiError::verification("precondition might not hold", span.into()); + if let Some(reason_span) = reason_span_opt { + error.add_note_mut("the failing precondition is here", Some(reason_span.into())); + } + Some(vec![error]) }); self.stmt(self.vcx.alloc(vir::StmtGenData::new( self.vcx.alloc(func_out.method_ref.apply(self.vcx, &method_args)), @@ -935,7 +934,16 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< .alloc(vir::CfgBlockLabelData::BasicBlock(target.as_usize())), ) }) - .unwrap_or_else(|| self.unreachable()) + .unwrap_or_else(|| { + // TODO: detect panic causes, adjust message accordingly + self.vcx().with_span(span, |vcx| { + vcx.handle_error("exhale.failed:assertion.false", move |_| { + Some(vec![PrustiError::verification("unreachable statement might be reached", span.into())]) + }); + self.stmt(self.vcx.mk_exhale_stmt(self.vcx.mk_bool::())); + self.vcx.mk_assume_false_stmt() + }) + }) } mir::TerminatorKind::Assert { cond, @@ -976,7 +984,15 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< &[], )]), otherwise, &[]) } - mir::TerminatorKind::Unreachable => self.unreachable(), + mir::TerminatorKind::Unreachable => { + self.vcx().with_span(span, |vcx| { + vcx.handle_error("exhale.failed:assertion.false", move |_| { + Some(vec![PrustiError::verification("unreachable statement might be reached", span.into())]) + }); + self.stmt(self.vcx.mk_exhale_stmt(self.vcx.mk_bool::())); + self.vcx.mk_assume_false_stmt() + }) + } mir::TerminatorKind::Drop { target, .. } => { self.vcx.mk_goto_stmt( diff --git a/prusti-encoder/src/encoders/pure/spec.rs b/prusti-encoder/src/encoders/pure/spec.rs index 62fdde766d9..656133e50f6 100644 --- a/prusti-encoder/src/encoders/pure/spec.rs +++ b/prusti-encoder/src/encoders/pure/spec.rs @@ -102,7 +102,10 @@ impl TaskEncoder for MirSpecEnc { .unwrap() .expr; let expr = expr.reify(vcx, (*spec_def_id, pre_args)); - to_bool.apply(vcx, [expr]) + let span = vcx.tcx().def_span(spec_def_id); + vcx.with_span(span, |vcx| { + to_bool.apply(vcx, [expr]) + }) }) .collect::>>(); @@ -122,7 +125,7 @@ impl TaskEncoder for MirSpecEnc { .map(|spec_def_id| { let span = vcx.tcx().def_span(spec_def_id); vcx.with_span(span, |vcx| { - vcx.handle_error("postcondition.violated:assertion.false", move || { + vcx.handle_error("postcondition.violated:assertion.false", move |_| { Some(vec![PrustiError::verification("postcondition might not hold", span.into())]) }); let expr = deps diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index a8692a62e3b..b283366c420 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -239,9 +239,14 @@ pub fn test_entrypoint<'tcx>( } } -pub fn backtranslate_error(error_kind: &str, offending_pos_id: usize) -> Option> { +pub fn backtranslate_error( + error_kind: &str, + offending_pos_id: usize, + reason_pos_id: Option, +) -> Option> { vir::with_vcx(|vcx| vcx.backtranslate( error_kind, offending_pos_id, + reason_pos_id, )) } diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 0ccc26b5a42..bc4f1538cb0 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -90,6 +90,7 @@ pub fn verify<'tcx>( .flat_map(|error| prusti_encoder::backtranslate_error( &error.full_id, error.offending_pos_id.unwrap().parse::().unwrap(), + error.reason_pos_id.and_then(|id| id.parse::().ok()), ) .expect("verification error could not be backtranslated") .into_iter()) diff --git a/vir/src/spans.rs b/vir/src/spans.rs index c530f300ca6..9e508878b2d 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -5,7 +5,7 @@ use crate::VirCtxt; pub struct VirSpanHandler<'vir> { error_kind: &'static str, - handler: Box Option> + 'vir>, + handler: Box) -> Option> + 'vir>, next: Option>>, } @@ -93,7 +93,7 @@ impl<'tcx> VirCtxt<'tcx> { pub fn handle_error( &'tcx self, error_kind: &'static str, - handler: impl Fn() -> Option> + 'tcx, + handler: impl Fn(Option) -> Option> + 'tcx, ) { let top_span_id = self.top_span().unwrap().id; let mut manager = self.spans.borrow_mut(); @@ -111,14 +111,22 @@ impl<'tcx> VirCtxt<'tcx> { } /// Attempt to backtranslate the given error at the given position. - pub fn backtranslate(&'tcx self, error_kind: &str, pos: usize) -> Option> { + pub fn backtranslate( + &'tcx self, + error_kind: &str, + offending_pos_id: usize, + reason_pos_id: Option, + ) -> Option> { let manager = self.spans.borrow(); - let mut span_opt = manager.all.get(pos); + let reason_span_opt = reason_pos_id + .and_then(|id| manager.all.get(id)) + .map(|vir_span| vir_span.span); + let mut span_opt = manager.all.get(offending_pos_id); while let Some(span) = span_opt { let mut handler_opt = manager.handlers.get(&span.id); while let Some(handler) = handler_opt { if handler.error_kind == error_kind { - if let Some(errors) = (handler.handler)() { + if let Some(errors) = (handler.handler)(reason_span_opt) { return Some(errors); } } From 846c4479033fcbef0eb80898ef24caeed1d49696 Mon Sep 17 00:00:00 2001 From: trk Date: Mon, 15 Jul 2024 10:49:33 +0200 Subject: [PATCH 24/53] Post-rebase fixes Move backtranslation to where server messages are processed. Move `EncodingInfo` to encoder crate. --- Cargo.lock | 4 +- prusti-encoder/Cargo.toml | 3 +- .../src/ide/encoding_info.rs | 0 prusti-encoder/src/ide/mod.rs | 2 + .../src/ide/vsc_span.rs | 0 prusti-encoder/src/lib.rs | 3 +- prusti-server/Cargo.toml | 1 + prusti-server/src/ide/mod.rs | 4 +- prusti-server/src/lib.rs | 48 +++++++++++-------- prusti/src/ide_helper/compiler_info.rs | 2 +- prusti/src/verifier.rs | 26 ++-------- 11 files changed, 44 insertions(+), 49 deletions(-) rename {prusti-server => prusti-encoder}/src/ide/encoding_info.rs (100%) create mode 100644 prusti-encoder/src/ide/mod.rs rename {prusti-server => prusti-encoder}/src/ide/vsc_span.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 4b46391f835..19ae5df0434 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1872,8 +1872,9 @@ dependencies = [ "mir-state-analysis", "prusti-interface", "prusti-rustc-interface", - "prusti-server", "prusti-utils", + "serde", + "serde_json", "task-encoder", "tracing 0.1.0", "vir", @@ -1926,6 +1927,7 @@ dependencies = [ "log", "num_cpus", "once_cell", + "prusti-encoder", "prusti-interface", "prusti-rustc-interface", "prusti-utils", diff --git a/prusti-encoder/Cargo.toml b/prusti-encoder/Cargo.toml index 9e713a490ea..4aefb915972 100644 --- a/prusti-encoder/Cargo.toml +++ b/prusti-encoder/Cargo.toml @@ -12,12 +12,13 @@ prusti-utils = { path = "../prusti-utils" } cfg-if = "1.0.0" prusti-rustc-interface = { path = "../prusti-rustc-interface" } prusti-interface = { path = "../prusti-interface" } -prusti-server = { path = "../prusti-server" } mir-ssa-analysis = { path = "../mir-ssa-analysis" } mir-state-analysis = { path = "../mir-state-analysis" } task-encoder = { path = "../task-encoder" } vir = { path = "../vir" } tracing = { path = "../tracing" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/prusti-server/src/ide/encoding_info.rs b/prusti-encoder/src/ide/encoding_info.rs similarity index 100% rename from prusti-server/src/ide/encoding_info.rs rename to prusti-encoder/src/ide/encoding_info.rs diff --git a/prusti-encoder/src/ide/mod.rs b/prusti-encoder/src/ide/mod.rs new file mode 100644 index 00000000000..8ff9365ccdc --- /dev/null +++ b/prusti-encoder/src/ide/mod.rs @@ -0,0 +1,2 @@ +pub mod encoding_info; +pub mod vsc_span; \ No newline at end of file diff --git a/prusti-server/src/ide/vsc_span.rs b/prusti-encoder/src/ide/vsc_span.rs similarity index 100% rename from prusti-server/src/ide/vsc_span.rs rename to prusti-encoder/src/ide/vsc_span.rs diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index b283366c420..1d49b622cd0 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -10,6 +10,7 @@ extern crate rustc_type_ir; mod encoders; mod encoder_traits; pub mod request; +pub mod ide; use prusti_interface::{environment::{EnvBody, EnvQuery}, PrustiError}; @@ -22,12 +23,12 @@ use prusti_rustc_interface::{ }; use prusti_utils::config; use task_encoder::TaskEncoder; -use prusti_server::ide::encoding_info::SpanOfCallContracts; use crate::encoders::{lifted::{ casters::{CastTypeImpure, CastTypePure, CastersEnc}, ty_constructor::TyConstructorEnc }, MirPolyImpureEnc}; +use crate::ide::encoding_info::SpanOfCallContracts; pub fn test_entrypoint<'tcx>( tcx: ty::TyCtxt<'tcx>, diff --git a/prusti-server/Cargo.toml b/prusti-server/Cargo.toml index 7e510584dcc..697b2951202 100644 --- a/prusti-server/Cargo.toml +++ b/prusti-server/Cargo.toml @@ -20,6 +20,7 @@ doctest = false log = { version = "0.4", features = ["release_max_level_info"] } vir = { path = "../vir" } viper = { path = "../viper" } +prusti-encoder = { path = "../prusti-encoder" } prusti-interface = { path = "../prusti-interface" } prusti-viper = { path = "../prusti-viper" } prusti-utils = { path = "../prusti-utils" } diff --git a/prusti-server/src/ide/mod.rs b/prusti-server/src/ide/mod.rs index 08c75998eb4..5cec7f6cdd8 100644 --- a/prusti-server/src/ide/mod.rs +++ b/prusti-server/src/ide/mod.rs @@ -1,3 +1 @@ -pub mod ide_verification_result; -pub mod encoding_info; -pub mod vsc_span; \ No newline at end of file +pub mod ide_verification_result; \ No newline at end of file diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 765740ede6f..806fc2a534d 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -24,7 +24,7 @@ use prusti_rustc_interface::{ errors::MultiSpan, }; use viper::{self, PersistentCache, Viper}; -use ide::{encoding_info::EncodingInfo, ide_verification_result::IdeVerificationResult}; +use crate::ide::ide_verification_result::IdeVerificationResult; use once_cell::sync::Lazy; mod client; @@ -168,24 +168,34 @@ fn handle_termination_message( *overall_result = VerificationResult::Failure; } viper::VerificationResultKind::Failure(errors) => { - for verification_error in errors { - debug!( - "Verification error in {}: {:?}", - program_name.clone(), - verification_error - ); - // FIXME: temporary error emition, delete when the above is implemented again - env_diagnostic.span_err_with_help_and_notes( - MultiSpan::from_span(DUMMY_SP.into()), - &format!( - "Verification error in {}: {:?}", - program_name.clone(), - verification_error - ), - &None, - &[], - ); - } + errors + .into_iter() + .flat_map(|error| prusti_encoder::backtranslate_error( + &error.full_id, + error.offending_pos_id.unwrap().parse::().unwrap(), + error.reason_pos_id.and_then(|id| id.parse::().ok()), + ) + .expect("verification error could not be backtranslated") + .into_iter()) + .for_each(|prusti_error| prusti_error.emit(env_diagnostic)); + // for verification_error in errors { + // debug!( + // "Verification error in {}: {:?}", + // program_name.clone(), + // verification_error + // ); + // // FIXME: temporary error emition, delete when the above is implemented again + // env_diagnostic.span_err_with_help_and_notes( + // MultiSpan::from_span(DUMMY_SP.into()), + // &format!( + // "Verification error in {}: {:?}", + // program_name.clone(), + // verification_error + // ), + // &None, + // &[], + // ); + // } *overall_result = VerificationResult::Failure; } viper::VerificationResultKind::JavaException(exception) => { diff --git a/prusti/src/ide_helper/compiler_info.rs b/prusti/src/ide_helper/compiler_info.rs index 430233ae2c8..f13c975d8f4 100644 --- a/prusti/src/ide_helper/compiler_info.rs +++ b/prusti/src/ide_helper/compiler_info.rs @@ -5,7 +5,7 @@ use prusti_rustc_interface::{ span::{source_map::SourceMap, Span}, data_structures::fx::FxHashMap, }; -use prusti_server::ide::{vsc_span::VscSpan, encoding_info::SpanOfCallContracts}; +use prusti_encoder::ide::{vsc_span::VscSpan, encoding_info::SpanOfCallContracts}; use serde::Serialize; /// This struct will be passed to prusti-assistant containing information diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index bc4f1538cb0..d220d78d7b1 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -14,7 +14,7 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashMap, hir::def_id::DefId, }; -use prusti_server::ide::encoding_info::{SpanOfCallContracts, EncodingInfo}; +use prusti_encoder::ide::encoding_info::{SpanOfCallContracts, EncodingInfo}; use crate::ide_helper::compiler_info::ProcDef; #[tracing::instrument(name = "prusti::verify", level = "debug", skip(env))] @@ -74,31 +74,11 @@ pub fn verify<'tcx>( let program = request.program; - let mut results = prusti_server::verify_programs(vec![program]); - assert_eq!(results.len(), 1); // TODO: eventually verify separate methods as separate programs again? + let mut result = prusti_server::verify_programs(&env.diagnostic, vec![program]); - let result = results.pop().unwrap().1; println!("verification result: {result:?}"); - let success = match result { - viper::VerificationResult::Success => true, - viper::VerificationResult::JavaException(_e) => false, - viper::VerificationResult::ConsistencyErrors(_e) => false, - viper::VerificationResult::Failure(errors) => { - errors - .into_iter() - .flat_map(|error| prusti_encoder::backtranslate_error( - &error.full_id, - error.offending_pos_id.unwrap().parse::().unwrap(), - error.reason_pos_id.and_then(|id| id.parse::().ok()), - ) - .expect("verification error could not be backtranslated") - .into_iter()) - .for_each(|prusti_error| prusti_error.emit(&env.diagnostic)); - false - } - }; - if !success { + if matches!(result, VerificationResult::Failure) { // TODO: This will be unnecessary if diagnostic errors are emitted // earlier, it's useful for now to ensure that Prusti returns an // error code when verification fails From 14025ce09e9c35d3101ef7dfbcabcdd916c0f0fe Mon Sep 17 00:00:00 2001 From: trk Date: Tue, 16 Jul 2024 11:50:40 +0200 Subject: [PATCH 25/53] Port quantifier features skelleton from PR --- docs/dev-guide/src/config/flags.md | 20 +++- prusti-server/src/backend.rs | 66 +++++++++++++ prusti-server/src/lib.rs | 107 ++++++++++++++++++---- prusti-server/src/server_message.rs | 30 +++--- prusti-server/src/verification_request.rs | 16 ++-- prusti-utils/src/config.rs | 12 +++ viper-sys/build.rs | 16 ++-- viper/src/ast_factory/expression.rs | 4 +- 8 files changed, 213 insertions(+), 58 deletions(-) diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index 7c19210eea7..9564911b390 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -70,8 +70,10 @@ | [`SMT_QI_BOUND_GLOBAL_KIND`](#smt_qi_bound_global_kind) | `Option` | `None` | A | | [`SMT_QI_BOUND_TRACE`](#smt_qi_bound_trace) | `Option` | `None` | A | | [`SMT_QI_BOUND_TRACE_KIND`](#smt_qi_bound_trace_kind) | `Option` | `None` | A | -| [`SMT_QI_IGNORE_BUILTIN`](#smt_qi_ignore_builtin) | `bool` | `true` | A | | [`SMT_QI_EAGER_THRESHOLD`](#smt_qi_eager_threshold) | `u64` | `1000` | A | +| [`SMT_QI_IGNORE_BUILTIN`](#smt_qi_ignore_builtin) | `bool` | `true` | A | +| [`SMT_QI_PROFILE`](#smt_qi_profile) | `Option` | `None` | A | +| [`SMT_QI_PROFILE_FREQ`](#smt_qi_profile_freq) | `Option` | `None` | A | | [`SMT_SOLVER_PATH`](#smt_solver_path) | `Option` | `env::var("Z3_EXE")` | A | | [`SMT_SOLVER_WRAPPER_PATH`](#smt_solver_wrapper_path) | `Option` | `None` | A | | [`SMT_UNIQUE_TRIGGERS_BOUND`](#smt_unique_triggers_bound) | `Option` | `None` | A | @@ -437,10 +439,6 @@ If not `None`, checks that the number of quantifier instantiations in each trace > **Note:** Requires `USE_SMT_WRAPPER` to be `true`. -## `SMT_QI_IGNORE_BUILTIN` - -When enabled, ignores the built-in quantifiers in SMT quantifier instantiation bounds checking. - ## `SMT_QI_EAGER_THRESHOLD` A threshold controlling how many times Z3 should instantiate a single quantifier. This option controls a tradeoff between performance and completeness: @@ -448,6 +446,18 @@ A threshold controlling how many times Z3 should instantiate a single quantifier * Setting it to a too small value, may lead to spurious verification errors and unstable verification. + Setting it to a too large value, may significantly impact performance. +## `SMT_QI_IGNORE_BUILTIN` + +When enabled, ignores the built-in quantifiers in SMT quantifier instantiation bounds checking. + +## `SMT_QI_PROFILE` + +When enabled, the Z3 backend periodically (and on finish) reports the number of quantifier instantiations to Viper. + +## `SMT_QI_PROFILE_FREQ` + +Frequency of the quantifier instantiation reporting of Z3 (every X instantiations, a report is issued). + ## `SMT_SOLVER_PATH` Path to Z3. diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index c85db67fdde..ef70f266394 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -112,6 +112,72 @@ fn polling_function( .unwrap(); debug!("Polling thread received {}", jni.class_name(msg).as_str()); match jni.class_name(msg).as_str() { + "viper.silver.reporter.QuantifierInstantiationsMessage" => { + let msg_wrapper = silver::reporter::QuantifierInstantiationsMessage::with(env); + let q_name = + jni.get_string(jni.unwrap_result(msg_wrapper.call_quantifier(msg))); + let q_inst = jni.unwrap_result(msg_wrapper.call_instantiations(msg)); + debug!("QuantifierInstantiationsMessage: {} {}", q_name, q_inst); + // also matches the "-aux" and "_precondition" quantifiers generated + // we encoded the position id in the line and column number since this is not used by + // prusti either way + if q_name.starts_with("prog.l") { + let no_pref = q_name.strip_prefix("prog.l").unwrap(); + let stripped = no_pref + .strip_suffix("-aux") + .or(no_pref.strip_suffix("_precondition")) + .unwrap_or(no_pref); + let parsed = stripped.parse::(); + match parsed { + Ok(pos_id) => { + sender + .send(ServerMessage::QuantifierInstantiation { + q_name, + insts: u64::try_from(q_inst).unwrap(), + pos_id, + }) + .unwrap(); + } + _ => info!("Unexpected quantifier name {}", q_name), + } + } + }, + "viper.silver.reporter.QuantifierChosenTriggersMessage" => { + let obj_wrapper = java::lang::Object::with(env); + let positioned_wrapper = silver::ast::Positioned::with(env); + let msg_wrapper = silver::reporter::QuantifierChosenTriggersMessage::with(env); + + let viper_quant = jni.unwrap_result(msg_wrapper.call_quantifier(msg)); + let viper_quant_str = + jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(viper_quant))); + debug!("quantifier chosen trigger quant: {viper_quant_str}"); + // we encoded the position id in the line and column number since this is not used by + // prusti either way + let pos = jni.unwrap_result(positioned_wrapper.call_pos(viper_quant)); + let pos_string = + jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(pos))); + debug!("quantifier chosen trigger pos: {pos_string}"); + // TODO: the PR unconditionally does `pos_string.rfind('.').unwrap()` which crashes when there is no position. + // Is that intended? + if let Some(pos_id_index) = pos_string.rfind('.') { + // let pos_id_index = pos_string.rfind('.').unwrap(); + let pos_id = pos_string[pos_id_index + 1..].parse::().unwrap(); + + let viper_triggers = + jni.get_string(jni.unwrap_result(msg_wrapper.call_triggers__string(msg))); + debug!( + "QuantifierChosenTriggersMessage: {} {} {}", + viper_quant_str, viper_triggers, pos_id + ); + sender + .send(ServerMessage::QuantifierChosenTriggers { + viper_quant: viper_quant_str, + triggers: viper_triggers, + pos_id, + }) + .unwrap(); + } + }, "viper.silver.reporter.VerificationTerminationMessage" => return, "viper.silver.reporter.BlockReachedMessage" => { let msg_wrapper = silver::reporter::BlockReachedMessage::with(env); diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 806fc2a534d..451d307917b 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -25,7 +25,6 @@ use prusti_rustc_interface::{ }; use viper::{self, PersistentCache, Viper}; use crate::ide::ide_verification_result::IdeVerificationResult; -use once_cell::sync::Lazy; mod client; mod process_verification; @@ -100,6 +99,12 @@ async fn handle_stream( verification_messages: impl Stream, ) -> VerificationResult { let mut overall_result = VerificationResult::Success; + // we want quantifier_pos_ID + program_name + q_name as identifier because there are + // different q_names for the same ID and each program reports independent results + // key: (pos_id, program_name), key to result: q_name result: num_instantiations + let mut quantifier_instantiations: FxHashMap<(u64, String), FxHashMap> = + FxHashMap::default(); + let mut prusti_errors: Vec<_> = vec![]; pin_mut!(verification_messages); @@ -107,6 +112,16 @@ async fn handle_stream( while let Some((program_name, server_msg)) = verification_messages.next().await { match server_msg { ServerMessage::Termination(result) => handle_termination_message(env_diagnostic, program_name, result, &mut prusti_errors, &mut overall_result), + ServerMessage::QuantifierInstantiation { + q_name, + insts, + pos_id, + } => handle_quantifier_instantiation_message(env_diagnostic, program_name, q_name, insts, pos_id, &mut quantifier_instantiations), + ServerMessage::QuantifierChosenTriggers { + viper_quant, + triggers, + pos_id, + } => handle_quantifier_chosen_triggers_message(env_diagnostic, program_name, viper_quant, triggers, pos_id), ServerMessage::BlockReached { viper_method, vir_label, @@ -171,12 +186,12 @@ fn handle_termination_message( errors .into_iter() .flat_map(|error| prusti_encoder::backtranslate_error( - &error.full_id, - error.offending_pos_id.unwrap().parse::().unwrap(), - error.reason_pos_id.and_then(|id| id.parse::().ok()), - ) - .expect("verification error could not be backtranslated") - .into_iter()) + &error.full_id, + error.offending_pos_id.unwrap().parse::().unwrap(), + error.reason_pos_id.and_then(|id| id.parse::().ok()), + ) + .expect("verification error could not be backtranslated") + .into_iter()) .for_each(|prusti_error| prusti_error.emit(env_diagnostic)); // for verification_error in errors { // debug!( @@ -184,18 +199,6 @@ fn handle_termination_message( // program_name.clone(), // verification_error // ); - // // FIXME: temporary error emition, delete when the above is implemented again - // env_diagnostic.span_err_with_help_and_notes( - // MultiSpan::from_span(DUMMY_SP.into()), - // &format!( - // "Verification error in {}: {:?}", - // program_name.clone(), - // verification_error - // ), - // &None, - // &[], - // ); - // } *overall_result = VerificationResult::Failure; } viper::VerificationResultKind::JavaException(exception) => { @@ -210,7 +213,71 @@ fn handle_termination_message( } } -// TODO: probably in some other util module +fn handle_quantifier_instantiation_message( + env_diagnostic: &EnvDiagnostic<'_>, + program_name: String, + q_name: String, + insts: u64, + pos_id: u64, + quantifier_instantiations: &mut FxHashMap<(u64, String), FxHashMap> +) { + if config::report_viper_messages() { + debug!("Received #{insts} quantifier instantiations of {q_name} for position id {pos_id} in verification of {program_name}"); + // match self.encoder.error_manager().position_manager().get_span_from_id(pos_id) { + // Some(span) => { + // let key = (pos_id, program_name.clone()); + // if !quantifier_instantiations.contains_key(&key) { + // quantifier_instantiations.insert(key.clone(), FxHashMap::default()); + // } + // let map = quantifier_instantiations.get_mut(&key).unwrap(); + // // for some reason, the aux quantifiers by the same z3 instance (same uniqueId + // // in silicon) can have different amount of instantiations. + // // e.g. we receive a message with 100 instantiations for a certain quantifier + // // and afterwards a message with 20 instantiations for the same one. + // // All verifying the same viper program and by the same z3 instance. + // // Since I don see a better way to take this into account than taking the + // // maximum, that is exactly what we do here. + // let old_inst = map.get(&q_name).unwrap_or(&0); + // map.insert(q_name, std::cmp::max(insts, *old_inst)); + // let mut n: u64 = 0; + // for (q_name, insts) in map.iter() { + // debug!("Key: {q_name}, Value: {insts}"); + // n += *insts; + // } + // PrustiError::message( + // format!("quantifierInstantiationsMessage{}", + // json!({"instantiations": n, "method": program_name}), + // ), span.clone() + // ).emit(env_diagnostic); + // }, + // None => error!("#{insts} quantifier instantiations of {q_name} for unknown position id {pos_id} in verification of {program_name}"), + // } + } +} + +fn handle_quantifier_chosen_triggers_message( + env_diagnostic: &EnvDiagnostic<'_>, + program_name: String, + viper_quant: String, + triggers: String, + pos_id: u64 +) { + if config::report_viper_messages() && pos_id != 0 { + debug!("Received quantifier triggers {triggers} for quantifier {viper_quant} for position id {pos_id} in verification of {program_name}"); + // match self.encoder.error_manager().position_manager().get_span_from_id(pos_id) { + // Some(span) => { + // PrustiError::message( + // format!("quantifierChosenTriggersMessage{}", + // json!({"viper_quant": viper_quant, "triggers": triggers}), + // ), span.clone() + // ).emit(&self.env.diagnostic); + // }, + // None => error!("Invalid position id {pos_id} for viper quantifier {viper_quant} in verification of {program_name}"), + // } + } +} + +// TODO: may or may not need program name translation fn viper_method_to_rust_method(_viper_method: &String, program_name: &String) -> Option { Some(program_name.clone()) } diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs index bff938cbf3e..456bb2fe674 100644 --- a/prusti-server/src/server_message.rs +++ b/prusti-server/src/server_message.rs @@ -14,22 +14,22 @@ pub enum ServerMessage { /// has terminated. Termination(VerificationResult), - // /// A message created by the Viper backend with Z3 about - // /// the number of instantiations of the named quantifier. - // QuantifierInstantiation { - // q_name: String, - // insts: u64, - // pos_id: u64, - // }, + /// A message created by the Viper backend with Z3 about + /// the number of instantiations of the named quantifier. + QuantifierInstantiation { + q_name: String, + insts: u64, + pos_id: u64, + }, - // /// Also created by the Viper backend. The viper_quant is the expression the - // /// quantifier was translated to in silver syntax while the triggers are the - // /// triggers provided or automatically derived for this quantifier. - // QuantifierChosenTriggers { - // viper_quant: String, - // triggers: String, - // pos_id: u64, - // }, + /// Also created by the Viper backend. The viper_quant is the expression the + /// quantifier was translated to in silver syntax while the triggers are the + /// triggers provided or automatically derived for this quantifier. + QuantifierChosenTriggers { + viper_quant: String, + triggers: String, + pos_id: u64, + }, /// Contains a path id, label and viper method name corresponding to a symbolic /// execution path through the program being verified, the VIR label of the cfg diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 32473f9dad5..a1ad71de8b2 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -58,14 +58,14 @@ impl ViperBackendConfig { config::counterexample() ); - // if let Some(smt_qi_profile) = config::smt_qi_profile() { - // prover_args = format!("{prover_args} smt.qi.profile={smt_qi_profile}"); - // } - // if let Some(smt_qi_profile_freq) = config::smt_qi_profile_freq() { - // prover_args = - // format!("{prover_args} smt.qi.profile_freq={smt_qi_profile_freq}"); - // } - + if let Some(smt_qi_profile) = config::smt_qi_profile() { + prover_args = format!("{prover_args} smt.qi.profile={smt_qi_profile}"); + } + if let Some(smt_qi_profile_freq) = config::smt_qi_profile_freq() { + prover_args = + format!("{prover_args} smt.qi.profile_freq={smt_qi_profile_freq}"); + } + verifier_args.push(prover_args); verifier_args.extend(vec!["--logLevel".to_string(), "ERROR".to_string()]); diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index ff2d3310671..037329522f7 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -103,6 +103,8 @@ lazy_static::lazy_static! { settings.set_default("quiet", false).unwrap(); settings.set_default("assert_timeout", 10_000).unwrap(); settings.set_default("smt_qi_eager_threshold", 1000).unwrap(); + settings.set_default::>("smt_qi_profile", None).unwrap(); + settings.set_default::>("smt_qi_profile_freq", None).unwrap(); settings.set_default("report_viper_messages", false).unwrap(); settings.set_default("use_more_complete_exhale", true).unwrap(); settings.set_default("skip_unsupported_features", false).unwrap(); @@ -510,6 +512,16 @@ pub fn smt_qi_eager_threshold() -> u64 { read_setting("smt_qi_eager_threshold") } +/// Whether to make Z3 periodically report quantifier instantiations to Viper. +pub fn smt_qi_profile() -> Option { + read_setting("smt_qi_profile") +} + +/// The frequency for the report of quantifier instantiations of Z3 to Viper. +pub fn smt_qi_profile_freq() -> Option { + read_setting("smt_qi_profile_freq") +} + /// Whether to report the messages produced by the viper backend (e.g. quantifier instantiations, /// quantifier triggers) pub fn report_viper_messages() -> bool { diff --git a/viper-sys/build.rs b/viper-sys/build.rs index 33f94022e55..5d624364f00 100644 --- a/viper-sys/build.rs +++ b/viper-sys/build.rs @@ -163,14 +163,14 @@ fn main() { method!("hasNewMessage"), method!("getNewMessage"), ]), - // java_class!("viper.silver.reporter.QuantifierChosenTriggersMessage", vec![ - // method!("quantifier"), - // method!("triggers_string"), - // ]), - // java_class!("viper.silver.reporter.QuantifierInstantiationsMessage", vec![ - // method!("quantifier"), - // method!("instantiations"), - // ]), + java_class!("viper.silver.reporter.QuantifierChosenTriggersMessage", vec![ + method!("quantifier"), + method!("triggers_string"), + ]), + java_class!("viper.silver.reporter.QuantifierInstantiationsMessage", vec![ + method!("quantifier"), + method!("instantiations"), + ]), java_class!("viper.silver.reporter.BlockReachedMessage", vec![ method!("methodName"), method!("label"), diff --git a/viper/src/ast_factory/expression.rs b/viper/src/ast_factory/expression.rs index 5806840e792..4a46eb69983 100644 --- a/viper/src/ast_factory/expression.rs +++ b/viper/src/ast_factory/expression.rs @@ -1196,7 +1196,7 @@ impl<'a> AstFactory<'a> { variables: &[LocalVarDecl], triggers: &[Trigger], expr: Expr, - _pos: Position, // FIXME: Why??? + pos: Position, ) -> Expr<'a> { build_ast_node_with_pos!( self, @@ -1205,7 +1205,7 @@ impl<'a> AstFactory<'a> { self.jni.new_seq(&map_to_jobjects!(variables)), self.jni.new_seq(&map_to_jobjects!(triggers)), expr.to_jobject(), - self.no_position().to_jobject() + pos.to_jobject() ) } From 70b9855ffa27dcfec549e203d1f2433ccf401311 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 18 Jul 2024 16:25:11 +0200 Subject: [PATCH 26/53] Fix typo --- prusti-interface/src/specs/typed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prusti-interface/src/specs/typed.rs b/prusti-interface/src/specs/typed.rs index 2041266c555..8f4624cf511 100644 --- a/prusti-interface/src/specs/typed.rs +++ b/prusti-interface/src/specs/typed.rs @@ -410,7 +410,7 @@ impl SpecGraph { /// fn main() { /// let s = SomeStruct; /// let r = s.foo::(100); // i32 implements MarkerTrait -> C_S is active -/// let r = s.foo::(100); // i32 does not implement MarkerTrait -> B_S is active +/// let r = s.foo::(100); // u32 does not implement MarkerTrait -> B_S is active /// } /// ``` impl SpecGraph { From d195424732db804183805031f0f9cb6a909c6886 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 18 Jul 2024 16:57:02 +0200 Subject: [PATCH 27/53] Fix viper program passing and factor out cache - The viper program is now sent using a `GlobalRef`, so it actually compiles. - Cache usage no longer involves the `VerificationRequestProcessing` object. - The cache is now lazy, therefore only loads if the `ENABLE_CACHE` flag is set. - The `VerificationRequestProcessing` object is no longer constructed when all the requests are cache hits. The thread it holds is also not spawned as a result. --- prusti-server/src/lib.rs | 85 +++++++++++++++++------ prusti-server/src/process_verification.rs | 65 ++++------------- prusti-server/src/server.rs | 62 ++++++++++++++--- prusti-server/src/verification_request.rs | 46 ++++++------ viper/src/ast_factory/macros.rs | 2 +- 5 files changed, 150 insertions(+), 110 deletions(-) diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 216c05afe17..cc9199e0578 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -23,7 +23,7 @@ use prusti_rustc_interface::{ span::DUMMY_SP, errors::MultiSpan, }; -use viper::{self, PersistentCache, Viper}; +use viper::{self, Cache, PersistentCache}; use ide::{encoding_info::EncodingInfo, ide_verification_result::IdeVerificationResult}; use once_cell::sync::Lazy; @@ -49,6 +49,7 @@ use rustc_hash::FxHashMap; use serde_json::json; use async_stream::stream; use futures_util::{pin_mut, Stream, StreamExt}; +use std::sync::{self, Arc}; /// Verify a list of programs. /// Returns a list of (program_name, verification_result) tuples. @@ -67,11 +68,7 @@ pub fn verify_programs( backend_config: ViperBackendConfig::new(backend), }; (program_name, request) - }); - - if config::show_ide_info() { - emit_contract_spans(env_diagnostic); - } + }).collect(); let mut stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); // runtime used either for client connecting to server sequentially @@ -87,11 +84,14 @@ pub fn verify_programs( let overall_result = rt.block_on(async { if let Some(server_address) = config::server_address() { - let verification_messages = verify_requests_server(verification_requests.collect(), server_address); + let verification_messages = verify_requests_server(verification_requests, server_address); handle_stream(env_diagnostic, verification_messages).await } else { - let vrp = VerificationRequestProcessing::new(); - let verification_messages = verify_requests_local(verification_requests.collect(), &vrp); + let cache = Lazy::new(move || + Arc::new(sync::Mutex::new(PersistentCache::load_cache(config::cache_path()))) + ); + let vrp = Lazy::new(VerificationRequestProcessing::new); + let verification_messages = verify_requests_local(verification_requests, &cache, &vrp); handle_stream(env_diagnostic, verification_messages).await } }); @@ -262,24 +262,67 @@ fn verify_requests_server( fn verify_requests_local<'a>( verification_requests: Vec<(String, VerificationRequest)>, - vrp: &'a VerificationRequestProcessing, + cache: &'a Lazy>, impl FnOnce() -> Arc>>, + vrp: &'a Lazy VerificationRequestProcessing + 'a>, ) -> impl Stream + 'a { let verification_stream = stream! { for (program_name, request) in verification_requests { - yield vrp.verify(request).map(move |msg| (program_name.clone(), msg)); + let program_name_clone = program_name.clone(); + let request_hash = request.get_hash(); + if config::enable_cache() { + if let Some(result) = fetch_from_cache(cache, request_hash, &program_name) { + yield futures::stream::once(async move { + (program_name.clone(), ServerMessage::Termination(result)) + }).left_stream(); + } + } + yield vrp.verify(request).map(move |msg| { + if let ServerMessage::Termination(result) = &msg { + if config::enable_cache() && !matches!(result.kind, viper::VerificationResultKind::JavaException(_)) { + store_to_cache(cache, request_hash, &program_name_clone, &result); + } + } + (program_name_clone.clone(), msg) + }).right_stream(); } }; verification_stream.flatten() } -pub fn emit_contract_spans(env_diagnostic: &EnvDiagnostic<'_>) { - let encoding_info = EncodingInfo { - // call_contract_spans: self.encoder.spans_of_call_contracts.borrow().to_vec(), - call_contract_spans: "call contract spans not implemented".to_string(), - }; - PrustiError::message( - format!("encodingInfo{}", encoding_info.to_json_string()), - DUMMY_SP.into(), - ) - .emit(env_diagnostic); +pub(crate) fn fetch_from_cache( + cache: &Lazy>, impl FnOnce() -> Arc>>, + hash: u64, + program_name: &str, +) -> Option { + if let Some(mut result) = (&mut *cache.lock().unwrap()).get(hash) { + info!( + "Using cached result {:?} for program {}", + &result, + program_name + ); + /*if config::dump_viper_program() { + ast_utils.with_local_frame(16, || { + let _ = build_or_dump_viper_program(); + }); + } + normalization_info.denormalize_result(&mut result);*/ + result.cached = true; + Some(result) + } else { + None + } } + +pub(crate) fn store_to_cache( + cache: &Lazy>, impl FnOnce() -> Arc>>, + hash: u64, + program_name: &str, + result: &viper::VerificationResult, +) { + info!( + "Storing new cached result {:?} for program {}", + result, + program_name + ); + (&mut *cache.lock().unwrap()).insert(hash, result.clone()); +} \ No newline at end of file diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 18c9b123f40..f77347ba3e7 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -37,8 +37,6 @@ pub struct VerificationRequestProcessing { // mtx_tx_verreq has to be dropped before thread_join #[allow(dead_code)] thread_join: ThreadJoin, - // the cache is used across different threads - mtx_cache_arc: Arc>, } impl Default for VerificationRequestProcessing { @@ -57,17 +55,14 @@ impl VerificationRequestProcessing { let (tx_verreq, rx_verreq) = mpsc::channel(); let mtx_rx_servermsg = lock::Mutex::new(rx_servermsg); let mtx_tx_verreq = sync::Mutex::new(tx_verreq); - let cache = PersistentCache::load_cache(config::cache_path()); - let mtx_cache_arc = Arc::new(sync::Mutex::new(cache)); - let handle = thread::spawn(move || verification_thread(rx_verreq, tx_servermsg, mtx_cache_arc)); + let handle = thread::spawn(move || verification_thread(rx_verreq, tx_servermsg)); Self { mtx_rx_servermsg, mtx_tx_verreq, thread_join: ThreadJoin { handle: Some(handle), }, - mtx_cache_arc, } } @@ -79,69 +74,33 @@ impl VerificationRequestProcessing { request.program.get_name(), ); - // Early return in case of cache hit - // let mut cache = PersistentCache::load_cache(config::cache_path()); - if config::enable_cache() { - if let Some(mut result) = (&mut *self.mtx_cache_arc.lock().unwrap()).get(hash) { - info!( - "Using cached result {:?} for program {}", - &result, - request.program.get_name() - ); - /*if config::dump_viper_program() { - ast_utils.with_local_frame(16, || { - let _ = build_or_dump_viper_program(); - }); - } - normalization_info.denormalize_result(&mut result);*/ - result.cached = true; - return Either::Left( - futures::stream::once(async {ServerMessage::Termination(result)}) - ); - } - }; - request.send_request(&self.mtx_tx_verreq); - // return a stream that has as last non-None message the ServerMessage::Termination - Either::Right( - futures::stream::unfold(false, move |done: bool| async move { - if done { - return None; - } - let msg = self.mtx_rx_servermsg.lock().await.recv().unwrap(); - let mut done = false; - if let ServerMessage::Termination(_) = msg { - done = true; - } - Some((msg, done)) - }) - ) - } - - pub fn save_cache(&self) { - self.mtx_tx_verreq - .lock() - .unwrap() - .send(ServerRequest::SaveCache) - .unwrap(); + futures::stream::unfold(false, move |done: bool| async move { + if done { + return None; + } + let msg = self.mtx_rx_servermsg.lock().await.recv().unwrap(); + let mut done = false; + if let ServerMessage::Termination(_) = msg { + done = true; + } + Some((msg, done)) + }) } } fn verification_thread( rx_verreq: mpsc::Receiver, tx_servermsg: mpsc::Sender, - mtx_cache_arc: Arc>, ) { debug!("Verification thread started."); while let Ok(request) = rx_verreq.recv() { match request { ServerRequest::Verification(verification_request) => verification_request.execute( - mtx_cache_arc, &tx_servermsg, ), - ServerRequest::SaveCache => mtx_cache_arc.lock().unwrap().save(), } } debug!("Verification thread finished."); diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index 2f32a20ec6f..9764d014fae 100644 --- a/prusti-server/src/server.rs +++ b/prusti-server/src/server.rs @@ -4,18 +4,19 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::VerificationRequestProcessing; +use crate::{VerificationRequestProcessing, ServerMessage, VerificationRequest}; use futures_util::{pin_mut, SinkExt, StreamExt}; use log::info; use once_cell::sync::Lazy; use std::{ net::{Ipv4Addr, SocketAddr}, - sync::mpsc, + sync::{self, mpsc, Arc}, thread, }; use tokio::runtime::Builder; -use viper::{PersistentCache, Viper}; +use viper::{Cache, PersistentCache, Viper, VerificationResultKind}; use warp::Filter; +use prusti_utils::config; #[derive(Debug)] struct BincodeReject(bincode::Error); @@ -47,7 +48,9 @@ pub fn spawn_server_thread() -> SocketAddr { // It has to have a static lifetime because warp websockets need their closures to have a static // lifetime and we need to access this object in them. static VERIFICATION_REQUEST_PROCESSING: Lazy = - Lazy::new(VerificationRequestProcessing::new); + Lazy::new(|| VerificationRequestProcessing::new()); +static CACHE: Lazy>> = + Lazy::new(|| Arc::new(sync::Mutex::new(PersistentCache::load_cache(config::cache_path())))); fn listen_on_port_with_address_callback(port: u16, address_callback: F) -> ! where @@ -68,13 +71,34 @@ where ws.on_upgrade(|websocket| async { let (mut ws_send, mut ws_recv) = websocket.split(); let req_msg = ws_recv.next().await.unwrap().unwrap(); - let verification_request = req_msg + let verification_request: VerificationRequest = req_msg .to_str() .and_then(|s: &str| serde_json::from_str(s).unwrap()) .unwrap(); - let stream = VERIFICATION_REQUEST_PROCESSING.verify(verification_request); + + let request_hash = verification_request.get_hash(); + let program_name = verification_request.program.get_name().to_string(); + // return early in case of a cache hit + let stream = if config::enable_cache() { + match crate::fetch_from_cache(&CACHE, request_hash, &program_name) { + Some(result) => + futures::stream::once(async move { + ServerMessage::Termination(result) + }) + .left_stream(), + None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + } + } else { + VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + }; pin_mut!(stream); + while let Some(server_msg) = stream.next().await { + if let ServerMessage::Termination(result) = &server_msg { + if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { + crate::store_to_cache(&CACHE, request_hash, &program_name, &result); + } + }; ws_send .send(warp::filters::ws::Message::text( serde_json::to_string(&server_msg).unwrap(), @@ -95,10 +119,30 @@ where ws.on_upgrade(|websocket| async { let (mut ws_send, mut ws_recv) = websocket.split(); let req_msg = ws_recv.next().await.unwrap().unwrap(); - let verification_request = bincode::deserialize(req_msg.as_bytes()).unwrap(); - let stream = VERIFICATION_REQUEST_PROCESSING.verify(verification_request); + let verification_request: VerificationRequest = bincode::deserialize(req_msg.as_bytes()).unwrap(); + let request_hash = verification_request.get_hash(); + let program_name = verification_request.program.get_name().to_string(); + // return early in case of a cache hit + let stream = if config::enable_cache() { + match crate::fetch_from_cache(&CACHE, request_hash, &program_name) { + Some(result) => + futures::stream::once(async move { + ServerMessage::Termination(result) + }) + .left_stream(), + None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + } + } else { + VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + }; pin_mut!(stream); + while let Some(server_msg) = stream.next().await { + if let ServerMessage::Termination(result) = &server_msg { + if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { + crate::store_to_cache(&CACHE, request_hash, &program_name, &result); + } + }; ws_send .send(warp::filters::ws::Message::binary( bincode::serialize(&server_msg).unwrap(), @@ -116,7 +160,7 @@ where .and(warp::path("save")) .and(warp::path::end()) .map(move || { - VERIFICATION_REQUEST_PROCESSING.save_cache(); + CACHE.lock().unwrap().save(); warp::reply::html("Saved") }); diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index fe428afe9f2..02066907973 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -19,18 +19,20 @@ use std::{ path::PathBuf, }; +/// Server requests that are sent between client and server +/// These are oblivious to the backend being used for verification +/// and could potentially be something other than verification. pub(crate) enum ServerRequest { Verification(ServerVerificationRequest), - SaveCache, } -pub struct ServerVerificationRequest { - // hash of the original VerificationRequest to store in the cache - hash: u64, +/// Server requests that are sent between threads of the verifying process. +pub(crate) struct ServerVerificationRequest { kind: ServerVerificationRequestKind, } -pub enum ServerVerificationRequestKind { +/// Specifies the kind of backend to be used for verification and carries necessary data. +enum ServerVerificationRequestKind { // viper program, program name, backend config JVMViperRequest(jni::objects::GlobalRef, Arc, String, ViperBackendConfig), } @@ -38,7 +40,6 @@ pub enum ServerVerificationRequestKind { impl ServerVerificationRequest { pub fn execute<'v, 't: 'v>( &self, - mtx_cache_arc: Arc>, sender: &mpsc::Sender, ) { let mut stopwatch = Stopwatch::start("prusti-server", "verifier startup"); @@ -49,36 +50,28 @@ impl ServerVerificationRequest { time_ms: 0, }; - match self.kind { + match &self.kind { ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, viper_arc, program_name, backend_config) => { let verification_context = viper_arc.attach_current_thread(); - let backend = match backend_config.backend { + let mut backend = match backend_config.backend { VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( new_viper_verifier( &program_name, &verification_context, - backend_config, + backend_config.clone(), ), &verification_context, &viper_arc, ), }; stopwatch.start_next("backend verification"); - result.item_name = program_name; - result.kind = backend.verify(viper_program_ref.as_obj(), sender.clone()); + result.item_name = program_name.clone(); + result.kind = backend.verify(viper::Program::new(viper_program_ref.as_obj()), sender.clone()); + drop(viper_program_ref); result.time_ms = stopwatch.finish().as_millis(); } } - // Don't cache Java exceptions, which might be due to misconfigured paths. - if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { - info!( - "Storing new cached result {:?}", - &result, - ); - (&mut *mtx_cache_arc.lock().unwrap()).insert(self.hash, result.clone()); - } - /*normalization_info.denormalize_result(&mut result.kind);*/ sender.send(ServerMessage::Termination(result)).unwrap(); } @@ -95,6 +88,8 @@ impl<'vir> VerificationRequest { self.program.get_hash() } + /// Builds a more specific request based on the backend configuration and sends it. + /// This includes the vir-viper translation if the Viper backend is used. pub(crate) fn send_request( &self, mtx_tx_verreq: &sync::Mutex>, @@ -116,6 +111,8 @@ impl<'vir> VerificationRequest { let mut stopwatch = Stopwatch::start("prusti-server backend", "construction of JVM objects"); + // TODO: this panics when run over a server and two different requests are sent + // because it tries to construct the same JVM twice. let viper_arc = Arc::new( Viper::new_with_args(&config::viper_home(), config::extra_jvm_args()) ); @@ -146,14 +143,11 @@ impl<'vir> VerificationRequest { let kind = ServerVerificationRequestKind::JVMViperRequest( viper_program_ref, - viper_arc, + viper_arc.clone(), self.program.get_name().to_string(), - self.backend_config, + self.backend_config.clone(), ); - ServerVerificationRequest { - hash: self.get_hash(), - kind, - } + ServerVerificationRequest { kind } }) }, } diff --git a/viper/src/ast_factory/macros.rs b/viper/src/ast_factory/macros.rs index 2e6d513b992..4a20a2fff9b 100644 --- a/viper/src/ast_factory/macros.rs +++ b/viper/src/ast_factory/macros.rs @@ -11,7 +11,7 @@ macro_rules! jobject_wrapper { } impl<'a> $name<'a> { - pub(crate) fn new(obj: JObject<'a>) -> Self { + pub fn new(obj: JObject<'a>) -> Self { $name { obj } } pub fn to_jobject(self) -> JObject<'a> { From 0b309596c9313709d7d27ddba43b17cdf3a3f6f8 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 19 Jul 2024 09:56:12 +0200 Subject: [PATCH 28/53] Fix the JVM being attempted to be instantiated multiple times --- prusti-server/src/backend.rs | 13 ++++----- prusti-server/src/process_verification.rs | 4 +-- prusti-server/src/verification_request.rs | 33 +++++++++++------------ 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 98a3bcd741b..5c4dee0a073 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -1,4 +1,4 @@ -use crate::{dump_viper_program, ServerMessage}; +use crate::{dump_viper_program, ServerMessage, VIPER}; use log::{debug, info}; use prusti_utils::{ config, @@ -15,7 +15,6 @@ pub enum Backend<'a> { Viper( viper::Verifier<'a>, &'a VerificationContext<'a>, - &'a Arc, ), } @@ -26,14 +25,14 @@ impl<'a> Backend<'a> { sender: mpsc::Sender, ) -> VerificationResultKind { match self { - Backend::Viper(ref mut verifier, viper_thread, viper_arc) => { + Backend::Viper(ref mut verifier, viper_thread) => { let mut stopwatch = Stopwatch::start("prusti-server backend", "viper verification"); let ast_utils = viper_thread.new_ast_utils(); ast_utils.with_local_frame(16, || { if config::report_viper_messages() { - verify_and_poll_msgs(verifier, viper_thread, viper_arc, viper_program, sender) + verify_and_poll_msgs(verifier, viper_thread, viper_program, sender) } else { verifier.verify(viper_program) } @@ -46,7 +45,6 @@ impl<'a> Backend<'a> { fn verify_and_poll_msgs( verifier: &mut viper::Verifier, verification_context: &viper::VerificationContext, - viper_arc: &Arc, viper_program: viper::Program, sender: mpsc::Sender, ) -> VerificationResultKind { @@ -65,7 +63,7 @@ fn verify_and_poll_msgs( // start thread for polling messages thread::scope(|scope| { - let polling_thread = scope.spawn(|| polling_function(viper_arc, &rep_glob_ref, sender)); + let polling_thread = scope.spawn(|| polling_function(&rep_glob_ref, sender)); kind = verifier.verify(viper_program); polling_thread.join().unwrap(); }); @@ -74,11 +72,10 @@ fn verify_and_poll_msgs( } fn polling_function( - viper_arc: &Arc, rep_glob_ref: &jni::objects::GlobalRef, sender: mpsc::Sender, ) { - let verification_context = viper_arc.attach_current_thread(); + let verification_context = VIPER.get().expect("Viper was not instantiated before polling").attach_current_thread(); let env = verification_context.env(); let jni = JniUtils::new(env); let reporter_instance = rep_glob_ref.as_obj(); diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index f77347ba3e7..4706d21fe77 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -74,7 +74,7 @@ impl VerificationRequestProcessing { request.program.get_name(), ); - request.send_request(&self.mtx_tx_verreq); + request.send(&self.mtx_tx_verreq); futures::stream::unfold(false, move |done: bool| async move { if done { @@ -98,7 +98,7 @@ fn verification_thread( while let Ok(request) = rx_verreq.recv() { match request { - ServerRequest::Verification(verification_request) => verification_request.execute( + ServerRequest::Verification(verification_request) => verification_request.process( &tx_servermsg, ), } diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 02066907973..8ad8192dd28 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -14,11 +14,17 @@ use prusti_utils::{ use crate::{ServerMessage, Backend}; use once_cell::sync::Lazy; use std::{ - sync::{self, mpsc, Arc}, + sync::{self, mpsc, Arc, OnceLock}, fs::create_dir_all, path::PathBuf, }; +/// The JVM object should only instantiated once, so it is stored in a +/// global reference, shareable between threads and execution of different +/// requests when running the server. No Mutex is used because +/// the value should only ever be used through non mutable references. +pub(crate) static VIPER: OnceLock = OnceLock::new(); + /// Server requests that are sent between client and server /// These are oblivious to the backend being used for verification /// and could potentially be something other than verification. @@ -34,11 +40,11 @@ pub(crate) struct ServerVerificationRequest { /// Specifies the kind of backend to be used for verification and carries necessary data. enum ServerVerificationRequestKind { // viper program, program name, backend config - JVMViperRequest(jni::objects::GlobalRef, Arc, String, ViperBackendConfig), + JVMViperRequest(jni::objects::GlobalRef, String, ViperBackendConfig), } impl ServerVerificationRequest { - pub fn execute<'v, 't: 'v>( + pub fn process<'v, 't: 'v>( &self, sender: &mpsc::Sender, ) { @@ -51,8 +57,9 @@ impl ServerVerificationRequest { }; match &self.kind { - ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, viper_arc, program_name, backend_config) => { - let verification_context = viper_arc.attach_current_thread(); + ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, program_name, backend_config) => { + let viper = VIPER.get().expect("ServerVerificationRequest: Viper was not instantiated before processing a request"); + let verification_context = viper.attach_current_thread(); let mut backend = match backend_config.backend { VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( new_viper_verifier( @@ -61,7 +68,6 @@ impl ServerVerificationRequest { backend_config.clone(), ), &verification_context, - &viper_arc, ), }; stopwatch.start_next("backend verification"); @@ -90,7 +96,7 @@ impl<'vir> VerificationRequest { /// Builds a more specific request based on the backend configuration and sends it. /// This includes the vir-viper translation if the Viper backend is used. - pub(crate) fn send_request( + pub(crate) fn send( &self, mtx_tx_verreq: &sync::Mutex>, ) { @@ -103,20 +109,14 @@ impl<'vir> VerificationRequest { .unwrap(); } - fn build_request( - &self, - ) -> ServerVerificationRequest { + fn build_request(&self) -> ServerVerificationRequest { match self.backend_config.backend { VerificationBackend::Carbon | VerificationBackend::Silicon => { let mut stopwatch = Stopwatch::start("prusti-server backend", "construction of JVM objects"); - // TODO: this panics when run over a server and two different requests are sent - // because it tries to construct the same JVM twice. - let viper_arc = Arc::new( - Viper::new_with_args(&config::viper_home(), config::extra_jvm_args()) - ); - let context = viper_arc.attach_current_thread(); + let viper = VIPER.get_or_init(|| Viper::new_with_args(&config::viper_home(), config::extra_jvm_args())); + let context = viper.attach_current_thread(); let ast_utils = context.new_ast_utils(); ast_utils.with_local_frame(16, || { @@ -143,7 +143,6 @@ impl<'vir> VerificationRequest { let kind = ServerVerificationRequestKind::JVMViperRequest( viper_program_ref, - viper_arc.clone(), self.program.get_name().to_string(), self.backend_config.clone(), ); From 62d2a089c78f8b60a62e46a28ce01bf7c6c7c3e3 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 19 Jul 2024 10:17:15 +0200 Subject: [PATCH 29/53] Fix `GlobalRef` drop warning, change `Backend` verify signature --- prusti-server/src/backend.rs | 5 +++-- prusti-server/src/verification_request.rs | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 5c4dee0a073..59f5feac679 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -15,22 +15,23 @@ pub enum Backend<'a> { Viper( viper::Verifier<'a>, &'a VerificationContext<'a>, + jni::objects::GlobalRef, ), } impl<'a> Backend<'a> { pub fn verify( &mut self, - viper_program: viper::Program, sender: mpsc::Sender, ) -> VerificationResultKind { match self { - Backend::Viper(ref mut verifier, viper_thread) => { + Backend::Viper(ref mut verifier, viper_thread, viper_program_ref) => { let mut stopwatch = Stopwatch::start("prusti-server backend", "viper verification"); let ast_utils = viper_thread.new_ast_utils(); ast_utils.with_local_frame(16, || { + let viper_program = viper::Program::new(viper_program_ref.as_obj()); if config::report_viper_messages() { verify_and_poll_msgs(verifier, viper_thread, viper_program, sender) } else { diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 8ad8192dd28..4cfed6637ea 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -44,8 +44,9 @@ enum ServerVerificationRequestKind { } impl ServerVerificationRequest { + /// Process and consume the request pub fn process<'v, 't: 'v>( - &self, + self, sender: &mpsc::Sender, ) { let mut stopwatch = Stopwatch::start("prusti-server", "verifier startup"); @@ -56,7 +57,7 @@ impl ServerVerificationRequest { time_ms: 0, }; - match &self.kind { + match self.kind { ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, program_name, backend_config) => { let viper = VIPER.get().expect("ServerVerificationRequest: Viper was not instantiated before processing a request"); let verification_context = viper.attach_current_thread(); @@ -68,12 +69,12 @@ impl ServerVerificationRequest { backend_config.clone(), ), &verification_context, + viper_program_ref, ), }; stopwatch.start_next("backend verification"); result.item_name = program_name.clone(); - result.kind = backend.verify(viper::Program::new(viper_program_ref.as_obj()), sender.clone()); - drop(viper_program_ref); + result.kind = backend.verify(sender.clone()); result.time_ms = stopwatch.finish().as_millis(); } } From 9daeac665e99a64551f3e357e898e865bf4a03ef Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 19 Jul 2024 15:04:02 +0200 Subject: [PATCH 30/53] Refactor some cache and closure handling --- prusti-server/src/lib.rs | 52 +++-------- prusti-server/src/server.rs | 176 ++++++++++++++++++------------------ 2 files changed, 101 insertions(+), 127 deletions(-) diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index cc9199e0578..52c2eb94ec7 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -270,7 +270,12 @@ fn verify_requests_local<'a>( let program_name_clone = program_name.clone(); let request_hash = request.get_hash(); if config::enable_cache() { - if let Some(result) = fetch_from_cache(cache, request_hash, &program_name) { + if let Some(result) = Lazy::force(cache).get(request_hash) { + info!( + "Using cached result {:?} for program {}", + &result, + &program_name + ); yield futures::stream::once(async move { (program_name.clone(), ServerMessage::Termination(result)) }).left_stream(); @@ -279,7 +284,12 @@ fn verify_requests_local<'a>( yield vrp.verify(request).map(move |msg| { if let ServerMessage::Termination(result) = &msg { if config::enable_cache() && !matches!(result.kind, viper::VerificationResultKind::JavaException(_)) { - store_to_cache(cache, request_hash, &program_name_clone, &result); + info!( + "Storing new cached result {:?} for program {}", + &result, + &program_name_clone + ); + cache.insert(request_hash, result.clone()); } } (program_name_clone.clone(), msg) @@ -288,41 +298,3 @@ fn verify_requests_local<'a>( }; verification_stream.flatten() } - -pub(crate) fn fetch_from_cache( - cache: &Lazy>, impl FnOnce() -> Arc>>, - hash: u64, - program_name: &str, -) -> Option { - if let Some(mut result) = (&mut *cache.lock().unwrap()).get(hash) { - info!( - "Using cached result {:?} for program {}", - &result, - program_name - ); - /*if config::dump_viper_program() { - ast_utils.with_local_frame(16, || { - let _ = build_or_dump_viper_program(); - }); - } - normalization_info.denormalize_result(&mut result);*/ - result.cached = true; - Some(result) - } else { - None - } -} - -pub(crate) fn store_to_cache( - cache: &Lazy>, impl FnOnce() -> Arc>>, - hash: u64, - program_name: &str, - result: &viper::VerificationResult, -) { - info!( - "Storing new cached result {:?} for program {}", - result, - program_name - ); - (&mut *cache.lock().unwrap()).insert(hash, result.clone()); -} \ No newline at end of file diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index 9764d014fae..3bbb842239e 100644 --- a/prusti-server/src/server.rs +++ b/prusti-server/src/server.rs @@ -65,103 +65,24 @@ where let json_verify = warp::path!("json" / "verify") .and(warp::filters::ws::ws()) - // unsure why these are needed since the thread encoding the viper program is a different one .map(init_vcx) - .map(|ws: warp::filters::ws::Ws| { - ws.on_upgrade(|websocket| async { - let (mut ws_send, mut ws_recv) = websocket.split(); - let req_msg = ws_recv.next().await.unwrap().unwrap(); - let verification_request: VerificationRequest = req_msg - .to_str() - .and_then(|s: &str| serde_json::from_str(s).unwrap()) - .unwrap(); - - let request_hash = verification_request.get_hash(); - let program_name = verification_request.program.get_name().to_string(); - // return early in case of a cache hit - let stream = if config::enable_cache() { - match crate::fetch_from_cache(&CACHE, request_hash, &program_name) { - Some(result) => - futures::stream::once(async move { - ServerMessage::Termination(result) - }) - .left_stream(), - None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() - } - } else { - VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() - }; - pin_mut!(stream); - - while let Some(server_msg) = stream.next().await { - if let ServerMessage::Termination(result) = &server_msg { - if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { - crate::store_to_cache(&CACHE, request_hash, &program_name, &result); - } - }; - ws_send - .send(warp::filters::ws::Message::text( - serde_json::to_string(&server_msg).unwrap(), - )) - .await - .unwrap(); - } - ws_send.close().await.unwrap(); - // receive the client close to complete the handshake - ws_recv.next().await.unwrap().unwrap(); - }) - }); + .map(move |ws: warp::filters::ws::Ws| WsJsonMessageHandler::on_upgrade(ws)); let bincode_verify = warp::path!("bincode" / "verify") .and(warp::filters::ws::ws()) .map(init_vcx) - .map(|ws: warp::filters::ws::Ws| { - ws.on_upgrade(|websocket| async { - let (mut ws_send, mut ws_recv) = websocket.split(); - let req_msg = ws_recv.next().await.unwrap().unwrap(); - let verification_request: VerificationRequest = bincode::deserialize(req_msg.as_bytes()).unwrap(); - let request_hash = verification_request.get_hash(); - let program_name = verification_request.program.get_name().to_string(); - // return early in case of a cache hit - let stream = if config::enable_cache() { - match crate::fetch_from_cache(&CACHE, request_hash, &program_name) { - Some(result) => - futures::stream::once(async move { - ServerMessage::Termination(result) - }) - .left_stream(), - None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() - } - } else { - VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() - }; - pin_mut!(stream); - - while let Some(server_msg) = stream.next().await { - if let ServerMessage::Termination(result) = &server_msg { - if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { - crate::store_to_cache(&CACHE, request_hash, &program_name, &result); - } - }; - ws_send - .send(warp::filters::ws::Message::binary( - bincode::serialize(&server_msg).unwrap(), - )) - .await - .unwrap(); - } - ws_send.close().await.unwrap(); - // receive the client close to complete the handshake - ws_recv.next().await.unwrap().unwrap(); - }) - }); + .map(move |ws: warp::filters::ws::Ws| WsBincodeMessageHandler::on_upgrade(ws)); let save_cache = warp::post() .and(warp::path("save")) .and(warp::path::end()) .map(move || { - CACHE.lock().unwrap().save(); - warp::reply::html("Saved") + if let Some(cache) = Lazy::get(&CACHE){ + cache.lock().unwrap().save(); + warp::reply::html("Saved") + } else { + warp::reply::html("Nothing to save") + } }); let endpoints = json_verify.or(bincode_verify).or(save_cache); @@ -189,3 +110,84 @@ where unreachable!("The server unexpectedly stopped."); } + +trait WsMessageHandler { + fn handle_websocket_message(msg: warp::ws::Message) -> VerificationRequest; + fn make_websocket_message(msg: &ServerMessage) -> warp::ws::Message; + fn on_upgrade(ws: warp::filters::ws::Ws) -> Box { + Box::new(ws.on_upgrade(move |websocket| async move { + let (mut ws_send, mut ws_recv) = websocket.split(); + let req_msg = ws_recv.next().await.unwrap().unwrap(); + let verification_request = Self::handle_websocket_message(req_msg); + let request_hash = verification_request.get_hash(); + let program_name = verification_request.program.get_name().to_string(); + // return early in case of a cache hit + let stream = if config::enable_cache() { + match Lazy::force(&CACHE).get(request_hash) { + Some(mut result) => { + info!( + "Using cached result {:?} for program {}", + &result, + &program_name + ); + result.cached = true; + futures::stream::once(async move { + ServerMessage::Termination(result) + }) + .left_stream() + }, + None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + } + } else { + VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + }; + pin_mut!(stream); + + while let Some(server_msg) = stream.next().await { + if let ServerMessage::Termination(result) = &server_msg { + if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { + if !result.cached { + info!( + "Storing new cached result {:?} for program {}", + &result, + &program_name + ); + CACHE.insert(request_hash, result.clone()); + } + } + }; + let msg = Self::make_websocket_message(&server_msg); + ws_send + .send(msg) + .await + .unwrap(); + } + ws_send.close().await.unwrap(); + // receive the client close to complete the handshake + ws_recv.next().await.unwrap().unwrap(); + })) + } +} + +struct WsJsonMessageHandler; +impl WsMessageHandler for WsJsonMessageHandler { + fn handle_websocket_message(msg: warp::ws::Message) -> VerificationRequest { + msg + .to_str() + .and_then(|s: &str| serde_json::from_str(s).unwrap()) + .unwrap() + } + fn make_websocket_message(msg: &ServerMessage) -> warp::ws::Message { + warp::filters::ws::Message::text(serde_json::to_string(&msg).unwrap()) + } +} + +struct WsBincodeMessageHandler; +impl WsMessageHandler for WsBincodeMessageHandler { + fn handle_websocket_message(msg: warp::ws::Message) -> VerificationRequest { + bincode::deserialize(msg.as_bytes()).unwrap() + } + fn make_websocket_message(msg: &ServerMessage) -> warp::ws::Message { + warp::filters::ws::Message::binary(bincode::serialize(&msg).unwrap()) + } +} \ No newline at end of file From c8a7399f1a341f036f589857cd275c885c593c5c Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 19 Jul 2024 16:23:43 +0200 Subject: [PATCH 31/53] Clean up some imports, minor other fixes --- prusti-server/src/backend.rs | 12 +++---- prusti-server/src/lib.rs | 42 +++++++++++------------ prusti-server/src/process_verification.rs | 11 ++---- prusti-server/src/server.rs | 2 +- prusti-server/src/verification_request.rs | 7 ++-- 5 files changed, 32 insertions(+), 42 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 59f5feac679..fab16b91ab1 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -1,15 +1,14 @@ -use crate::{dump_viper_program, ServerMessage, VIPER}; +use crate::{ServerMessage, VIPER}; use log::{debug, info}; use prusti_utils::{ config, - Stopwatch, }; use std::{ - sync::{mpsc, Arc}, + sync::mpsc, thread, time, }; -use viper::{jni_utils::JniUtils, VerificationContext, VerificationResultKind, Viper}; -use viper_sys::wrappers::{java, viper::*}; +use viper::{jni_utils::JniUtils, VerificationContext, VerificationResultKind}; +use viper_sys::wrappers::{viper::*}; pub enum Backend<'a> { Viper( @@ -27,13 +26,12 @@ impl<'a> Backend<'a> { match self { Backend::Viper(ref mut verifier, viper_thread, viper_program_ref) => { - let mut stopwatch = Stopwatch::start("prusti-server backend", "viper verification"); let ast_utils = viper_thread.new_ast_utils(); ast_utils.with_local_frame(16, || { let viper_program = viper::Program::new(viper_program_ref.as_obj()); if config::report_viper_messages() { - verify_and_poll_msgs(verifier, viper_thread, viper_program, sender) + verify_and_poll_msgs(verifier, viper_thread, viper_program, sender) } else { verifier.verify(viper_program) } diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 52c2eb94ec7..d1b5af2a1ba 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -9,14 +9,13 @@ use ::log::{debug, error, info}; use crate::{ - spawn_server_thread, PrustiClient, ServerMessage, VerificationRequest, + PrustiClient, ServerMessage, VerificationRequest, VerificationRequestProcessing, ViperBackendConfig, }; use prusti_utils::{config, Stopwatch}; use prusti_interface::{ - data::{VerificationResult, VerificationTask}, + data::VerificationResult, environment::EnvDiagnostic, - specs::typed, PrustiError, }; use prusti_rustc_interface::{ @@ -24,8 +23,7 @@ use prusti_rustc_interface::{ errors::MultiSpan, }; use viper::{self, Cache, PersistentCache}; -use ide::{encoding_info::EncodingInfo, ide_verification_result::IdeVerificationResult}; -use once_cell::sync::Lazy; +use ide::ide_verification_result::IdeVerificationResult; mod client; mod process_verification; @@ -35,21 +33,21 @@ mod verification_request; mod backend; pub mod ide; -pub use backend::*; -pub use client::*; -pub use process_verification::*; +pub(crate) use backend::*; +pub(crate) use client::*; +pub(crate) use process_verification::*; pub use server::*; -pub use server_message::*; -pub use verification_request::*; +pub(crate) use server_message::*; +pub(crate) use verification_request::*; // Futures returned by `Client` need to be executed in a compatible tokio runtime. pub use tokio; use tokio::runtime::Builder; -use rustc_hash::FxHashMap; use serde_json::json; use async_stream::stream; use futures_util::{pin_mut, Stream, StreamExt}; use std::sync::{self, Arc}; +use once_cell::sync::Lazy; /// Verify a list of programs. /// Returns a list of (program_name, verification_result) tuples. @@ -70,7 +68,7 @@ pub fn verify_programs( (program_name, request) }).collect(); - let mut stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); + let stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); // runtime used either for client connecting to server sequentially // or to sequentially verify the requests -> single thread is sufficient // why single thread if everything is asynchronous? VerificationRequestProcessing in @@ -178,17 +176,19 @@ fn handle_termination_message( program_name.clone(), verification_error ); - // FIXME: temporary error emition, delete when the above is implemented again - env_diagnostic.span_err_with_help_and_notes( + // FIXME: dummy span until backtranslation is implemented again + let prusti_error = PrustiError::verification( + format!("{:?}", verification_error), MultiSpan::from_span(DUMMY_SP.into()), - &format!( - "Verification error in {}: {:?}", - program_name.clone(), - verification_error - ), - &None, - &[], ); + debug!("Prusti error: {:?}", prusti_error); + if prusti_error.is_disabled() { + prusti_error.cancel(); + } else if config::show_ide_info() { + prusti_error.emit(env_diagnostic); + } else { + prusti_errors.push(prusti_error); + } } *overall_result = VerificationResult::Failure; } diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 4706d21fe77..f5ac57fc02c 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -5,19 +5,12 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{ServerMessage, VerificationRequest, ServerRequest}; -use futures::{lock, stream::Stream, future::Either}; +use futures::{lock, stream::Stream}; use log::{debug, info}; -use prusti_utils::{ - config, -}; use std::{ - sync::{self, mpsc, Arc}, + sync::{self, mpsc}, thread, }; -use viper::{ - Cache, PersistentCache, -}; -use async_stream::stream; struct ThreadJoin { handle: Option>, diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index 3bbb842239e..db97644bfaf 100644 --- a/prusti-server/src/server.rs +++ b/prusti-server/src/server.rs @@ -14,7 +14,7 @@ use std::{ thread, }; use tokio::runtime::Builder; -use viper::{Cache, PersistentCache, Viper, VerificationResultKind}; +use viper::{Cache, PersistentCache, VerificationResultKind}; use warp::Filter; use prusti_utils::config; diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 4cfed6637ea..3739005a434 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -5,16 +5,15 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use log::{debug, info}; -use viper::{self, VerificationBackend, Program, AstUtils, Viper, VerificationContext, VerificationResultKind, smt_manager::SmtManager, VerificationResult, PersistentCache, Cache}; +use viper::{self, VerificationBackend, Viper, VerificationResultKind, smt_manager::SmtManager, VerificationResult}; use prusti_utils::{ config, Stopwatch, report::log::{report, to_legal_file_name}, }; use crate::{ServerMessage, Backend}; -use once_cell::sync::Lazy; use std::{ - sync::{self, mpsc, Arc, OnceLock}, + sync::{self, mpsc, OnceLock}, fs::create_dir_all, path::PathBuf, }; @@ -198,7 +197,7 @@ impl ViperBackendConfig { ]); // model.partial changes the default case of functions in counterexamples // to #unspecified - let mut prover_args = format!( + let prover_args = format!( "smt.qi.eager_threshold={} model.partial={}", config::smt_qi_eager_threshold(), config::counterexample() From 1b64b36b7e44553a1cafdeabf5fe7e784ce60b32 Mon Sep 17 00:00:00 2001 From: trk Date: Sat, 20 Jul 2024 11:00:57 +0200 Subject: [PATCH 32/53] Add back stopwatch calls --- prusti-server/src/backend.rs | 6 +++++- prusti-server/src/verification_request.rs | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index fab16b91ab1..bfe7022476e 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -74,7 +74,11 @@ fn polling_function( rep_glob_ref: &jni::objects::GlobalRef, sender: mpsc::Sender, ) { - let verification_context = VIPER.get().expect("Viper was not instantiated before polling").attach_current_thread(); + debug!("attach polling thread to JVM."); + let verification_context = VIPER + .get() + .expect("Viper was not instantiated before polling") + .attach_current_thread(); let env = verification_context.env(); let jni = JniUtils::new(env); let reporter_instance = rep_glob_ref.as_obj(); diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 3739005a434..bdc303b35f2 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -113,12 +113,14 @@ impl<'vir> VerificationRequest { match self.backend_config.backend { VerificationBackend::Carbon | VerificationBackend::Silicon => { let mut stopwatch = - Stopwatch::start("prusti-server backend", "construction of JVM objects"); - - let viper = VIPER.get_or_init(|| Viper::new_with_args(&config::viper_home(), config::extra_jvm_args())); + Stopwatch::start("prusti-server backend", "JVM startup"); + let viper = VIPER + .get_or_init(|| Viper::new_with_args(&config::viper_home(), config::extra_jvm_args())); + stopwatch.start_next("attach current thread to the JVM"); let context = viper.attach_current_thread(); let ast_utils = context.new_ast_utils(); + stopwatch.start_next("construction of JVM objects"); ast_utils.with_local_frame(16, || { let ast_factory = context.new_ast_factory(); From a9e50216da91e5694c244b8057d06c7a443e62d2 Mon Sep 17 00:00:00 2001 From: trk Date: Mon, 22 Jul 2024 10:52:58 +0200 Subject: [PATCH 33/53] Translate and emit quantifier messages --- prusti-server/src/lib.rs | 109 ++++++++++++++++++++++----------------- vir/src/spans.rs | 11 ++++ 2 files changed, 73 insertions(+), 47 deletions(-) diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 451d307917b..86818030ad6 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -99,6 +99,8 @@ async fn handle_stream( verification_messages: impl Stream, ) -> VerificationResult { let mut overall_result = VerificationResult::Success; + // let encoding_errors_count = self.encoder.count_encoding_errors(); + // we want quantifier_pos_ID + program_name + q_name as identifier because there are // different q_names for the same ID and each program reports independent results // key: (pos_id, program_name), key to result: q_name result: num_instantiations @@ -147,6 +149,9 @@ async fn handle_stream( } } + // if encoding_errors_count != 0 { + // overall_result = VerificationResult::Failure; + // } overall_result } @@ -192,13 +197,17 @@ fn handle_termination_message( ) .expect("verification error could not be backtranslated") .into_iter()) - .for_each(|prusti_error| prusti_error.emit(env_diagnostic)); - // for verification_error in errors { - // debug!( - // "Verification error in {}: {:?}", - // program_name.clone(), - // verification_error - // ); + .for_each(|prusti_error| { + debug!("Prusti error: {:?}", prusti_error); + if prusti_error.is_disabled() { + prusti_error.cancel(); + } else if config::show_ide_info() { + prusti_error.emit(env_diagnostic); + } else { + prusti_errors.push(prusti_error); + } + }); + *overall_result = VerificationResult::Failure; } viper::VerificationResultKind::JavaException(exception) => { @@ -223,35 +232,39 @@ fn handle_quantifier_instantiation_message( ) { if config::report_viper_messages() { debug!("Received #{insts} quantifier instantiations of {q_name} for position id {pos_id} in verification of {program_name}"); - // match self.encoder.error_manager().position_manager().get_span_from_id(pos_id) { - // Some(span) => { - // let key = (pos_id, program_name.clone()); - // if !quantifier_instantiations.contains_key(&key) { - // quantifier_instantiations.insert(key.clone(), FxHashMap::default()); - // } - // let map = quantifier_instantiations.get_mut(&key).unwrap(); - // // for some reason, the aux quantifiers by the same z3 instance (same uniqueId - // // in silicon) can have different amount of instantiations. - // // e.g. we receive a message with 100 instantiations for a certain quantifier - // // and afterwards a message with 20 instantiations for the same one. - // // All verifying the same viper program and by the same z3 instance. - // // Since I don see a better way to take this into account than taking the - // // maximum, that is exactly what we do here. - // let old_inst = map.get(&q_name).unwrap_or(&0); - // map.insert(q_name, std::cmp::max(insts, *old_inst)); - // let mut n: u64 = 0; - // for (q_name, insts) in map.iter() { - // debug!("Key: {q_name}, Value: {insts}"); - // n += *insts; - // } - // PrustiError::message( - // format!("quantifierInstantiationsMessage{}", - // json!({"instantiations": n, "method": program_name}), - // ), span.clone() - // ).emit(env_diagnostic); - // }, - // None => error!("#{insts} quantifier instantiations of {q_name} for unknown position id {pos_id} in verification of {program_name}"), - // } + vir::with_vcx(|vcx| { + match vcx.get_span_from_id(pos_id.try_into().unwrap()) { + Some(span) => { + let key = (pos_id, program_name.clone()); + if !quantifier_instantiations.contains_key(&key) { + quantifier_instantiations.insert(key.clone(), FxHashMap::default()); + } + let map = quantifier_instantiations.get_mut(&key).unwrap(); + // for some reason, the aux quantifiers by the same z3 instance (same uniqueId + // in silicon) can have different amount of instantiations. + // e.g. we receive a message with 100 instantiations for a certain quantifier + // and afterwards a message with 20 instantiations for the same one. + // All verifying the same viper program and by the same z3 instance. + // Since I don see a better way to take this into account than taking the + // maximum, that is exactly what we do here. + let old_inst = map.get(&q_name).unwrap_or(&0); + map.insert(q_name, std::cmp::max(insts, *old_inst)); + let mut n: u64 = 0; + for (q_name, insts) in map.iter() { + debug!("Key: {q_name}, Value: {insts}"); + n += *insts; + } + PrustiError::message( + format!("quantifierInstantiationsMessage{}", + json!({"instantiations": n, "method": program_name}), + ), span.clone().into() + ).emit(env_diagnostic); + }, + None => error!( + "#{insts} quantifier instantiations of {q_name} for unknown position id {pos_id} in verification of {program_name}" + ), + } + }); } } @@ -264,16 +277,18 @@ fn handle_quantifier_chosen_triggers_message( ) { if config::report_viper_messages() && pos_id != 0 { debug!("Received quantifier triggers {triggers} for quantifier {viper_quant} for position id {pos_id} in verification of {program_name}"); - // match self.encoder.error_manager().position_manager().get_span_from_id(pos_id) { - // Some(span) => { - // PrustiError::message( - // format!("quantifierChosenTriggersMessage{}", - // json!({"viper_quant": viper_quant, "triggers": triggers}), - // ), span.clone() - // ).emit(&self.env.diagnostic); - // }, - // None => error!("Invalid position id {pos_id} for viper quantifier {viper_quant} in verification of {program_name}"), - // } + vir::with_vcx(|vcx| { + match vcx.get_span_from_id(pos_id.try_into().unwrap()) { + Some(span) => { + PrustiError::message( + format!("quantifierChosenTriggersMessage{}", + json!({"viper_quant": viper_quant, "triggers": triggers}), + ), span.clone().into() + ).emit(env_diagnostic); + }, + None => error!("Invalid position id {pos_id} for viper quantifier {viper_quant} in verification of {program_name}"), + } + }); } } @@ -299,7 +314,7 @@ fn handle_block_processing_message( let processed = result != None; debug!("Received {}: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label}, path_id: {path_id} }}", if processed {"path processed"} else {"block reached"}); - if let Some(method_name) = viper_method_to_rust_method(&viper_method, & program_name) { + if let Some(method_name) = viper_method_to_rust_method(&viper_method, & program_name) { if let Some(span) = vir_label_to_pos(&vir_label) { PrustiError::message( format!("{}{}", diff --git a/vir/src/spans.rs b/vir/src/spans.rs index 9e508878b2d..8fff58a9f3d 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -136,4 +136,15 @@ impl<'tcx> VirCtxt<'tcx> { } None } + + /// Attempt to backtranslate a position id to a rust span + pub fn get_span_from_id( + &'tcx self, + pos_id: usize + ) -> Option { + let manager = self.spans.borrow(); + manager.all + .get(pos_id) + .map(|vir_span| vir_span.span) + } } From b5722f35628d4a3347a6d181cca0787b01d055d4 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 25 Jul 2024 17:04:50 +0200 Subject: [PATCH 34/53] Parse pos_id as usize instead of u64 in quant messages --- prusti-server/src/backend.rs | 4 +- prusti-server/src/lib.rs | 59 +++++++++++++++++++++++++++-- prusti-server/src/server_message.rs | 4 +- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index ef70f266394..ec6fca0b813 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -127,7 +127,7 @@ fn polling_function( .strip_suffix("-aux") .or(no_pref.strip_suffix("_precondition")) .unwrap_or(no_pref); - let parsed = stripped.parse::(); + let parsed = stripped.parse::(); match parsed { Ok(pos_id) => { sender @@ -161,7 +161,7 @@ fn polling_function( // Is that intended? if let Some(pos_id_index) = pos_string.rfind('.') { // let pos_id_index = pos_string.rfind('.').unwrap(); - let pos_id = pos_string[pos_id_index + 1..].parse::().unwrap(); + let pos_id = pos_string[pos_id_index + 1..].parse::().unwrap(); let viper_triggers = jni.get_string(jni.unwrap_result(msg_wrapper.call_triggers__string(msg))); diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 86818030ad6..3a280840dee 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -104,7 +104,7 @@ async fn handle_stream( // we want quantifier_pos_ID + program_name + q_name as identifier because there are // different q_names for the same ID and each program reports independent results // key: (pos_id, program_name), key to result: q_name result: num_instantiations - let mut quantifier_instantiations: FxHashMap<(u64, String), FxHashMap> = + let mut quantifier_instantiations: FxHashMap<(usize, String), FxHashMap> = FxHashMap::default(); let mut prusti_errors: Vec<_> = vec![]; @@ -208,6 +208,57 @@ fn handle_termination_message( } }); + // // annotate with counterexample, if requested + // if config::counterexample() { + // if config::unsafe_core_proof() { + // if let Some(silicon_counterexample) = + // &verification_error.counterexample + // { + // let error_manager = self.encoder.error_manager(); + // if let Some(def_id) = error_manager + // .get_def_id(&verification_error) + // { + // let counterexample = counterexample_translation_refactored::backtranslate( + // &self.encoder, + // error_manager.position_manager(), + // def_id, + // silicon_counterexample, + // ); + // prusti_error = + // counterexample.annotate_error(prusti_error); + // } else { + // prusti_error = prusti_error.add_note( + // format!( + // "the verifier produced a counterexample for {program_name}, but it could not be mapped to source code" + // ), + // None, + // ); + // } + // } + // } else if let Some(silicon_counterexample) = + // &verification_error.counterexample + // { + // if let Some(def_id) = self.encoder.error_manager() + // .get_def_id(&verification_error) + // { + // let counterexample = + // counterexample_translation::backtranslate( + // &self.encoder, + // def_id, + // silicon_counterexample, + // ); + // prusti_error = + // counterexample.annotate_error(prusti_error); + // } else { + // prusti_error = prusti_error.add_note( + // format!( + // "the verifier produced a counterexample for {program_name}, but it could not be mapped to source code" + // ), + // None, + // ); + // } + // } + // } *overall_result = VerificationResult::Failure; } viper::VerificationResultKind::JavaException(exception) => { @@ -227,8 +278,8 @@ fn handle_quantifier_instantiation_message( program_name: String, q_name: String, insts: u64, - pos_id: u64, - quantifier_instantiations: &mut FxHashMap<(u64, String), FxHashMap> + pos_id: usize, + quantifier_instantiations: &mut FxHashMap<(usize, String), FxHashMap> ) { if config::report_viper_messages() { debug!("Received #{insts} quantifier instantiations of {q_name} for position id {pos_id} in verification of {program_name}"); @@ -273,7 +324,7 @@ fn handle_quantifier_chosen_triggers_message( program_name: String, viper_quant: String, triggers: String, - pos_id: u64 + pos_id: usize ) { if config::report_viper_messages() && pos_id != 0 { debug!("Received quantifier triggers {triggers} for quantifier {viper_quant} for position id {pos_id} in verification of {program_name}"); diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs index 456bb2fe674..ac3949bbeb4 100644 --- a/prusti-server/src/server_message.rs +++ b/prusti-server/src/server_message.rs @@ -19,7 +19,7 @@ pub enum ServerMessage { QuantifierInstantiation { q_name: String, insts: u64, - pos_id: u64, + pos_id: usize, }, /// Also created by the Viper backend. The viper_quant is the expression the @@ -28,7 +28,7 @@ pub enum ServerMessage { QuantifierChosenTriggers { viper_quant: String, triggers: String, - pos_id: u64, + pos_id: usize, }, /// Contains a path id, label and viper method name corresponding to a symbolic From 2f8c64fcea2816357943282efdbaa5739978d570 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 25 Jul 2024 18:12:03 +0200 Subject: [PATCH 35/53] More server refactoring --- prusti-server/src/server.rs | 160 +++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 75 deletions(-) diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index db97644bfaf..4370b5a696e 100644 --- a/prusti-server/src/server.rs +++ b/prusti-server/src/server.rs @@ -62,16 +62,45 @@ where vir::init_vcx(vir::VirCtxt::new_without_tcx()); data } + + fn handle_json_websocket_message(msg: warp::ws::Message) -> VerificationRequest { + msg + .to_str() + .and_then(|s: &str| serde_json::from_str(s).unwrap()) + .unwrap() + } + fn make_json_websocket_message(msg: &ServerMessage) -> warp::ws::Message { + warp::filters::ws::Message::text(serde_json::to_string(&msg).unwrap()) + } + + fn handle_bincode_websocket_message(msg: warp::ws::Message) -> VerificationRequest { + bincode::deserialize(msg.as_bytes()).unwrap() + } + fn make_bincode_websocket_message(msg: &ServerMessage) -> warp::ws::Message { + warp::filters::ws::Message::binary(bincode::serialize(&msg).unwrap()) + } let json_verify = warp::path!("json" / "verify") .and(warp::filters::ws::ws()) .map(init_vcx) - .map(move |ws: warp::filters::ws::Ws| WsJsonMessageHandler::on_upgrade(ws)); + .map(move |ws: warp::filters::ws::Ws| + on_upgrade( + ws, + handle_json_websocket_message, + make_json_websocket_message + ) + ); let bincode_verify = warp::path!("bincode" / "verify") .and(warp::filters::ws::ws()) .map(init_vcx) - .map(move |ws: warp::filters::ws::Ws| WsBincodeMessageHandler::on_upgrade(ws)); + .map(move |ws: warp::filters::ws::Ws| + on_upgrade( + ws, + handle_bincode_websocket_message, + make_bincode_websocket_message + ) + ); let save_cache = warp::post() .and(warp::path("save")) @@ -111,83 +140,64 @@ where unreachable!("The server unexpectedly stopped."); } -trait WsMessageHandler { - fn handle_websocket_message(msg: warp::ws::Message) -> VerificationRequest; - fn make_websocket_message(msg: &ServerMessage) -> warp::ws::Message; - fn on_upgrade(ws: warp::filters::ws::Ws) -> Box { - Box::new(ws.on_upgrade(move |websocket| async move { - let (mut ws_send, mut ws_recv) = websocket.split(); - let req_msg = ws_recv.next().await.unwrap().unwrap(); - let verification_request = Self::handle_websocket_message(req_msg); - let request_hash = verification_request.get_hash(); - let program_name = verification_request.program.get_name().to_string(); - // return early in case of a cache hit - let stream = if config::enable_cache() { - match Lazy::force(&CACHE).get(request_hash) { - Some(mut result) => { +fn on_upgrade( + ws: warp::ws::Ws, + handle_websocket_message: F, + make_websocket_message: G, +) -> impl warp::Reply +where + F: Fn(warp::ws::Message) -> VerificationRequest + Send + Sync + 'static, + G: Fn(&ServerMessage) -> warp::ws::Message + Send + Sync + 'static, +{ + ws.on_upgrade(move |websocket| async move { + let (mut ws_send, mut ws_recv) = websocket.split(); + let req_msg = ws_recv.next().await.unwrap().unwrap(); + let verification_request = handle_websocket_message(req_msg); + let request_hash = verification_request.get_hash(); + let program_name = verification_request.program.get_name().to_string(); + // return early in case of a cache hit + let stream = if config::enable_cache() { + match Lazy::force(&CACHE).get(request_hash) { + Some(mut result) => { + info!( + "Using cached result {:?} for program {}", + &result, + &program_name + ); + result.cached = true; + futures::stream::once(async move { + ServerMessage::Termination(result) + }) + .left_stream() + }, + None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + } + } else { + VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + }; + pin_mut!(stream); + + while let Some(server_msg) = stream.next().await { + if let ServerMessage::Termination(result) = &server_msg { + if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { + if !result.cached { info!( - "Using cached result {:?} for program {}", + "Storing new cached result {:?} for program {}", &result, &program_name ); - result.cached = true; - futures::stream::once(async move { - ServerMessage::Termination(result) - }) - .left_stream() - }, - None => VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() + CACHE.insert(request_hash, result.clone()); + } } - } else { - VERIFICATION_REQUEST_PROCESSING.verify(verification_request).right_stream() }; - pin_mut!(stream); - - while let Some(server_msg) = stream.next().await { - if let ServerMessage::Termination(result) = &server_msg { - if config::enable_cache() && !matches!(result.kind, VerificationResultKind::JavaException(_)) { - if !result.cached { - info!( - "Storing new cached result {:?} for program {}", - &result, - &program_name - ); - CACHE.insert(request_hash, result.clone()); - } - } - }; - let msg = Self::make_websocket_message(&server_msg); - ws_send - .send(msg) - .await - .unwrap(); - } - ws_send.close().await.unwrap(); - // receive the client close to complete the handshake - ws_recv.next().await.unwrap().unwrap(); - })) - } -} - -struct WsJsonMessageHandler; -impl WsMessageHandler for WsJsonMessageHandler { - fn handle_websocket_message(msg: warp::ws::Message) -> VerificationRequest { - msg - .to_str() - .and_then(|s: &str| serde_json::from_str(s).unwrap()) - .unwrap() - } - fn make_websocket_message(msg: &ServerMessage) -> warp::ws::Message { - warp::filters::ws::Message::text(serde_json::to_string(&msg).unwrap()) - } -} - -struct WsBincodeMessageHandler; -impl WsMessageHandler for WsBincodeMessageHandler { - fn handle_websocket_message(msg: warp::ws::Message) -> VerificationRequest { - bincode::deserialize(msg.as_bytes()).unwrap() - } - fn make_websocket_message(msg: &ServerMessage) -> warp::ws::Message { - warp::filters::ws::Message::binary(bincode::serialize(&msg).unwrap()) - } + let msg = make_websocket_message(&server_msg); + ws_send + .send(msg) + .await + .unwrap(); + } + ws_send.close().await.unwrap(); + // receive the client close to complete the handshake + ws_recv.next().await.unwrap().unwrap(); + }) } \ No newline at end of file From f06740feb2aef9dec292d021ef7a4c95487bafa6 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 1 Aug 2024 11:41:16 +0200 Subject: [PATCH 36/53] Fix flag to use actual vectors --- docs/dev-guide/src/config/flags.md | 6 ++++-- prusti-utils/src/config.rs | 21 +++------------------ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index 9564911b390..379dd45c603 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -83,7 +83,7 @@ | [`USE_SMT_WRAPPER`](#use_smt_wrapper) | `bool` | `false` | A | | [`VERIFICATION_DEADLINE`](#verification_deadline) | `Option` | `None` | A | | [`VERIFY_ONLY_BASIC_BLOCK_PATH`](#verify_only_basic_block_path) | `Vec` | `vec![]` | A | -| [`VERIFY_ONLY_DEFPATH`](#verify_only_defpath) | `Option` | `None` | A | +| [`VERIFY_ONLY_DEFPATH`](#verify_only_defpath) | `Vec` | `vec![]` | A | | [`VERIFY_ONLY_PREAMBLE`](#verify_only_preamble) | `bool` | `false` | A | | [`VIPER_BACKEND`](#viper_backend) | `String` | `"Silicon"` | A | | [`VIPER_HOME`](#viper_home) | `Option` | `None` | A | @@ -510,10 +510,12 @@ Verify only the single execution path goes through the given basic blocks. All b > **Note:** This option is only for debugging Prusti. -## `VERIFY_ONLY_DEFPATH` +## `VERIFY_ONLY_DEFPATHS` When set to the defpath of a local method, prusti will only verify the specified method. A fake error will be generated to avoid caching of a success. +> **Note:** When passing this flag through environment variables, it should be a string of space-separated defpaths. + ## `VERIFY_ONLY_PREAMBLE` When enabled, only the preamble will be verified: domains, functions, and predicates. diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 037329522f7..82aaf78190a 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -176,7 +176,7 @@ lazy_static::lazy_static! { // Flags specifically for Prusti-Assistant: settings.set_default("show_ide_info", false).unwrap(); settings.set_default("skip_verification", false).unwrap(); - settings.set_default::>("verify_only_defpaths", None).unwrap(); + settings.set_default::>("verify_only_defpaths", vec![]).unwrap(); settings.set_default::>("query_method_signature", None).unwrap(); settings.set_default("report_block_messages", false).unwrap(); @@ -222,6 +222,7 @@ lazy_static::lazy_static! { .with_list_parse_key("extra_jvm_args") .with_list_parse_key("extra_verifier_args") .with_list_parse_key("verify_only_basic_block_path") + .with_list_parse_key("verify_only_defpaths") .list_separator(" ") ).unwrap(); check_keys(&settings, &allowed_keys, "environment variables"); @@ -1080,23 +1081,7 @@ pub fn skip_verification() -> bool { /// Used for selective verification, can be passed a String containing /// the DefPath of the method to be verified pub fn verify_only_defpaths() -> Vec { - if let Some(input) = read_setting::>("verify_only_defpaths") { - if !input.starts_with('[') || !input.ends_with(']') { - panic!("verify_only_defpaths: invalid format. Make sure to enclose the list in brackets (`[]`). Was `{input}` but expected form `[\"\",\"\",...]`") - } - let trimmed = &input[1..input.len()-1]; - let parts: Vec<&str> = trimmed.split(',').collect(); - parts.into_iter() - .filter(|s| !s.is_empty()) - .map(|s| - if s.len() >= 2 && s.starts_with('\"') && s.ends_with('\"') { - s[1..s.len()-1].to_string() - } else { - panic!("verify_only_defpaths: invalid format. Make sure to use double quotes (`\"`) for each element. Element was `{s}` but expected \"\"") - } - ) - .collect() - } else {vec![]} + read_setting("verify_only_defpaths") } /// A flag that can be used to ask the compiler for the declaration / From 9b2bf781995759d27c8e36b320ff0da13a2044ba Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 1 Aug 2024 12:07:40 +0200 Subject: [PATCH 37/53] Move IDE related functionality to an own top-level crate --- Cargo.lock | 16 +++- Cargo.toml | 1 + ide/Cargo.toml | 15 ++++ .../src/ide_helper => ide/src}/call_finder.rs | 81 +++++++++---------- .../ide_helper => ide/src}/compiler_info.rs | 69 +++------------- .../src/ide => ide/src}/encoding_info.rs | 6 +- .../src/ide_helper => ide/src}/fake_error.rs | 0 .../src}/ide_verification_result.rs | 31 +++---- ide/src/lib.rs | 18 +++++ .../ide_helper => ide/src}/query_signature.rs | 2 +- .../src/ide => ide/src}/vsc_span.rs | 0 prusti-encoder/Cargo.toml | 3 +- prusti-encoder/src/ide/mod.rs | 2 - prusti-encoder/src/lib.rs | 1 - prusti-server/Cargo.toml | 1 + prusti-server/src/ide/mod.rs | 1 - prusti-server/src/lib.rs | 35 ++++---- prusti/Cargo.toml | 1 + prusti/src/callbacks.rs | 6 +- prusti/src/driver.rs | 1 - prusti/src/ide_helper/mod.rs | 4 - prusti/src/verifier.rs | 4 +- 22 files changed, 145 insertions(+), 153 deletions(-) create mode 100644 ide/Cargo.toml rename {prusti/src/ide_helper => ide/src}/call_finder.rs (58%) rename {prusti/src/ide_helper => ide/src}/compiler_info.rs (57%) rename {prusti-encoder/src/ide => ide/src}/encoding_info.rs (92%) rename {prusti/src/ide_helper => ide/src}/fake_error.rs (100%) rename {prusti-server/src/ide => ide/src}/ide_verification_result.rs (53%) create mode 100644 ide/src/lib.rs rename {prusti/src/ide_helper => ide/src}/query_signature.rs (99%) rename {prusti-encoder/src/ide => ide/src}/vsc_span.rs (100%) delete mode 100644 prusti-encoder/src/ide/mod.rs delete mode 100644 prusti-server/src/ide/mod.rs delete mode 100644 prusti/src/ide_helper/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 19ae5df0434..b7e0404e04a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1214,6 +1214,17 @@ dependencies = [ "cc", ] +[[package]] +name = "ide" +version = "0.3.0" +dependencies = [ + "prusti-interface", + "prusti-rustc-interface", + "prusti-utils", + "serde", + "serde_json", +] + [[package]] name = "idna" version = "0.4.0" @@ -1837,6 +1848,7 @@ version = "0.3.0" dependencies = [ "chrono", "env_logger", + "ide", "lazy_static", "log", "mir-state-analysis", @@ -1868,13 +1880,12 @@ name = "prusti-encoder" version = "0.1.0" dependencies = [ "cfg-if", + "ide", "mir-ssa-analysis", "mir-state-analysis", "prusti-interface", "prusti-rustc-interface", "prusti-utils", - "serde", - "serde_json", "task-encoder", "tracing 0.1.0", "vir", @@ -1922,6 +1933,7 @@ dependencies = [ "env_logger", "futures", "futures-util", + "ide", "jni", "lazy_static", "log", diff --git a/Cargo.toml b/Cargo.toml index 8a4e4fef6f7..0bd40cf9205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "viper-sys", "vir", "vir-proc-macro", + "ide" ] resolver = "2" diff --git a/ide/Cargo.toml b/ide/Cargo.toml new file mode 100644 index 00000000000..ac022a8179b --- /dev/null +++ b/ide/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ide" +version = "0.3.0" +authors = ["Prusti Devs "] +edition = "2021" + +[dependencies] +prusti-interface = { path = "../prusti-interface" } +prusti-utils = { path = "../prusti-utils" } +prusti-rustc-interface = { path = "../prusti-rustc-interface" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[package.metadata.rust-analyzer] +rustc_private = true \ No newline at end of file diff --git a/prusti/src/ide_helper/call_finder.rs b/ide/src/call_finder.rs similarity index 58% rename from prusti/src/ide_helper/call_finder.rs rename to ide/src/call_finder.rs index f8870d35300..5b78ea52783 100644 --- a/prusti/src/ide_helper/call_finder.rs +++ b/ide/src/call_finder.rs @@ -14,7 +14,6 @@ pub struct CallSpanFinder<'tcx> { pub env_query: EnvQuery<'tcx>, pub tcx: TyCtxt<'tcx>, pub called_functions: Vec<(String, DefId, Span)>, - pub called_functions_local: Vec<(String, DefId, Span)>, } impl<'tcx> CallSpanFinder<'tcx> { @@ -22,7 +21,6 @@ impl<'tcx> CallSpanFinder<'tcx> { Self { env_query: env.query, called_functions: Vec::new(), - called_functions_local: Vec::new(), tcx: env.tcx(), } } @@ -61,11 +59,10 @@ impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { let tyck_res = self.tcx.typeck(e1.hir_id.owner.def_id); let res = tyck_res.qpath_res(qself, e1.hir_id); if let prusti_rustc_interface::hir::def::Res::Def(_, def_id) = res { - let defpath = self.tcx.def_path_str(def_id); - let called_functions = - if def_id.as_local().is_none() {&mut self.called_functions} - else {&mut self.called_functions_local}; - called_functions.push((defpath, def_id, expr.span)); + if def_id.as_local().is_none() { + let defpath = self.tcx.def_path_str(def_id); + self.called_functions.push((defpath, def_id, expr.span)); + } } } } @@ -74,18 +71,18 @@ impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { if let Ok((method_def_id, resolved_def_id)) = resolve_res { let defpath_unresolved = self.tcx.def_path_str(method_def_id); let defpath_resolved = self.tcx.def_path_str(resolved_def_id); - let called_functions = - if method_def_id.as_local().is_none() {&mut self.called_functions} - else {&mut self.called_functions_local}; - if defpath_unresolved == defpath_resolved { - called_functions - .push((defpath_resolved, resolved_def_id, sp)); - } else { - // in this case we want both - called_functions - .push((defpath_resolved, resolved_def_id, sp)); - called_functions - .push((defpath_unresolved, method_def_id, sp)); + + if method_def_id.as_local().is_none() { + if defpath_unresolved == defpath_resolved { + self.called_functions + .push((defpath_resolved, resolved_def_id, sp)); + } else { + // in this case we want both + self.called_functions + .push((defpath_resolved, resolved_def_id, sp)); + self.called_functions + .push((defpath_unresolved, method_def_id, sp)); + } } } } @@ -95,33 +92,33 @@ impl<'tcx> Visitor<'tcx> for CallSpanFinder<'tcx> { if let Ok((method_def_id, resolved_def_id)) = resolve_res { let defpath_unresolved = self.tcx.def_path_str(method_def_id); let defpath_resolved = self.tcx.def_path_str(resolved_def_id); - let called_functions = - if method_def_id.as_local().is_none() {&mut self.called_functions} - else {&mut self.called_functions_local}; - if defpath_unresolved == defpath_resolved { - called_functions.push(( - defpath_resolved, - resolved_def_id, - expr.span, - )); - } else { - // For binary operations this will be the operation - // from the standard libary and the "overriding" method - called_functions.push(( - defpath_resolved, - resolved_def_id, - expr.span, - )); - called_functions.push(( - defpath_unresolved, - method_def_id, - expr.span, - )); + if method_def_id.as_local().is_none() { + if defpath_unresolved == defpath_resolved { + self.called_functions.push(( + defpath_resolved, + resolved_def_id, + expr.span, + )); + } else { + // For binary operations this will be the operation + // from the standard libary and the "overriding" method + + self.called_functions.push(( + defpath_resolved, + resolved_def_id, + expr.span, + )); + self.called_functions.push(( + defpath_unresolved, + method_def_id, + expr.span, + )); + } } } } _ => {} } } -} +} \ No newline at end of file diff --git a/prusti/src/ide_helper/compiler_info.rs b/ide/src/compiler_info.rs similarity index 57% rename from prusti/src/ide_helper/compiler_info.rs rename to ide/src/compiler_info.rs index f13c975d8f4..b6310680d4d 100644 --- a/prusti/src/ide_helper/compiler_info.rs +++ b/ide/src/compiler_info.rs @@ -1,11 +1,9 @@ -use super::{call_finder, query_signature}; use prusti_interface::{environment::Environment, specs::typed}; use prusti_rustc_interface::{ hir::def_id::DefId, span::{source_map::SourceMap, Span}, - data_structures::fx::FxHashMap, }; -use prusti_encoder::ide::{vsc_span::VscSpan, encoding_info::SpanOfCallContracts}; +use crate::{VscSpan, call_finder, query_signature}; use serde::Serialize; /// This struct will be passed to prusti-assistant containing information @@ -13,14 +11,8 @@ use serde::Serialize; #[derive(Serialize)] pub struct IdeInfo { procedure_defs: Vec, - // this only contains calls to external functions function_calls: Vec, - // this only contains calls to functions defined in this crate - #[serde(skip)] - function_calls_local: Vec, queried_source: Option, - #[serde(skip)] - contract_spans_map: Option>, } impl IdeInfo { @@ -31,63 +23,24 @@ impl IdeInfo { ) -> Self { let procedure_defs = collect_procedures(env, procedures, def_spec); let source_map = env.tcx().sess.source_map(); - let (fn_calls, fn_calls_local) = collect_fncalls(env); - let mut contract_spans_map: FxHashMap = FxHashMap::default(); - let function_calls = fn_calls - .into_iter() - .map(|(name, defid, sp)| { - contract_spans_map.entry(defid) - .and_modify(|cs: &mut SpanOfCallContracts| cs.push_call_span(&sp, source_map)) - .or_insert(SpanOfCallContracts::new( - name.clone(), - vec![sp], - vec![], - source_map, - )); - ProcDef { - name, - defid, - span: VscSpan::from_span(&sp, source_map), - } - }) - .collect::>(); - let function_calls_local = fn_calls_local + let function_calls = collect_fncalls(env) .into_iter() - .map(|(name, defid, sp)| { - contract_spans_map.entry(defid) - .and_modify(|cs: &mut SpanOfCallContracts| cs.push_call_span(&sp, source_map)) - .or_insert(SpanOfCallContracts::new( - name.clone(), - vec![sp], - vec![], - source_map, - )); - ProcDef { - name, - defid, - span: VscSpan::from_span(&sp, source_map), - } + .map(|(name, defid, span)| ProcDef { + name, + defid, + // span, + span: VscSpan::from_span(&span, source_map), }) - .collect::>(); + .collect::>(); // For declaring external specifications: let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); Self { procedure_defs, function_calls, - function_calls_local, queried_source, - contract_spans_map: Some(contract_spans_map), } } - - // pub fn get_calls(&self) -> Vec { - pub fn get_call_spans_map(&mut self) -> FxHashMap { - // let mut fncalls = self.function_calls.clone(); - // fncalls.append(&mut self.function_calls_local.clone()); - // fncalls - self.contract_spans_map.take().expect("Map has already been taken") - } } /// A struct that contains either a reference to a procedure that can be verified @@ -152,11 +105,11 @@ fn collect_procedures( /// collect all the function calls, so the extension can query external_spec /// templates for it -fn collect_fncalls(env: &Environment<'_>) -> (Vec<(String, DefId, Span)>, Vec<(String, DefId, Span)>) { +fn collect_fncalls(env: &Environment<'_>) -> Vec<(String, DefId, Span)> { let mut fnvisitor = call_finder::CallSpanFinder::new(env); env.tcx() .hir() .visit_all_item_likes_in_crate(&mut fnvisitor); - (fnvisitor.called_functions, fnvisitor.called_functions_local) -} + fnvisitor.called_functions +} \ No newline at end of file diff --git a/prusti-encoder/src/ide/encoding_info.rs b/ide/src/encoding_info.rs similarity index 92% rename from prusti-encoder/src/ide/encoding_info.rs rename to ide/src/encoding_info.rs index 96c6655fe34..d215e322708 100644 --- a/prusti-encoder/src/ide/encoding_info.rs +++ b/ide/src/encoding_info.rs @@ -1,6 +1,6 @@ -use prusti_rustc_interface::span::{source_map::SourceMap, Span}; -use serde::Serialize; -use super::vsc_span::VscSpan; +use prusti_rustc_interface::span::{source_map::SourceMap, Span, DUMMY_SP}; +use serde::{Serialize, Serializer}; +use crate::vsc_span::VscSpan; /// Represents the locations of specifications of a function call. /// Generated for each encoded function call to be used by prusti-assistant. diff --git a/prusti/src/ide_helper/fake_error.rs b/ide/src/fake_error.rs similarity index 100% rename from prusti/src/ide_helper/fake_error.rs rename to ide/src/fake_error.rs diff --git a/prusti-server/src/ide/ide_verification_result.rs b/ide/src/ide_verification_result.rs similarity index 53% rename from prusti-server/src/ide/ide_verification_result.rs rename to ide/src/ide_verification_result.rs index 378dc0a9bd4..c51700875da 100644 --- a/prusti-server/src/ide/ide_verification_result.rs +++ b/ide/src/ide_verification_result.rs @@ -1,5 +1,5 @@ use serde::Serialize; -use viper::VerificationResult; +// use viper::VerificationResult; /// Generated for each verification item, containing information /// about the result of the verification. This information will be emitted @@ -8,22 +8,23 @@ use viper::VerificationResult; #[derive(Serialize)] pub struct IdeVerificationResult { /// the name / defpath of the method - item_name: String, + pub item_name: String, /// whether the verification of that method was successful - success: bool, + pub success: bool, /// how long the verification took - time_ms: u128, + pub time_ms: u128, /// whether this result was cached or is fresh - cached: bool, + pub cached: bool, } -impl From<&VerificationResult> for IdeVerificationResult { - fn from(res: &VerificationResult) -> Self { - Self { - item_name: res.item_name.clone(), - success: res.is_success(), - time_ms: res.time_ms, - cached: res.cached, - } - } -} + +// impl From<&VerificationResult> for IdeVerificationResult { +// fn from(res: &VerificationResult) -> Self { +// Self { +// item_name: res.item_name.clone(), +// success: res.is_success(), +// time_ms: res.time_ms, +// cached: res.cached, +// } +// } +// } diff --git a/ide/src/lib.rs b/ide/src/lib.rs new file mode 100644 index 00000000000..b6f82040eb3 --- /dev/null +++ b/ide/src/lib.rs @@ -0,0 +1,18 @@ + +#![feature(rustc_private)] + +mod call_finder; +mod query_signature; +mod fake_error; +mod vsc_span; +mod ide_verification_result; +mod compiler_info; +mod encoding_info; + +pub use compiler_info::*; +pub use encoding_info::*; +pub use fake_error::*; +pub use ide_verification_result::*; +pub(crate) use call_finder::*; +pub(crate) use vsc_span::*; +pub(crate) use query_signature::*; \ No newline at end of file diff --git a/prusti/src/ide_helper/query_signature.rs b/ide/src/query_signature.rs similarity index 99% rename from prusti/src/ide_helper/query_signature.rs rename to ide/src/query_signature.rs index 4affede8dcc..19d0c82be8e 100644 --- a/prusti/src/ide_helper/query_signature.rs +++ b/ide/src/query_signature.rs @@ -1,7 +1,7 @@ use prusti_utils::config; use std::{collections::HashMap, fmt}; -use super::compiler_info::ProcDef; +use crate::ProcDef; use prusti_rustc_interface::{ hir::{def::DefKind, def_id::DefId}, // middle::ty::{self, Clause, DefIdTree, ImplSubject, PredicateKind, TyCtxt}, diff --git a/prusti-encoder/src/ide/vsc_span.rs b/ide/src/vsc_span.rs similarity index 100% rename from prusti-encoder/src/ide/vsc_span.rs rename to ide/src/vsc_span.rs diff --git a/prusti-encoder/Cargo.toml b/prusti-encoder/Cargo.toml index 4aefb915972..17d5466ad78 100644 --- a/prusti-encoder/Cargo.toml +++ b/prusti-encoder/Cargo.toml @@ -16,9 +16,8 @@ mir-ssa-analysis = { path = "../mir-ssa-analysis" } mir-state-analysis = { path = "../mir-state-analysis" } task-encoder = { path = "../task-encoder" } vir = { path = "../vir" } +ide = { path = "../ide" } tracing = { path = "../tracing" } -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/prusti-encoder/src/ide/mod.rs b/prusti-encoder/src/ide/mod.rs deleted file mode 100644 index 8ff9365ccdc..00000000000 --- a/prusti-encoder/src/ide/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod encoding_info; -pub mod vsc_span; \ No newline at end of file diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index 1d49b622cd0..37676a6f5cb 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -10,7 +10,6 @@ extern crate rustc_type_ir; mod encoders; mod encoder_traits; pub mod request; -pub mod ide; use prusti_interface::{environment::{EnvBody, EnvQuery}, PrustiError}; diff --git a/prusti-server/Cargo.toml b/prusti-server/Cargo.toml index 697b2951202..98a0631063a 100644 --- a/prusti-server/Cargo.toml +++ b/prusti-server/Cargo.toml @@ -19,6 +19,7 @@ doctest = false [dependencies] log = { version = "0.4", features = ["release_max_level_info"] } vir = { path = "../vir" } +ide = { path = "../ide" } viper = { path = "../viper" } prusti-encoder = { path = "../prusti-encoder" } prusti-interface = { path = "../prusti-interface" } diff --git a/prusti-server/src/ide/mod.rs b/prusti-server/src/ide/mod.rs deleted file mode 100644 index 5cec7f6cdd8..00000000000 --- a/prusti-server/src/ide/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ide_verification_result; \ No newline at end of file diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 3a280840dee..1ecc5720874 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -8,10 +8,6 @@ #![feature(rustc_private)] use ::log::{debug, error, info}; -use crate::{ - spawn_server_thread, PrustiClient, ServerMessage, VerificationRequest, - VerificationRequestProcessing, ViperBackendConfig, -}; use prusti_utils::{config, Stopwatch}; use prusti_interface::{ data::VerificationResult, @@ -24,7 +20,15 @@ use prusti_rustc_interface::{ errors::MultiSpan, }; use viper::{self, PersistentCache, Viper}; -use crate::ide::ide_verification_result::IdeVerificationResult; +use ide::IdeVerificationResult; +use crate::{ + server::spawn_server_thread, + PrustiClient, + ServerMessage, + VerificationRequest, + ViperBackendConfig, + VerificationRequestProcessing, +}; mod client; mod process_verification; @@ -32,14 +36,13 @@ mod server; mod server_message; mod verification_request; mod backend; -pub mod ide; -pub use backend::*; -pub use client::*; -pub use process_verification::*; -pub use server::*; -pub use server_message::*; -pub use verification_request::*; +pub use server::start_server_on_port; +pub(crate) use backend::*; +pub(crate) use client::*; +pub(crate) use process_verification::*; +pub(crate) use server_message::*; +pub(crate) use verification_request::*; // Futures returned by `Client` need to be executed in a compatible tokio runtime. pub use tokio; @@ -167,8 +170,12 @@ fn handle_termination_message( PrustiError::message( format!( "ideVerificationResult{}", - serde_json::to_string(&IdeVerificationResult::from(&result)) - .unwrap() + serde_json::to_string(&IdeVerificationResult { + item_name: result.item_name.clone(), + success: result.is_success(), + cached: result.cached, + time_ms: result.time_ms, + }).unwrap() ), DUMMY_SP.into(), ) diff --git a/prusti/Cargo.toml b/prusti/Cargo.toml index b21e6db66cf..c9d7c524fc2 100644 --- a/prusti/Cargo.toml +++ b/prusti/Cargo.toml @@ -18,6 +18,7 @@ prusti-encoder = { path = "../prusti-encoder" } prusti-server = { path = "../prusti-server" } prusti-viper = { path = "../prusti-viper" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } +ide = { path = "../ide" } log = { version = "0.4", features = ["release_max_level_info"] } lazy_static = "1.4.0" serde = { version = "1.0", features = ["derive"] } diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 673dec67c69..55868ce19bc 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,7 +1,5 @@ -use crate::{ - ide_helper::{compiler_info, fake_error::fake_error}, - verifier::verify, -}; +use crate::verifier::verify; +use ide::{IdeInfo, fake_error}; use mir_state_analysis::test_free_pcs; use prusti_utils::config; use prusti_interface::{ diff --git a/prusti/src/driver.rs b/prusti/src/driver.rs index 02a8cdac644..d606454aa9a 100644 --- a/prusti/src/driver.rs +++ b/prusti/src/driver.rs @@ -12,7 +12,6 @@ mod arg_value; mod callbacks; mod verifier; -mod ide_helper; use arg_value::arg_value; use callbacks::PrustiCompilerCalls; diff --git a/prusti/src/ide_helper/mod.rs b/prusti/src/ide_helper/mod.rs deleted file mode 100644 index 617dc40c368..00000000000 --- a/prusti/src/ide_helper/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod compiler_info; -pub mod fake_error; -mod call_finder; -mod query_signature; \ No newline at end of file diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index d220d78d7b1..cba0630f983 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -14,8 +14,6 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashMap, hir::def_id::DefId, }; -use prusti_encoder::ide::encoding_info::{SpanOfCallContracts, EncodingInfo}; -use crate::ide_helper::compiler_info::ProcDef; #[tracing::instrument(name = "prusti::verify", level = "debug", skip(env))] pub fn verify<'tcx>( @@ -74,7 +72,7 @@ pub fn verify<'tcx>( let program = request.program; - let mut result = prusti_server::verify_programs(&env.diagnostic, vec![program]); + let result = prusti_server::verify_programs(&env.diagnostic, vec![program]); println!("verification result: {result:?}"); From 47591c4fc3879703c4f994c308161ea2cdda4ea7 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 1 Aug 2024 12:40:47 +0200 Subject: [PATCH 38/53] Move contract span collection and fix selective encoding --- Cargo.lock | 2 + .../src/encoder_traits/impure_function_enc.rs | 4 +- .../src/encoder_traits/pure_function_enc.rs | 4 +- prusti-encoder/src/encoders/mir_impure.rs | 54 ++++++++++- prusti-encoder/src/lib.rs | 92 +++++++++---------- prusti/src/callbacks.rs | 41 +++++---- prusti/src/verifier.rs | 42 ++------- vir/Cargo.toml | 4 +- vir/src/spans.rs | 50 +++++++++- 9 files changed, 178 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7e0404e04a..87a7c1f17fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3040,10 +3040,12 @@ dependencies = [ "bincode", "bumpalo", "cfg-if", + "ide", "prusti-interface", "prusti-rustc-interface", "sealed", "serde", + "serde_json", "vir-proc-macro", ] diff --git a/prusti-encoder/src/encoder_traits/impure_function_enc.rs b/prusti-encoder/src/encoder_traits/impure_function_enc.rs index 0eff53dce5c..72ff5c8d97e 100644 --- a/prusti-encoder/src/encoder_traits/impure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/impure_function_enc.rs @@ -83,7 +83,9 @@ where // Do not encode the method body if it is external, trusted or just // a call stub. - let local_def_id = def_id.as_local().filter(|_| !trusted); + // Also do not encode if we are doing selective verification and the + // current method is not selected. + let local_def_id = def_id.as_local().filter(|_| !trusted && crate::selected(&def_id)); let blocks = if let Some(local_def_id) = local_def_id { let body = vcx .body_mut() diff --git a/prusti-encoder/src/encoder_traits/pure_function_enc.rs b/prusti-encoder/src/encoder_traits/pure_function_enc.rs index 6a7269653a7..c6ad9765356 100644 --- a/prusti-encoder/src/encoder_traits/pure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_function_enc.rs @@ -124,7 +124,9 @@ where }) })); - let expr = if trusted { + // don't encode the body if it is trusted or we are doing selective verification + // and the current item is not selected + let expr = if trusted || !crate::selected(&def_id) { None } else { // Encode the body of the function diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index f0bc696ab22..a46b0e7f272 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -49,7 +49,8 @@ use crate::{ func_app_ty_params::LiftedFuncAppTyParamsEnc }, FunctionCallTaskDescription, MirBuiltinEnc - } + }, + Span, }; use super::{ @@ -63,6 +64,7 @@ use super::{ MirMonoImpureEnc, MirPolyImpureEnc }; +use prusti_utils::config; const ENCODE_REACH_BB: bool = false; @@ -813,9 +815,55 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< .. } => { let (func_def_id, caller_substs) = self.get_def_id_and_caller_substs(func); - let is_pure = crate::encoders::with_proc_spec(func_def_id, |spec| + let is_pure = crate::encoders::with_proc_spec(func_def_id, |spec| { + // FIXME: WIP. only handling inherent specs for now + if config::show_ide_info() { + let mut spans: Vec = Vec::new(); + let tcx = self.vcx().tcx(); + if let Some(pre_def_ids) = spec.pres.expect_empty_or_inherent() { + let mut pre_spans = pre_def_ids + .iter() + .map(|pre_def_id| tcx.def_span(pre_def_id)) + .collect::>(); + spans.append(&mut pre_spans); + } + if let Some(post_def_ids) = spec.posts.expect_empty_or_inherent() { + let mut post_spans = post_def_ids + .iter() + .map(|pre_def_id| tcx.def_span(pre_def_id)) + .collect::>(); + spans.append(&mut post_spans); + } + if let Some(pledges) = spec.pledges.expect_empty_or_inherent() { + let mut pledge_spans = pledges + .iter() + .map(|pledge| { + let rhs = tcx.def_span(pledge.rhs); + if let Some(lhs) = pledge.lhs { tcx.def_span(lhs).to(rhs) } + else { rhs } + }).collect::>(); + spans.append(&mut pledge_spans); + } + // FIXME: not detecting the pure annotation in local-testing/generics/mono.rs + if let Some(Some(purity_def_id)) = spec.purity.expect_empty_or_inherent() { + spans.push(tcx.def_span(purity_def_id)); + } + if let Some(Some(terminates_def_id)) = spec.terminates.expect_empty_or_inherent() { + spans.push(tcx.def_span(terminates_def_id.to_def_id())); + } + let krate = tcx.crate_name(func_def_id.krate); + let defpath = tcx.def_path_str(func_def_id); + let unique_item_name = format!("{}::{}", krate, defpath); + self.vcx().push_call_contract_span( + unique_item_name, + vec![span.clone()], + spans, + tcx.sess.source_map(), + ) + } + spec.kind.is_pure().unwrap_or_default() - ).unwrap_or_default(); + }).unwrap_or_default(); let dest = self.encode_place(Place::from(*destination)).expr; diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index 37676a6f5cb..f5f588bf846 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -11,8 +11,7 @@ mod encoders; mod encoder_traits; pub mod request; - -use prusti_interface::{environment::{EnvBody, EnvQuery}, PrustiError}; +use prusti_interface::{environment::{EnvBody, EnvQuery, EnvDiagnostic}, PrustiError}; use prusti_rustc_interface::{ middle::ty, hir, @@ -27,7 +26,25 @@ use crate::encoders::{lifted::{ casters::{CastTypeImpure, CastTypePure, CastersEnc}, ty_constructor::TyConstructorEnc }, MirPolyImpureEnc}; -use crate::ide::encoding_info::SpanOfCallContracts; + +// TODO: find a better way of handling selective verification. +// Currently, this thread local static is used to converst the initial list of defpaths from the +// `VERIFY_ONLY_DEFPATHS` option from `Vec` to `Vec`. This is done, +// so the encoder (impure/pure_function_enc) can check elements for containment. +// Because currently, it does not have the crate name available to it. But that crate name +// is part of the defpaths passed through the option. +thread_local!( + pub static SELECTIVE_TASKS: std::cell::OnceCell> = std::cell::OnceCell::new() +); + +pub fn selected(def_id: &DefId) -> bool { + SELECTIVE_TASKS.with(|selective_tasks| + selective_tasks + .get() + .as_ref() + .map_or(true, |procs| procs.contains(&def_id)) + ) +} pub fn test_entrypoint<'tcx>( tcx: ty::TyCtxt<'tcx>, @@ -36,12 +53,19 @@ pub fn test_entrypoint<'tcx>( def_spec: prusti_interface::specs::typed::DefSpecificationMap, // this is None if the verification is not selective - all procedures should be encoded. // if the verification is selective, only the procedures in this vector should be encoded with body - procedures: Option<&Vec>, - contract_spans_map: &mut FxHashMap, + procedures: Option>, + env_diagnostic: &EnvDiagnostic<'tcx>, ) -> request::RequestWithContext { crate::encoders::init_def_spec(def_spec); vir::init_vcx(vir::VirCtxt::new(tcx, body)); + SELECTIVE_TASKS.with(|selective_tasks| { + if let Some(procs) = procedures { + selective_tasks + .set(procs) + .expect("Selective tasks were already set"); + } + }); // TODO: this should be a "crate" encoder, which will deps.require all the methods in the crate let source_map = tcx.sess.source_map(); @@ -52,61 +76,21 @@ pub fn test_entrypoint<'tcx>( hir::def::DefKind::Fn | hir::def::DefKind::AssocFn => { let def_id = def_id.to_def_id(); - if prusti_interface::specs::is_spec_fn(tcx, def_id) { + // During selective verification, the second condition means that non-selected + // methods that also aren't called from a selected method are not present + // in the viper program. Called methods are only stubs, but this is handled + // during the actual encoding (treated as trusted). + if prusti_interface::specs::is_spec_fn(tcx, def_id) || !selected(&def_id) { continue; } let (is_pure, is_trusted) = crate::encoders::with_proc_spec(def_id, |proc_spec| { let is_pure = proc_spec.kind.is_pure().unwrap_or_default(); let is_trusted = proc_spec.trusted.extract_inherit().unwrap_or_default(); - - if config::show_ide_info() { - // TODO: only handles inherent spec items - contract_spans_map - .entry(def_id) - .and_modify(|contract_spans| { - let mut spans: Vec = Vec::new(); - // the `get` method has a comment about how it is a bad API, but does it matter - // if we know if the result is inkerent, inherited or refined here? - // if let Some((_, pre_def_ids)) = proc_spec.pres.get() { - if let Some(pre_def_ids) = proc_spec.pres.expect_empty_or_inherent() { - let mut pre_spans = pre_def_ids - .iter() - .map(|pre_def_id| query.get_def_span(pre_def_id)) - .collect::>(); - spans.append(&mut pre_spans); - } - if let Some(post_def_ids) = proc_spec.posts.expect_empty_or_inherent() { - let mut post_spans = post_def_ids - .iter() - .map(|pre_def_id| query.get_def_span(pre_def_id)) - .collect::>(); - spans.append(&mut post_spans); - } - if let Some(pledges) = proc_spec.pledges.expect_empty_or_inherent() { - let mut pledge_spans = pledges - .iter() - .map(|pledge| { - let rhs = query.get_def_span(pledge.rhs); - if let Some(lhs) = pledge.lhs { query.get_def_span(lhs).to(rhs) } - else { rhs } - }).collect::>(); - spans.append(&mut pledge_spans); - } - if let Some(Some(purity_def_id)) = proc_spec.purity.expect_empty_or_inherent() { - spans.push(query.get_def_span(purity_def_id)); - } - if let Some(Some(terminates_def_id)) = proc_spec.terminates.expect_empty_or_inherent() { - spans.push(query.get_def_span(terminates_def_id.to_def_id())); - } - contract_spans.set_contract_spans(spans, source_map); - }); - } - (is_pure, is_trusted) }).unwrap_or_default(); - if procedures.map_or(true, |procs| procs.contains(&def_id)) && !(is_trusted && is_pure) { + if !(is_trusted && is_pure) { let res = MirPolyImpureEnc::encode(def_id, false); assert!(res.is_ok()); } @@ -234,6 +218,12 @@ pub fn test_entrypoint<'tcx>( .to_owned(); */ + if config::show_ide_info() { + vir::with_vcx(|vcx| vcx.emit_contract_spans( + &env_diagnostic, + )); + } + request::RequestWithContext { program: program.to_ref(), } diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 55868ce19bc..9c8fd6d34d5 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -10,7 +10,7 @@ use prusti_interface::{ }; use prusti_rustc_interface::{ borrowck::consumers, - data_structures::{steal::Steal, fx::FxHashMap}, + data_structures::steal::Steal, driver::Compilation, index::IndexVec, interface::{interface::Compiler, Config, Queries}, @@ -27,7 +27,7 @@ use prusti_rustc_interface::{ session::{EarlyErrorHandler, Session}, span::DUMMY_SP, }; -use ::log::debug; +use ::log::{debug, info}; #[derive(Default)] pub struct PrustiCompilerCalls; @@ -186,15 +186,12 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // that is already in `def_spec`? let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); - let mut call_spans_map = FxHashMap::default(); if config::show_ide_info() && !config::no_verify() { - let mut compiler_info = - compiler_info::IdeInfo::collect(&env, &annotated_procedures, &def_spec); + let compiler_info = + IdeInfo::collect(&env, &annotated_procedures, &def_spec); let out = serde_json::to_string(&compiler_info).unwrap(); PrustiError::message(format!("compilerInfo{out}"), DUMMY_SP.into()) .emit(&env.diagnostic); - // TODO: might only need local ones if we can assume that external calls have no contract spans - call_spans_map = compiler_info.get_call_spans_map(); } // as long as we have to throw a fake error we need to check this let is_primary_package = std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); @@ -209,21 +206,25 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // of the fake_error, otherwise verification stops early // with local dependencies if is_primary_package { + let env_diagnostic = env.diagnostic.clone(); let procedures = annotated_procedures .into_iter() .filter(|x| target_def_paths.contains(&env.name.get_unique_item_name(*x))) - .collect(); - let selective_task = VerificationTask { - procedures, - types, - selective: true - }; - // fake_error because otherwise a verification-success - // (for a single method for example) will cause this result - // to be cached by compiler at the moment - let env_diagnostic = env.diagnostic.clone(); - verify(env, def_spec, selective_task, call_spans_map); - fake_error(&env_diagnostic); + .collect::>(); + if !procedures.is_empty() { + let selective_task = VerificationTask { + procedures, + types, + selective: true + }; + // fake_error because otherwise a verification-success + // (for a single method for example) will cause this result + // to be cached by compiler at the moment + verify(env, def_spec, selective_task); + } else { + info!("Passed selective defpaths were empty or had no matching procedures - skipping verification."); + } + fake_error(&env_diagnostic); } } else { let verification_task = VerificationTask { @@ -231,7 +232,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { types, selective: false, }; - verify(env, def_spec, verification_task, call_spans_map); + verify(env, def_spec, verification_task); } } else if config::skip_verification() && !config::no_verify() && is_primary_package { // add a fake error, reason explained in issue #1261 diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index cba0630f983..c1004f10335 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -4,23 +4,16 @@ use log::{debug, warn}; use prusti_utils::{config, report::user}; use prusti_interface::{ data::{VerificationResult, VerificationTask}, - environment::{Environment, EnvDiagnostic}, + environment::Environment, specs::typed, - PrustiError, -}; -use prusti_rustc_interface::{ - errors::MultiSpan, - span::DUMMY_SP, - data_structures::fx::FxHashMap, - hir::def_id::DefId, }; +use prusti_rustc_interface::errors::MultiSpan; #[tracing::instrument(name = "prusti::verify", level = "debug", skip(env))] pub fn verify<'tcx>( env: Environment<'tcx>, def_spec: typed::DefSpecificationMap, verification_task: VerificationTask<'tcx>, - mut contract_spans_map: FxHashMap, ) { if env.diagnostic.has_errors() { warn!("The compiler reported an error, so the program will not be verified."); @@ -36,7 +29,8 @@ pub fn verify<'tcx>( debug!("Verification task: {:?}", &verification_task); user::message(format!( - "Verification of {} items...", + "{}erification of {} items...", + if verification_task.selective { "Selective v" } else { "V" }, verification_task.procedures.len() )); @@ -62,14 +56,10 @@ pub fn verify<'tcx>( env.body, env.query, def_spec, - if verification_task.selective { Some(&verification_task.procedures) } else { None }, - &mut contract_spans_map, + if verification_task.selective { Some(verification_task.procedures) } else { None }, + &env.diagnostic, ); - if config::show_ide_info() { - emit_contract_spans(&env.diagnostic, contract_spans_map); - } - let program = request.program; let result = prusti_server::verify_programs(&env.diagnostic, vec![program]); @@ -128,23 +118,3 @@ pub fn verify<'tcx>( //}; } } - -pub fn emit_contract_spans( - env_diagnostic: &EnvDiagnostic<'_>, - contract_spans_map: FxHashMap, -) { - let mut contract_spans: Vec = contract_spans_map - .into_values() - .collect(); - contract_spans.retain(|cs| !cs.contracts_spans.is_empty()); - // sort, so the order does not randomly change between runs - contract_spans - .sort_by(|a,b| a.defpath.cmp(&b.defpath)); - - let encoding_info = EncodingInfo { call_contract_spans: contract_spans }; - PrustiError::message( - format!("encodingInfo{}", encoding_info.to_json_string()), - DUMMY_SP.into(), - ) - .emit(env_diagnostic); -} diff --git a/vir/Cargo.toml b/vir/Cargo.toml index a5a1c456ce3..298417835ea 100644 --- a/vir/Cargo.toml +++ b/vir/Cargo.toml @@ -12,9 +12,11 @@ prusti-rustc-interface = { path = "../prusti-rustc-interface" } vir-proc-macro = { path = "../vir-proc-macro" } bumpalo = { version = "3.12.0" } prusti-interface = { path = "../prusti-interface" } +ide = { path = "../ide" } sealed = "0.5" cfg-if = "1.0.0" -serde = "*" +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } [dev-dependencies] bincode = "1.3" diff --git a/vir/src/spans.rs b/vir/src/spans.rs index 8fff58a9f3d..5639ee83119 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -1,7 +1,12 @@ use std::collections::HashMap; -use prusti_interface::PrustiError; -use prusti_rustc_interface::span::Span; +use prusti_interface::{PrustiError, environment::EnvDiagnostic}; +use prusti_rustc_interface::span::{ + Span, + DUMMY_SP, + source_map::SourceMap +}; use crate::VirCtxt; +use ide::{SpanOfCallContracts, EncodingInfo}; pub struct VirSpanHandler<'vir> { error_kind: &'static str, @@ -63,6 +68,8 @@ pub struct VirSpanManager<'vir> { stack: Vec<&'vir crate::spans::VirSpan<'vir>>, handlers: HashMap>, + + call_contract_spans: Vec, } impl<'tcx> VirCtxt<'tcx> { @@ -147,4 +154,43 @@ impl<'tcx> VirCtxt<'tcx> { .get(pos_id) .map(|vir_span| vir_span.span) } + + pub fn push_call_contract_span( + &'tcx self, + defpath: String, + call_spans: Vec, + contracts_spans: Vec, + source_map: &SourceMap + ) { + let span_of_call_contracts = SpanOfCallContracts::new( + defpath, + call_spans, + contracts_spans, + source_map, + ); + let mut manager = self.spans.borrow_mut(); + manager.call_contract_spans.push(span_of_call_contracts); + } + + pub fn emit_contract_spans( + &'tcx self, + env_diagnostic: &EnvDiagnostic<'_>, + ) { + let mut call_contract_spans = self + .spans + .borrow() + .call_contract_spans + .clone(); + call_contract_spans.retain(|cs| !cs.contracts_spans.is_empty()); + // sort, so the is deterministic + call_contract_spans + .sort_by(|a,b| a.defpath.cmp(&b.defpath)); + + let encoding_info = EncodingInfo { call_contract_spans }; + PrustiError::message( + format!("encodingInfo{}", encoding_info.to_json_string()), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + } } From ee1747cd399ddafa028d98bc181371f3b7ebb7ab Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 2 Aug 2024 10:41:11 +0200 Subject: [PATCH 39/53] Emit entire procedure span rather than just signature in CompilerInfo --- ide/src/compiler_info.rs | 63 ++++++++++++----------- prusti-interface/src/environment/query.rs | 8 +++ 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/ide/src/compiler_info.rs b/ide/src/compiler_info.rs index b6310680d4d..dd3381f6fd9 100644 --- a/ide/src/compiler_info.rs +++ b/ide/src/compiler_info.rs @@ -65,40 +65,41 @@ fn collect_procedures( let mut procs = Vec::new(); for defid in procedures { let defpath = env.name.get_unique_item_name(*defid); - let span = env.query.get_def_span(defid); - let vscspan = VscSpan::from_span(&span, sourcemap); - - // Filter out the predicates and trusted methods, - // since we don't want to allow selective verification - // for them - let mut is_predicate = false; - let mut is_trusted = false; - - let proc_spec_opt = def_spec.get_proc_spec(defid); - if let Some(proc_spec) = proc_spec_opt { - let kind_spec = proc_spec - .base_spec - .kind - .extract_with_selective_replacement(); - let trusted_spec = proc_spec - .base_spec - .trusted - .extract_with_selective_replacement(); - if let Some(typed::ProcedureSpecificationKind::Predicate(..)) = kind_spec { - is_predicate = true; + if let Some(span) = env.query.get_def_with_body_span(defid) { + let vscspan = VscSpan::from_span(&span, sourcemap); + + // Filter out the predicates and trusted methods, + // since we don't want to allow selective verification + // for them + let mut is_predicate = false; + let mut is_trusted = false; + + let proc_spec_opt = def_spec.get_proc_spec(defid); + if let Some(proc_spec) = proc_spec_opt { + let kind_spec = proc_spec + .base_spec + .kind + .extract_with_selective_replacement(); + let trusted_spec = proc_spec + .base_spec + .trusted + .extract_with_selective_replacement(); + if let Some(typed::ProcedureSpecificationKind::Predicate(..)) = kind_spec { + is_predicate = true; + } + if let Some(true) = trusted_spec { + is_trusted = true; + } } - if let Some(true) = trusted_spec { - is_trusted = true; + + if !is_trusted && !is_predicate { + procs.push(ProcDef { + name: defpath, + defid: *defid, + span: vscspan, + }); } } - - if !is_trusted && !is_predicate { - procs.push(ProcDef { - name: defpath, - defid: *defid, - span: vscspan, - }); - } } procs } diff --git a/prusti-interface/src/environment/query.rs b/prusti-interface/src/environment/query.rs index 299839e285e..c5d0cb254f0 100644 --- a/prusti-interface/src/environment/query.rs +++ b/prusti-interface/src/environment/query.rs @@ -88,6 +88,14 @@ impl<'tcx> EnvQuery<'tcx> { self.tcx.def_span(def_id.into_param()) } + /// Get the span including the body of the given definition. + pub fn get_def_with_body_span(self, def_id: impl IntoParam) -> Option { + if let Some(local_def_id) = def_id.into_param().as_local() { + let hir_id = self.as_hir_id(local_def_id); + Some(self.tcx.hir().span_with_body(hir_id)) + } else { None } + } + /// Returns true iff `def_id` has an MIR body which we may want to access pub fn has_body(self, def_id: impl IntoParam) -> bool { let id = def_id.into_param(); From 174aef50316eeec425188c5f8fc0543e67100b83 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 2 Aug 2024 10:57:30 +0200 Subject: [PATCH 40/53] Add viper method name backtranslation --- prusti-server/src/lib.rs | 37 +++++++++++++++++++++++++------------ vir/src/spans.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 1ecc5720874..446d9e8c081 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -350,14 +350,26 @@ fn handle_quantifier_chosen_triggers_message( } } -// TODO: may or may not need program name translation -fn viper_method_to_rust_method(_viper_method: &String, program_name: &String) -> Option { - Some(program_name.clone()) +// Counter part to (private) `vir::viper_ident::sanitize_str` +fn desanitize_string(s: &str) -> String { + s + .replace("$lt$", "<") + .replace("$gt$", ">") + .replace("$space$", " ") + .replace("$comma$", ",") + .replace("$colon$", ":") } -// TODO -fn vir_label_to_pos(_vir_label: &String) -> Option { - Some(MultiSpan::from_span(DUMMY_SP.into())) +fn viper_method_to_rust_method(viper_method: &str, crate_name: &str) -> Option { + if viper_method.starts_with("m_") { + Some(format!( + "{}::{}", + crate_name, + desanitize_string(&viper_method[2..]) + )) + } else { + None + } } fn handle_block_processing_message( @@ -370,10 +382,11 @@ fn handle_block_processing_message( ) { if config::report_viper_messages() && config::report_block_messages() { let processed = result != None; - debug!("Received {}: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label}, path_id: {path_id} }}", + debug!("Received {} message: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label}, path_id: {path_id} }}", if processed {"path processed"} else {"block reached"}); - if let Some(method_name) = viper_method_to_rust_method(&viper_method, & program_name) { - if let Some(span) = vir_label_to_pos(&vir_label) { + let location = vir::with_vcx(|vcx| vcx.get_span_and_crate_name(&vir_label)); + if let Some((span, krate)) = location { + if let Some(method_name) = viper_method_to_rust_method(&viper_method, &krate) { PrustiError::message( format!("{}{}", if processed {"pathProcessedMessage"} else {"blockReachedMessage"}, @@ -381,10 +394,10 @@ fn handle_block_processing_message( // FIXME: outputting vir_label only because it makes the messages different, otherwise the errors get merged. // should be removed once backtranslation of labels is implemented so the resulting spans are different. else {json!({"method": method_name, "path_id": path_id, "label": vir_label})}, - ), span.clone() + ), span.clone().into() ).emit(env_diagnostic); - } else { error!("Could not map vir label {vir_label} to a position in verification of method {method_name} in {program_name}") } - } else { error!("Could not map viper method {viper_method} to a Rust method in verification of {program_name}") } + } else { error!("Could not map viper method {viper_method} to a Rust method in verification of {program_name}") } + } else { error!("Could not map vir label {vir_label} to a position in {program_name}") } } } diff --git a/vir/src/spans.rs b/vir/src/spans.rs index 5639ee83119..61fa6de1fc2 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use prusti_interface::{PrustiError, environment::EnvDiagnostic}; +use prusti_rustc_interface::hir::def_id::LOCAL_CRATE; use prusti_rustc_interface::span::{ Span, DUMMY_SP, @@ -69,7 +70,10 @@ pub struct VirSpanManager<'vir> { handlers: HashMap>, + /// Vector of objects holding for each method call the spans of any + /// associated contracts. Intended for consuption by Prusti-Assistant. call_contract_spans: Vec, + } impl<'tcx> VirCtxt<'tcx> { @@ -155,6 +159,32 @@ impl<'tcx> VirCtxt<'tcx> { .map(|vir_span| vir_span.span) } + // TODO + pub fn get_span_from_label( + &'tcx self, + _label: &str + ) -> Option { + Some(DUMMY_SP.into()) + } + + pub fn get_crate_name( + &'tcx self, + ) -> String { + self + .tcx() + .crate_name(LOCAL_CRATE) + .to_string() + } + + pub fn get_span_and_crate_name( + &'tcx self, + vir_label: &String + ) -> Option<(Span, String)> { + if let Some(span) = self.get_span_from_label(vir_label) { + Some((span, self.get_crate_name())) + } else { None } + } + pub fn push_call_contract_span( &'tcx self, defpath: String, From b8c37ed02d793180171a01b4e8ccd125b0880d22 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 2 Aug 2024 11:41:07 +0200 Subject: [PATCH 41/53] Revert contract spans having multiple call spans --- ide/src/encoding_info.rs | 23 ++++------------------- prusti-encoder/src/encoders/mir_impure.rs | 2 +- vir/src/spans.rs | 5 ++--- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/ide/src/encoding_info.rs b/ide/src/encoding_info.rs index d215e322708..6969cca76ec 100644 --- a/ide/src/encoding_info.rs +++ b/ide/src/encoding_info.rs @@ -9,7 +9,7 @@ pub struct SpanOfCallContracts { /// the defpath of the method that is called pub defpath: String, /// the spans where this method is called - pub call_spans: Vec, + pub call_span: VscSpan, /// the spans of all the specifications of the called method pub contracts_spans: Vec, } @@ -17,36 +17,21 @@ pub struct SpanOfCallContracts { impl SpanOfCallContracts { pub fn new( defpath: String, - call_spans: Vec, + call_span: Span, contracts_spans: Vec, source_map: &SourceMap ) -> Self { - let call_spans = call_spans - .iter() - .map(|sp| VscSpan::from_span(sp, source_map)) - .collect::>(); + let call_span = VscSpan::from_span(&call_span, source_map); let contracts_spans = contracts_spans .iter() .map(|sp| VscSpan::from_span(sp, source_map)) .collect::>(); Self { defpath, - call_spans, + call_span, contracts_spans, } } - - pub fn set_contract_spans(&mut self, spans: Vec, source_map: &SourceMap) { - self.contracts_spans = spans - .iter() - .map(|sp| VscSpan::from_span(sp, source_map)) - .collect::>(); - } - - pub fn push_call_span(&mut self, span: &Span, source_map: &SourceMap) { - let vsc_span = VscSpan::from_span(span, source_map); - self.call_spans.push(vsc_span); - } } #[derive(Serialize)] diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index a46b0e7f272..62701382fec 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -856,7 +856,7 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< let unique_item_name = format!("{}::{}", krate, defpath); self.vcx().push_call_contract_span( unique_item_name, - vec![span.clone()], + span.clone(), spans, tcx.sess.source_map(), ) diff --git a/vir/src/spans.rs b/vir/src/spans.rs index 61fa6de1fc2..69a2c3e36a3 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -188,13 +188,13 @@ impl<'tcx> VirCtxt<'tcx> { pub fn push_call_contract_span( &'tcx self, defpath: String, - call_spans: Vec, + call_span: Span, contracts_spans: Vec, source_map: &SourceMap ) { let span_of_call_contracts = SpanOfCallContracts::new( defpath, - call_spans, + call_span, contracts_spans, source_map, ); @@ -211,7 +211,6 @@ impl<'tcx> VirCtxt<'tcx> { .borrow() .call_contract_spans .clone(); - call_contract_spans.retain(|cs| !cs.contracts_spans.is_empty()); // sort, so the is deterministic call_contract_spans .sort_by(|a,b| a.defpath.cmp(&b.defpath)); From 1ebf5204f275491e9d6f47aa48ba81ebe1de9807 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 8 Aug 2024 13:43:52 +0200 Subject: [PATCH 42/53] Report results per method, backtranslate block labels Also change backtranslation of viper identifiers. Note that caching may not work properly for now if the `GENERATE_VIPER_MESSAGES` flag is set. Fixing this is a WIP. --- .../src/encoder_traits/impure_function_enc.rs | 3 + prusti-encoder/src/encoders/mir_impure.rs | 19 + prusti-server/src/backend.rs | 79 +++- prusti-server/src/lib.rs | 204 +++++++--- prusti-server/src/server_message.rs | 9 +- viper-sys/build.rs | 12 + viper/src/verifier.rs | 380 ++++++++++-------- vir/src/spans.rs | 100 ++++- 8 files changed, 550 insertions(+), 256 deletions(-) diff --git a/prusti-encoder/src/encoder_traits/impure_function_enc.rs b/prusti-encoder/src/encoder_traits/impure_function_enc.rs index 72ff5c8d97e..cb8f9232aa4 100644 --- a/prusti-encoder/src/encoder_traits/impure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/impure_function_enc.rs @@ -87,6 +87,9 @@ where // current method is not selected. let local_def_id = def_id.as_local().filter(|_| !trusted && crate::selected(&def_id)); let blocks = if let Some(local_def_id) = local_def_id { + // Store identifiers for backtranslation, move this if other ones should also be included + vcx.insert_viper_identifier(method_name.to_str().to_string(), &def_id); + let body = vcx .body_mut() .get_impure_fn_body(local_def_id, substs, caller_def_id); diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index 62701382fec..a61217403da 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -541,6 +541,25 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< self.super_basic_block_data(block, data); let stmts = self.current_stmts.take().unwrap(); let terminator = self.current_terminator.take().unwrap(); + + if prusti_utils::config::report_block_messages() { + let stmt_count = data.statements.len(); + if stmt_count > 0 { + let terminator_span = data + .terminator + .as_ref() + .unwrap() + .source_info + .span; + let block_span = data + .statements + .iter() + .fold(terminator_span, |acc, stmt| acc.to(stmt.source_info.span)); + let label = format!("bb_{}", block.as_usize()); + self.vcx.insert_block_span((self.def_id, label), block_span); + } + } + self.encoded_blocks.push( self.vcx.mk_cfg_block( self.vcx.alloc(vir::CfgBlockLabelData::BasicBlock(block.as_usize())), diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index ec6fca0b813..7a8c8343332 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -7,6 +7,7 @@ use prusti_utils::{ use std::{ sync::{mpsc, Arc}, thread, time, + collections::HashSet, }; use viper::{jni_utils::JniUtils, VerificationContext, VerificationResultKind, Viper}; use viper_sys::wrappers::{java, viper::*}; @@ -54,7 +55,7 @@ impl<'a> Backend<'a> { if config::report_viper_messages() { verify_and_poll_msgs(verifier, context, viper_arc, viper_program, sender) } else { - verifier.verify(viper_program) + verifier.verify(viper_program, None) } }) } @@ -85,8 +86,7 @@ fn verify_and_poll_msgs( // start thread for polling messages thread::scope(|scope| { let polling_thread = scope.spawn(|| polling_function(viper_arc, &rep_glob_ref, sender)); - kind = verifier.verify(viper_program); - polling_thread.join().unwrap(); + kind = verifier.verify(viper_program, Some(polling_thread)); }); debug!("Viper message polling thread terminated"); kind @@ -96,12 +96,14 @@ fn polling_function( viper_arc: &Arc, rep_glob_ref: &jni::objects::GlobalRef, sender: mpsc::Sender, -) { +) -> HashSet { let verification_context = viper_arc.attach_current_thread(); let env = verification_context.env(); let jni = JniUtils::new(env); let reporter_instance = rep_glob_ref.as_obj(); let reporter_wrapper = silver::reporter::PollingReporter::with(env); + + let mut error_hashes = HashSet::new(); loop { while reporter_wrapper .call_hasNewMessage(reporter_instance) @@ -117,7 +119,7 @@ fn polling_function( let q_name = jni.get_string(jni.unwrap_result(msg_wrapper.call_quantifier(msg))); let q_inst = jni.unwrap_result(msg_wrapper.call_instantiations(msg)); - debug!("QuantifierInstantiationsMessage: {} {}", q_name, q_inst); + // debug!("QuantifierInstantiationsMessage: {} {}", q_name, q_inst); // also matches the "-aux" and "_precondition" quantifiers generated // we encoded the position id in the line and column number since this is not used by // prusti either way @@ -150,13 +152,13 @@ fn polling_function( let viper_quant = jni.unwrap_result(msg_wrapper.call_quantifier(msg)); let viper_quant_str = jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(viper_quant))); - debug!("quantifier chosen trigger quant: {viper_quant_str}"); + // debug!("quantifier chosen trigger quant: {viper_quant_str}"); // we encoded the position id in the line and column number since this is not used by // prusti either way let pos = jni.unwrap_result(positioned_wrapper.call_pos(viper_quant)); let pos_string = jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(pos))); - debug!("quantifier chosen trigger pos: {pos_string}"); + // debug!("quantifier chosen trigger pos: {pos_string}"); // TODO: the PR unconditionally does `pos_string.rfind('.').unwrap()` which crashes when there is no position. // Is that intended? if let Some(pos_id_index) = pos_string.rfind('.') { @@ -178,7 +180,68 @@ fn polling_function( .unwrap(); } }, - "viper.silver.reporter.VerificationTerminationMessage" => return, + "viper.silver.reporter.VerificationTerminationMessage" => return error_hashes, + "viper.silver.reporter.EntitySuccessMessage" => { + let msg_wrapper = silver::reporter::EntitySuccessMessage::with(env); + let concerning = jni.unwrap_result(msg_wrapper.call_concerning(msg)); + if jni.is_instance_of(concerning, "viper/silver/ast/Method") { + let method_wrapper = silver::ast::Method::with(env); + let method_name = + jni.get_string(jni.unwrap_result(method_wrapper.call_name(concerning))); + debug!("Entity success for method: {method_name} (only processed if starting with \"m_\")"); + // this should only match local methods and extern specs + if method_name.starts_with("m_") { + let verification_time = jni.unwrap_result(msg_wrapper.call_verificationTime(msg)); + // verification_time is a long -> i64. but we are using u128 + if verification_time >= 0 { + let verification_time_u128 = verification_time as u64 as u128; + sender + .send(ServerMessage::MethodTermination { + viper_method_name: method_name.to_string(), + result: VerificationResultKind::Success, + verification_time: verification_time_u128, + }) + .unwrap(); + } else { + debug!("EntitySuccessMessage for {} had negative verification time {}", method_name, verification_time); + } + } + } else { + debug!("Received entity was not a method"); + } + }, + "viper.silver.reporter.EntityFailureMessage" => { + let msg_wrapper = silver::reporter::EntityFailureMessage::with(env); + let concerning = jni.unwrap_result(msg_wrapper.call_concerning(msg)); + if jni.is_instance_of(concerning, "viper/silver/ast/Method") { + let method_wrapper = silver::ast::Method::with(env); + let method_name = + jni.get_string(jni.unwrap_result(method_wrapper.call_name(concerning))); + debug!("Entity failure for method: {method_name}"); + // this should only match local methods and extern specs + if method_name.starts_with("m_") { + let verification_time = jni.unwrap_result(msg_wrapper.call_verificationTime(msg)); + // verification_time is a long -> i64. but we are using u128 + if verification_time >= 0 { + let viper_result = jni.unwrap_result(msg_wrapper.call_result(msg)); + let result = viper::extract_errors(&jni, &env, viper_result, Some(&mut error_hashes)); + let verification_time_u128 = verification_time as u64 as u128; + sender + .send(ServerMessage::MethodTermination { + viper_method_name: method_name.to_string(), + result, + verification_time: verification_time_u128, + }) + .unwrap(); + } else { + debug!("EntityFailureMessage for {} had negative verification time {}", method_name, verification_time); + } + } + } else { + debug!("Received entity was not a method"); + } + + }, "viper.silver.reporter.BlockReachedMessage" => { let msg_wrapper = silver::reporter::BlockReachedMessage::with(env); let method_name = diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 446d9e8c081..0dd73733b8b 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -116,28 +116,74 @@ async fn handle_stream( while let Some((program_name, server_msg)) = verification_messages.next().await { match server_msg { - ServerMessage::Termination(result) => handle_termination_message(env_diagnostic, program_name, result, &mut prusti_errors, &mut overall_result), + ServerMessage::Termination(result) => handle_termination_message( + env_diagnostic, + program_name, + result, + &mut prusti_errors, + &mut overall_result + ), + ServerMessage::MethodTermination { + viper_method_name, + result, + verification_time, + } => handle_method_termination_message( + env_diagnostic, + program_name, + viper_method_name, + result, + verification_time, + &mut prusti_errors, + &mut overall_result + ), ServerMessage::QuantifierInstantiation { q_name, insts, pos_id, - } => handle_quantifier_instantiation_message(env_diagnostic, program_name, q_name, insts, pos_id, &mut quantifier_instantiations), + } => handle_quantifier_instantiation_message( + env_diagnostic, + program_name, + q_name, + insts, + pos_id, + &mut quantifier_instantiations + ), ServerMessage::QuantifierChosenTriggers { viper_quant, triggers, pos_id, - } => handle_quantifier_chosen_triggers_message(env_diagnostic, program_name, viper_quant, triggers, pos_id), + } => handle_quantifier_chosen_triggers_message( + env_diagnostic, + program_name, + viper_quant, + triggers, + pos_id + ), ServerMessage::BlockReached { viper_method, vir_label, path_id, - } => handle_block_processing_message(env_diagnostic, program_name, viper_method, vir_label, path_id, None), + } => handle_block_processing_message( + env_diagnostic, + program_name, + viper_method, + vir_label, + path_id, + None + ), ServerMessage::PathProcessed { viper_method, vir_label, path_id, result, - } => handle_block_processing_message(env_diagnostic, program_name, viper_method, vir_label, path_id, Some(result)), + } => handle_block_processing_message( + env_diagnostic, + program_name, + viper_method, + vir_label, + path_id, + Some(result) + ), } } @@ -158,29 +204,13 @@ async fn handle_stream( overall_result } -fn handle_termination_message( +fn handle_result( env_diagnostic: &EnvDiagnostic<'_>, program_name: String, result: viper::VerificationResult, prusti_errors: &mut Vec, overall_result: &mut VerificationResult ) { - debug!("Received termination message with result {result:?} in verification of {program_name}"); - if config::show_ide_info() { - PrustiError::message( - format!( - "ideVerificationResult{}", - serde_json::to_string(&IdeVerificationResult { - item_name: result.item_name.clone(), - success: result.is_success(), - cached: result.cached, - time_ms: result.time_ms, - }).unwrap() - ), - DUMMY_SP.into(), - ) - .emit(env_diagnostic); - } match result.kind { // nothing to do viper::VerificationResultKind::Success => (), @@ -280,6 +310,72 @@ fn handle_termination_message( } } +fn handle_method_termination_message( + env_diagnostic: &EnvDiagnostic<'_>, + _program_name: String, + viper_method_name: String, + result_kind: viper::VerificationResultKind, + verification_time: u128, + prusti_errors: &mut Vec, + overall_result: &mut VerificationResult +) { + debug!("Received method termination message with result {result_kind:?} in verification of {viper_method_name}"); + if let Some(rust_method) = vir::with_vcx(|vcx| vcx.viper_to_rust_identifier(&viper_method_name)) { + let result = viper::VerificationResult { + item_name: rust_method, + kind: result_kind, + cached: false, + time_ms: verification_time, + }; + + if config::show_ide_info() { + PrustiError::message( + format!( + "ideVerificationResult{}", + serde_json::to_string(&IdeVerificationResult { + item_name: result.item_name.clone(), + success: result.is_success(), + cached: result.cached, + time_ms: result.time_ms, + }).unwrap() + ), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + } + + handle_result(env_diagnostic, result.item_name.clone(), result, prusti_errors, overall_result); + } else { + debug!("Could not map method identifier to def id: {viper_method_name}"); + } +} + +fn handle_termination_message( + env_diagnostic: &EnvDiagnostic<'_>, + program_name: String, + result: viper::VerificationResult, + prusti_errors: &mut Vec, + overall_result: &mut VerificationResult +) { + debug!("Received termination message with result {result:?} in verification of {program_name}"); + if config::show_ide_info() { + PrustiError::message( + format!( + "ideVerificationResult{}", + serde_json::to_string(&IdeVerificationResult { + item_name: result.item_name.clone(), + success: result.is_success(), + cached: result.cached, + time_ms: result.time_ms, + }).unwrap() + ), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + } + handle_result(env_diagnostic, program_name, result, prusti_errors, overall_result); +} + fn handle_quantifier_instantiation_message( env_diagnostic: &EnvDiagnostic<'_>, program_name: String, @@ -350,28 +446,6 @@ fn handle_quantifier_chosen_triggers_message( } } -// Counter part to (private) `vir::viper_ident::sanitize_str` -fn desanitize_string(s: &str) -> String { - s - .replace("$lt$", "<") - .replace("$gt$", ">") - .replace("$space$", " ") - .replace("$comma$", ",") - .replace("$colon$", ":") -} - -fn viper_method_to_rust_method(viper_method: &str, crate_name: &str) -> Option { - if viper_method.starts_with("m_") { - Some(format!( - "{}::{}", - crate_name, - desanitize_string(&viper_method[2..]) - )) - } else { - None - } -} - fn handle_block_processing_message( env_diagnostic: &EnvDiagnostic<'_>, program_name: String, @@ -383,21 +457,33 @@ fn handle_block_processing_message( if config::report_viper_messages() && config::report_block_messages() { let processed = result != None; debug!("Received {} message: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label}, path_id: {path_id} }}", - if processed {"path processed"} else {"block reached"}); - let location = vir::with_vcx(|vcx| vcx.get_span_and_crate_name(&vir_label)); - if let Some((span, krate)) = location { - if let Some(method_name) = viper_method_to_rust_method(&viper_method, &krate) { - PrustiError::message( - format!("{}{}", - if processed {"pathProcessedMessage"} else {"blockReachedMessage"}, - if processed {json!({"method": method_name, "path_id": path_id, "result": result.unwrap()})} - // FIXME: outputting vir_label only because it makes the messages different, otherwise the errors get merged. - // should be removed once backtranslation of labels is implemented so the resulting spans are different. - else {json!({"method": method_name, "path_id": path_id, "label": vir_label})}, - ), span.clone().into() - ).emit(env_diagnostic); - } else { error!("Could not map viper method {viper_method} to a Rust method in verification of {program_name}") } - } else { error!("Could not map vir label {vir_label} to a position in {program_name}") } + if processed {"path processed"} else {"block reached"} + ); + if vir_label == "start" { return } + vir::with_vcx(|vcx| { + if let Some(def_id) = vcx.get_viper_identifier(&viper_method) { + let rust_method = vcx.get_unique_item_name(&def_id); + if vir_label == "end" { + PrustiError::message( + format!("{}{}", + "pathProcessedMessage", + json!({"method": rust_method, "path_id": path_id}) + ), DUMMY_SP.into() + ).emit(env_diagnostic); + } else { + let key = (def_id, vir_label); + if let Some(span) = vcx.get_block_span(&key) { + PrustiError::message( + format!("{}{}", + if processed {"pathProcessedMessage"} else {"blockReachedMessage"}, + if processed {json!({"method": rust_method, "path_id": path_id, "result": result.unwrap()})} + else {json!({"method": rust_method, "path_id": path_id})}, + ), span.clone().into() + ).emit(env_diagnostic); + } else { error!("Could not map vir label {} to a position in {rust_method}", key.1) } + } + } else { error!("Could not map method identifier to def id: {viper_method}") } + }) } } diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs index ac3949bbeb4..4d986a16b7d 100644 --- a/prusti-server/src/server_message.rs +++ b/prusti-server/src/server_message.rs @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use viper::VerificationResult; +use viper::{VerificationResult, VerificationResultKind}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] /// A message from a Prusti server to a Prusti client. It may contain a result @@ -14,6 +14,13 @@ pub enum ServerMessage { /// has terminated. Termination(VerificationResult), + /// Signals the termination of the verification of a method. Contains the result. + MethodTermination { + viper_method_name: String, + result: VerificationResultKind, + verification_time: u128, + }, + /// A message created by the Viper backend with Z3 about /// the number of instantiations of the named quantifier. QuantifierInstantiation { diff --git a/viper-sys/build.rs b/viper-sys/build.rs index 5d624364f00..ee0ac556509 100644 --- a/viper-sys/build.rs +++ b/viper-sys/build.rs @@ -182,6 +182,17 @@ fn main() { method!("pathId"), method!("verificationResultKind"), ]), + java_class!("viper.silver.reporter.EntitySuccessMessage", vec![ + method!("concerning"), + method!("verificationTime"), + // method!("cached"), + ]), + java_class!("viper.silver.reporter.EntityFailureMessage", vec![ + method!("concerning"), + method!("result", "()Lviper/silver/verifier/Failure;"), + method!("verificationTime"), + // method!("cached"), + ]), java_class!("viper.silver.verifier.Verifier", vec![ method!("name"), method!("buildVersion"), @@ -525,6 +536,7 @@ fn main() { ]), java_class!("viper.silver.ast.Method", vec![ constructor!(), + method!("name"), ]), java_class!("viper.silver.ast.MethodCall", vec![ constructor!(), diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index f3d17bac8ea..cb317a9308a 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -15,7 +15,11 @@ use crate::{ }; use jni::{errors::Result, objects::JObject, JNIEnv}; use log::{debug, error, info}; -use std::path::PathBuf; +use std::{ + path::PathBuf, + collections::HashSet, + thread::ScopedJoinHandle, +}; use viper_sys::wrappers::{scala, viper::*}; pub struct Verifier<'a> { @@ -169,27 +173,12 @@ impl<'a> Verifier<'a> { self } - /// Extract a position identifier from a `Position` object. - fn extract_pos_id(&self, pos: JObject<'_>) -> Option { - let has_identifier_wrapper = silver::ast::HasIdentifier::with(self.env); - - if self - .jni - .is_instance_of(pos, "viper/silver/ast/HasIdentifier") - { - Some( - self.jni.get_string( - self.jni - .unwrap_result(has_identifier_wrapper.call_id(pos)), - ), - ) - } else { - None - } - } - #[tracing::instrument(name = "viper::verify", level = "debug", skip_all)] - pub fn verify(&mut self, program: Program) -> VerificationResultKind { + pub fn verify( + &mut self, + program: Program, + polling_thread: Option>>, + ) -> VerificationResultKind { let ast_utils = self.ast_utils; ast_utils.with_local_frame(16, || { debug!( @@ -222,7 +211,7 @@ impl<'a> Verifier<'a> { .jni .unwrap_result(consistency_error_wrapper.call_pos(e)); - let pos_id = self.extract_pos_id(pos); + let pos_id = extract_pos_id(&self.jni, self.env, pos); let message = self.jni.to_string(self.jni.unwrap_result( @@ -253,158 +242,16 @@ impl<'a> Verifier<'a> { self.smt_manager.stop_and_check(); + // wait for the polling thread if present so no errors get processed here that should + // be processed by the polling thread. Also aviods the need for locking. + let error_hashes_opt = polling_thread.map(|pt| pt.join().unwrap()); if is_failure { - let mut errors: Vec = vec![]; - - let viper_errors = self.jni.seq_to_vec(self.jni.unwrap_result( - silver::verifier::Failure::with(self.env).call_errors(viper_result), - )); - - - let verification_error_wrapper = silver::verifier::VerificationError::with(self.env); - - let error_node_positioned_wrapper = silver::ast::Positioned::with(self.env); - - let failure_context_wrapper = silver::verifier::FailureContext::with(self.env); - - let error_reason_wrapper = silver::verifier::ErrorReason::with(self.env); - - for viper_error in viper_errors { - let is_verification_error = self - .jni - .is_instance_of(viper_error, "viper/silver/verifier/VerificationError"); - - if !is_verification_error { - let is_aborted_exceptionally = self - .jni - .is_instance_of(viper_error, "viper/silver/verifier/AbortedExceptionally"); - - if is_aborted_exceptionally { - let exception = self.jni.unwrap_result( - silver::verifier::AbortedExceptionally::with(self.env) - .call_cause(viper_error), - ); - let stack_trace = - self.jni.unwrap_result(self.jni.get_stack_trace(exception)); - error!( - "The verification aborted due to the following exception: {}", - stack_trace - ); - } else { - error!( - "The verifier returned an unhandled error of type {}: {}", - self.jni.class_name(viper_error), - self.jni.to_string(viper_error) - ); - } - unreachable!( - "The verifier returned an unknown error of type {}: {}", - self.jni.class_name(viper_error), - self.jni.to_string(viper_error) - ); - }; - let mut failure_contexts = self.jni.seq_to_vec(self - .jni - .unwrap_result(verification_error_wrapper.call_failureContexts(viper_error))); - - let counterexample: Option = { - if let Some(failure_context) = failure_contexts.pop() { - let option_original_counterexample = self - .jni - .unwrap_result(failure_context_wrapper.call_counterExample(failure_context)); - if !self - .jni - .is_instance_of(option_original_counterexample, "scala/None$") - { - let original_counterexample = self.jni.unwrap_result( - scala::Some::with(self.env).call_get(option_original_counterexample), - ); - if self.jni.is_instance_of( - original_counterexample, - "viper/silicon/interfaces/SiliconMappedCounterexample", - ) { - // only mapped counterexamples are processed - Some(SiliconCounterexample::new( - self.env, - self.jni, - original_counterexample, - )) - } else { - None - } - } else { - None - } - } else { - None - } - }; - - let reason = self - .jni - .unwrap_result(verification_error_wrapper.call_reason(viper_error)); - - let reason_pos = self - .jni - .unwrap_result(error_reason_wrapper.call_pos(reason)); - - let reason_pos_id = self.extract_pos_id(reason_pos); - if reason_pos_id.is_none() { - debug!( - "The verifier returned an error whose offending node position has no identifier: {:?}", - self.jni.to_string(viper_error) - ); - } - - let error_full_id = self.jni.get_string( - self.jni - .unwrap_result(verification_error_wrapper.call_fullId(viper_error)), - ); - - let pos = self - .jni - .unwrap_result(verification_error_wrapper.call_pos(viper_error)); - - let message = - self.jni.to_string(self.jni.unwrap_result( - verification_error_wrapper.call_readableMessage(viper_error), - )); - - let pos_id = self.extract_pos_id(pos); - if pos_id.is_none() { - debug!( - "The verifier returned an error whose position has no identifier: {:?}", - self.jni.to_string(viper_error) - ); - } - - let offending_node = self - .jni - .unwrap_result(verification_error_wrapper.call_offendingNode(viper_error)); - - let offending_pos = self - .jni - .unwrap_result(error_node_positioned_wrapper.call_pos(offending_node)); - - let offending_pos_id = self.extract_pos_id(offending_pos); - if offending_pos_id.is_none() { - debug!( - "The verifier returned an error whose offending node position has no identifier: {:?}", - self.jni.to_string(viper_error) - ); - } - - errors.push(VerificationError::new( - error_full_id, - pos_id, - offending_pos_id, - reason_pos_id, - message, - counterexample, - )) + if let Some(mut error_hashes) = error_hashes_opt { + debug!("processing final errors. already did {error_hashes:?}"); + extract_errors(&self.jni, self.env, viper_result, Some(&mut error_hashes)) + } else { + extract_errors(&self.jni, self.env, viper_result, None) } - - VerificationResultKind::Failure(errors) } else { VerificationResultKind::Success } @@ -429,3 +276,192 @@ impl<'a> Drop for Verifier<'a> { .unwrap_result(self.env.delete_local_ref(self.frontend_instance)); } } + +/// Extract a position identifier from a `Position` object. +fn extract_pos_id( + jni_utils: &JniUtils<'_>, + env: &JNIEnv<'_>, + pos: JObject<'_> +) -> Option { + let has_identifier_wrapper = silver::ast::HasIdentifier::with(env); + + if jni_utils + .is_instance_of(pos, "viper/silver/ast/HasIdentifier") + { + Some( + jni_utils.get_string( + jni_utils + .unwrap_result(has_identifier_wrapper.call_id(pos)), + ), + ) + } else { + None + } +} + +fn get_java_object_hash(env: &JNIEnv, obj: JObject) -> i32 { + let hash_code = env.call_method(obj, "hashCode", "()I", &[]) + .expect("Failed to call hashCode") + .i() + .expect("Failed to get hashCode as int"); + hash_code +} + +pub fn extract_errors( + jni_utils: &JniUtils<'_>, + env: &JNIEnv<'_>, + viper_result: JObject<'_>, // viper::silicon::verification::Failure + mut error_hashes_opt: Option<&mut HashSet>, +) -> VerificationResultKind { + let mut errors: Vec = vec![]; + + let viper_errors = jni_utils.seq_to_vec(jni_utils.unwrap_result( + silver::verifier::Failure::with(env).call_errors(viper_result), + )); + + let verification_error_wrapper = silver::verifier::VerificationError::with(env); + let error_node_positioned_wrapper = silver::ast::Positioned::with(env); + let failure_context_wrapper = silver::verifier::FailureContext::with(env); + let error_reason_wrapper = silver::verifier::ErrorReason::with(env); + + for viper_error in viper_errors { + + // We only process errors that have not been processed yet. This mainly skips errors + // that occurred during the verification of user-written rust functions. Any other errors + // will still be processed here by the verifier for the overall result. + if let Some(ref mut error_hashes) = error_hashes_opt { + let error_hash = get_java_object_hash(env, viper_error); + if (error_hashes).contains(&error_hash) { + // debug!("already processed {error_hash}"); + continue; + } + (error_hashes).insert(error_hash); + // debug!("processing {error_hash}"); + } + + let is_verification_error = jni_utils + .is_instance_of(viper_error, "viper/silver/verifier/VerificationError"); + + if !is_verification_error { + let is_aborted_exceptionally = jni_utils + .is_instance_of(viper_error, "viper/silver/verifier/AbortedExceptionally"); + + if is_aborted_exceptionally { + let exception = jni_utils.unwrap_result( + silver::verifier::AbortedExceptionally::with(env) + .call_cause(viper_error), + ); + let stack_trace = + jni_utils.unwrap_result(jni_utils.get_stack_trace(exception)); + error!( + "The verification aborted due to the following exception: {}", + stack_trace + ); + } else { + error!( + "The verifier returned an unhandled error of type {}: {}", + jni_utils.class_name(viper_error), + jni_utils.to_string(viper_error) + ); + } + unreachable!( + "The verifier returned an unknown error of type {}: {}", + jni_utils.class_name(viper_error), + jni_utils.to_string(viper_error) + ); + }; + let mut failure_contexts = jni_utils.seq_to_vec(jni_utils + .unwrap_result(verification_error_wrapper.call_failureContexts(viper_error))); + + let counterexample: Option = { + if let Some(failure_context) = failure_contexts.pop() { + let option_original_counterexample = jni_utils + .unwrap_result(failure_context_wrapper.call_counterExample(failure_context)); + if !jni_utils + .is_instance_of(option_original_counterexample, "scala/None$") + { + let original_counterexample = jni_utils.unwrap_result( + scala::Some::with(env).call_get(option_original_counterexample), + ); + if jni_utils.is_instance_of( + original_counterexample, + "viper/silicon/interfaces/SiliconMappedCounterexample", + ) { + // only mapped counterexamples are processed + Some(SiliconCounterexample::new( + env, + *jni_utils, + original_counterexample, + )) + } else { + None + } + } else { + None + } + } else { + None + } + }; + + let reason = jni_utils + .unwrap_result(verification_error_wrapper.call_reason(viper_error)); + + let reason_pos = jni_utils + .unwrap_result(error_reason_wrapper.call_pos(reason)); + + let reason_pos_id = extract_pos_id(jni_utils, env, reason_pos); + if reason_pos_id.is_none() { + debug!( + "The verifier returned an error whose offending node position has no identifier: {:?}", + jni_utils.to_string(viper_error) + ); + } + + let error_full_id = jni_utils.get_string( + jni_utils + .unwrap_result(verification_error_wrapper.call_fullId(viper_error)), + ); + + let pos = jni_utils + .unwrap_result(verification_error_wrapper.call_pos(viper_error)); + + let message = jni_utils + .to_string(jni_utils.unwrap_result( + verification_error_wrapper.call_readableMessage(viper_error), + )); + + let pos_id = extract_pos_id(jni_utils, env, pos); + if pos_id.is_none() { + debug!( + "The verifier returned an error whose position has no identifier: {:?}", + jni_utils.to_string(viper_error) + ); + } + + let offending_node = jni_utils + .unwrap_result(verification_error_wrapper.call_offendingNode(viper_error)); + + let offending_pos = jni_utils + .unwrap_result(error_node_positioned_wrapper.call_pos(offending_node)); + + let offending_pos_id = extract_pos_id(jni_utils, env, offending_pos); + if offending_pos_id.is_none() { + debug!( + "The verifier returned an error whose offending node position has no identifier: {:?}", + jni_utils.to_string(viper_error) + ); + } + + errors.push(VerificationError::new( + error_full_id, + pos_id, + offending_pos_id, + reason_pos_id, + message, + counterexample, + )) + } + + VerificationResultKind::Failure(errors) +} diff --git a/vir/src/spans.rs b/vir/src/spans.rs index 69a2c3e36a3..1af8708c1b8 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -4,7 +4,8 @@ use prusti_rustc_interface::hir::def_id::LOCAL_CRATE; use prusti_rustc_interface::span::{ Span, DUMMY_SP, - source_map::SourceMap + source_map::SourceMap, + def_id::DefId }; use crate::VirCtxt; use ide::{SpanOfCallContracts, EncodingInfo}; @@ -74,6 +75,19 @@ pub struct VirSpanManager<'vir> { /// associated contracts. Intended for consuption by Prusti-Assistant. call_contract_spans: Vec, + /// Map of identifiers composed of a method `DefId` and a label to the + /// span of the respective block. + /// Only populated if `GENERATE_BLOCK_MESSAGES` is set. + block_spans: HashMap<(DefId, String), Span>, + + /// Map of specifically local, non-trusted, selected, + /// impure method identifier strings to their `DefId`s. + /// (See `prusti_encoder::ImpureFunctionEnc::encode`) + /// Used for backtranslation of viper names where the identifier is all to + /// go off of. + // TODO: If useful, extend to include other identifiers too. + viper_identifiers: HashMap, + } impl<'tcx> VirCtxt<'tcx> { @@ -159,30 +173,84 @@ impl<'tcx> VirCtxt<'tcx> { .map(|vir_span| vir_span.span) } - // TODO - pub fn get_span_from_label( + pub fn viper_to_rust_identifier( &'tcx self, - _label: &str - ) -> Option { - Some(DUMMY_SP.into()) + viper_method: &str, + ) -> Option { + self + .get_viper_identifier(viper_method) + .map(|def_id| + self.get_unique_item_name(&def_id) + ) } + /// Get the crate name of `def_id_opt` or the local crate name if it is `None` pub fn get_crate_name( &'tcx self, + def_id_opt: Option, ) -> String { - self - .tcx() - .crate_name(LOCAL_CRATE) - .to_string() + def_id_opt.map_or( + self + .tcx() + .crate_name(LOCAL_CRATE) + .to_string(), + |def_id| self + .tcx() + .crate_name(def_id.krate) + .to_string(), + ) + } + + pub fn insert_block_span( + &'tcx self, + key: (DefId, String), + span: Span, + ) { + let mut manager = self.spans.borrow_mut(); + manager.block_spans.insert(key, span); + } + + pub fn get_block_span( + &'tcx self, + key: &(DefId, String), + ) -> Option { + let manager = self.spans.borrow(); + manager + .block_spans + .get(key) + .copied() } - pub fn get_span_and_crate_name( + pub fn insert_viper_identifier( &'tcx self, - vir_label: &String - ) -> Option<(Span, String)> { - if let Some(span) = self.get_span_from_label(vir_label) { - Some((span, self.get_crate_name())) - } else { None } + identifier: String, + def_id: &DefId, + ) { + let mut manager = self.spans.borrow_mut(); + manager.viper_identifiers.insert(identifier, *def_id); + } + + /// Attempt to retrieve the def id from a viper identifier string. + /// Currently, these are only stored for locally defined, selected methods. + /// See `prusti_encoder::ImpureFunctionEnc::encode`. + pub fn get_viper_identifier( + &'tcx self, + identifier: &str, + ) -> Option { + let manager = self.spans.borrow(); + manager.viper_identifiers.get(identifier).copied() + } + + /// The unique itemname is of form `::` + pub fn get_unique_item_name( + &'tcx self, + def_id: &DefId, + ) -> String { + format!( + "{}::{}", + self.tcx().crate_name(def_id.krate), + self.tcx().def_path_str(def_id), + ).to_string() } pub fn push_call_contract_span( From 81230b274eb58cec49a2d79776eeee3f969a9401 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 15 Aug 2024 09:49:10 +0200 Subject: [PATCH 43/53] Fix error hashes colliding The same error with different positions used to produce the same hashes (generated using java's `hashCode` function). --- prusti-server/src/backend.rs | 2 +- viper/src/verifier.rs | 128 ++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 56 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 39a165c5272..bb66d29772f 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -74,7 +74,7 @@ fn verify_and_poll_msgs( fn polling_function( rep_glob_ref: &jni::objects::GlobalRef, sender: mpsc::Sender, -) -> HashSet { +) -> HashSet { debug!("attach polling thread to JVM."); let verification_context = VIPER .get() diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index cb317a9308a..e2d6105b16b 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -17,8 +17,9 @@ use jni::{errors::Result, objects::JObject, JNIEnv}; use log::{debug, error, info}; use std::{ path::PathBuf, - collections::HashSet, + collections::{hash_map::DefaultHasher, HashSet}, thread::ScopedJoinHandle, + hash::{Hash, Hasher}, }; use viper_sys::wrappers::{scala, viper::*}; @@ -177,7 +178,7 @@ impl<'a> Verifier<'a> { pub fn verify( &mut self, program: Program, - polling_thread: Option>>, + polling_thread: Option>>, ) -> VerificationResultKind { let ast_utils = self.ast_utils; ast_utils.with_local_frame(16, || { @@ -311,7 +312,7 @@ pub fn extract_errors( jni_utils: &JniUtils<'_>, env: &JNIEnv<'_>, viper_result: JObject<'_>, // viper::silicon::verification::Failure - mut error_hashes_opt: Option<&mut HashSet>, + mut error_hashes_opt: Option<&mut HashSet>, ) -> VerificationResultKind { let mut errors: Vec = vec![]; @@ -326,19 +327,6 @@ pub fn extract_errors( for viper_error in viper_errors { - // We only process errors that have not been processed yet. This mainly skips errors - // that occurred during the verification of user-written rust functions. Any other errors - // will still be processed here by the verifier for the overall result. - if let Some(ref mut error_hashes) = error_hashes_opt { - let error_hash = get_java_object_hash(env, viper_error); - if (error_hashes).contains(&error_hash) { - // debug!("already processed {error_hash}"); - continue; - } - (error_hashes).insert(error_hash); - // debug!("processing {error_hash}"); - } - let is_verification_error = jni_utils .is_instance_of(viper_error, "viper/silver/verifier/VerificationError"); @@ -370,39 +358,6 @@ pub fn extract_errors( jni_utils.to_string(viper_error) ); }; - let mut failure_contexts = jni_utils.seq_to_vec(jni_utils - .unwrap_result(verification_error_wrapper.call_failureContexts(viper_error))); - - let counterexample: Option = { - if let Some(failure_context) = failure_contexts.pop() { - let option_original_counterexample = jni_utils - .unwrap_result(failure_context_wrapper.call_counterExample(failure_context)); - if !jni_utils - .is_instance_of(option_original_counterexample, "scala/None$") - { - let original_counterexample = jni_utils.unwrap_result( - scala::Some::with(env).call_get(option_original_counterexample), - ); - if jni_utils.is_instance_of( - original_counterexample, - "viper/silicon/interfaces/SiliconMappedCounterexample", - ) { - // only mapped counterexamples are processed - Some(SiliconCounterexample::new( - env, - *jni_utils, - original_counterexample, - )) - } else { - None - } - } else { - None - } - } else { - None - } - }; let reason = jni_utils .unwrap_result(verification_error_wrapper.call_reason(viper_error)); @@ -413,7 +368,7 @@ pub fn extract_errors( let reason_pos_id = extract_pos_id(jni_utils, env, reason_pos); if reason_pos_id.is_none() { debug!( - "The verifier returned an error whose offending node position has no identifier: {:?}", + "The verifier returned an error whose reason position has no identifier: {:?}", jni_utils.to_string(viper_error) ); } @@ -426,11 +381,6 @@ pub fn extract_errors( let pos = jni_utils .unwrap_result(verification_error_wrapper.call_pos(viper_error)); - let message = jni_utils - .to_string(jni_utils.unwrap_result( - verification_error_wrapper.call_readableMessage(viper_error), - )); - let pos_id = extract_pos_id(jni_utils, env, pos); if pos_id.is_none() { debug!( @@ -453,6 +403,60 @@ pub fn extract_errors( ); } + // We only process errors that have not been processed yet. This mainly skips errors + // that occurred during the verification of user-written rust functions. Any other errors + // will still be processed here by the verifier for the overall result. + if let Some(ref mut error_hashes) = error_hashes_opt { + // let error_hash = jni_utils + // .unwrap_result(verification_error_wrapper.call_fullId(viper_error)); + let error_hash = hash_error(&error_full_id, &pos_id, &offending_pos_id, &reason_pos_id); + if (error_hashes).contains(&error_hash) { + debug!("already processed {error_hash}"); + continue; + } + debug!("processing {error_hash}"); + (error_hashes).insert(error_hash); + } + + let message = jni_utils + .to_string(jni_utils.unwrap_result( + verification_error_wrapper.call_readableMessage(viper_error), + )); + + let mut failure_contexts = jni_utils.seq_to_vec(jni_utils + .unwrap_result(verification_error_wrapper.call_failureContexts(viper_error))); + + let counterexample: Option = { + if let Some(failure_context) = failure_contexts.pop() { + let option_original_counterexample = jni_utils + .unwrap_result(failure_context_wrapper.call_counterExample(failure_context)); + if !jni_utils + .is_instance_of(option_original_counterexample, "scala/None$") + { + let original_counterexample = jni_utils.unwrap_result( + scala::Some::with(env).call_get(option_original_counterexample), + ); + if jni_utils.is_instance_of( + original_counterexample, + "viper/silicon/interfaces/SiliconMappedCounterexample", + ) { + // only mapped counterexamples are processed + Some(SiliconCounterexample::new( + env, + *jni_utils, + original_counterexample, + )) + } else { + None + } + } else { + None + } + } else { + None + } + }; + errors.push(VerificationError::new( error_full_id, pos_id, @@ -465,3 +469,17 @@ pub fn extract_errors( VerificationResultKind::Failure(errors) } + +fn hash_error( + full_id: &str, + pos_id: &Option, + offending_pos_id: &Option, + reason_pos_id: &Option +) -> u64 { + let mut hasher = DefaultHasher::new(); + full_id.hash(&mut hasher); + pos_id.hash(&mut hasher); + offending_pos_id.hash(&mut hasher); + reason_pos_id.hash(&mut hasher); + hasher.finish() +} \ No newline at end of file From 1063d9dedfd5b244d5b52f8dd5233030f203c477 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 15 Aug 2024 10:32:47 +0200 Subject: [PATCH 44/53] Filter entity messages encoded methods rather than starting with "m_" --- prusti-server/src/backend.rs | 19 +++++++++++-------- prusti-server/src/verification_request.rs | 8 +++++--- vir/src/spans.rs | 10 +++++++++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index bb66d29772f..0b04075a000 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -23,6 +23,7 @@ pub enum Backend<'a> { impl<'a> Backend<'a> { pub fn verify( &mut self, + procedures: HashSet, sender: mpsc::Sender, ) -> VerificationResultKind { match self { @@ -33,7 +34,7 @@ impl<'a> Backend<'a> { ast_utils.with_local_frame(16, || { let viper_program = viper::Program::new(viper_program_ref.as_obj()); if config::report_viper_messages() { - verify_and_poll_msgs(verifier, viper_thread, viper_program, sender) + verify_and_poll_msgs(verifier, viper_thread, viper_program, procedures, sender) } else { verifier.verify(viper_program, None) } @@ -47,6 +48,7 @@ fn verify_and_poll_msgs( verifier: &mut viper::Verifier, verification_context: &viper::VerificationContext, viper_program: viper::Program, + procedures: HashSet, sender: mpsc::Sender, ) -> VerificationResultKind { let mut kind = VerificationResultKind::Success; @@ -64,7 +66,7 @@ fn verify_and_poll_msgs( // start thread for polling messages thread::scope(|scope| { - let polling_thread = scope.spawn(|| polling_function(&rep_glob_ref, sender)); + let polling_thread = scope.spawn(|| polling_function(&rep_glob_ref, procedures, sender)); kind = verifier.verify(viper_program, Some(polling_thread)); }); debug!("Viper message polling thread terminated"); @@ -73,6 +75,7 @@ fn verify_and_poll_msgs( fn polling_function( rep_glob_ref: &jni::objects::GlobalRef, + procedures: HashSet, sender: mpsc::Sender, ) -> HashSet { debug!("attach polling thread to JVM."); @@ -170,9 +173,9 @@ fn polling_function( let method_wrapper = silver::ast::Method::with(env); let method_name = jni.get_string(jni.unwrap_result(method_wrapper.call_name(concerning))); - debug!("Entity success for method: {method_name} (only processed if starting with \"m_\")"); - // this should only match local methods and extern specs - if method_name.starts_with("m_") { + debug!("Entity success for method: {method_name}"); + // this should only match local methods + if procedures.contains(&method_name) { let verification_time = jni.unwrap_result(msg_wrapper.call_verificationTime(msg)); // verification_time is a long -> i64. but we are using u128 if verification_time >= 0 { @@ -189,7 +192,7 @@ fn polling_function( } } } else { - debug!("Received entity was not a method"); + debug!("Entity is not a method"); } }, "viper.silver.reporter.EntityFailureMessage" => { @@ -200,8 +203,8 @@ fn polling_function( let method_name = jni.get_string(jni.unwrap_result(method_wrapper.call_name(concerning))); debug!("Entity failure for method: {method_name}"); - // this should only match local methods and extern specs - if method_name.starts_with("m_") { + // this should only match local methods + if procedures.contains(&method_name) { let verification_time = jni.unwrap_result(msg_wrapper.call_verificationTime(msg)); // verification_time is a long -> i64. but we are using u128 if verification_time >= 0 { diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index c9f3b1f325f..5ebc24d1473 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -16,6 +16,7 @@ use std::{ sync::{self, mpsc, OnceLock}, fs::create_dir_all, path::PathBuf, + collections::HashSet, }; /// The JVM object should only instantiated once, so it is stored in a @@ -39,7 +40,7 @@ pub(crate) struct ServerVerificationRequest { /// Specifies the kind of backend to be used for verification and carries necessary data. enum ServerVerificationRequestKind { // viper program, program name, backend config - JVMViperRequest(jni::objects::GlobalRef, String, ViperBackendConfig), + JVMViperRequest(jni::objects::GlobalRef, String, ViperBackendConfig, HashSet), } impl ServerVerificationRequest { @@ -57,7 +58,7 @@ impl ServerVerificationRequest { }; match self.kind { - ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, program_name, backend_config) => { + ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, program_name, backend_config, procedures) => { let viper = VIPER.get().expect("ServerVerificationRequest: Viper was not instantiated before processing a request"); let verification_context = viper.attach_current_thread(); let mut backend = match backend_config.backend { @@ -73,7 +74,7 @@ impl ServerVerificationRequest { }; stopwatch.start_next("backend verification"); result.item_name = program_name.clone(); - result.kind = backend.verify(sender.clone()); + result.kind = backend.verify(procedures, sender.clone()); result.time_ms = stopwatch.finish().as_millis(); } } @@ -147,6 +148,7 @@ impl<'vir> VerificationRequest { viper_program_ref, self.program.get_name().to_string(), self.backend_config.clone(), + vir::with_vcx(|vir| vir.get_viper_identifiers()), ); ServerVerificationRequest { kind } }) diff --git a/vir/src/spans.rs b/vir/src/spans.rs index 1af8708c1b8..afb2a9171be 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use prusti_interface::{PrustiError, environment::EnvDiagnostic}; use prusti_rustc_interface::hir::def_id::LOCAL_CRATE; use prusti_rustc_interface::span::{ @@ -241,6 +241,14 @@ impl<'tcx> VirCtxt<'tcx> { manager.viper_identifiers.get(identifier).copied() } + /// Return the set of all viper identifiers with encoded bodies + pub fn get_viper_identifiers( + &'tcx self, + ) -> HashSet { + let manager = self.spans.borrow(); + manager.viper_identifiers.keys().cloned().collect() + } + /// The unique itemname is of form `::` pub fn get_unique_item_name( &'tcx self, From 110e6334d6c3c00a986444032dab5ac8464b23ee Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 15 Aug 2024 11:03:12 +0200 Subject: [PATCH 45/53] Fix single files not being recognized as primary package This previously made it impossible to selectively verify in single file programs. It still does not work when running through x.py. --- prusti/src/callbacks.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 9c8fd6d34d5..48aef62700a 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -194,7 +194,11 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { .emit(&env.diagnostic); } // as long as we have to throw a fake error we need to check this - let is_primary_package = std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); + // note that is_single_file will still be false if this is run through x.py. + // it will find a manifest in prusti-launch but CARGO_PRIMARY_PACKAGE will still + // not be ok. + let is_single_file = !std::env::var("CARGO_MANIFEST_DIR").is_ok(); + let is_primary_package = is_single_file || std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); // collect and output Information used by IDE: if !config::no_verify() && !config::skip_verification() { From 35fc6a1fddb380c5b44826f8dd23597a0bb034f5 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 15 Aug 2024 13:43:33 +0200 Subject: [PATCH 46/53] Fix procedure identifiers not being avalable on the server process --- prusti-server/src/backend.rs | 2 +- prusti-server/src/lib.rs | 2 ++ prusti-server/src/verification_request.rs | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 0b04075a000..25b025dc680 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -144,7 +144,7 @@ fn polling_function( let pos_string = jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(pos))); // debug!("quantifier chosen trigger pos: {pos_string}"); - // TODO: the PR unconditionally does `pos_string.rfind('.').unwrap()` which crashes when there is no position. + // TODO: the PR (https://github.com/viperproject/prusti-dev/pull/1334) unconditionally does `pos_string.rfind('.').unwrap()` which crashes when there is no position. // Is that intended? if let Some(pos_id_index) = pos_string.rfind('.') { // let pos_id_index = pos_string.rfind('.').unwrap(); diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 64156b4c9bd..6ca8d175d74 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -61,11 +61,13 @@ pub fn verify_programs( let verification_requests = programs.into_iter().map(|mut program| { let rust_program_name = program.get_rust_name().to_string(); let program_name = program.get_name().to_string(); + let procedures = vir::with_vcx(|vcx| vcx.get_viper_identifiers()); // Prepend the Rust file name to the program. program.set_name(&format!("{rust_program_name}_{program_name}")); let backend = config::viper_backend().parse().unwrap(); let request = VerificationRequest { program, + procedures, backend_config: ViperBackendConfig::new(backend), }; (program_name, request) diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 5ebc24d1473..d4c208794e5 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -87,6 +87,7 @@ impl ServerVerificationRequest { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct VerificationRequest { pub program: vir::ProgramRef, + pub procedures: HashSet, pub backend_config: ViperBackendConfig, } @@ -148,7 +149,7 @@ impl<'vir> VerificationRequest { viper_program_ref, self.program.get_name().to_string(), self.backend_config.clone(), - vir::with_vcx(|vir| vir.get_viper_identifiers()), + self.procedures.clone(), ); ServerVerificationRequest { kind } }) From b708b7c6b7bd0510e3e771c04b227c1dc4f23626 Mon Sep 17 00:00:00 2001 From: trk Date: Tue, 20 Aug 2024 14:02:59 +0200 Subject: [PATCH 47/53] Revert "small generic fixes" This reverts commit f3cf4efe0e95c7c881f3634d707f71690cfc116a. --- prusti-encoder/src/encoders/type/lifted/cast.rs | 5 +---- prusti-encoder/src/encoders/type/most_generic_ty.rs | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/prusti-encoder/src/encoders/type/lifted/cast.rs b/prusti-encoder/src/encoders/type/lifted/cast.rs index ee82d45baca..eb680ae8e0f 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast.rs @@ -192,10 +192,7 @@ where .require_local::>(task_key.actual) .unwrap(); if let CastersEncOutputRef::Casters { make_generic, .. } = generic_cast.cast { - GenericCastOutputRef::Cast(Cast::new( - T::to_generic_applicator(make_generic), - generic_cast.ty_args, - )) + GenericCastOutputRef::Cast(Cast::new(T::to_generic_applicator(make_generic), &[])) } else { unreachable!() } diff --git a/prusti-encoder/src/encoders/type/most_generic_ty.rs b/prusti-encoder/src/encoders/type/most_generic_ty.rs index 8dd10b4c1e1..498b23e2267 100644 --- a/prusti-encoder/src/encoders/type/most_generic_ty.rs +++ b/prusti-encoder/src/encoders/type/most_generic_ty.rs @@ -90,8 +90,7 @@ impl<'tcx> MostGenericTy<'tcx> { | TyKind::Int(_) | TyKind::Never | TyKind::Param(_) - | TyKind::Uint(_) - | TyKind::Str => Vec::new(), + | TyKind::Uint(_) => Vec::new(), other => todo!("generics for {:?}", other), } } From fd951edad3a00125ff877ecec2e2ecf7a394e90f Mon Sep 17 00:00:00 2001 From: Christoph Erdmann Date: Wed, 5 Jun 2024 19:39:05 +0200 Subject: [PATCH 48/53] Add type parameters to pure make_generic method for consistency --- .../src/encoders/type/lifted/cast.rs | 2 +- .../src/encoders/type/lifted/casters.rs | 56 +++++++++++++------ .../src/encoders/type/lifted/rust_ty_cast.rs | 2 +- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/prusti-encoder/src/encoders/type/lifted/cast.rs b/prusti-encoder/src/encoders/type/lifted/cast.rs index eb680ae8e0f..0139a0ed2a2 100644 --- a/prusti-encoder/src/encoders/type/lifted/cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/cast.rs @@ -192,7 +192,7 @@ where .require_local::>(task_key.actual) .unwrap(); if let CastersEncOutputRef::Casters { make_generic, .. } = generic_cast.cast { - GenericCastOutputRef::Cast(Cast::new(T::to_generic_applicator(make_generic), &[])) + GenericCastOutputRef::Cast(Cast::new(T::to_generic_applicator(make_generic), generic_cast.ty_args)) } else { unreachable!() } diff --git a/prusti-encoder/src/encoders/type/lifted/casters.rs b/prusti-encoder/src/encoders/type/lifted/casters.rs index ea724c76347..5aa14a05fe0 100644 --- a/prusti-encoder/src/encoders/type/lifted/casters.rs +++ b/prusti-encoder/src/encoders/type/lifted/casters.rs @@ -20,10 +20,16 @@ impl CastTypePure { casters: &Casters<'vir, Self>, vcx: &'vir vir::VirCtxt<'_>, snap: vir::ExprGen<'vir, Curr, Next>, + ty_args: &'vir [LiftedTy<'vir, LiftedGeneric<'vir>>], ) -> vir::ExprGen<'vir, Curr, Next> { match casters { CastFunctionsOutputRef::AlreadyGeneric => snap, - CastFunctionsOutputRef::Casters { make_generic, .. } => make_generic.apply(vcx, [snap]), + CastFunctionsOutputRef::Casters { make_generic, .. } => make_generic.apply( + vcx, + &std::iter::once(snap) + .chain(ty_args.iter().map(|t| t.expr(vcx))) + .collect::>(), + ), } } } @@ -198,7 +204,7 @@ impl CastersEncOutputRef { } } -pub type MakeGenericCastFunction<'vir> = FunctionIdent<'vir, UnaryArity<'vir>>; +pub type MakeGenericCastFunction<'vir> = FunctionIdent<'vir, UnknownArity<'vir>>; pub type MakeConcreteCastFunction<'vir> = FunctionIdent<'vir, UnknownArity<'vir>>; /// Takes as input the most generic version (c.f. [`MostGenericTy`]) of a Rust @@ -243,21 +249,23 @@ impl TaskEncoder for CastersEnc { .unwrap() .ty_constructor; - let make_generic_arg_tys = [self_ty]; - let make_generic_ident = FunctionIdent::new( - vir::vir_format_identifier!(vcx, "make_generic_s_{base_name}"), - UnaryArity::new(vcx.alloc(make_generic_arg_tys)), - generic_ref.param_snapshot, - ); - - let make_concrete_ty_params = ty + let ty_params = ty .generics() .into_iter() .map(|g| deps.require_ref::(*g).unwrap()) .collect::>(); + let make_generic_arg_tys = std::iter::once(self_ty) + .chain(ty_params.iter().map(|t| t.ty())) + .collect::>(); + let make_generic_ident = FunctionIdent::new( + vir::vir_format_identifier!(vcx, "make_generic_s_{base_name}"), + UnknownArity::new(vcx.alloc(make_generic_arg_tys)), + generic_ref.param_snapshot, + ); + let make_concrete_arg_tys = std::iter::once(generic_ref.param_snapshot) - .chain(make_concrete_ty_params.iter().map(|t| t.ty())) + .chain(ty_params.iter().map(|t| t.ty())) .collect::>(); let make_concrete_ident = FunctionIdent::new( @@ -276,9 +284,12 @@ impl TaskEncoder for CastersEnc { let make_generic_arg = vcx.mk_local_decl("self", self_ty); let make_generic_expr = vcx.mk_local_ex(make_generic_arg.name, make_generic_arg.ty); - let make_generic_arg_decls = vcx.alloc_slice(&[make_generic_arg]); + let make_generic_arg_decls = vcx.alloc_slice(&std::iter::once(make_generic_arg) + .chain(ty_params.iter().map(|t| t.decl())) + .collect::>() + ); - let make_concrete_ty_param_exprs = make_concrete_ty_params + let make_concrete_ty_param_exprs = ty_params .iter() .map(|t| t.expr(vcx)) .collect::>(); @@ -323,7 +334,7 @@ impl TaskEncoder for CastersEnc { let make_concrete_snap_arg_decl = vcx.mk_local_decl("snap", generic_ref.param_snapshot); let make_concrete_arg_decls = vcx.alloc_slice( &std::iter::once(make_concrete_snap_arg_decl) - .chain(make_concrete_ty_params.iter().map(|t| t.decl())) + .chain(ty_params.iter().map(|t| t.decl())) .collect::>(), ); @@ -335,8 +346,15 @@ impl TaskEncoder for CastersEnc { &make_concrete_ty_param_exprs, ); + let arg_ty_exprs = ty_params + .iter() + .map(|t| vcx.mk_local_ex(t.decl().name, t.decl().ty)) + .collect::>(); + let make_generic_args = std::iter::once(vcx.mk_result(self_ty)) + .chain(arg_ty_exprs) + .collect::>(); let make_concrete_post = vcx.mk_eq_expr( - make_generic_ident.apply(vcx, [vcx.mk_result(self_ty)]), + make_generic_ident.apply(vcx, &make_generic_args), vcx.mk_local_ex( make_concrete_snap_arg_decl.name, make_concrete_snap_arg_decl.ty, @@ -458,14 +476,18 @@ impl TaskEncoder for CastersEnc { let generic_predicate = vcx.mk_predicate_app_expr(generic_predicate); + let make_generic_pure_arg_exprs = std::iter::once(concrete_snap) + .chain(arg_ty_exprs.into_iter()) + .collect::>(); + let make_generic_same_snap = vcx.mk_eq_expr( - vcx.mk_old_expr(make_generic_pure.apply(vcx, [concrete_snap])), + vcx.mk_old_expr(make_generic_pure.apply(vcx, &make_generic_pure_arg_exprs)), generic_snap, ); let make_concrete_same_snap = vcx.mk_eq_expr( vcx.mk_old_expr(generic_snap), - make_generic_pure.apply(vcx, [concrete_snap]), + make_generic_pure.apply(vcx, &make_generic_pure_arg_exprs), ); let make_generic = vcx.mk_method( diff --git a/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs b/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs index 7164279ba67..3a14cfcd40f 100644 --- a/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/rust_ty_cast.rs @@ -61,7 +61,7 @@ impl<'vir> RustTyGenericCastEncOutput<'vir, CastFunctionsOutputRef<'vir>> { vcx: &'vir vir::VirCtxt<'tcx>, snap: vir::ExprGen<'vir, Curr, Next>, ) -> vir::ExprGen<'vir, Curr, Next> { - CastTypePure::cast_to_generic_if_necessary(&self.cast, vcx, snap) + CastTypePure::cast_to_generic_if_necessary(&self.cast, vcx, snap, self.ty_args) } } From 19de80ec1e233ba261d56b45132fcf8e6921def0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20B=C3=ADl=C3=BD?= Date: Tue, 20 Aug 2024 23:31:49 -0700 Subject: [PATCH 49/53] improve support for closures/quantifiers --- prusti-contracts/prusti-contracts/src/lib.rs | 2 +- .../src/specifications/preparser.rs | 2 +- prusti-encoder/src/encoders/mir_pure.rs | 160 +++++++++--------- prusti-encoder/src/encoders/type/domain.rs | 18 ++ .../encoders/type/lifted/aggregate_cast.rs | 38 ++++- .../src/encoders/type/most_generic_ty.rs | 25 ++- prusti-encoder/src/encoders/type/predicate.rs | 19 +++ prusti-encoder/src/loops.rs | 87 ++++++++++ 8 files changed, 260 insertions(+), 91 deletions(-) create mode 100644 prusti-encoder/src/loops.rs diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index e1d1f17648a..09450ca50af 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -343,7 +343,7 @@ pub fn old(arg: T) -> T { /// /// This is a Prusti-internal representation of the `forall` syntax. #[prusti::builtin="forall"] -pub fn forall(_trigger_set: T, _closure: F) -> bool { +pub fn forall(_trigger_set: T, _closure: &F) -> bool { true } diff --git a/prusti-contracts/prusti-specs/src/specifications/preparser.rs b/prusti-contracts/prusti-specs/src/specifications/preparser.rs index c1daeb438bd..9f684ce72ff 100644 --- a/prusti-contracts/prusti-specs/src/specifications/preparser.rs +++ b/prusti-contracts/prusti-specs/src/specifications/preparser.rs @@ -757,7 +757,7 @@ impl Quantifier { match self { Self::Forall => quote_spanned! { full_span => ::prusti_contracts::forall( ( #( #trigger_sets, )* ), - #[prusti::spec_only] | #args | -> bool { #body } + &( #[prusti::spec_only] | #args | -> bool { #body } ), ) }, Self::Exists => quote_spanned! { full_span => ::prusti_contracts::exists( ( #( #trigger_sets, )* ), diff --git a/prusti-encoder/src/encoders/mir_pure.rs b/prusti-encoder/src/encoders/mir_pure.rs index 3d2dec42071..2a0e501c2d4 100644 --- a/prusti-encoder/src/encoders/mir_pure.rs +++ b/prusti-encoder/src/encoders/mir_pure.rs @@ -5,6 +5,7 @@ use prusti_rustc_interface::{ span::def_id::DefId, type_ir::sty::TyKind, ast, }; +use rustc_middle::ty::{Binder, FnSig}; use task_encoder::{ TaskEncoder, TaskEncoderDependencies, @@ -253,16 +254,11 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { fn get_ty_for_local(&mut self, local: mir::Local) -> vir::Type<'vir> { let ty = self.body.local_decls[local].ty; - if let ty::TyKind::Closure(..) = ty.kind() { - // TODO: Support closure types - &vir::TypeData::Unsupported(vir::UnsupportedType { name: "closure" }) - } else { - self.deps - .require_ref::(ty) - .unwrap() - .generic_snapshot - .snapshot - } + self.deps + .require_ref::(ty) + .unwrap() + .generic_snapshot + .snapshot } fn mk_local_ex( @@ -486,18 +482,18 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { let is_pure = crate::encoders::with_proc_spec(def_id, |def_spec| def_spec.kind.is_pure().unwrap_or_default() ).unwrap_or_default(); + let sig = self.vcx().tcx().fn_sig(def_id); + let sig = if self.monomorphize { + let param_env = self.vcx().tcx().param_env(self.def_id); + self.vcx().tcx().subst_and_normalize_erasing_regions( + arg_tys, + param_env, + sig + ) + } else { + sig.instantiate_identity() + }; if is_pure { - let sig = self.vcx().tcx().fn_sig(def_id); - let sig = if self.monomorphize { - let param_env = self.vcx().tcx().param_env(self.def_id); - self.vcx().tcx().subst_and_normalize_erasing_regions( - arg_tys, - param_env, - sig - ) - } else { - sig.instantiate_identity() - }; self.encode_pure_func_app( def_id, sig, @@ -505,10 +501,16 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { args, destination, self.def_id, - &new_curr_ver + &new_curr_ver, ) } else { - self.encode_prusti_builtin(&new_curr_ver, def_id, arg_tys, args) + self.encode_prusti_builtin( + def_id, + sig, + arg_tys, + args, + &new_curr_ver, + ) } }; @@ -633,17 +635,7 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { } // Discriminant mir::Rvalue::Aggregate(box kind, fields) => match kind { - mir::AggregateKind::Closure(..) => { - // TODO: only when this is a spec closure? - let tuple_ref = self.deps.require_local::( - fields.len(), - ).unwrap(); - let fields = fields.iter() - .map(|field| self.encode_operand(curr_ver, field)) - .collect::>(); - tuple_ref.mk_cons(self.vcx, &fields) - } - mir::AggregateKind::Adt(..) | mir::AggregateKind::Tuple => { + mir::AggregateKind::Adt(..) | mir::AggregateKind::Tuple | mir::AggregateKind::Closure(..) => { let e_rvalue_ty = self.deps.require_ref::(rvalue_ty).unwrap(); let sl = match kind { mir::AggregateKind::Adt(_, vidx, _, _, _) => { @@ -782,58 +774,53 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { (expr, place_ref) } mir::ProjectionElem::Field(field_idx, ty) => { - match place_ty.ty.kind() { - TyKind::Closure(_def_id, args) => { - let upvars = args.as_closure().upvar_tys().iter().collect::>().len(); - let tuple_ref = self.deps.require_local::( - upvars, - ).unwrap(); - (tuple_ref.mk_elem(self.vcx, expr, field_idx.as_usize()), place_ref) - } - tykind => { - let e_ty = self.deps.require_ref::(place_ty.ty).unwrap(); - let struct_like = e_ty - .generic_predicate - .expect_variant_opt(place_ty.variant_index); - let proj = struct_like.snap_data.field_access[field_idx.as_usize()].read; - let proj_app = proj.apply(self.vcx, [expr]); - let proj_app = if let TyKind::Adt(def, _) = tykind { - - // The ADT type for the field might be generic, concretize if necessary - let variant = def.variant(place_ty.variant_index.unwrap_or( - abi::FIRST_VARIANT - )); - let generic_field_ty = variant.fields[field_idx].ty( - self.vcx.tcx(), - GenericArgs::identity_for_item(self.vcx.tcx(), def.did()) - ); - let cast_args = CastArgs { - expected: ty, - actual: generic_field_ty - }; - self.deps.require_ref::>(cast_args) - .unwrap().apply_cast_if_necessary(self.vcx, proj_app) - - } else if let TyKind::Tuple(_) = tykind { - self - .deps.require_local::>(ty) - .unwrap().cast_to_concrete_if_possible(self.vcx, proj_app) - } else { - proj_app - }; - let place_ref = place_ref.map(|pr| { - struct_like.ref_to_field_refs[field_idx.as_usize()].apply(self.vcx, [pr]) - }); - (proj_app, place_ref) - } - } + let tykind = place_ty.ty.kind(); + let e_ty = self.deps.require_ref::(place_ty.ty).unwrap(); + let struct_like = e_ty + .generic_predicate + .expect_variant_opt(place_ty.variant_index); + let proj = struct_like.snap_data.field_access[field_idx.as_usize()].read; + let proj_app = proj.apply(self.vcx, [expr]); + let proj_app = if let TyKind::Adt(def, _) = tykind { + // The ADT type for the field might be generic, concretize if necessary + let variant = def.variant(place_ty.variant_index.unwrap_or( + abi::FIRST_VARIANT + )); + let generic_field_ty = variant.fields[field_idx].ty( + self.vcx.tcx(), + GenericArgs::identity_for_item(self.vcx.tcx(), def.did()) + ); + let cast_args = CastArgs { + expected: ty, + actual: generic_field_ty + }; + self.deps.require_ref::>(cast_args) + .unwrap().apply_cast_if_necessary(self.vcx, proj_app) + } else if let TyKind::Tuple(_) = tykind { + self + .deps.require_local::>(ty) + .unwrap().cast_to_concrete_if_possible(self.vcx, proj_app) + } else { + proj_app + }; + let place_ref = place_ref.map(|pr| { + struct_like.ref_to_field_refs[field_idx.as_usize()].apply(self.vcx, [pr]) + }); + (proj_app, place_ref) } mir::ProjectionElem::Downcast(..) => (expr, place_ref), _ => todo!("Unsupported ProjectionElem {:?}", elem), } } - fn encode_prusti_builtin(&mut self, curr_ver: &HashMap, def_id: DefId, arg_tys: ty::GenericArgsRef<'vir>, args: &Vec>) -> ExprRet<'vir> { + fn encode_prusti_builtin( + &mut self, + def_id: DefId, + sig: Binder<'vir, FnSig<'vir>>, + arg_tys: ty::GenericArgsRef<'vir>, + args: &Vec>, + curr_ver: &HashMap, + ) -> ExprRet<'vir> { #[derive(Debug)] enum PrustiBuiltin { Forall, @@ -885,7 +872,16 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { } PrustiBuiltin::Forall => { assert_eq!(arg_tys.len(), 2); - let (qvar_tys, upvar_tys, cl_def_id) = match arg_tys[1].expect_ty().kind() { + + let encoded_args = self.encode_fn_args(sig, arg_tys, args, curr_ver); + // TODO: for now, let's expect this to give us these four: + // - type of the trigger param (unit unless triggers provided) + // - type of the body param (a closure type) + // - expression for the triggers + // - expression for the body + assert_eq!(encoded_args.len(), 4); + + let (qvar_tys, upvar_tys, cl_def_id) = match arg_tys[1].expect_ty().peel_refs().kind() { TyKind::Closure(cl_def_id, cl_args) => ( match cl_args.as_closure().sig().skip_binder().inputs()[0].kind() { TyKind::Tuple(list) => list, @@ -927,7 +923,7 @@ impl<'vir: 'enc, 'enc> Enc<'vir, 'enc> { // operation, which will work like reify // but panicking on a Lazy(..)? reify_args.push(unsafe { - std::mem::transmute(self.encode_operand(&curr_ver, &args[1])) + std::mem::transmute(encoded_args[3]) }); reify_args.extend(qvars.iter().map(|qvar| { self.vcx.mk_local_ex(qvar.name, qvar.ty) diff --git a/prusti-encoder/src/encoders/type/domain.rs b/prusti-encoder/src/encoders/type/domain.rs index 56d166a0531..382da63a4e0 100644 --- a/prusti-encoder/src/encoders/type/domain.rs +++ b/prusti-encoder/src/encoders/type/domain.rs @@ -157,6 +157,24 @@ impl TaskEncoder for DomainEnc { ); Ok((Some(enc.finalize(task_key)), specifics)) } + TyKind::Closure(def_id, args) => { + let cl_args = args.as_closure(); + let params = cl_args.parent_args(); + let generics = params + .iter() + .filter_map(|p| p.as_type()) + .map(|ty| deps.require_local::>(ty).unwrap().expect_generic()) + .collect(); + let fields = cl_args + .upvar_tys() + .iter() + .map(|ty| FieldTy::from_ty(vcx, deps, ty)) + .collect::, _>>()?; + let mut enc = DomainEncData::new(vcx, task_key, generics, deps); + enc.deps.emit_output_ref(*task_key, enc.output_ref(base_name))?; + let specifics = enc.mk_struct_specifics(fields); + Ok((Some(enc.finalize(task_key)), specifics)) + } TyKind::Adt(adt, params) => { let generics = params diff --git a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs index 710b09d8be5..381b3b2b681 100644 --- a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs @@ -3,6 +3,7 @@ use prusti_rustc_interface::{ middle::{mir, ty::{GenericArgs, Ty}}, span::def_id::DefId, }; +use rustc_middle::ty::ClosureArgs; use task_encoder::{TaskEncoder, EncodeFullResult}; use crate::encoders::lifted::cast::{CastArgs, CastToEnc}; @@ -17,22 +18,30 @@ pub struct AggregateSnapArgsCastEnc; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AggregateSnapArgsCastEncTask<'tcx> { pub tys: Vec>, - pub aggregate_type: AggregateType, + pub aggregate_type: AggregateType<'tcx>, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum AggregateType { +pub enum AggregateType<'tcx> { Tuple, + Closure { + def_id: DefId, + args: &'tcx GenericArgs<'tcx>, + }, Adt { def_id: DefId, variant_index: VariantIdx, }, } -impl From<&mir::AggregateKind<'_>> for AggregateType { - fn from(aggregate_kind: &mir::AggregateKind) -> Self { +impl<'tcx> From<&mir::AggregateKind<'tcx>> for AggregateType<'tcx> { + fn from(aggregate_kind: &mir::AggregateKind<'tcx>) -> Self { match aggregate_kind { mir::AggregateKind::Tuple => Self::Tuple, + mir::AggregateKind::Closure(def_id, args) => Self::Closure { + def_id: *def_id, + args: args, + }, mir::AggregateKind::Adt(def_id, variant_index, ..) => { Self::Adt { def_id: *def_id, @@ -96,6 +105,27 @@ impl TaskEncoder for AggregateSnapArgsCastEnc { cast_functions.to_generic_cast().map(|c| c.map_applicator(|f| f.as_unknown_arity())) }) .collect::>(), + AggregateType::Closure { + def_id, + args, + } => { + let cl_args = args.as_closure(); + let upvar_tys = cl_args.upvar_tys(); + assert!(upvar_tys.len() == task_key.tys.len()); + upvar_tys + .iter() + .zip(task_key.tys.iter()) + .map(|(v_ty, actual_ty)| { + let cast = deps + .require_ref::>(CastArgs { + expected: v_ty, + actual: *actual_ty, + }) + .unwrap(); + cast.cast_function() + }) + .collect::>() + } AggregateType::Adt { def_id, variant_index, diff --git a/prusti-encoder/src/encoders/type/most_generic_ty.rs b/prusti-encoder/src/encoders/type/most_generic_ty.rs index 498b23e2267..688155697bc 100644 --- a/prusti-encoder/src/encoders/type/most_generic_ty.rs +++ b/prusti-encoder/src/encoders/type/most_generic_ty.rs @@ -1,4 +1,5 @@ use prusti_rustc_interface::{ + hir::{self, def_id::DefId}, middle::ty::{self, TyKind}, span::symbol, }; @@ -42,7 +43,21 @@ impl<'tcx> MostGenericTy<'tcx> { } }, TyKind::Param(_) => String::from("Param"), - other => unimplemented!("get_domain_base_name for {:?}", other), + TyKind::Closure(def_id, _) => { + let def_key = vcx.tcx().def_key(def_id); + match def_key.disambiguated_data.data { + // Asking for the item_name of a closure triggers an ICE in + // the compiler, so we give it a name based on its parent. + hir::definitions::DefPathData::ClosureExpr => format!( + "{}_Closure_{}", + vcx.tcx().item_name(DefId { krate: def_id.krate, index: def_key.parent.unwrap() }), + def_key.disambiguated_data.disambiguator, + ), + _ => vcx.tcx().item_name(*def_id).to_ident_string() + } + } + TyKind::FnPtr(..) => String::from("FnPtr"), + other => unimplemented!("get_vir_base_name for {:?}", other), } } @@ -90,7 +105,10 @@ impl<'tcx> MostGenericTy<'tcx> { | TyKind::Int(_) | TyKind::Never | TyKind::Param(_) - | TyKind::Uint(_) => Vec::new(), + | TyKind::Uint(_) + | TyKind::Str + | TyKind::Closure(..) + | TyKind::FnPtr(..) => Vec::new(), other => todo!("generics for {:?}", other), } } @@ -149,7 +167,8 @@ pub fn extract_type_params<'tcx>( (MostGenericTy(ty), vec![orig]) } TyKind::Param(_) => (MostGenericTy(to_placeholder(tcx, None)), Vec::new()), - TyKind::Bool | TyKind::Char | TyKind::Int(_) | TyKind::Uint(_) | TyKind::Float(_) | TyKind::Never | TyKind::Str => { + TyKind::Bool | TyKind::Char | TyKind::Int(_) | TyKind::Uint(_) | TyKind::Float(_) + | TyKind::Never | TyKind::Str | TyKind::Closure(..) | TyKind::FnPtr(..) => { (MostGenericTy(ty), Vec::new()) } _ => todo!("extract_type_params for {:?}", ty), diff --git a/prusti-encoder/src/encoders/type/predicate.rs b/prusti-encoder/src/encoders/type/predicate.rs index ac0a526a0f1..19c6ae48c04 100644 --- a/prusti-encoder/src/encoders/type/predicate.rs +++ b/prusti-encoder/src/encoders/type/predicate.rs @@ -259,6 +259,25 @@ impl TaskEncoder for PredicateEnc { deps.emit_output_ref(*task_key, enc.output_ref(specifics)); Ok((enc.mk_prim(&snap.base_name), ())) } + TyKind::Closure(def_id, args) => { + let snap_data = snap.specifics.expect_structlike(); + let specifics = enc.mk_struct_ref(None, snap_data); + deps.emit_output_ref( + *task_key, + enc.output_ref(PredicateEncData::StructLike(specifics)), + ); + + let fields: Vec<_> = args + .as_closure() + .upvar_tys() + .iter() + .map(|ty| deps.require_ref::(ty).unwrap()) + .collect(); + let fields = enc.mk_field_apps(specifics.ref_to_field_refs, fields); + let fn_snap_body = + enc.mk_struct_ref_to_snap_body(None, fields, snap_data.field_snaps_to_snap); + Ok((enc.mk_struct(fn_snap_body), ())) + } TyKind::Tuple(tys) => { let snap_data = snap.specifics.expect_structlike(); let specifics = enc.mk_struct_ref(None, snap_data); diff --git a/prusti-encoder/src/loops.rs b/prusti-encoder/src/loops.rs new file mode 100644 index 00000000000..ddb394d7bfd --- /dev/null +++ b/prusti-encoder/src/loops.rs @@ -0,0 +1,87 @@ +use prusti_rustc_interface::{ + index::IndexVec, + middle::{mir, ty}, + span::def_id::DefId, +}; +use rustc_hash::{FxHashMap, FxHashSet}; + +#[derive(Debug)] +pub(crate) struct Loop { + pub head: mir::BasicBlock, + pub back_edges: FxHashSet, + // span?, depth? +} + +#[derive(Debug)] +pub(crate) struct LoopAnalysis { + pub loops: FxHashMap, + pub back_edges: FxHashSet<(mir::BasicBlock, mir::BasicBlock)>, +} + +pub(crate) struct LoopVisitor<'a, 'tcx: 'a> { + body: &'a mir::Body<'tcx>, + path: Vec, + in_path: IndexVec, + loop_heads: IndexVec>, +} + +impl LoopAnalysis { + pub fn analyse(body: &mir::Body<'_>) -> Self { + let block_count = body.basic_blocks.len(); + let mut analysis = LoopVisitor { + body, + path: vec![], + in_path: IndexVec::from_elem_n(false, block_count), + loop_heads: IndexVec::from_fn_n(|_| None, block_count), + }; + analysis.walk_block(mir::START_BLOCK); + let mut back_edges: FxHashSet<(mir::BasicBlock, mir::BasicBlock)> = Default::default(); + analysis.loop_heads + .iter() + .for_each(|e| if let Some(e) = e { + for prev in &e.back_edges { + back_edges.insert((*prev, e.head)); + } + }); + Self { + loops: analysis.loop_heads + .into_iter() + .filter_map(|e| { + let e = e?; + Some((e.head, e)) + }) + .collect(), + back_edges, + } + } +} + +impl<'a, 'tcx: 'a> LoopVisitor<'a, 'tcx> { + fn walk_block( + &mut self, + block: mir::BasicBlock, + // depth: usize, + ) { + if self.in_path[block] { + self.loop_heads[block] + .get_or_insert(Loop { + head: block, + back_edges: Default::default(), + }) + .back_edges + .insert(self.path[self.path.len() - 2]); + // TODO: find full loop sub-CFG? + return; + } + self.in_path[block] = true; + + use prusti_rustc_interface::data_structures::graph::WithSuccessors; + for succ in self.body.basic_blocks.successors(block) { + self.path.push(succ); + self.walk_block(succ); + assert_eq!(self.path.pop().unwrap(), succ); + } + + self.in_path[block] = false; + } +} From da102ccd095848fd621a0b435aeb41632f09edb9 Mon Sep 17 00:00:00 2001 From: trk Date: Wed, 21 Aug 2024 19:09:33 +0200 Subject: [PATCH 50/53] Filter some block statements for range map --- prusti-encoder/src/encoders/mir_impure.rs | 122 ++++++++++++++++++++-- prusti-server/src/lib.rs | 7 +- 2 files changed, 118 insertions(+), 11 deletions(-) diff --git a/prusti-encoder/src/encoders/mir_impure.rs b/prusti-encoder/src/encoders/mir_impure.rs index a61217403da..affeaef4501 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -64,6 +64,8 @@ use super::{ MirMonoImpureEnc, MirPolyImpureEnc }; +use tracing::log::debug; +use crate::hir::PatKind::Box; use prusti_utils::config; const ENCODE_REACH_BB: bool = false; @@ -542,21 +544,125 @@ impl<'vir, 'enc, E: TaskEncoder> mir::visit::Visitor<'vir> for ImpureEncVisitor< let stmts = self.current_stmts.take().unwrap(); let terminator = self.current_terminator.take().unwrap(); + // Register the spans of individual blocks. Since these ranges are supposed to give an idea + // of the more granular range of what has been verified already, we need to filter them a bit. if prusti_utils::config::report_block_messages() { let stmt_count = data.statements.len(); if stmt_count > 0 { - let terminator_span = data - .terminator - .as_ref() - .unwrap() - .source_info - .span; + debug!("bb_{}", block.as_usize()); + let terminator = data.terminator.as_ref().unwrap(); + // TODO more (or more precise) filters may be needed + // note that if there is a possibility for errors to occur in blocks that do not end up having + // any spans, this will cause that the block messages finally emitted do not show this error. + // so some care is needed (how should the ranges of such blocks be reported?). + let terminator_span = match terminator.kind { + // Terminator kinds: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/syntax/enum.TerminatorKind.html + // `Goto` may have large ranges (yet to figure out what the ranges really refer to) + // `SwitchInt` seem to cover a condition, a `match expr` or an `assert!` argument, which is ok + // `UnwindResume` not observed but probably similar to `Return` + // `UnwindTerminate` not observed but probably similar to `Return` + // + // `Return` may include the end of a method body, including the braces (and a column past it). + // According to my observations, if there are actual statements or expressions associated with + // the return (as in `return val;`), the ranges thereof are in the block statements. So we can + // skip these. + // + // `Unreachable` not observed. + // `Drop` seems to just cover the end of a method similar to what `Return` may do. Only observed + // one case though. + // + // `Call` includes the span of the call, which may not be included in any statements of the block + // so we need to include these spans. + // `TailCall` is not present in the rust version we use? + // + // `Assert` are not `assert!` calls. These are compiler inserted and seem to have the range of + // the statements or expressions that trigger them to be inserted. E.g. this may be an overflow + // check on the range of `+1` in an expression `var+1`. In this case the span is also included in + // block statements. Are there examples where they are not/where `Assert` has problematic spans? + // + // `Yield` not observed. Should be like `Return` in terms of spans? + // `CoroutineDrop` not observed. Should be like `Return` in terms of spans? + // + // `FalseEdge` not observed. The description states it behaves like a goto, so assuming the same + // for associated spans. They do occur in the .vpr file but the blocks don't seem to pass through + // here. + // `FalseUnwind` not observed. They do occur in the .vpr file but the blocks don't seem to pass + // through here. + // + // `InlineAsm` not observed + // + mir::TerminatorKind::Call {..} => Some(terminator.source_info.span), + _ => None, + }; + debug!("{}terminator: --{:?}--", if terminator_span.is_none() {"(ignored) "} else {""}, data.terminator); let block_span = data .statements .iter() - .fold(terminator_span, |acc, stmt| acc.to(stmt.source_info.span)); + .fold(terminator_span, |acc, stmt| { + // Statement kinds: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/syntax/enum.StatementKind.html + // Some statements include excessively large ranges, so we try to filter them out. + // + // `Assign` sometimes occurs in the form `lval = const ()`. These assignments (according to inexhaustive observation) + // either refer to small ranges that are contained in other statements of the block or to large, encompassing ones. + // Sometimes even the entire method body or if-else statements. The lvals of these assignments, from my observation, + // only seem to occur in `StorageDead` & `StorageLive` statements. + // Might be generalizing too much. If there are unit assignments that have a relevant local range, they should be included. + // Could other rvalues also be problematic? + // Rvalues: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/syntax/enum.Rvalue.html + // + // `FakeRead` may be problematic. e.g. `FakeRead(ForLet(None), _2)` seems to have the range of where `_2` was defined + // (same range as the corresponding `StorageLive`). Which means this range may go outside the current block. + // `FakeRead(ForMatchedPlace(None), _2)` on the other hand, seems to cover the range of a statement to be matched rather + // than the definition (using `let`) of `_2`. But this range is also covered by an assignment like `_4 = discriminant(_2)`. + // + // `SetDiscriminant` not observed (should be ok?) + // `Deinit` not observed + // + // `StorageLive` & `StorageDead` may cover ranges that cover entire statement blocks or branching statements + // like if-else. Additionally, `StorageDead` often refers to places of variables outside the current block. + // They aren't really relevant to user provided code locations anyhow, should be save to skip. + // + // `Retag` not observed (requires `-Z mir-emit-retag`) but should not cause errors and are not part of user code + // `PlaceMention` seems ok (observed to range over the rvalue inline, not definition place) + // `AscribeUserType` seems ok + // `Coverage` not observed (requires `-Cinstrument-coverage`) but should not cause errors and are not part of user code + // `Intrinsic` not observed + // `ConstEvalCounter` not observed (but probably not part of user code?) + // `Nop` not observed + // + match &stmt.kind { + mir::StatementKind::Assign(box (_, mir::Rvalue::Use(operand))) => { + let constant = operand.constant(); + if let Some(constant) = constant { + if constant.ty().is_unit() { + debug!("(ignored) stmt: --{:?}--({:?}) with span: {:?}", stmt, stmt.kind.name(), stmt.source_info.span); + return acc; + } + } + debug!("stmt: --{:?}--({:?}) with span: {:?}", stmt, stmt.kind.name(), stmt.source_info.span); + acc.map_or(Some(stmt.source_info.span), |acc| Some(acc.to(stmt.source_info.span))) + }, + mir::StatementKind::FakeRead(..) | + mir::StatementKind::StorageLive(..) | + mir::StatementKind::StorageDead(..) | + mir::StatementKind::Retag(..) | + mir::StatementKind::Coverage(..) | + mir::StatementKind::ConstEvalCounter => { + debug!("(ignored) stmt: --{:?}--({:?}) with span: {:?}", stmt, stmt.kind.name(), stmt.source_info.span); + acc + }, + _ => { + debug!("stmt: --{:?}--({:?}) with span: {:?}", stmt, stmt.kind.name(), stmt.source_info.span); + acc.map_or(Some(stmt.source_info.span), |acc| Some(acc.to(stmt.source_info.span))) + } + } + }); let label = format!("bb_{}", block.as_usize()); - self.vcx.insert_block_span((self.def_id, label), block_span); + if let Some(block_span) = block_span{ + self.vcx.insert_block_span((self.def_id, label), block_span); + } else { + debug!("no spans for block {label}!"); + } } } diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 6ca8d175d74..3ac95f66b10 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -469,10 +469,11 @@ fn handle_block_processing_message( if let Some(def_id) = vcx.get_viper_identifier(&viper_method) { let rust_method = vcx.get_unique_item_name(&def_id); if vir_label == "end" { + // if we reach end, the path finished successfully PrustiError::message( format!("{}{}", "pathProcessedMessage", - json!({"method": rust_method, "path_id": path_id}) + json!({"method": rust_method, "path_id": path_id, "result": "Success"}) ), DUMMY_SP.into() ).emit(env_diagnostic); } else { @@ -485,9 +486,9 @@ fn handle_block_processing_message( else {json!({"method": rust_method, "path_id": path_id})}, ), span.clone().into() ).emit(env_diagnostic); - } else { error!("Could not map vir label {} to a position in {rust_method}", key.1) } + } else { debug!("Could not map vir label {} to a position in {rust_method}", key.1) } } - } else { error!("Could not map method identifier to def id: {viper_method}") } + } else { debug!("Could not map method identifier to def id: {viper_method}") } }) } } From e5a323b0161baf15a4166c183dbecf95ac5f51f2 Mon Sep 17 00:00:00 2001 From: trk Date: Thu, 29 Aug 2024 13:15:16 +0200 Subject: [PATCH 51/53] Adapt for passing through `BlockFailureMessage`s --- prusti-server/src/backend.rs | 22 +++++++--- prusti-server/src/lib.rs | 64 +++++++++++++++++++---------- prusti-server/src/server_message.rs | 17 +++++--- viper-sys/build.rs | 8 +++- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 25b025dc680..d9fd070cc0e 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -227,7 +227,7 @@ fn polling_function( } }, - "viper.silver.reporter.BlockReachedMessage" => { + "viper.silver.reporter.BlockReachedMessage" => { let msg_wrapper = silver::reporter::BlockReachedMessage::with(env); let method_name = jni.get_string(jni.unwrap_result(msg_wrapper.call_methodName(msg))); @@ -242,19 +242,31 @@ fn polling_function( }) .unwrap(); }, - "viper.silver.reporter.PathProcessedMessage" => { - let msg_wrapper = silver::reporter::PathProcessedMessage::with(env); + "viper.silver.reporter.BlockFailureMessage" => { + let msg_wrapper = silver::reporter::BlockFailureMessage::with(env); let method_name = jni.get_string(jni.unwrap_result(msg_wrapper.call_methodName(msg))); let label = jni.get_string(jni.unwrap_result(msg_wrapper.call_label(msg))); let path_id = jni.unwrap_result(msg_wrapper.call_pathId(msg)); + sender + .send(ServerMessage::BlockFailure { + viper_method: method_name, + vir_label: label, + path_id: path_id, + }) + .unwrap(); + }, + "viper.silver.reporter.PathProcessedMessage" => { + let msg_wrapper = silver::reporter::PathProcessedMessage::with(env); + let method_name = + jni.get_string(jni.unwrap_result(msg_wrapper.call_methodName(msg))); + let path_id = jni.unwrap_result(msg_wrapper.call_pathId(msg)); let result = - jni.get_string(jni.unwrap_result(msg_wrapper.call_verificationResultKind(msg))); + jni.get_string(jni.unwrap_result(msg_wrapper.call_result(msg))); sender .send(ServerMessage::PathProcessed { viper_method: method_name, - vir_label: label, path_id: path_id, result: result, }) diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 3ac95f66b10..3725762efca 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -172,22 +172,34 @@ async fn handle_stream( env_diagnostic, program_name, viper_method, - vir_label, + Some(vir_label), path_id, None ), - ServerMessage::PathProcessed { + ServerMessage::BlockFailure { viper_method, vir_label, path_id, + } => handle_block_processing_message( + env_diagnostic, + program_name, + viper_method, + Some(vir_label), + path_id, + // for now, we don't report any details about failures here + Some("Failure".to_owned()), + ), + ServerMessage::PathProcessed { + viper_method, + path_id, result, } => handle_block_processing_message( env_diagnostic, program_name, viper_method, - vir_label, + None, path_id, - Some(result) + None, ), } } @@ -455,38 +467,46 @@ fn handle_block_processing_message( env_diagnostic: &EnvDiagnostic<'_>, program_name: String, viper_method: String, - vir_label: String, + vir_label: Option, path_id: i32, result: Option, ) { if config::report_viper_messages() && config::report_block_messages() { - let processed = result != None; - debug!("Received {} message: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label}, path_id: {path_id} }}", - if processed {"path processed"} else {"block reached"} - ); - if vir_label == "start" { return } + let mut token = if result.is_none() && vir_label.is_none() { + "pathProcessedMessage" + } else if result.is_none() { + "blockReachedMessage" + } else { + "blockFailureMessage" + }; + debug!("Received {token}: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label:?}, path_id: {path_id}, result: {result:?} }}"); + vir::with_vcx(|vcx| { if let Some(def_id) = vcx.get_viper_identifier(&viper_method) { let rust_method = vcx.get_unique_item_name(&def_id); - if vir_label == "end" { - // if we reach end, the path finished successfully - PrustiError::message( - format!("{}{}", - "pathProcessedMessage", - json!({"method": rust_method, "path_id": path_id, "result": "Success"}) - ), DUMMY_SP.into() - ).emit(env_diagnostic); - } else { + if let Some(vir_label) = vir_label { + if vir_label == "start" { return } let key = (def_id, vir_label); if let Some(span) = vcx.get_block_span(&key) { PrustiError::message( format!("{}{}", - if processed {"pathProcessedMessage"} else {"blockReachedMessage"}, - if processed {json!({"method": rust_method, "path_id": path_id, "result": result.unwrap()})} - else {json!({"method": rust_method, "path_id": path_id})}, + token, + if token == "blockFailureMessage" { + json!({"method": rust_method, "path_id": path_id, "result": result.unwrap()}) + } else { + json!({"method": rust_method, "path_id": path_id}) + }, ), span.clone().into() ).emit(env_diagnostic); } else { debug!("Could not map vir label {} to a position in {rust_method}", key.1) } + } else { + // no label means this is a pathProcessedMessage. This also covers the case of label == "end" + PrustiError::message( + format!("{}{}", + "pathProcessedMessage", + json!({"method": rust_method, "path_id": path_id}) + ), DUMMY_SP.into() + ).emit(env_diagnostic); } } else { debug!("Could not map method identifier to def id: {viper_method}") } }) diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs index 4d986a16b7d..45d28bf8c18 100644 --- a/prusti-server/src/server_message.rs +++ b/prusti-server/src/server_message.rs @@ -47,14 +47,21 @@ pub enum ServerMessage { path_id: i32, }, - /// Contains a path id, label, viper method name and a result corresponding to a - /// symbolic execution path through the program being verified, the VIR label of - /// the cfg basic block in the program, name of the method in the generated - /// viper file, and the tentative verification result of the block. - PathProcessed { + /// Contains a path id, label and viper method name corresponding to a symbolic + /// execution path through the program being verified, the VIR label of the cfg + /// basic block in the program and name of the method in the generated viper file + BlockFailure{ viper_method: String, vir_label: String, path_id: i32, + }, + + /// Contains a path id, viper method name and a result corresponding to a + /// symbolic execution path through the program being verified, + /// and the tentative verification result of the last explored block. + PathProcessed { + viper_method: String, + path_id: i32, result: String, }, } \ No newline at end of file diff --git a/viper-sys/build.rs b/viper-sys/build.rs index ee0ac556509..d23808363eb 100644 --- a/viper-sys/build.rs +++ b/viper-sys/build.rs @@ -176,11 +176,15 @@ fn main() { method!("label"), method!("pathId"), ]), - java_class!("viper.silver.reporter.PathProcessedMessage", vec![ + java_class!("viper.silver.reporter.BlockFailureMessage", vec![ method!("methodName"), method!("label"), method!("pathId"), - method!("verificationResultKind"), + ]), + java_class!("viper.silver.reporter.PathProcessedMessage", vec![ + method!("methodName"), + method!("pathId"), + method!("result"), ]), java_class!("viper.silver.reporter.EntitySuccessMessage", vec![ method!("concerning"), From ef98dc65cdb33e9dad2ef2a2fa970f89aef4f6b1 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 30 Aug 2024 14:10:19 +0200 Subject: [PATCH 52/53] Remove most `program_name` variables from calls --- prusti-server/src/lib.rs | 85 +++++++++-------------- prusti-server/src/verification_request.rs | 13 ++-- 2 files changed, 40 insertions(+), 58 deletions(-) diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 3725762efca..4c46a709432 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -53,24 +53,18 @@ use std::sync::{self, Arc}; use once_cell::sync::Lazy; /// Verify a list of programs. -/// Returns a list of (program_name, verification_result) tuples. pub fn verify_programs( env_diagnostic: &EnvDiagnostic<'_>, programs: Vec ) -> VerificationResult { - let verification_requests = programs.into_iter().map(|mut program| { - let rust_program_name = program.get_rust_name().to_string(); - let program_name = program.get_name().to_string(); + let verification_requests = programs.into_iter().map(|program| { let procedures = vir::with_vcx(|vcx| vcx.get_viper_identifiers()); - // Prepend the Rust file name to the program. - program.set_name(&format!("{rust_program_name}_{program_name}")); let backend = config::viper_backend().parse().unwrap(); - let request = VerificationRequest { + VerificationRequest { program, procedures, backend_config: ViperBackendConfig::new(backend), - }; - (program_name, request) + } }).collect(); let stopwatch = Stopwatch::start("prusti-server", "verifying Viper program"); @@ -104,7 +98,7 @@ pub fn verify_programs( async fn handle_stream( env_diagnostic: &EnvDiagnostic<'_>, - verification_messages: impl Stream, + verification_messages: impl Stream, ) -> VerificationResult { let mut overall_result = VerificationResult::Success; // let encoding_errors_count = self.encoder.count_encoding_errors(); @@ -119,11 +113,10 @@ async fn handle_stream( pin_mut!(verification_messages); - while let Some((program_name, server_msg)) = verification_messages.next().await { + while let Some(server_msg) = verification_messages.next().await { match server_msg { ServerMessage::Termination(result) => handle_termination_message( env_diagnostic, - program_name, result, &mut prusti_errors, &mut overall_result @@ -134,7 +127,6 @@ async fn handle_stream( verification_time, } => handle_method_termination_message( env_diagnostic, - program_name, viper_method_name, result, verification_time, @@ -147,7 +139,6 @@ async fn handle_stream( pos_id, } => handle_quantifier_instantiation_message( env_diagnostic, - program_name, q_name, insts, pos_id, @@ -159,7 +150,6 @@ async fn handle_stream( pos_id, } => handle_quantifier_chosen_triggers_message( env_diagnostic, - program_name, viper_quant, triggers, pos_id @@ -170,7 +160,6 @@ async fn handle_stream( path_id, } => handle_block_processing_message( env_diagnostic, - program_name, viper_method, Some(vir_label), path_id, @@ -182,7 +171,6 @@ async fn handle_stream( path_id, } => handle_block_processing_message( env_diagnostic, - program_name, viper_method, Some(vir_label), path_id, @@ -192,10 +180,9 @@ async fn handle_stream( ServerMessage::PathProcessed { viper_method, path_id, - result, + result: _, } => handle_block_processing_message( env_diagnostic, - program_name, viper_method, None, path_id, @@ -223,7 +210,6 @@ async fn handle_stream( fn handle_result( env_diagnostic: &EnvDiagnostic<'_>, - program_name: String, result: viper::VerificationResult, prusti_errors: &mut Vec, overall_result: &mut VerificationResult @@ -234,7 +220,7 @@ fn handle_result( viper::VerificationResultKind::ConsistencyErrors(errors) => { for error in errors { PrustiError::internal( - format!("consistency error in {program_name}: {error:?}"), + format!("consistency error: {error:?}"), DUMMY_SP.into(), ) .emit(env_diagnostic); @@ -318,7 +304,7 @@ fn handle_result( viper::VerificationResultKind::JavaException(exception) => { error!("Java exception: {}", exception.get_stack_trace()); PrustiError::internal( - format!("in {program_name}: {exception}"), + format!("in: {exception}"), DUMMY_SP.into(), ) .emit(env_diagnostic); @@ -329,7 +315,6 @@ fn handle_result( fn handle_method_termination_message( env_diagnostic: &EnvDiagnostic<'_>, - _program_name: String, viper_method_name: String, result_kind: viper::VerificationResultKind, verification_time: u128, @@ -361,7 +346,7 @@ fn handle_method_termination_message( .emit(env_diagnostic); } - handle_result(env_diagnostic, result.item_name.clone(), result, prusti_errors, overall_result); + handle_result(env_diagnostic, result, prusti_errors, overall_result); } else { debug!("Could not map method identifier to def id: {viper_method_name}"); } @@ -369,12 +354,11 @@ fn handle_method_termination_message( fn handle_termination_message( env_diagnostic: &EnvDiagnostic<'_>, - program_name: String, result: viper::VerificationResult, prusti_errors: &mut Vec, overall_result: &mut VerificationResult ) { - debug!("Received termination message with result {result:?} in verification of {program_name}"); + debug!("Received termination message with result {result:?} during verification"); if config::show_ide_info() { PrustiError::message( format!( @@ -390,22 +374,22 @@ fn handle_termination_message( ) .emit(env_diagnostic); } - handle_result(env_diagnostic, program_name, result, prusti_errors, overall_result); + handle_result(env_diagnostic, result, prusti_errors, overall_result); } fn handle_quantifier_instantiation_message( env_diagnostic: &EnvDiagnostic<'_>, - program_name: String, q_name: String, insts: u64, pos_id: usize, quantifier_instantiations: &mut FxHashMap<(usize, String), FxHashMap> ) { if config::report_viper_messages() { - debug!("Received #{insts} quantifier instantiations of {q_name} for position id {pos_id} in verification of {program_name}"); + debug!("Received #{insts} quantifier instantiations of {q_name} for position id {pos_id} durign verification"); vir::with_vcx(|vcx| { match vcx.get_span_from_id(pos_id.try_into().unwrap()) { Some(span) => { + let program_name = "program".to_owned(); let key = (pos_id, program_name.clone()); if !quantifier_instantiations.contains_key(&key) { quantifier_instantiations.insert(key.clone(), FxHashMap::default()); @@ -427,12 +411,16 @@ fn handle_quantifier_instantiation_message( } PrustiError::message( format!("quantifierInstantiationsMessage{}", - json!({"instantiations": n, "method": program_name}), + // FIXME: assistant can't display these at the moment because it expects + // them to be associated with a method. our options are: + // - resolve the method name (probably somehow from the pos_id) + // - report quantifier instantiations stand-alone and make assistant inlay those + json!({"instantiations": n, "method": "program"}), ), span.clone().into() ).emit(env_diagnostic); }, None => error!( - "#{insts} quantifier instantiations of {q_name} for unknown position id {pos_id} in verification of {program_name}" + "#{insts} quantifier instantiations of {q_name} for unknown position id {pos_id} during verification" ), } }); @@ -441,13 +429,12 @@ fn handle_quantifier_instantiation_message( fn handle_quantifier_chosen_triggers_message( env_diagnostic: &EnvDiagnostic<'_>, - program_name: String, viper_quant: String, triggers: String, pos_id: usize ) { if config::report_viper_messages() && pos_id != 0 { - debug!("Received quantifier triggers {triggers} for quantifier {viper_quant} for position id {pos_id} in verification of {program_name}"); + debug!("Received quantifier triggers {triggers} for quantifier {viper_quant} for position id {pos_id} during verification"); vir::with_vcx(|vcx| { match vcx.get_span_from_id(pos_id.try_into().unwrap()) { Some(span) => { @@ -457,7 +444,7 @@ fn handle_quantifier_chosen_triggers_message( ), span.clone().into() ).emit(env_diagnostic); }, - None => error!("Invalid position id {pos_id} for viper quantifier {viper_quant} in verification of {program_name}"), + None => error!("Invalid position id {pos_id} for viper quantifier {viper_quant} during verification"), } }); } @@ -465,21 +452,20 @@ fn handle_quantifier_chosen_triggers_message( fn handle_block_processing_message( env_diagnostic: &EnvDiagnostic<'_>, - program_name: String, viper_method: String, vir_label: Option, path_id: i32, result: Option, ) { if config::report_viper_messages() && config::report_block_messages() { - let mut token = if result.is_none() && vir_label.is_none() { + let token = if result.is_none() && vir_label.is_none() { "pathProcessedMessage" } else if result.is_none() { "blockReachedMessage" } else { "blockFailureMessage" }; - debug!("Received {token}: {{ method: {viper_method} ({program_name}) message, vir_label: {vir_label:?}, path_id: {path_id}, result: {result:?} }}"); + debug!("Received {token}: {{ method: {viper_method} message, vir_label: {vir_label:?}, path_id: {path_id}, result: {result:?} }}"); vir::with_vcx(|vcx| { if let Some(def_id) = vcx.get_viper_identifier(&viper_method) { @@ -514,9 +500,9 @@ fn handle_block_processing_message( } fn verify_requests_server( - verification_requests: Vec<(String, VerificationRequest)>, + verification_requests: Vec, server_address: String, -) -> impl Stream { +) -> impl Stream { let server_address = if server_address == "MOCK" { spawn_server_thread().to_string() } else { @@ -524,31 +510,29 @@ fn verify_requests_server( }; info!("Connecting to Prusti server at {}", server_address); let verification_stream = stream! { - for (program_name, request) in verification_requests { - yield PrustiClient::verify(server_address.clone(), request).await.map(move |msg| (program_name.clone(), msg)); + for request in verification_requests { + yield PrustiClient::verify(server_address.clone(), request).await; } }; verification_stream.flatten() } fn verify_requests_local<'a>( - verification_requests: Vec<(String, VerificationRequest)>, + verification_requests: Vec, cache: &'a Lazy>, impl FnOnce() -> Arc>>, vrp: &'a Lazy VerificationRequestProcessing + 'a>, -) -> impl Stream + 'a { +) -> impl Stream + 'a { let verification_stream = stream! { - for (program_name, request) in verification_requests { - let program_name_clone = program_name.clone(); + for request in verification_requests { let request_hash = request.get_hash(); if config::enable_cache() { if let Some(result) = Lazy::force(cache).get(request_hash) { info!( - "Using cached result {:?} for program {}", + "Using cached result {:?}", &result, - &program_name ); yield futures::stream::once(async move { - (program_name.clone(), ServerMessage::Termination(result)) + ServerMessage::Termination(result) }).left_stream(); } } @@ -556,14 +540,13 @@ fn verify_requests_local<'a>( if let ServerMessage::Termination(result) = &msg { if config::enable_cache() && !matches!(result.kind, viper::VerificationResultKind::JavaException(_)) { info!( - "Storing new cached result {:?} for program {}", + "Storing new cached result {:?}", &result, - &program_name_clone ); cache.insert(request_hash, result.clone()); } } - (program_name_clone.clone(), msg) + msg }).right_stream(); } }; diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index d4c208794e5..492c4b6c9cc 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -39,32 +39,33 @@ pub(crate) struct ServerVerificationRequest { /// Specifies the kind of backend to be used for verification and carries necessary data. enum ServerVerificationRequestKind { - // viper program, program name, backend config - JVMViperRequest(jni::objects::GlobalRef, String, ViperBackendConfig, HashSet), + // viper program, backend config, set of viper identifiers + JVMViperRequest(jni::objects::GlobalRef, ViperBackendConfig, HashSet), } impl ServerVerificationRequest { /// Process and consume the request + // FIXME: can we do without the "program" strings? pub fn process<'v, 't: 'v>( self, sender: &mpsc::Sender, ) { let mut stopwatch = Stopwatch::start("prusti-server", "verifier startup"); let mut result = VerificationResult { - item_name: "".to_string(), + item_name: "program".to_string(), kind: VerificationResultKind::Success, cached: false, time_ms: 0, }; match self.kind { - ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, program_name, backend_config, procedures) => { + ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, backend_config, procedures) => { let viper = VIPER.get().expect("ServerVerificationRequest: Viper was not instantiated before processing a request"); let verification_context = viper.attach_current_thread(); let mut backend = match backend_config.backend { VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( new_viper_verifier( - &program_name, + "program", &verification_context, backend_config.clone(), ), @@ -73,7 +74,6 @@ impl ServerVerificationRequest { ), }; stopwatch.start_next("backend verification"); - result.item_name = program_name.clone(); result.kind = backend.verify(procedures, sender.clone()); result.time_ms = stopwatch.finish().as_millis(); } @@ -147,7 +147,6 @@ impl<'vir> VerificationRequest { let kind = ServerVerificationRequestKind::JVMViperRequest( viper_program_ref, - self.program.get_name().to_string(), self.backend_config.clone(), self.procedures.clone(), ); From a6147694939f84b53fe4f6cad93045c35f1a2c55 Mon Sep 17 00:00:00 2001 From: trk Date: Fri, 30 Aug 2024 16:33:03 +0200 Subject: [PATCH 53/53] Do some simplifying and add comments --- docs/dev-guide/src/config/flags.md | 6 --- ide/src/encoding_info.rs | 2 +- ide/src/ide_verification_result.rs | 13 ------- ide/src/query_signature.rs | 17 --------- ide/src/vsc_span.rs | 3 +- .../src/encoder_traits/impure_function_enc.rs | 2 +- .../src/encoder_traits/pure_function_enc.rs | 2 +- prusti-encoder/src/lib.rs | 6 +-- prusti-server/src/backend.rs | 7 +--- prusti-server/src/lib.rs | 37 +++++++------------ prusti-server/src/server.rs | 3 ++ prusti-server/src/verification_request.rs | 15 +++----- prusti/src/callbacks.rs | 10 ++++- prusti/src/verifier.rs | 9 ----- viper/src/verifier.rs | 5 +-- vir/src/spans.rs | 7 +++- 16 files changed, 46 insertions(+), 98 deletions(-) diff --git a/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index 379dd45c603..c78fd5cba53 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -377,12 +377,6 @@ When enabled for both server and client, certain supported Viper messages will b When enabled for both server and client, messages for individual basic blocks will be reported to the user. Does nothing if [`REPORT_VIPER_MESSAGES`](#report_viper_messages) is not enabled. Intended for usage with the Prusti Assistant (IDE). -## `VERIFY_ONLY_DEFPATHS` - -When set to a list of defpaths of local methods, prusti will only verify the specified methods. A fake error will be generated to avoid caching of a success. - -> **Note:** This argument should be of JSON compatible form, e.g. `["","METHOD2>",...]`, with no white spaces. - ## `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/ide/src/encoding_info.rs b/ide/src/encoding_info.rs index 2870f4da231..3c321e752a8 100644 --- a/ide/src/encoding_info.rs +++ b/ide/src/encoding_info.rs @@ -8,7 +8,7 @@ use crate::vsc_span::VscSpan; pub struct SpanOfCallContracts { /// the defpath of the method that is called pub defpath: String, - /// the spans where this method is called + /// the span where this method is called pub call_span: VscSpan, /// the spans of all the specifications of the called method pub contracts_spans: Vec, diff --git a/ide/src/ide_verification_result.rs b/ide/src/ide_verification_result.rs index c51700875da..2c1aac8dc38 100644 --- a/ide/src/ide_verification_result.rs +++ b/ide/src/ide_verification_result.rs @@ -1,5 +1,4 @@ use serde::Serialize; -// use viper::VerificationResult; /// Generated for each verification item, containing information /// about the result of the verification. This information will be emitted @@ -16,15 +15,3 @@ pub struct IdeVerificationResult { /// whether this result was cached or is fresh pub cached: bool, } - - -// impl From<&VerificationResult> for IdeVerificationResult { -// fn from(res: &VerificationResult) -> Self { -// Self { -// item_name: res.item_name.clone(), -// success: res.is_success(), -// time_ms: res.time_ms, -// cached: res.cached, -// } -// } -// } diff --git a/ide/src/query_signature.rs b/ide/src/query_signature.rs index 19d0c82be8e..559d18f0fad 100644 --- a/ide/src/query_signature.rs +++ b/ide/src/query_signature.rs @@ -4,7 +4,6 @@ use std::{collections::HashMap, fmt}; use crate::ProcDef; use prusti_rustc_interface::{ hir::{def::DefKind, def_id::DefId}, - // middle::ty::{self, Clause, DefIdTree, ImplSubject, PredicateKind, TyCtxt}, middle::ty::{ImplSubject, ClauseKind, TyCtxt}, }; @@ -53,7 +52,6 @@ impl ExternSpecBlock { Some(impl_defid) => { // function is part of impl block let mut trait_name = None; - // let impl_subj = tcx.impl_subject(impl_defid); let impl_subj = tcx.impl_subject(impl_defid).skip_binder(); let self_ty = match impl_subj { ImplSubject::Trait(tref) => { @@ -320,13 +318,8 @@ fn generic_params(tcx: TyCtxt<'_>, defid: DefId) -> Vec { if ident == "Self" { None } else { - // let substs = ty::subst::InternalSubsts::identity_for_item(tcx, defid); let default_value = param .default_value(tcx) - // FIXME: i'm not sure what subst really is but am assuming ty::generic_args::GenerigArg is its analog - // in this rust nightly version. - // the construct seems to be removed from documentation? https://doc.rust-lang.org/stable/nightly-rustc/rustc_middle/ty/subst/ - // .map(|val| val.skip_binder().subst(tcx, substs).to_string()); .map(|val| val.skip_binder().to_string()); Some(GenericArg { name: ident, @@ -343,9 +336,7 @@ fn trait_bounds(tcx: TyCtxt<'_>, defid: DefId) -> HashMap { ClauseKind::Trait(t) => { let bound_traitref = t.trait_ref; let trait_name = tcx.def_path_str(bound_traitref.def_id); @@ -353,18 +344,11 @@ fn trait_bounds(tcx: TyCtxt<'_>, defid: DefId) -> HashMap>(); - // .collect::>() - // } else { - // vec![] - // }; let bound = TraitBound { name: trait_name, args: trait_args, @@ -376,7 +360,6 @@ fn trait_bounds(tcx: TyCtxt<'_>, defid: DefId) -> HashMap { ClauseKind::Projection(p) => { let item_id = p.projection_ty.def_id; let self_ty = format!("{}", p.projection_ty.self_ty()); diff --git a/ide/src/vsc_span.rs b/ide/src/vsc_span.rs index 21428a688c5..d9f2b7ed69d 100644 --- a/ide/src/vsc_span.rs +++ b/ide/src/vsc_span.rs @@ -1,7 +1,8 @@ use prusti_rustc_interface::span::{source_map::SourceMap, Span}; use serde::Serialize; -/// a representation of spans that is more usable with VSCode. +/// a representation of spans that is more usable with VSCode and can be emitted +/// without an environment diagnostic. #[derive(Serialize, Clone, Debug)] pub struct VscSpan { column_end: usize, diff --git a/prusti-encoder/src/encoder_traits/impure_function_enc.rs b/prusti-encoder/src/encoder_traits/impure_function_enc.rs index cb8f9232aa4..cf396e7d089 100644 --- a/prusti-encoder/src/encoder_traits/impure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/impure_function_enc.rs @@ -85,7 +85,7 @@ where // a call stub. // Also do not encode if we are doing selective verification and the // current method is not selected. - let local_def_id = def_id.as_local().filter(|_| !trusted && crate::selected(&def_id)); + let local_def_id = def_id.as_local().filter(|_| !trusted && crate::is_selected(&def_id)); let blocks = if let Some(local_def_id) = local_def_id { // Store identifiers for backtranslation, move this if other ones should also be included vcx.insert_viper_identifier(method_name.to_str().to_string(), &def_id); diff --git a/prusti-encoder/src/encoder_traits/pure_function_enc.rs b/prusti-encoder/src/encoder_traits/pure_function_enc.rs index c6ad9765356..72d3117c3e6 100644 --- a/prusti-encoder/src/encoder_traits/pure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_function_enc.rs @@ -126,7 +126,7 @@ where // don't encode the body if it is trusted or we are doing selective verification // and the current item is not selected - let expr = if trusted || !crate::selected(&def_id) { + let expr = if trusted || !crate::is_selected(&def_id) { None } else { // Encode the body of the function diff --git a/prusti-encoder/src/lib.rs b/prusti-encoder/src/lib.rs index aeb3247a3b0..9d06402a678 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -36,7 +36,7 @@ thread_local!( pub static SELECTIVE_TASKS: std::cell::OnceCell> = std::cell::OnceCell::new() ); -pub fn selected(def_id: &DefId) -> bool { +pub fn is_selected(def_id: &DefId) -> bool { SELECTIVE_TASKS.with(|selective_tasks| selective_tasks .get() @@ -77,7 +77,7 @@ pub fn test_entrypoint<'tcx>( // methods that also aren't called from a selected method are not present // in the viper program. Called methods are only stubs, but this is handled // during the actual encoding (treated as trusted). - if prusti_interface::specs::is_spec_fn(tcx, def_id) || !selected(&def_id) { + if prusti_interface::specs::is_spec_fn(tcx, def_id) || !is_selected(&def_id) { continue; } @@ -195,7 +195,7 @@ pub fn test_entrypoint<'tcx>( program_methods.push(output.method_assign); } - // std::fs::write("local-testing/simple.vpr", viper_code).unwrap(); + std::fs::write("local-testing/simple.vpr", viper_code).unwrap(); let program = vir::with_vcx(|vcx| vcx.mk_program( vcx.alloc_slice(&program_fields), diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index d9fd070cc0e..ea708a45d4c 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -137,17 +137,12 @@ fn polling_function( let viper_quant = jni.unwrap_result(msg_wrapper.call_quantifier(msg)); let viper_quant_str = jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(viper_quant))); - // debug!("quantifier chosen trigger quant: {viper_quant_str}"); // we encoded the position id in the line and column number since this is not used by // prusti either way let pos = jni.unwrap_result(positioned_wrapper.call_pos(viper_quant)); let pos_string = jni.get_string(jni.unwrap_result(obj_wrapper.call_toString(pos))); - // debug!("quantifier chosen trigger pos: {pos_string}"); - // TODO: the PR (https://github.com/viperproject/prusti-dev/pull/1334) unconditionally does `pos_string.rfind('.').unwrap()` which crashes when there is no position. - // Is that intended? if let Some(pos_id_index) = pos_string.rfind('.') { - // let pos_id_index = pos_string.rfind('.').unwrap(); let pos_id = pos_string[pos_id_index + 1..].parse::().unwrap(); let viper_triggers = @@ -163,6 +158,8 @@ fn polling_function( pos_id, }) .unwrap(); + } else { + debug!("quantifier chosen trigger for {viper_quant_str} had no position."); } }, "viper.silver.reporter.VerificationTerminationMessage" => return error_hashes, diff --git a/prusti-server/src/lib.rs b/prusti-server/src/lib.rs index 4c46a709432..a1d4a4a7fdc 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -321,7 +321,6 @@ fn handle_method_termination_message( prusti_errors: &mut Vec, overall_result: &mut VerificationResult ) { - debug!("Received method termination message with result {result_kind:?} in verification of {viper_method_name}"); if let Some(rust_method) = vir::with_vcx(|vcx| vcx.viper_to_rust_identifier(&viper_method_name)) { let result = viper::VerificationResult { item_name: rust_method, @@ -330,25 +329,9 @@ fn handle_method_termination_message( time_ms: verification_time, }; - if config::show_ide_info() { - PrustiError::message( - format!( - "ideVerificationResult{}", - serde_json::to_string(&IdeVerificationResult { - item_name: result.item_name.clone(), - success: result.is_success(), - cached: result.cached, - time_ms: result.time_ms, - }).unwrap() - ), - DUMMY_SP.into(), - ) - .emit(env_diagnostic); - } - - handle_result(env_diagnostic, result, prusti_errors, overall_result); + handle_termination_message(env_diagnostic, result, prusti_errors, overall_result); } else { - debug!("Could not map method identifier to def id: {viper_method_name}"); + debug!("Could not map method identifier to def id in termination message: {viper_method_name}"); } } @@ -358,7 +341,7 @@ fn handle_termination_message( prusti_errors: &mut Vec, overall_result: &mut VerificationResult ) { - debug!("Received termination message with result {result:?} during verification"); + debug!("Received termination message for {} with result {result:?} during verification", result.item_name); if config::show_ide_info() { PrustiError::message( format!( @@ -411,10 +394,16 @@ fn handle_quantifier_instantiation_message( } PrustiError::message( format!("quantifierInstantiationsMessage{}", - // FIXME: assistant can't display these at the moment because it expects - // them to be associated with a method. our options are: - // - resolve the method name (probably somehow from the pos_id) - // - report quantifier instantiations stand-alone and make assistant inlay those + // TODO: in assistant, this quantifier message becomes an inlay hint at the given + // span. on hover of this inlay, a list of method names and their instantiation count + // for this range are displayed. currently, methods aren't resolved here, meaning the + // hover text will only display the isntantiations of the last message for the range. + // which is probably ok since all methods are in the same program. but the hover is useless. + // it should count which quantifier was instantiated how many times _by_ which method. + // resolving the method name here would merely tell what method the quantifier belongs + // to, not which one instantiated it. this is an artifact of verifying the entire program + // at once rather than having different programs for each method (as in the original PR). + // if it stays that way, the logic can be simplified. json!({"instantiations": n, "method": "program"}), ), span.clone().into() ).emit(env_diagnostic); diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index 4370b5a696e..182d2636649 100644 --- a/prusti-server/src/server.rs +++ b/prusti-server/src/server.rs @@ -49,6 +49,9 @@ pub fn spawn_server_thread() -> SocketAddr { // lifetime and we need to access this object in them. static VERIFICATION_REQUEST_PROCESSING: Lazy = Lazy::new(|| VerificationRequestProcessing::new()); +// TODO: caching currently does not work properly. The subject of caching needs to be redetermined. +// currently, it is the whole program, and the returned result is the final errors (without +// per-method ones). static CACHE: Lazy>> = Lazy::new(|| Arc::new(sync::Mutex::new(PersistentCache::load_cache(config::cache_path())))); diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 492c4b6c9cc..559c1284506 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -33,12 +33,8 @@ pub(crate) enum ServerRequest { } /// Server requests that are sent between threads of the verifying process. -pub(crate) struct ServerVerificationRequest { - kind: ServerVerificationRequestKind, -} - /// Specifies the kind of backend to be used for verification and carries necessary data. -enum ServerVerificationRequestKind { +pub(crate) enum ServerVerificationRequest { // viper program, backend config, set of viper identifiers JVMViperRequest(jni::objects::GlobalRef, ViperBackendConfig, HashSet), } @@ -58,8 +54,8 @@ impl ServerVerificationRequest { time_ms: 0, }; - match self.kind { - ServerVerificationRequestKind::JVMViperRequest(viper_program_ref, backend_config, procedures) => { + match self { + ServerVerificationRequest::JVMViperRequest(viper_program_ref, backend_config, procedures) => { let viper = VIPER.get().expect("ServerVerificationRequest: Viper was not instantiated before processing a request"); let verification_context = viper.attach_current_thread(); let mut backend = match backend_config.backend { @@ -145,12 +141,11 @@ impl<'vir> VerificationRequest { .new_global_ref(viper_program.to_jobject()) .unwrap(); - let kind = ServerVerificationRequestKind::JVMViperRequest( + ServerVerificationRequest::JVMViperRequest( viper_program_ref, self.backend_config.clone(), self.procedures.clone(), - ); - ServerVerificationRequest { kind } + ) }) }, } diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 48aef62700a..8a5a7702956 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -196,14 +196,20 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { // as long as we have to throw a fake error we need to check this // note that is_single_file will still be false if this is run through x.py. // it will find a manifest in prusti-launch but CARGO_PRIMARY_PACKAGE will still - // not be ok. + // not be ok. meaning that selective verification can't currently be tested through + // x.py if the target is a single-file program. let is_single_file = !std::env::var("CARGO_MANIFEST_DIR").is_ok(); let is_primary_package = is_single_file || std::env::var("CARGO_PRIMARY_PACKAGE").is_ok(); // collect and output Information used by IDE: if !config::no_verify() && !config::skip_verification() { let target_def_paths = config::verify_only_defpaths(); - debug!("Received def paths: {:?}. Package is primary: {}", target_def_paths, is_primary_package); + debug!( + "Received def paths: {:?}. Package is primary: {}, Package is single-file: {}", + target_def_paths, + is_primary_package, + is_single_file, + ); if !target_def_paths.is_empty() { // if we do selective verification, then definitely only // for methods of the primary package. Check needed because diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 286ac7252eb..cafc21e1131 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -18,16 +18,7 @@ pub fn verify<'tcx>( if env.diagnostic.has_errors() { warn!("The compiler reported an error, so the program will not be verified."); } else { - debug!("Prepare verification task..."); - // // TODO: can we replace `get_annotated_procedures` with information - // // that is already in `def_spec`? - // let (annotated_procedures, types) = env.get_annotated_procedures_and_types(); - // let verification_task = VerificationTask { - // procedures: annotated_procedures, - // types, - // }; debug!("Verification task: {:?}", &verification_task); - user::message(format!( "{}erification of {} items...", if verification_task.selective { "Selective v" } else { "V" }, diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index e2d6105b16b..2baeb702b4c 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -244,7 +244,7 @@ impl<'a> Verifier<'a> { self.smt_manager.stop_and_check(); // wait for the polling thread if present so no errors get processed here that should - // be processed by the polling thread. Also aviods the need for locking. + // be processed by the polling thread. Also avoids the need for locking. let error_hashes_opt = polling_thread.map(|pt| pt.join().unwrap()); if is_failure { if let Some(mut error_hashes) = error_hashes_opt { @@ -407,8 +407,6 @@ pub fn extract_errors( // that occurred during the verification of user-written rust functions. Any other errors // will still be processed here by the verifier for the overall result. if let Some(ref mut error_hashes) = error_hashes_opt { - // let error_hash = jni_utils - // .unwrap_result(verification_error_wrapper.call_fullId(viper_error)); let error_hash = hash_error(&error_full_id, &pos_id, &offending_pos_id, &reason_pos_id); if (error_hashes).contains(&error_hash) { debug!("already processed {error_hash}"); @@ -470,6 +468,7 @@ pub fn extract_errors( VerificationResultKind::Failure(errors) } +/// manually hash identifying parts of an error fn hash_error( full_id: &str, pos_id: &Option, diff --git a/vir/src/spans.rs b/vir/src/spans.rs index afb2a9171be..e3c40aab664 100644 --- a/vir/src/spans.rs +++ b/vir/src/spans.rs @@ -85,7 +85,9 @@ pub struct VirSpanManager<'vir> { /// (See `prusti_encoder::ImpureFunctionEnc::encode`) /// Used for backtranslation of viper names where the identifier is all to /// go off of. - // TODO: If useful, extend to include other identifiers too. + // TODO: If useful, extend to include other identifiers too. But take care + // to also change the usage as well - these currently server as a filter + // for EntitySuccess/FailureMessages in prusti-server's viper backend viper_identifiers: HashMap, } @@ -278,6 +280,7 @@ impl<'tcx> VirCtxt<'tcx> { manager.call_contract_spans.push(span_of_call_contracts); } + /// Emit contract spans as diagnostic. (For Prusti-Assistant) pub fn emit_contract_spans( &'tcx self, env_diagnostic: &EnvDiagnostic<'_>, @@ -287,7 +290,7 @@ impl<'tcx> VirCtxt<'tcx> { .borrow() .call_contract_spans .clone(); - // sort, so the is deterministic + // sort, so the order is deterministic call_contract_spans .sort_by(|a,b| a.defpath.cmp(&b.defpath));