diff --git a/Cargo.lock b/Cargo.lock index 2fc7185a20d..92feeccecc5 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", ] @@ -680,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" @@ -1174,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" @@ -1194,6 +1245,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 +1610,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" @@ -1778,10 +1844,11 @@ dependencies = [ [[package]] name = "prusti" -version = "0.2.2" +version = "0.3.0" dependencies = [ "chrono", "env_logger", + "ide", "lazy_static", "log", "mir-state-analysis", @@ -1791,6 +1858,8 @@ dependencies = [ "prusti-server", "prusti-utils", "prusti-viper", + "serde", + "serde_json", "tracing 0.1.0", "tracing-chrome", "tracing-subscriber", @@ -1810,10 +1879,13 @@ dependencies = [ name = "prusti-encoder" version = "0.1.0" dependencies = [ + "cfg-if", + "ide", "mir-ssa-analysis", "mir-state-analysis", "prusti-interface", "prusti-rustc-interface", + "prusti-utils", "task-encoder", "tracing 0.1.0", "vir", @@ -1855,22 +1927,33 @@ version = "0.1.0" name = "prusti-server" version = "0.1.0" dependencies = [ + "async-stream", "bincode", "clap", "env_logger", + "futures", + "futures-util", + "ide", + "jni", "lazy_static", "log", "num_cpus", "once_cell", + "prusti-encoder", + "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 +2387,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 +2408,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2323,7 +2419,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2563,6 +2659,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 +2681,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.20.1", ] [[package]] @@ -2725,6 +2834,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" @@ -2912,10 +3040,12 @@ dependencies = [ "bincode", "bumpalo", "cfg-if", + "ide", "prusti-interface", "prusti-rustc-interface", "sealed", "serde", + "serde_json", "vir-proc-macro", ] @@ -2977,7 +3107,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/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/docs/dev-guide/src/config/flags.md b/docs/dev-guide/src/config/flags.md index b3ef1913dc9..c78fd5cba53 100644 --- a/docs/dev-guide/src/config/flags.md +++ b/docs/dev-guide/src/config/flags.md @@ -55,18 +55,25 @@ | [`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 | | [`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 | | [`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 | @@ -76,6 +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) | `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 | @@ -351,12 +359,24 @@ 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`. > **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. + +## `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). + ## `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 +393,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 +405,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. @@ -405,10 +433,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: @@ -416,6 +440,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. @@ -468,6 +504,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_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/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/ide/src/call_finder.rs b/ide/src/call_finder.rs new file mode 100644 index 00000000000..5b78ea52783 --- /dev/null +++ b/ide/src/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, + )); + } + } + } + } + _ => {} + } + } +} \ No newline at end of file diff --git a/ide/src/compiler_info.rs b/ide/src/compiler_info.rs new file mode 100644 index 00000000000..dd3381f6fd9 --- /dev/null +++ b/ide/src/compiler_info.rs @@ -0,0 +1,116 @@ +use prusti_interface::{environment::Environment, specs::typed}; +use prusti_rustc_interface::{ + hir::def_id::DefId, + span::{source_map::SourceMap, Span}, +}; +use crate::{VscSpan, call_finder, query_signature}; +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, + function_calls: Vec, + queried_source: Option, +} + +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(); + let function_calls = collect_fncalls(env) + .into_iter() + .map(|(name, defid, span)| ProcDef { + name, + defid, + // span, + span: VscSpan::from_span(&span, source_map), + }) + .collect::>(); + + // For declaring external specifications: + let queried_source = query_signature::collect_queried_signature(env.tcx(), &function_calls); + Self { + procedure_defs, + function_calls, + queried_source, + } + } +} + +/// 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, Debug, Clone)] +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); + 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 !is_trusted && !is_predicate { + procs.push(ProcDef { + name: defpath, + defid: *defid, + span: vscspan, + }); + } + } + } + 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 +} \ No newline at end of file diff --git a/ide/src/encoding_info.rs b/ide/src/encoding_info.rs new file mode 100644 index 00000000000..3c321e752a8 --- /dev/null +++ b/ide/src/encoding_info.rs @@ -0,0 +1,47 @@ +use prusti_rustc_interface::span::{source_map::SourceMap, Span}; +use serde::Serialize; +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. +#[derive(Serialize, Clone, Debug)] +pub struct SpanOfCallContracts { + /// the defpath of the method that is called + pub defpath: String, + /// 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, +} + +impl SpanOfCallContracts { + pub fn new( + defpath: String, + call_span: Span, + contracts_spans: Vec, + source_map: &SourceMap + ) -> Self { + 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_span, + contracts_spans, + } + } +} + +#[derive(Serialize)] +pub struct EncodingInfo { + pub call_contract_spans: Vec, +} + +impl EncodingInfo { + pub fn to_json_string(self) -> String { + serde_json::to_string(&self).unwrap() + } +} + diff --git a/ide/src/fake_error.rs b/ide/src/fake_error.rs new file mode 100644 index 00000000000..8e608601e76 --- /dev/null +++ b/ide/src/fake_error.rs @@ -0,0 +1,16 @@ +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_diagnostic: &EnvDiagnostic<'_>) { + 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/ide/src/ide_verification_result.rs b/ide/src/ide_verification_result.rs new file mode 100644 index 00000000000..2c1aac8dc38 --- /dev/null +++ b/ide/src/ide_verification_result.rs @@ -0,0 +1,17 @@ +use serde::Serialize; + +/// 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 + pub item_name: String, + /// whether the verification of that method was successful + pub success: bool, + /// how long the verification took + pub time_ms: u128, + /// whether this result was cached or is fresh + pub cached: bool, +} diff --git a/ide/src/lib.rs b/ide/src/lib.rs new file mode 100644 index 00000000000..e8573a4ece2 --- /dev/null +++ b/ide/src/lib.rs @@ -0,0 +1,16 @@ + +#![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 vsc_span::*; \ No newline at end of file diff --git a/ide/src/query_signature.rs b/ide/src/query_signature.rs new file mode 100644 index 00000000000..559d18f0fad --- /dev/null +++ b/ide/src/query_signature.rs @@ -0,0 +1,420 @@ +use prusti_utils::config; +use std::{collections::HashMap, fmt}; + +use crate::ProcDef; +use prusti_rustc_interface::{ + hir::{def::DefKind, def_id::DefId}, + 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).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 default_value = param + .default_value(tcx) + .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(); + match kind { + 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 = bound_traitref.args + .iter() + .skip(1) // the first one is the self type + .map(|ty| format!("{ty}")) + .collect::>(); + 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]); + } + } + 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 +} diff --git a/ide/src/vsc_span.rs b/ide/src/vsc_span.rs new file mode 100644 index 00000000000..d9f2b7ed69d --- /dev/null +++ b/ide/src/vsc_span.rs @@ -0,0 +1,35 @@ +use prusti_rustc_interface::span::{source_map::SourceMap, Span}; +use serde::Serialize; + +/// 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, + 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/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-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/Cargo.toml b/prusti-encoder/Cargo.toml index b79a39469a7..17d5466ad78 100644 --- a/prusti-encoder/Cargo.toml +++ b/prusti-encoder/Cargo.toml @@ -8,12 +8,15 @@ edition = "2021" 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" } 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" } [package.metadata.rust-analyzer] @@ -21,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..1ae34f70b09 --- /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<'vir>( + vcx: &vir::VirCtxt<'vir>, + substs_src: &Self::TaskKey<'vir>, + ) -> &'vir GenericArgs<'vir>; +} + +/// 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<'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 new file mode 100644 index 00000000000..cf396e7d089 --- /dev/null +++ b/prusti-encoder/src/encoder_traits/impure_function_enc.rs @@ -0,0 +1,197 @@ +use prusti_rustc_interface::middle::mir; +use task_encoder::{EncodeFullError, 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>( + vcx: &'vir vir::VirCtxt<'vir>, + task_key: &Self::TaskKey<'vir>, + ) -> ViperIdent<'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| { + 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))?; + + // 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)? + .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. + // 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::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); + + 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))?; + 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(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..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,19 +5,18 @@ use prusti_rustc_interface::{ }, span::def_id::DefId, }; -use task_encoder::TaskEncoderDependencies; +use task_encoder::{TaskEncoder, 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`], /// [`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; @@ -30,29 +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), @@ -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, - substs: &'tcx List>, - param_env: ty::ParamEnv<'tcx>, - args: &[mir::Operand<'tcx>], + sig: Binder<'vir, FnSig<'vir>>, + substs: &'vir List>, + args: &[mir::Operand<'vir>], 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,31 +118,26 @@ pub trait PureFuncAppEnc<'tcx: 'vir, 'vir> { fn encode_pure_func_app( &mut self, def_id: DefId, - 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> { 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..72d3117c3e6 100644 --- a/prusti-encoder/src/encoder_traits/pure_function_enc.rs +++ b/prusti-encoder/src/encoder_traits/pure_function_enc.rs @@ -1,6 +1,6 @@ use prusti_rustc_interface::{ middle::{mir, ty::{GenericArgs, Ty}}, - span::def_id::DefId + span::def_id::DefId, }; use task_encoder::{TaskEncoder, TaskEncoderDependencies}; use vir::{CallableIdent, ExprGen, FunctionIdent, Reify, UnknownArity, ViperIdent}; @@ -9,6 +9,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,40 +27,27 @@ 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 - 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>; - /// 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(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) @@ -88,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); @@ -117,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)) @@ -135,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::is_selected(&def_id) { None } else { // Encode the body of the function diff --git a/prusti-encoder/src/encoders/const.rs b/prusti-encoder/src/encoders/const.rs index fae6af3cd34..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}; @@ -20,14 +21,13 @@ impl<'vir> 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); - type TaskDescription<'tcx> = ( - mir::ConstantKind<'tcx>, + type TaskDescription<'vir> = ( + mir::ConstantKind<'vir>, usize, // current encoding depth DefId, // DefId of the current function ); @@ -38,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(); @@ -85,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, &[]); @@ -113,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 bdbee603e97..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,32 +45,22 @@ 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> { 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_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 65a3f615ac8..affeaef4501 100644 --- a/prusti-encoder/src/encoders/mir_impure.rs +++ b/prusti-encoder/src/encoders/mir_impure.rs @@ -2,14 +2,19 @@ use mir_state_analysis::{ free_pcs::{CapabilityKind, FreePcsAnalysis, FreePcsBasicBlock, FreePcsLocation, RepackOp}, utils::Place, }; +use prusti_interface::PrustiError; use prusti_rustc_interface::{ - middle::{mir, ty}, + abi, + middle::{ + mir, + ty::{GenericArgs, TyKind}, + }, span::def_id::DefId, }; //use mir_ssa_analysis::{ // SsaAnalysis, //}; -use task_encoder::{TaskEncoder, TaskEncoderDependencies}; +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; use vir::{MethodIdent, UnknownArity}; pub struct MirImpureEnc; @@ -30,24 +35,54 @@ 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 + }, + Span, +}; + +use super::{ + lifted::{ + cast::{CastArgs, CastToEnc}, + casters::CastTypeImpure, + rust_ty_cast::RustTyCastersEnc + }, + rust_ty_predicates::RustTyPredicatesEnc, + ConstEnc, + MirMonoImpureEnc, + MirPolyImpureEnc +}; +use tracing::log::debug; +use crate::hir::PatKind::Box; +use prusti_utils::config; 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<'vir> = FunctionCallTaskDescription<'vir>; - type OutputRef<'vir> = MirImpureEncOutputRef<'vir>; - type OutputFullLocal<'vir> = MirImpureEncOutput<'vir>; + type OutputRef<'vir> = ImpureFunctionEncOutputRef<'vir>; + type OutputFullLocal<'vir> = ImpureFunctionEncOutput<'vir>; type EncodingError = MirImpureEncError; @@ -55,206 +90,60 @@ 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>, - )> { - 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, - ), - }, - (), - )) - }) + 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)? + } else { + deps.require_ref::(task_key.def_id)? + }; + deps.emit_output_ref(*task_key, output_ref); + let output: ImpureFunctionEncOutput<'_> = if monomorphize { + deps.require_local::(*task_key)? + } else { + deps.require_local::(task_key.def_id)? + }; + Ok((output, ())) } } -struct EncVisitor<'tcx, 'vir, 'enc> +pub struct ImpureEncVisitor<'vir, 'enc, E: TaskEncoder> 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<'vir>, + // Are we monomorphizing functions? + pub monomorphize: bool, + pub deps: &'enc mut TaskEncoderDependencies<'vir, E>, + pub def_id: DefId, + pub local_decls: &'enc mir::LocalDecls<'vir>, //ssa_analysis: SsaAnalysis, - fpcs_analysis: FreePcsAnalysis<'enc, 'tcx>, - local_defs: crate::encoders::local_def::MirLocalDefEncOutput<'vir>, + pub fpcs_analysis: FreePcsAnalysis<'enc, 'vir>, + 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<'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 } @@ -265,13 +154,38 @@ impl<'tcx: 'vir, 'vir> PureFuncAppEnc<'tcx, 'vir> for EncVisitor<'tcx, 'vir, '_> 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) } + + 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<'vir, 'enc, E: TaskEncoder> ImpureEncVisitor<'vir, 'enc, E> { fn stmt(&mut self, stmt: vir::Stmt<'vir>) { self.current_stmts .as_mut() @@ -279,11 +193,6 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { .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, @@ -343,7 +252,7 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { fn fpcs_repacks( &mut self, - repacks: &[RepackOp<'tcx>], + repacks: &[RepackOp<'vir>], ) { for &repack_op in repacks { match repack_op { @@ -361,7 +270,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 +288,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 +321,34 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { self.current_fpcs = Some(current_fpcs); } - fn encode_operand_snap(&mut self, operand: &mir::Operand<'tcx>) -> vir::Expr<'vir> { + 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<'vir>) -> 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 @@ -445,60 +359,116 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'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 (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> { + place: Place<'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<'vir>, + elem: mir::PlaceElem<'vir>, + 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,14 +486,14 @@ impl<'tcx, 'vir, 'enc> EncVisitor<'tcx, 'vir, 'enc> { } } -impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'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)); @@ -573,6 +543,129 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc self.super_basic_block_data(block, data); 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 { + 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| { + // 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()); + 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}!"); + } + } + } + self.encoded_blocks.push( self.vcx.mk_cfg_block( self.vcx.alloc(vir::CfgBlockLabelData::BasicBlock(block.as_usize())), @@ -584,7 +677,7 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc 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 @@ -619,7 +712,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()); @@ -627,12 +720,12 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc 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)) => { @@ -653,7 +746,7 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc ]) } - //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()); @@ -709,12 +802,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" @@ -724,9 +817,9 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc } } - //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:?}")) @@ -764,13 +857,14 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc fn visit_terminator( &mut self, - terminator: &mir::Terminator<'tcx>, + terminator: &mir::Terminator<'vir>, location: mir::Location, ) { self.stmt(self.vcx.mk_comment_stmt( // 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 @@ -845,18 +939,82 @@ 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 is_pure = crate::encoders::with_proc_spec(func_def_id, |spec| + 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| { + // 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, + 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)); + 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 +1032,72 @@ 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); + } } - self.stmt( - self.vcx - .alloc(func_out.method_ref.apply(self.vcx, &method_args)), - ); + 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.vcx().with_span(span, |vcx| { + 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)), + ))); + }); + 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 @@ -902,7 +1107,16 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc .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, @@ -943,7 +1157,15 @@ impl<'tcx, 'vir, 'enc> mir::visit::Visitor<'tcx> for EncVisitor<'tcx, 'vir, 'enc &[], )]), 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/mir_poly_impure.rs b/prusti-encoder/src/encoders/mir_poly_impure.rs new file mode 100644 index 00000000000..8bd2f075518 --- /dev/null +++ b/prusti-encoder/src/encoders/mir_poly_impure.rs @@ -0,0 +1,55 @@ +use prusti_rustc_interface::span::def_id::DefId; +use task_encoder::{EncodeFullResult, 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<'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 4613344741f..2a0e501c2d4 100644 --- a/prusti-encoder/src/encoders/mir_pure.rs +++ b/prusti-encoder/src/encoders/mir_pure.rs @@ -5,17 +5,26 @@ use prusti_rustc_interface::{ span::def_id::DefId, type_ir::sty::TyKind, ast, }; +use rustc_middle::ty::{Binder, FnSig}; use task_encoder::{ TaskEncoder, TaskEncoderDependencies, + EncodeFullResult, }; 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; @@ -45,27 +54,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 ); @@ -73,7 +82,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, @@ -84,17 +93,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; @@ -108,7 +111,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 @@ -167,21 +170,21 @@ impl<'vir> Update<'vir> { } } -struct Enc<'tcx, 'vir: 'enc, 'enc> -{ - vcx: &'vir vir::VirCtxt<'tcx>, +struct Enc<'vir: 'enc, 'enc> { + monomorphize: bool, + 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 } @@ -191,9 +194,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 } @@ -204,24 +207,29 @@ 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) } + + fn monomorphize(&self) -> bool { + self.monomorphize + } } -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); Self { + monomorphize, vcx, encoding_depth, def_id, @@ -246,16 +254,11 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, '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( @@ -479,17 +482,35 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, '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 { self.encode_pure_func_app( def_id, + sig, arg_tys, 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, + ) } }; @@ -511,7 +532,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 { @@ -537,7 +558,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 { @@ -555,7 +576,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() { @@ -614,17 +635,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, '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, _, _, _) => { @@ -692,7 +703,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) @@ -705,7 +716,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 } @@ -713,7 +724,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)); @@ -733,8 +744,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>) { @@ -758,59 +769,58 @@ 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) } 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 { - 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<'tcx>, 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, @@ -862,7 +872,16 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, '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, @@ -904,7 +923,7 @@ impl<'tcx, 'vir: 'enc, 'enc> Enc<'tcx, '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) @@ -959,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(); @@ -980,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 2c584197e36..db2074ef718 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 task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; 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,22 +37,13 @@ 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>( - 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/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..b5975a02ec5 --- /dev/null +++ b/prusti-encoder/src/encoders/mono/mir_impure.rs @@ -0,0 +1,76 @@ +use prusti_rustc_interface::{middle::ty::GenericArgs, span::def_id::DefId}; +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; + +#[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<'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 2ef0350ed23..e984a882fa5 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 task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; 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) } } @@ -68,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/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/pure/spec.rs b/prusti-encoder/src/encoders/pure/spec.rs index 35626836d85..656133e50f6 100644 --- a/prusti-encoder/src/encoders/pure/spec.rs +++ b/prusti-encoder/src/encoders/pure/spec.rs @@ -1,9 +1,10 @@ +use prusti_interface::PrustiError; use prusti_rustc_interface::{ middle::{mir, ty}, 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 +36,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 +78,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; @@ -113,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::>>(); @@ -131,22 +123,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/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 bdfc1312e63..382da63a4e0 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 @@ -103,7 +102,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> { @@ -130,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() { @@ -152,13 +150,31 @@ 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 ); 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 @@ -167,18 +183,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 } ] @@ -189,9 +205,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 { @@ -201,8 +217,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, @@ -223,28 +238,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, @@ -252,13 +269,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)) } @@ -268,16 +285,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>>, @@ -287,15 +304,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, []); @@ -345,20 +362,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 { @@ -393,7 +413,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(); @@ -660,7 +680,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, ..., @@ -728,7 +748,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 => { @@ -758,7 +778,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 { .. } => { @@ -792,17 +812,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 6d64f32e17f..381b3b2b681 100644 --- a/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs +++ b/prusti-encoder/src/encoders/type/lifted/aggregate_cast.rs @@ -3,11 +3,12 @@ use prusti_rustc_interface::{ middle::{mir, ty::{GenericArgs, Ty}}, span::def_id::DefId, }; -use task_encoder::TaskEncoder; +use rustc_middle::ty::ClosureArgs; +use task_encoder::{TaskEncoder, EncodeFullResult}; -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, @@ -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, @@ -79,20 +88,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 { @@ -101,10 +101,31 @@ 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::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, @@ -119,7 +140,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..0139a0ed2a2 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, EncodeFullResult}; +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> { ), } } +} - pub fn cast_function(&self) -> Option> { +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 { - PureGenericCastOutputRef::NoCast => None, - PureGenericCastOutputRef::Cast(f) => Some(*f), + GenericCastOutputRef::NoCast => None, + GenericCastOutputRef::Cast(Cast { + cast_applicator, + ty_args, + }) => Some( + 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::>(), + )), + )), + ), + } + } +} + +impl<'vir, T: Copy> GenericCastOutputRef<'vir, T> { + pub fn cast_function(&self) -> Option> { + match self { + 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,58 +150,94 @@ 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 TaskEncoder for PureGenericCastEnc { - task_encoder::encoder_cache!(PureGenericCastEnc); - type TaskDescription<'tcx> = CastArgs<'tcx>; - type OutputRef<'vir> = PureGenericCastOutputRef<'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>, - ), - > { +impl CastToEnc +where + Self: TaskEncoder, + RustTyCastersEnc: for<'vir> TaskEncoder< + TaskDescription<'vir> = ty::Ty<'vir>, + OutputFullLocal<'vir> = RustTyGenericCastEncOutput<'vir, Casters<'vir, T>>, + >, + TaskEncoderError>: Sized, +{ + 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(_)); - let output_ref = if expected_is_param == actual_is_param { - PureGenericCastOutputRef::NoCast + 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) + .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)) + 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) + .require_local::>(task_key.actual) .unwrap(); - if let CastFunctionsOutputRef::CastFunctions { make_generic, .. } = generic_cast.cast { - PureGenericCastOutputRef::Cast(PureCast::new(make_generic.as_unknown_arity(), &[])) + if let CastersEncOutputRef::Casters { make_generic, .. } = generic_cast.cast { + GenericCastOutputRef::Cast(Cast::new(T::to_generic_applicator(make_generic), generic_cast.ty_args)) } else { unreachable!() } - }; - deps.emit_output_ref::(*task_key, output_ref); + } + } +} + +impl TaskEncoder for CastToEnc { + task_encoder::encoder_cache!(CastToEnc); + type TaskDescription<'tcx> = CastArgs<'tcx>; + type OutputRef<'vir> = GenericCastOutputRef<'vir, FunctionIdent<'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<'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)?; + 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<'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)?; 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 new file mode 100644 index 00000000000..5aa14a05fe0 --- /dev/null +++ b/prusti-encoder/src/encoders/type/lifted/casters.rs @@ -0,0 +1,513 @@ +use std::marker::PhantomData; + +use task_encoder::{TaskEncoder, TaskEncoderDependencies, EncodeFullResult}; +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>, + 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, + &std::iter::once(snap) + .chain(ty_args.iter().map(|t| t.expr(vcx))) + .collect::>(), + ), + } + } +} + +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(vir::StmtGenData::new( + vcx.alloc(make_concrete.apply(vcx, args)), + )), + vcx.alloc(vir::StmtGenData::new( + 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, UnknownArity<'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<'vir>( + ty: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + 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 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(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(&std::iter::once(make_generic_arg) + .chain(ty_params.iter().map(|t| t.decl())) + .collect::>() + ); + + let make_concrete_ty_param_exprs = 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(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 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, &make_generic_args), + 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<'vir>( + ty: &Self::TaskKey<'vir>, + deps: &mut TaskEncoderDependencies<'vir, Self>, + ) -> EncodeFullResult<'vir, Self> { + 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_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, &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, &make_generic_pure_arg_exprs), + ); + + 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..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, @@ -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>>]; @@ -27,24 +28,16 @@ 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 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/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/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..3a14cfcd40f 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,109 @@ +use std::marker::PhantomData; + use prusti_rustc_interface::middle::ty; -use task_encoder::TaskEncoder; +use task_encoder::{TaskEncoder, TaskEncoderError, EncodeFullResult}; 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, self.ty_args) } } -impl<'vir> task_encoder::OutputRefAny for RustTyGenericCastEncOutput<'vir> {} +impl<'vir, T> task_encoder::OutputRefAny for RustTyGenericCastEncOutput<'vir, T> {} + +impl RustTyCastersEnc +where + Self: TaskEncoder, + CastersEnc: for<'vir> TaskEncoder< + TaskDescription<'vir> = MostGenericTy<'vir>, + OutputRef<'vir> = Casters<'vir, T>, + >, + TaskEncoderError>: Sized, +{ + 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); + 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 RustTyGenericCastEnc { - task_encoder::encoder_cache!(RustTyGenericCastEnc); +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>; @@ -63,37 +113,35 @@ impl TaskEncoder for RustTyGenericCastEnc { *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>, - ), - > { - 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), - }, - (), - )) - }) + 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), ())) + } +} + +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<'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 c05b477c1cf..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 }; @@ -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,28 +47,17 @@ 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> { *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| { @@ -107,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/most_generic_ty.rs b/prusti-encoder/src/encoders/type/most_generic_ty.rs index b6102a5e38d..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, }; @@ -12,21 +13,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()), @@ -39,11 +43,24 @@ 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), } } - pub fn is_generic(&self) -> bool { matches!(self.kind(), TyKind::Param(_)) } @@ -82,12 +99,16 @@ 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(_) + | TyKind::Str + | TyKind::Closure(..) + | TyKind::FnPtr(..) => Vec::new(), other => todo!("generics for {:?}", other), } } @@ -146,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::Int(_) | TyKind::Uint(_) | TyKind::Str | TyKind::Never => { + 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 2c2b4034eef..19c6ae48c04 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, @@ -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>, @@ -193,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) }); @@ -223,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(), @@ -235,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, @@ -260,13 +256,32 @@ 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::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); - deps.emit_output_ref::( + deps.emit_output_ref( *task_key, enc.output_ref(PredicateEncData::StructLike(specifics)), ); @@ -284,7 +299,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)), ); @@ -311,7 +326,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)), ); @@ -343,7 +358,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)), ); @@ -353,22 +368,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)), ); @@ -603,10 +617,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..50f70ddb698 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}; @@ -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> { @@ -44,27 +46,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 } @@ -89,34 +91,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 c354bdb1510..9d06402a678 100644 --- a/prusti-encoder/src/lib.rs +++ b/prusti-encoder/src/lib.rs @@ -11,26 +11,61 @@ mod encoders; mod encoder_traits; pub mod request; -use prusti_interface::environment::EnvBody; +use prusti_interface::{environment::{EnvBody, EnvQuery, EnvDiagnostic}, PrustiError}; use prusti_rustc_interface::{ middle::ty, hir, + hir::def_id::DefId, + span::Span, }; +use prusti_utils::config; use task_encoder::TaskEncoder; -use crate::encoders::lifted::ty_constructor::TyConstructorEnc; +use crate::encoders::{lifted::{ + casters::{CastTypeImpure, CastTypePure, CastersEnc}, + ty_constructor::TyConstructorEnc +}, MirPolyImpureEnc}; + +// 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 is_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>, body: EnvBody<'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>, + 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 - for def_id in tcx.hir().body_owners() { tracing::debug!("test_entrypoint item: {def_id:?}"); let kind = tcx.def_kind(def_id); @@ -38,7 +73,11 @@ 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) || !is_selected(&def_id) { continue; } @@ -49,8 +88,7 @@ pub fn test_entrypoint<'tcx>( }).unwrap_or_default(); if !(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, false); assert!(res.is_ok()); } } @@ -73,8 +111,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); } @@ -99,14 +145,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)); @@ -161,7 +215,25 @@ 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(), } } + +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-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; + } +} 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-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/diagnostic.rs b/prusti-interface/src/environment/diagnostic.rs index 6991e6141a9..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>, @@ -78,6 +79,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/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/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(); 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-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 { diff --git a/prusti-server/Cargo.toml b/prusti-server/Cargo.toml index 053afaf0784..98a0631063a 100644 --- a/prusti-server/Cargo.toml +++ b/prusti-server/Cargo.toml @@ -19,9 +19,14 @@ 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" } 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 +34,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 15270d9316f..ea708a45d4c 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -1,44 +1,277 @@ -use crate::dump_viper_program; +use crate::{ServerMessage, VIPER}; +use log::{debug, info}; use prusti_utils::{ config, - Stopwatch, }; -use viper::{VerificationContext, VerificationResult}; +use std::{ + sync::mpsc, + thread, time, + collections::HashSet, +}; +use viper::{jni_utils::JniUtils, VerificationContext, VerificationResultKind}; +use viper_sys::wrappers::{viper::*}; +use viper_sys::wrappers::java; pub enum Backend<'a> { - Viper(viper::Verifier<'a>, &'a VerificationContext<'a>), + Viper( + viper::Verifier<'a>, + &'a VerificationContext<'a>, + jni::objects::GlobalRef, + ), } impl<'a> Backend<'a> { - pub fn verify(&mut self, program: vir::ProgramRef) -> VerificationResult { + pub fn verify( + &mut self, + procedures: HashSet, + sender: mpsc::Sender, + ) -> VerificationResultKind { match self { - Backend::Viper(viper, context) => { - let mut stopwatch = - Stopwatch::start("prusti-server backend", "construction of JVM objects"); + Backend::Viper(ref mut verifier, viper_thread, viper_program_ref) => { - let ast_utils = context.new_ast_utils(); + 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(), - ); + 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, procedures, sender) + } else { + verifier.verify(viper_program, None) } - - stopwatch.start_next("viper verification"); - viper.verify(viper_program) }) } } } } + +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; + + // 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(&rep_glob_ref, procedures, sender)); + kind = verifier.verify(viper_program, Some(polling_thread)); + }); + debug!("Viper message polling thread terminated"); + kind +} + +fn polling_function( + rep_glob_ref: &jni::objects::GlobalRef, + procedures: HashSet, + sender: mpsc::Sender, +) -> HashSet { + 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(); + let reporter_wrapper = silver::reporter::PollingReporter::with(env); + + let mut error_hashes = HashSet::new(); + 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.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))); + // 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))); + if let Some(pos_id_index) = pos_string.rfind('.') { + 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(); + } else { + debug!("quantifier chosen trigger for {viper_quant_str} had no position."); + } + }, + "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}"); + // 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 { + 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!("Entity is 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 + 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 { + 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 = + 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.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_result(msg))); + sender + .send(ServerMessage::PathProcessed { + viper_method: method_name, + path_id: path_id, + result: result, + }) + .unwrap(); + }, + _ => (), + } + } + thread::sleep(time::Duration::from_millis(10)); + } +} \ 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..a1d4a4a7fdc 100644 --- a/prusti-server/src/lib.rs +++ b/prusti-server/src/lib.rs @@ -5,83 +5,539 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![warn(clippy::disallowed_types)] +#![feature(rustc_private)] -use log::info; +use ::log::{debug, error, info}; use prusti_utils::{config, Stopwatch}; -use viper::{PersistentCache, Viper}; -use once_cell::sync::Lazy; +use prusti_interface::{ + data::VerificationResult, + environment::EnvDiagnostic, + PrustiError, +}; +use prusti_rustc_interface::{ + span::DUMMY_SP, + data_structures::fx::FxHashMap, +}; +use viper::{self, PersistentCache, Cache}; +use ide::IdeVerificationResult; +use crate::{ + server::spawn_server_thread, + PrustiClient, + ServerMessage, + VerificationRequest, + ViperBackendConfig, + VerificationRequestProcessing, +}; mod client; mod process_verification; mod server; +mod server_message; mod verification_request; mod backend; -pub use backend::*; -pub use client::*; -pub use process_verification::*; -pub use server::*; -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; +use tokio::runtime::Builder; +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. -pub fn verify_programs(programs: Vec) -> Vec<(String, viper::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(); - // Prepend the Rust file name to the program. - program.set_name(&format!("{rust_program_name}_{program_name}")); +pub fn verify_programs( + env_diagnostic: &EnvDiagnostic<'_>, + programs: Vec + ) -> VerificationResult { + let verification_requests = programs.into_iter().map(|program| { + let procedures = vir::with_vcx(|vcx| vcx.get_viper_identifiers()); let backend = config::viper_backend().parse().unwrap(); - let request = VerificationRequest { + VerificationRequest { program, + procedures, backend_config: ViperBackendConfig::new(backend), - }; - (program_name, request) - }); - if let Some(server_address) = config::server_address() { - let server_address = if server_address == "MOCK" { - spawn_server_thread().to_string() + } + }).collect(); + + 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 + // 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, 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:?}") + 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 + } + }); + stopwatch.finish(); + overall_result +} + +async fn handle_stream( + env_diagnostic: &EnvDiagnostic<'_>, + 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 + let mut quantifier_instantiations: FxHashMap<(usize, String), FxHashMap> = + FxHashMap::default(); + + let mut prusti_errors: Vec<_> = vec![]; + + pin_mut!(verification_messages); + + while let Some(server_msg) = verification_messages.next().await { + match server_msg { + ServerMessage::Termination(result) => handle_termination_message( + env_diagnostic, + result, + &mut prusti_errors, + &mut overall_result + ), + ServerMessage::MethodTermination { + viper_method_name, + result, + verification_time, + } => handle_method_termination_message( + env_diagnostic, + 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, + q_name, + insts, + pos_id, + &mut quantifier_instantiations + ), + ServerMessage::QuantifierChosenTriggers { + viper_quant, + triggers, + pos_id, + } => handle_quantifier_chosen_triggers_message( + env_diagnostic, + viper_quant, + triggers, + pos_id + ), + ServerMessage::BlockReached { + viper_method, + vir_label, + path_id, + } => handle_block_processing_message( + env_diagnostic, + viper_method, + Some(vir_label), + path_id, + None + ), + ServerMessage::BlockFailure { + viper_method, + vir_label, + path_id, + } => handle_block_processing_message( + env_diagnostic, + 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, + viper_method, + None, + path_id, + None, + ), + } + } + + // 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); + } + } + + // if encoding_errors_count != 0 { + // overall_result = VerificationResult::Failure; + // } + overall_result +} + +fn handle_result( + env_diagnostic: &EnvDiagnostic<'_>, + result: viper::VerificationResult, + prusti_errors: &mut Vec, + overall_result: &mut VerificationResult +) { + match result.kind { + // nothing to do + viper::VerificationResultKind::Success => (), + viper::VerificationResultKind::ConsistencyErrors(errors) => { + for error in errors { + PrustiError::internal( + format!("consistency error: {error:?}"), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + } + *overall_result = VerificationResult::Failure; + } + viper::VerificationResultKind::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| { + 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); + } }); - (program_name, result) - }) - .collect() + + // // 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) => { + error!("Java exception: {}", exception.get_stack_trace()); + PrustiError::internal( + format!("in: {exception}"), + DUMMY_SP.into(), + ) + .emit(env_diagnostic); + *overall_result = VerificationResult::Failure; + } + } +} + +fn handle_method_termination_message( + env_diagnostic: &EnvDiagnostic<'_>, + viper_method_name: String, + result_kind: viper::VerificationResultKind, + verification_time: u128, + prusti_errors: &mut Vec, + overall_result: &mut VerificationResult +) { + 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, + }; + + handle_termination_message(env_diagnostic, result, prusti_errors, overall_result); } 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 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() + debug!("Could not map method identifier to def id in termination message: {viper_method_name}"); + } +} + +fn handle_termination_message( + env_diagnostic: &EnvDiagnostic<'_>, + result: viper::VerificationResult, + prusti_errors: &mut Vec, + overall_result: &mut VerificationResult +) { + debug!("Received termination message for {} with result {result:?} during verification", result.item_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, result, prusti_errors, overall_result); +} + +fn handle_quantifier_instantiation_message( + env_diagnostic: &EnvDiagnostic<'_>, + 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} 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()); + } + 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{}", + // 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); + }, + None => error!( + "#{insts} quantifier instantiations of {q_name} for unknown position id {pos_id} during verification" + ), + } + }); + } +} + +fn handle_quantifier_chosen_triggers_message( + env_diagnostic: &EnvDiagnostic<'_>, + 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} during verification"); + 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} during verification"), + } + }); + } +} + +fn handle_block_processing_message( + env_diagnostic: &EnvDiagnostic<'_>, + viper_method: String, + vir_label: Option, + path_id: i32, + result: Option, +) { + if config::report_viper_messages() && config::report_block_messages() { + 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} 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 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!("{}{}", + 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}") } + }) } } + +fn verify_requests_server( + verification_requests: Vec, + 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 request in verification_requests { + yield PrustiClient::verify(server_address.clone(), request).await; + } + }; + verification_stream.flatten() +} + +fn verify_requests_local<'a>( + verification_requests: Vec, + cache: &'a Lazy>, impl FnOnce() -> Arc>>, + vrp: &'a Lazy VerificationRequestProcessing + 'a>, +) -> impl Stream + 'a { + let verification_stream = stream! { + 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 {:?}", + &result, + ); + yield futures::stream::once(async move { + 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(_)) { + info!( + "Storing new cached result {:?}", + &result, + ); + cache.insert(request_hash, result.clone()); + } + } + msg + }).right_stream(); + } + }; + verification_stream.flatten() +} diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 7c3480e8d02..f5ac57fc02c 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -4,221 +4,97 @@ // 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 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 viper::{ - smt_manager::SmtManager, Cache, VerificationBackend, VerificationContext, VerificationResult, +use crate::{ServerMessage, VerificationRequest, ServerRequest}; +use futures::{lock, stream::Stream}; +use log::{debug, info}; +use std::{ + sync::{self, mpsc}, + thread, }; -#[tracing::instrument(level = "debug", skip_all, fields(program = %request.program.get_name()))] -pub fn process_verification_request<'v, 't: 'v>( - verification_context: &'v Lazy, impl Fn() -> VerificationContext<'t>>, - request: VerificationRequest, - cache: impl Cache, -) -> viper::VerificationResult { - /* - let ast_utils = verification_context.new_ast_utils(); +struct ThreadJoin { + handle: Option>, +} - // 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 - }); +// 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(); } +} - // 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(), - ); - } +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, +} - viper_program - }; +impl Default for VerificationRequestProcessing { + fn default() -> Self { + Self::new() + } +} - // 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(); - }); +/// 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), + }, } - return viper::VerificationResult::Success; } -*/ - // Early return in case of cache hit - if config::enable_cache() { - if let Some(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);*/ - return result; - } - }; - - 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, - ), - }; - stopwatch.start_next("backend verification"); - let result = backend.verify(request.program); - - // Don't cache Java exceptions, which might be due to misconfigured paths. - if config::enable_cache() && !matches!(result, VerificationResult::JavaException(_)) { + pub fn verify(&self, request: VerificationRequest) -> impl Stream + '_ { + let hash = request.get_hash(); info!( - "Storing new cached result {:?} for program {}", - &result, + "Verification request hash: {} - for program {}", + hash, request.program.get_name(), ); - cache.insert(hash, result.clone()); - } - /*normalization_info.denormalize_result(&mut result);*/ - result + request.send(&self.mtx_tx_verreq); + + 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 dump_viper_program( - ast_utils: &viper::AstUtils, - program: viper::Program, - program_name: &str, +fn verification_thread( + rx_verreq: mpsc::Receiver, + tx_servermsg: mpsc::Sender, ) { - 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)); -} + debug!("Verification thread started."); -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"); + while let Ok(request) = rx_verreq.recv() { + match request { + ServerRequest::Verification(verification_request) => verification_request.process( + &tx_servermsg, + ), } - (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, - ) + debug!("Verification thread finished."); } diff --git a/prusti-server/src/server.rs b/prusti-server/src/server.rs index 3f412ffde84..182d2636649 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::{process_verification_request, VerificationRequest}; +use crate::{VerificationRequestProcessing, ServerMessage, VerificationRequest}; +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::{self, mpsc, Arc}, thread, }; use tokio::runtime::Builder; -use viper::{PersistentCache, Viper}; +use viper::{Cache, PersistentCache, VerificationResultKind}; use warp::Filter; +use prusti_utils::config; #[derive(Debug)] struct BincodeReject(bincode::Error); @@ -42,65 +43,78 @@ 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()); +// 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())))); + 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) - } - }; + + 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::body::json()) + .and(warp::filters::ws::ws()) .map(init_vcx) - .map(build_verification_request_handler( - viper.clone(), - cache.clone(), - )) - .map(|response| warp::reply::json(&response)); + .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::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(build_verification_request_handler(viper, cache.clone())) - .map(|result| { - warp::http::Response::new( - bincode::serialize(&result).expect("could not encode verification result"), + .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")) .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); @@ -128,3 +142,65 @@ where unreachable!("The server unexpectedly stopped."); } + +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!( + "Storing new cached result {:?} for program {}", + &result, + &program_name + ); + CACHE.insert(request_hash, result.clone()); + } + } + }; + 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 diff --git a/prusti-server/src/server_message.rs b/prusti-server/src/server_message.rs new file mode 100644 index 00000000000..45d28bf8c18 --- /dev/null +++ b/prusti-server/src/server_message.rs @@ -0,0 +1,67 @@ +// © 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, VerificationResultKind}; + +#[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), + + /// 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 { + q_name: String, + insts: u64, + pos_id: usize, + }, + + /// 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: usize, + }, + + /// 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 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/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index dc73215db10..559c1284506 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -4,12 +4,86 @@ // 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::info; +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 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 +/// 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. +pub(crate) enum ServerRequest { + Verification(ServerVerificationRequest), +} + +/// Server requests that are sent between threads of the verifying process. +/// Specifies the kind of backend to be used for verification and carries necessary data. +pub(crate) enum ServerVerificationRequest { + // 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: "program".to_string(), + kind: VerificationResultKind::Success, + cached: false, + time_ms: 0, + }; + + 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 { + VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( + new_viper_verifier( + "program", + &verification_context, + backend_config.clone(), + ), + &verification_context, + viper_program_ref, + ), + }; + stopwatch.start_next("backend verification"); + result.kind = backend.verify(procedures, sender.clone()); + result.time_ms = stopwatch.finish().as_millis(); + } + } + + /*normalization_info.denormalize_result(&mut result.kind);*/ + sender.send(ServerMessage::Termination(result)).unwrap(); + } +} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct VerificationRequest { pub program: vir::ProgramRef, + pub procedures: HashSet, pub backend_config: ViperBackendConfig, } @@ -17,6 +91,76 @@ impl<'vir> VerificationRequest { pub(crate) fn get_hash(&self) -> u64 { 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( + &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", "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(); + + 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(); + + ServerVerificationRequest::JVMViperRequest( + viper_program_ref, + self.backend_config.clone(), + self.procedures.clone(), + ) + }) + }, + } + } +} + +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). @@ -49,21 +193,35 @@ 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() + ); + + 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()]); if let Some(check_timeout) = config::check_timeout() { 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()]); @@ -75,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/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-utils/src/config.rs b/prusti-utils/src/config.rs index e11da7863fc..82aaf78190a 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -103,6 +103,9 @@ 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(); settings.set_default("internal_errors_as_warnings", false).unwrap(); @@ -170,6 +173,13 @@ 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(); + settings.set_default::>("verify_only_defpaths", 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. let mut allowed_keys = get_keys(&settings); allowed_keys.insert("server_max_stored_verifiers".to_string()); @@ -212,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"); @@ -502,6 +513,22 @@ 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 { + 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`. @@ -785,6 +812,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") @@ -1029,3 +1062,31 @@ 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") +} + +/// Used for selective verification, can be passed a String containing +/// the DefPath of the method to be verified +pub fn verify_only_defpaths() -> Vec { + read_setting("verify_only_defpaths") +} + +/// 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-viper/src/lib.rs b/prusti-viper/src/lib.rs index 695fc5d095b..d2ab81bb285 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}; +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,12 +41,42 @@ pub struct ToViperContext<'vir, 'v> { domain_axioms: FxHashMap<&'vir str, (vir::Domain<'vir>, vir::DomainAxiom<'vir>)>, } +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 + // self.ast.virtual_position(format!("{}", span.id)) + self.ast.identifier_position(0, 0, format!("{}", span.id)) + } else { + 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>) -> Self::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. @@ -54,7 +85,7 @@ pub trait ToViperVec<'vir, 'v> { 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 @@ -64,52 +95,82 @@ 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 + // `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(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), + 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.mod_with_pos(lhs, rhs, pos), + vir::BinOpKind::Implies => ctx.ast.implies_with_pos(lhs, rhs, pos), } } } @@ -119,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 @@ -138,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, ) } @@ -177,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, ) @@ -194,36 +257,36 @@ 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(ctx), - vir::ExprKindData::BinOp(v) => v.to_viper(ctx), - vir::ExprKindData::Const(v) => v.to_viper(ctx), - vir::ExprKindData::Field(recv, field) => ctx.ast.field_access( - recv.to_viper(ctx), - field.to_viper(ctx), - // TODO: position + 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_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(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::Old(v) => ctx.ast.old(v.to_viper(ctx)), // TODO: position - vir::ExprKindData::PredicateApp(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_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(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!(), @@ -235,44 +298,46 @@ 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), + self.result_ty.to_viper_no_pos(ctx), domain.name, - ctx.ast.no_position(), // TODO: position + pos, ) } 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 + &self.args.iter().map(|v| v.to_viper_no_pos(ctx)).collect::>(), + self.result_ty.to_viper_no_pos(ctx), + pos, ) } } @@ -280,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)), ) } } @@ -302,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_, @@ -336,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 { @@ -395,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, @@ -412,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!(), @@ -520,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.assert( + ctx.ast.false_lit_with_pos(pos), + pos, ), ], &[], @@ -545,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(), @@ -578,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)) @@ -596,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/prusti/Cargo.toml b/prusti/Cargo.toml index 6f76df4c703..c9d7c524fc2 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" @@ -18,8 +18,11 @@ 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"] } +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..8a5a7702956 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,9 +1,12 @@ use crate::verifier::verify; +use ide::{IdeInfo, fake_error}; 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,7 +25,9 @@ use prusti_rustc_interface::{ ty::TyCtxt, }, session::{EarlyErrorHandler, Session}, + span::DUMMY_SP, }; +use ::log::{debug, info}; #[derive(Default)] pub struct PrustiCompilerCalls; @@ -161,20 +166,87 @@ 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); + // 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(); + + if config::show_ide_info() && !config::no_verify() { + 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); + } + // 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. 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: {}, 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 + // 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::>(); + 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 {*/ - verify(env, def_spec); - //} + } else { + let verification_task = VerificationTask { + procedures: annotated_procedures, + types, + selective: false, + }; + 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.diagnostic); } }); diff --git a/prusti/src/verifier.rs b/prusti/src/verifier.rs index 1b05031a019..cafc21e1131 100644 --- a/prusti/src/verifier.rs +++ b/prusti/src/verifier.rs @@ -10,22 +10,18 @@ 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, - }; 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() )); @@ -50,12 +46,17 @@ pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { env.tcx(), env.body, def_spec, + if verification_task.selective { Some(verification_task.procedures) } else { None }, + &env.diagnostic, ); + let program = request.program; - let results = prusti_server::verify_programs(vec![program]); - println!("verification results: {results:?}"); - if !results.iter().all(|(_, r)| matches!(r, viper::VerificationResult::Success)) { + let result = prusti_server::verify_programs(&env.diagnostic, vec![program]); + + println!("verification result: {result:?}"); + + 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 @@ -66,7 +67,6 @@ pub fn verify(env: Environment<'_>, def_spec: typed::DefSpecificationMap) { &[], ); } - // TODO: backtranslate verification results //let verification_result = // if verification_task.procedures.is_empty() && verification_task.types.is_empty() { 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()); diff --git a/viper-sys/build.rs b/viper-sys/build.rs index 6d8dfd70785..d23808363eb 100644 --- a/viper-sys/build.rs +++ b/viper-sys/build.rs @@ -158,6 +158,45 @@ 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.reporter.BlockReachedMessage", vec![ + method!("methodName"), + method!("label"), + method!("pathId"), + ]), + java_class!("viper.silver.reporter.BlockFailureMessage", vec![ + method!("methodName"), + method!("label"), + method!("pathId"), + ]), + java_class!("viper.silver.reporter.PathProcessedMessage", vec![ + method!("methodName"), + method!("pathId"), + method!("result"), + ]), + 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"), @@ -165,6 +204,7 @@ fn main() { method!("start"), method!("stop"), method!("verify"), + method!("reporter"), ]), java_class!("viper.silver.frontend.DefaultStates", vec![ method!("ConsistencyCheck"), @@ -500,6 +540,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-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 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] diff --git a/viper/src/ast_factory/expression.rs b/viper/src/ast_factory/expression.rs index 6d73f415582..4a46eb69983 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> { @@ -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() ) } @@ -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/viper/src/ast_factory/macros.rs b/viper/src/ast_factory/macros.rs index 51ebc5bcf51..4a20a2fff9b 100644 --- a/viper/src/ast_factory/macros.rs +++ b/viper/src/ast_factory/macros.rs @@ -11,10 +11,10 @@ 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(crate) fn to_jobject(self) -> JObject<'a> { + pub fn to_jobject(self) -> JObject<'a> { self.obj } } 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/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..2baeb702b4c 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -11,11 +11,16 @@ 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}; -use std::path::PathBuf; +use std::{ + path::PathBuf, + collections::{hash_map::DefaultHasher, HashSet}, + thread::ScopedJoinHandle, + hash::{Hash, Hasher}, +}; use viper_sys::wrappers::{scala, viper::*}; pub struct Verifier<'a> { @@ -42,7 +47,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 +56,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); @@ -164,27 +174,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) -> VerificationResult { + pub fn verify( + &mut self, + program: Program, + polling_thread: Option>>, + ) -> VerificationResultKind { let ast_utils = self.ast_utils; ast_utils.with_local_frame(16, || { debug!( @@ -196,7 +191,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 +204,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| { @@ -217,7 +212,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( @@ -248,163 +243,25 @@ 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 avoids 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) } - - VerificationResult::Failure(errors) } else { - VerificationResult::Success + VerificationResultKind::Success } }) } + + pub fn verifier_instance(&self) -> &JObject<'a> { + &self.verifier_instance + } } impl<'a> Drop for Verifier<'a> { @@ -420,3 +277,208 @@ 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 { + + 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 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 reason 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 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) + ); + } + + // 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 = 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, + offending_pos_id, + reason_pos_id, + message, + counterexample, + )) + } + + VerificationResultKind::Failure(errors) +} + +/// manually hash identifying parts of an error +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 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, 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/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/callable_idents.rs b/vir/src/callable_idents.rs index eb51a30d3de..cb00618ccf5 100644 --- a/vir/src/callable_idents.rs +++ b/vir/src/callable_idents.rs @@ -1,7 +1,12 @@ 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}; pub trait CallableIdent<'vir, A: Arity<'vir>, ResultTy> { fn new(name: ViperIdent<'vir>, args: A, result_ty: ResultTy) -> Self; @@ -270,14 +275,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 +332,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>> { @@ -304,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), @@ -328,18 +364,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 +375,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>> { @@ -372,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: {}", @@ -382,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/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/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 3ebe9322a2a..8f5c14acc35 100644 --- a/vir/src/debug.rs +++ b/vir/src/debug.rs @@ -131,7 +131,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) } } @@ -300,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 a1470cf67b7..ee4dcd9ec3c 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(), + }) } } @@ -274,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/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 7b03a901435..6271eaee655 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, } }; } @@ -42,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, .. }) => { @@ -68,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>( @@ -282,8 +283,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 +445,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 { @@ -515,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>( @@ -538,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>( @@ -553,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>( @@ -586,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>( @@ -636,7 +649,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>], @@ -645,7 +658,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, @@ -665,15 +682,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); + } } } } 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>; 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..e3c40aab664 --- /dev/null +++ b/vir/src/spans.rs @@ -0,0 +1,304 @@ +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::{ + Span, + DUMMY_SP, + source_map::SourceMap, + def_id::DefId +}; +use crate::VirCtxt; +use ide::{SpanOfCallContracts, EncodingInfo}; + +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>, + + /// Vector of objects holding for each method call the spans of any + /// 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. 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, + +} + +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) -> 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, + offending_pos_id: usize, + reason_pos_id: Option, + ) -> Option> { + let manager = self.spans.borrow(); + 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)(reason_span_opt) { + return Some(errors); + } + } + handler_opt = handler.next.as_deref(); + } + span_opt = span.parent.as_ref(); + } + 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) + } + + pub fn viper_to_rust_identifier( + &'tcx self, + 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 { + 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 insert_viper_identifier( + &'tcx self, + 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() + } + + /// 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, + 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( + &'tcx self, + defpath: String, + call_span: Span, + contracts_spans: Vec, + source_map: &SourceMap + ) { + let span_of_call_contracts = SpanOfCallContracts::new( + defpath, + call_span, + contracts_spans, + source_map, + ); + let mut manager = self.spans.borrow_mut(); + 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<'_>, + ) { + let mut call_contract_spans = self + .spans + .borrow() + .call_contract_spans + .clone(); + // sort, so the order 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); + } +} diff --git a/vir/src/viper_ident.rs b/vir/src/viper_ident.rs index b3724cbbe30..60b888bb7fe 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 { @@ -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, } }