From 0928308ab7b9d4b1dfb0e04caa2b9147c6c7f90b Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 21 Feb 2023 18:50:30 +0100 Subject: [PATCH 01/54] Add Lithium back end --- Cargo.lock | 31 + backend-common/Cargo.toml | 10 + .../src/java_exception.rs | 0 backend-common/src/lib.rs | 7 + backend-common/src/silicon_counterexample.rs | 70 ++ .../src/verification_result.rs | 20 +- native-verifier/Cargo.toml | 20 + native-verifier/src/fol.rs | 141 +++++ native-verifier/src/lib.rs | 9 + native-verifier/src/main.rs | 3 + native-verifier/src/smt_lib.rs | 597 ++++++++++++++++++ native-verifier/src/theories/Map.smt2 | 2 + native-verifier/src/theories/MultiSet.smt2 | 2 + native-verifier/src/theories/Preamble.smt2 | 82 +++ native-verifier/src/theories/Seq.smt2 | 239 +++++++ native-verifier/src/theories/Set.smt2 | 163 +++++ native-verifier/src/theory_provider.rs | 36 ++ native-verifier/src/verifier.rs | 112 ++++ prusti-common/Cargo.toml | 1 + .../src/vir/program_normalization.rs | 2 +- prusti-server/Cargo.toml | 2 + prusti-server/src/backend.rs | 2 + prusti-server/src/client.rs | 2 +- prusti-server/src/process_verification.rs | 9 +- prusti-utils/src/utils/mod.rs | 1 + prusti-utils/src/utils/run_timed.rs | 20 + prusti-viper/Cargo.toml | 1 + .../counterexample_translation.rs | 2 +- .../counterexample_translation_refactored.rs | 2 +- .../src/encoder/errors/error_manager.rs | 4 +- prusti-viper/src/verifier.rs | 15 +- viper/Cargo.toml | 2 + viper/src/ast_utils.rs | 3 +- viper/src/cache.rs | 2 +- viper/src/jni_utils.rs | 2 +- viper/src/lib.rs | 9 +- viper/src/silicon_counterexample.rs | 112 +--- viper/src/utils.rs | 21 - viper/src/verifier.rs | 15 +- 39 files changed, 1592 insertions(+), 181 deletions(-) create mode 100644 backend-common/Cargo.toml rename {viper => backend-common}/src/java_exception.rs (100%) create mode 100644 backend-common/src/lib.rs create mode 100644 backend-common/src/silicon_counterexample.rs rename {viper => backend-common}/src/verification_result.rs (71%) create mode 100644 native-verifier/Cargo.toml create mode 100644 native-verifier/src/fol.rs create mode 100644 native-verifier/src/lib.rs create mode 100644 native-verifier/src/main.rs create mode 100644 native-verifier/src/smt_lib.rs create mode 100644 native-verifier/src/theories/Map.smt2 create mode 100644 native-verifier/src/theories/MultiSet.smt2 create mode 100644 native-verifier/src/theories/Preamble.smt2 create mode 100644 native-verifier/src/theories/Seq.smt2 create mode 100644 native-verifier/src/theories/Set.smt2 create mode 100644 native-verifier/src/theory_provider.rs create mode 100644 native-verifier/src/verifier.rs create mode 100644 prusti-utils/src/utils/run_timed.rs diff --git a/Cargo.lock b/Cargo.lock index e070c4b73aa..ca5672dbb26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,6 +291,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backend-common" +version = "0.1.0" +dependencies = [ + "rustc-hash", + "serde", + "vir", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -1900,6 +1909,22 @@ dependencies = [ "tempfile", ] +[[package]] +name = "native-verifier" +version = "0.1.0" +dependencies = [ + "backend-common", + "error-chain", + "lazy_static", + "log", + "prusti-common", + "prusti-utils", + "regex", + "serde", + "uuid", + "vir", +] + [[package]] name = "nix" version = "0.25.1" @@ -2279,6 +2304,7 @@ dependencies = [ name = "prusti-common" version = "0.1.0" dependencies = [ + "backend-common", "config", "itertools 0.11.0", "lazy_static", @@ -2338,11 +2364,13 @@ version = "0.1.0" name = "prusti-server" version = "0.1.0" dependencies = [ + "backend-common", "bincode", "clap", "env_logger", "lazy_static", "log", + "native-verifier", "num_cpus", "once_cell", "prusti-common", @@ -2413,6 +2441,7 @@ dependencies = [ name = "prusti-viper" version = "0.1.0" dependencies = [ + "backend-common", "backtrace", "derive_more", "diffy", @@ -3680,6 +3709,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "viper" version = "0.1.0" dependencies = [ + "backend-common", "bencher", "bincode", "env_logger", @@ -3688,6 +3718,7 @@ dependencies = [ "jni", "lazy_static", "log", + "prusti-utils", "rustc-hash", "serde", "smt-log-analyzer", diff --git a/backend-common/Cargo.toml b/backend-common/Cargo.toml new file mode 100644 index 00000000000..e596456518a --- /dev/null +++ b/backend-common/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "backend-common" +version = "0.1.0" +authors = ["Prusti Devs "] +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +rustc-hash = "1.1.0" +vir = { path = "../vir" } diff --git a/viper/src/java_exception.rs b/backend-common/src/java_exception.rs similarity index 100% rename from viper/src/java_exception.rs rename to backend-common/src/java_exception.rs diff --git a/backend-common/src/lib.rs b/backend-common/src/lib.rs new file mode 100644 index 00000000000..14a739948a4 --- /dev/null +++ b/backend-common/src/lib.rs @@ -0,0 +1,7 @@ +mod verification_result; +mod java_exception; +mod silicon_counterexample; + +pub use crate::{ + java_exception::*, silicon_counterexample::*, verification_result::*, +}; diff --git a/backend-common/src/silicon_counterexample.rs b/backend-common/src/silicon_counterexample.rs new file mode 100644 index 00000000000..34159e973b4 --- /dev/null +++ b/backend-common/src/silicon_counterexample.rs @@ -0,0 +1,70 @@ +use rustc_hash::FxHashMap; +use serde; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct SiliconCounterexample { + //pub heap: Heap, + //pub old_heaps: FxHashMap, + pub model: Model, + pub functions: Functions, + pub domains: Domains, + pub old_models: FxHashMap, + // label_order because HashMaps do not guarantee order of elements + // whereas the Map used in scala does guarantee it + pub label_order: Vec, +} + +// Model Definitions +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Model { + pub entries: FxHashMap, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum ModelEntry { + LitInt(String), + LitFloat(String), + LitBool(bool), + LitPerm(String), + Ref(String, FxHashMap), + NullRef(String), + RecursiveRef(String), + Var(String), + Seq(String, Vec), + Other(String, String), + DomainValue(String, String), + UnprocessedModel, //used for Silicon's Snap type +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Functions { + pub entries: FxHashMap, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct FunctionEntry { + pub options: Vec<(Vec>, Option)>, + pub default: Option, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Domains { + pub entries: FxHashMap, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct DomainEntry { + pub functions: Functions, +} + +impl FunctionEntry { + /// Given a vec of params it finds the correct entry in a function. + pub fn get_function_value(&self, params: &Vec>) -> &Option { + for option in &self.options { + if &option.0 == params { + return &option.1; + } + } + &None + } +} diff --git a/viper/src/verification_result.rs b/backend-common/src/verification_result.rs similarity index 71% rename from viper/src/verification_result.rs rename to backend-common/src/verification_result.rs index 4bf847217df..65d380813a9 100644 --- a/viper/src/verification_result.rs +++ b/backend-common/src/verification_result.rs @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{silicon_counterexample::SiliconCounterexample, JavaException}; +use crate::{SiliconCounterexample, JavaException}; /// The result of a verification request on a Viper program. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -57,21 +57,3 @@ impl VerificationError { } } } - -/// The consistency error reported by the verifier. -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct ConsistencyError { - /// To which method corresponds the program that triggered the error. - pub method: String, - /// The actual error. - pub error: String, -} - -/// The Java exception reported by the verifier. -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct JavaExceptionWithOrigin { - /// To which method corresponds the program that triggered the exception. - pub method: String, - /// The actual exception. - pub exception: JavaException, -} diff --git a/native-verifier/Cargo.toml b/native-verifier/Cargo.toml new file mode 100644 index 00000000000..40f7bd85bb1 --- /dev/null +++ b/native-verifier/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "native-verifier" +version = "0.1.0" +authors = ["Prusti Devs "] +edition = "2021" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = { version = "0.4", features = ["release_max_level_info"] } +error-chain = "0.12" +uuid = { version = "1.0", features = ["v4"] } +serde = { version = "1.0", features = ["derive"] } +prusti-common = { path = "../prusti-common" } +backend-common = { path = "../backend-common" } +prusti-utils = { path = "../prusti-utils" } +vir = { path = "../vir" } +lazy_static = "1.4" +regex = "1.7.1" \ No newline at end of file diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs new file mode 100644 index 00000000000..153e514fb52 --- /dev/null +++ b/native-verifier/src/fol.rs @@ -0,0 +1,141 @@ +use std::collections::HashMap; +use vir::low::{ + ast::{expression::*, statement::*}, + *, +}; + +pub enum FolStatement { + Comment(String), + Assume(Expression), + Assert(Expression), +} + +fn vir_statement_to_fol_statements( + statement: &Statement, + known_methods: &HashMap, +) -> Vec { + match statement { + Statement::Assert(expr) => vec![FolStatement::Assert(expr.expression.clone())], + Statement::Assume(expr) => vec![FolStatement::Assume(expr.expression.clone())], + Statement::Inhale(expr) => vec![FolStatement::Assume(expr.expression.clone())], + Statement::Exhale(expr) => vec![FolStatement::Assert(expr.expression.clone())], + Statement::Assign(assign) => { + let eq = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::EqCmp, + left: Box::new(Expression::Local(Local { + variable: assign.target.clone(), + position: assign.position, + })), + right: Box::new(assign.value.clone()), + position: assign.position, + }); + + vec![FolStatement::Assume(eq)] + } + Statement::MethodCall(method_call) => { + let method_decl = known_methods + .get(&method_call.method_name) + .expect("Method not found"); + + let mut params_to_args = method_decl + .parameters + .iter() + .zip(method_call.arguments.iter()) + .map(|(target, arg)| (target.clone(), arg.clone())) + .collect::>(); + + let returns_to_targets = method_decl + .targets + .iter() + .zip(method_call.targets.iter()) + .map(|(target, arg)| (target.clone(), arg.clone())) + .collect::>(); + + params_to_args.extend(returns_to_targets); + + fn substitute( + expr: &Expression, + mapping: &HashMap, + ) -> Expression { + match expr { + Expression::Local(local) => { + if let Some(arg) = mapping.get(&local.variable) { + arg.clone() + } else { + Expression::Local(local.clone()) + } + } + Expression::BinaryOp(op) => Expression::BinaryOp(BinaryOp { + op_kind: op.op_kind, + left: Box::new(substitute(&op.left, mapping)), + right: Box::new(substitute(&op.right, mapping)), + position: op.position, + }), + Expression::UnaryOp(op) => Expression::UnaryOp(UnaryOp { + op_kind: op.op_kind.clone(), // TODO: Copy trait derivation + argument: Box::new(substitute(&op.argument, mapping)), + position: op.position, + }), + Expression::ContainerOp(op) => Expression::ContainerOp(ContainerOp { + kind: op.kind.clone(), // TODO: Copy trait derivation + container_type: op.container_type.clone(), + operands: op + .operands + .iter() + .map(|arg| substitute(arg, mapping)) + .collect(), + position: op.position, + }), + Expression::Constant(constant) => Expression::Constant(constant.clone()), + Expression::DomainFuncApp(func) => Expression::DomainFuncApp(DomainFuncApp { + domain_name: func.domain_name.clone(), + function_name: func.function_name.clone(), + arguments: func + .arguments + .iter() + .map(|arg| substitute(arg, mapping)) + .collect(), + parameters: func.parameters.clone(), + return_type: func.return_type.clone(), + position: func.position, + }), + Expression::MagicWand(magic_wand) => Expression::MagicWand(MagicWand { + left: Box::new(substitute(&magic_wand.left, mapping)), + right: Box::new(substitute(&magic_wand.right, mapping)), + position: magic_wand.position, + }), + _ => unimplemented!("substitute not implemented for {:?}", expr), + } + } + + let preconds = method_decl.pres.iter().map(|pre| { + // substitute parameters for arguments + FolStatement::Assert(substitute(pre, ¶ms_to_args)) + }); + + let postconds = method_decl.posts.iter().map(|post| { + // substitute parameters for arguments + FolStatement::Assume(substitute(post, ¶ms_to_args)) + }); + + preconds.chain(postconds).collect::>() + } + Statement::Comment(comment) => vec![FolStatement::Comment(comment.comment.clone())], + Statement::LogEvent(_) => vec![], // TODO: Embed in SMT-LIB code + _ => { + log::warn!("Statement {:?} not yet supported", statement); + vec![] + } + } +} + +pub fn vir_to_fol( + statements: &Vec, + known_methods: &HashMap, +) -> Vec { + // reduce so that the order in the vector is now the Sequence operator + statements + .iter() + .flat_map(|s| vir_statement_to_fol_statements(s, known_methods)) + .collect() +} diff --git a/native-verifier/src/lib.rs b/native-verifier/src/lib.rs new file mode 100644 index 00000000000..bf33a15537e --- /dev/null +++ b/native-verifier/src/lib.rs @@ -0,0 +1,9 @@ +#![feature(try_blocks)] +#![feature(let_chains)] + +pub mod verifier; +mod smt_lib; +mod theory_provider; +mod fol; + +pub use crate::verifier::*; diff --git a/native-verifier/src/main.rs b/native-verifier/src/main.rs new file mode 100644 index 00000000000..adf706984a3 --- /dev/null +++ b/native-verifier/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + unimplemented!("This is a dummy main function."); +} diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs new file mode 100644 index 00000000000..939da8cc71c --- /dev/null +++ b/native-verifier/src/smt_lib.rs @@ -0,0 +1,597 @@ +use super::{ + fol::{vir_to_fol, FolStatement}, + theory_provider::*, +}; +use lazy_static::lazy_static; +use log::warn; +use regex::Regex; +use std::collections::{HashMap, HashSet}; +use vir::{ + common::position::Positioned, + low::{ + ast::{expression::*, ty::*}, + *, + }, +}; + +lazy_static! { + static ref ONE_PARAM_RE: Regex = Regex::new(r"(Set|Seq|MultiSet)<([^>]+)>").unwrap(); + static ref TWO_PARAM_RE: Regex = Regex::new(r"Map<([^>]+)\s*,\s*([^>]+)>").unwrap(); +} + +const SMT_PREAMBLE: &str = include_str!("theories/Preamble.smt2"); + +pub struct SMTLib { + sort_decls: Vec, + func_decls: Vec, + code: Vec, + methods: HashMap, + blocks: HashMap, +} + +impl SMTLib { + pub fn new() -> Self { + Self { + sort_decls: Vec::new(), + func_decls: Vec::new(), + code: Vec::new(), + methods: HashMap::new(), + blocks: Default::default(), + } + } + fn add_sort_decl(&mut self, sort_decl: String) { + self.sort_decls + .push(format!("(declare-sort {} 0)", sort_decl)); + } + fn add_func(&mut self, func_decl: String) { + self.func_decls.push(func_decl); + } + fn add_assert(&mut self, assert: String) { + self.code.push(format!("(assert {})", assert)); + } + fn add_code(&mut self, text: String) { + self.code.push(text); + } + fn follow(&mut self, block_label: &String, precond: Option<&Expression>) { + let block = self + .blocks + .get(block_label) + .expect("Block not found") + .clone(); + + if block.statements.is_empty() { + return; + } + + self.add_code(format!("; Basic block: {}", block.label)); + self.add_code("(push)".to_string()); + + // assume precond if any + if let Some(precond) = precond { + self.add_code("; Branch precond:".to_string()); + self.add_code(format!("(assert {})", precond.to_smt())); + } + + // verify body + let predicates = vir_to_fol(&block.statements, &self.methods); + + for predicate in predicates.into_iter() { + match predicate { + FolStatement::Comment(comment) => self.add_code(format!("; {}", comment)), + FolStatement::Assume(expression) => { + // assert predicate + self.add_code(format!("(assert {})", expression.to_smt())); + } + FolStatement::Assert(expression) => { + // negate predicate + let position = expression.position(); + let negated = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(expression), + position, + }); + // assert negated predicate + self.add_code("(push)".to_string()); + self.add_code(format!("(assert {})", negated.to_smt())); + self.add_code("(check-sat)".to_string()); + self.add_code("(pop)".to_string()); + } + } + } + + // process successor + match &block.successor { + Successor::Goto(label) => { + self.follow(&label.name, None); + } + Successor::GotoSwitch(mapping) => mapping.iter().for_each(|(expr, label)| { + self.follow(&label.name, Some(expr)); + }), + Successor::Return => {} + } + + self.add_code(format!("(pop); End basic block: {}", block.label)); + } +} + +impl ToString for SMTLib { + fn to_string(&self) -> String { + let mut result = String::new(); + result.push_str(SMT_PREAMBLE); + result.push_str("\n\n"); + result.push_str(&self.sort_decls.join("\n")); + result.push_str("\n\n"); + + let mut main: String = "\n\n".to_string(); + main.push_str(&self.func_decls.join("\n")); + main.push_str("\n\n"); + main.push_str(&self.code.join("\n")); + + // TODO: Sort wrappers + + // initialize the theory providers + let set_provider = ContainerTheoryProvider::new("Set"); + let seq_provider = ContainerTheoryProvider::new("Seq"); + let multiset_provider = ContainerTheoryProvider::new("MultiSet"); + let map_provider = MapTheoryProvider::new(); + + // set of seen combinations + let mut seen = std::collections::HashSet::new(); + + for cap in ONE_PARAM_RE.captures_iter(&main) { + let container = &cap[1]; + let element_type: &str = &cap[2]; + + // check if seen so far + if seen.contains(&(container.to_string(), element_type.to_string())) { + continue; + } + + match container { + "Set" => result.push_str(&set_provider.get_theory(element_type)), + "Seq" => result.push_str(&seq_provider.get_theory(element_type)), + "MultiSet" => result.push_str(&multiset_provider.get_theory(element_type)), + _ => panic!("Unknown container type: {}", container), + } + + // add the combination to the set of seen combinations + seen.insert((container.to_string(), element_type.to_string())); + } + + // set of seen combinations + let mut seen = std::collections::HashSet::new(); + + // same for Map in TWO_PARAM_RE + for cap in TWO_PARAM_RE.captures_iter(&main) { + let key_type = &cap[1]; + let value_type = &cap[2]; + + // check if seen so far + if seen.contains(&(key_type.to_string(), value_type.to_string())) { + continue; + } + + result.push_str(&map_provider.get_theory(key_type, value_type)); + + // add the combination to the set of seen combinations + seen.insert((key_type.to_string(), value_type.to_string())); + } + + result.push_str(&main); + + // strip all lines containing "$marker" + // TODO: SSO form for marker variables? + result + .lines() + .filter(|line| !line.contains("$marker")) + .collect::>() + .join("\n") + } +} + +pub trait SMTTranslatable { + fn build_smt(&self, _: &mut SMTLib) { + unimplemented!() + } + fn to_smt(&self) -> String { + unimplemented!() + } +} + +// args are any collection of SMTTranslatable +fn mk_app(name: &String, args: &Vec) -> String +where + T: SMTTranslatable, +{ + if args.len() == 0 { + name.clone() + } else { + format!( + "({} {})", + name, + args.iter() + .map(|a| a.to_smt()) + .collect::>() + .join(" ") + ) + } +} + +impl SMTTranslatable for Program { + fn build_smt(&self, smt: &mut SMTLib) { + self.domains.iter().for_each(|d| d.build_smt(smt)); + self.methods.iter().for_each(|d| d.build_smt(smt)); + self.procedures.iter().for_each(|d| d.build_smt(smt)); + // TODO: Everything else + } +} + +/* #region Domains */ + +impl SMTTranslatable for DomainDecl { + fn build_smt(&self, smt: &mut SMTLib) { + smt.add_sort_decl(self.name.clone()); + + if !self.functions.is_empty() { + smt.add_func(format!("; Functions for domain {}", self.name)); + self.functions.iter().for_each(|f| f.build_smt(smt)); + } + + if !self.axioms.is_empty() { + smt.add_code(format!("; Axioms for domain {}", self.name)); + self.axioms.iter().for_each(|a| a.build_smt(smt)); + } + } +} + +impl SMTTranslatable for DomainFunctionDecl { + // TODO: self.is_unique + fn build_smt(&self, smt: &mut SMTLib) { + let mut fun = String::new(); + + fun.push_str(&format!("(declare-fun {} (", self.name)); + fun.push_str( + &self + .parameters + .iter() + .map(|p| p.ty.to_smt()) + .collect::>() + .join(" "), + ); + fun.push_str(&format!(") {})", self.return_type.to_smt())); + + smt.add_func(fun); + } +} + +impl SMTTranslatable for DomainAxiomDecl { + fn build_smt(&self, smt: &mut SMTLib) { + if let Some(comment) = &self.comment { + smt.add_assert(format!("; {}", comment)); + } + + smt.add_assert(self.body.to_smt()); + } +} + +/* #endregion */ + +/* #region Methods and Procedures */ + +impl SMTTranslatable for MethodDecl { + fn build_smt(&self, smt: &mut SMTLib) { + // if body is None, we have a body-less method with only pre- and postconditions + // we assume these to be correct by default and collect their signatures + if self.body.is_none() { + smt.methods.insert(self.name.clone(), self.clone()); + } + } +} + +impl SMTTranslatable for ProcedureDecl { + fn build_smt(&self, smt: &mut SMTLib) { + smt.add_code(format!("; Procedure {}", self.name)); + + smt.add_code("(push)".to_string()); + + // declare locals + self.locals.iter().for_each(|l| l.build_smt(smt)); + + // process basic blocks + smt.blocks.clear(); + + self.basic_blocks.iter().for_each(|b| { + smt.blocks.insert(b.label.name.clone(), b.clone()); // TODO: Inefficient copy + }); + + // find a starting block + let mut start_blocks = smt.blocks.keys().collect::>(); + + for (_, block) in &smt.blocks { + match &block.successor { + Successor::Goto(label) => { + start_blocks.remove(&label.name); + } + Successor::GotoSwitch(labels) => { + labels.iter().for_each(|(_, l)| { + start_blocks.remove(&l.name); + }); + } + Successor::Return => {} + } + } + + assert!( + start_blocks.len() == 1, + "Expected exactly one start block, found {}", + start_blocks.len() + ); + + let start = start_blocks.drain().next().unwrap().clone(); + smt.follow(&start, None); + + smt.add_code("(pop)".to_string()); + } +} + +impl SMTTranslatable for VariableDecl { + fn build_smt(&self, smt: &mut SMTLib) { + smt.add_code(format!( + "(declare-const {} {})", + self.name, + self.ty.to_smt() + )); + } +} + +/* #endregion */ + +impl SMTTranslatable for Expression { + fn to_smt(&self) -> String { + match self { + Expression::Local(local) => local.variable.name.clone(), + Expression::Field(_) => unimplemented!(), + Expression::LabelledOld(_) => unimplemented!(), + Expression::Constant(constant) => match &constant.value { + ConstantValue::Bool(bool) => bool.to_string(), + ConstantValue::Int(i64) => i64.to_string(), + ConstantValue::BigInt(s) => s.clone(), + }, + Expression::MagicWand(magic_wand) => format!( + "(=> {} {})", // TODO: is this correct? + magic_wand.left.to_smt(), + magic_wand.right.to_smt() + ), + Expression::PredicateAccessPredicate(_access) => { + // TODO: access predicates for predicates + warn!("PredicateAccessPredicate not supported"); + "".to_string() + } + Expression::FieldAccessPredicate(_) => unimplemented!(), + Expression::Unfolding(_) => unimplemented!(), + Expression::UnaryOp(unary_op) => { + format!( + "({} {})", + unary_op.op_kind.to_smt(), + unary_op.argument.to_smt() + ) + } + Expression::BinaryOp(binary_op) => format!( + "({} {} {})", + binary_op.op_kind.to_smt(), + binary_op.left.to_smt(), + binary_op.right.to_smt() + ), + Expression::PermBinaryOp(perm_binary_op) => format!( + "({} {} {})", + perm_binary_op.op_kind.to_smt(), + perm_binary_op.left.to_smt(), + perm_binary_op.right.to_smt() + ), + Expression::ContainerOp(container_op) => container_op.to_smt(), + Expression::Conditional(conditional) => format!( + "(ite {} {} {})", + conditional.guard.to_smt(), + conditional.then_expr.to_smt(), + conditional.else_expr.to_smt() + ), + // TODO: Quantifier triggers + Expression::Quantifier(quantifier) => match quantifier.kind { + QuantifierKind::ForAll => { + let mut quant = String::new(); + quant.push_str("(forall ("); + quant.push_str( + &quantifier + .variables + .iter() + .map(|v| format!("({} {})", v.name, v.ty.to_smt())) + .collect::>() + .join(" "), + ); + quant.push_str(") "); + + if quantifier.triggers.is_empty() { + quant.push_str(&quantifier.body.to_smt()); + quant.push_str(")"); + } else { + // triggers are :pattern + quant.push_str("(!"); + quant.push_str(&quantifier.body.to_smt()); + + for trigger in &quantifier.triggers { + quant.push_str(" :pattern ("); + + quant.push_str( + &trigger + .terms + .iter() + .map(|expr| expr.to_smt()) + .collect::>() + .join(" "), + ); + + quant.push_str(")"); + } + + quant.push_str("))"); + } + + quant + } + QuantifierKind::Exists => unimplemented!(), + }, + Expression::LetExpr(_) => unimplemented!(), + Expression::FuncApp(_) => unimplemented!(), + Expression::DomainFuncApp(domain_func_app) => { + mk_app(&domain_func_app.function_name, &domain_func_app.arguments) + } + Expression::InhaleExhale(_) => unimplemented!(), + } + } +} + +impl SMTTranslatable for BinaryOpKind { + fn to_smt(&self) -> String { + match self { + BinaryOpKind::EqCmp => "=", + BinaryOpKind::NeCmp => "distinct", + BinaryOpKind::GtCmp => ">", + BinaryOpKind::GeCmp => ">=", + BinaryOpKind::LtCmp => "<", + BinaryOpKind::LeCmp => "<=", + BinaryOpKind::Add => "+", + BinaryOpKind::Sub => "-", + BinaryOpKind::Mul => "*", + BinaryOpKind::Div => "/", + BinaryOpKind::Mod => "%", + BinaryOpKind::And => "and", + BinaryOpKind::Or => "or", + BinaryOpKind::Implies => "=>", + } + .to_string() + } +} + +impl SMTTranslatable for PermBinaryOpKind { + fn to_smt(&self) -> String { + match self { + PermBinaryOpKind::Add => "+", + PermBinaryOpKind::Sub => "-", + PermBinaryOpKind::Mul => "*", + PermBinaryOpKind::Div => "/", + } + .to_string() + } +} + +impl SMTTranslatable for UnaryOpKind { + fn to_smt(&self) -> String { + match self { + UnaryOpKind::Not => "not", + UnaryOpKind::Minus => "-", + } + .to_string() + } +} + +impl SMTTranslatable for ContainerOp { + fn to_smt(&self) -> String { + match &self.kind { + ContainerOpKind::SetConstructor => { + if self.operands.len() == 1 { + return format!("(Set_singleton {})", self.operands[0].to_smt()); + } + + fn recurse(set: String, arg: &Expression) -> String { + format!("(Set_unionone {} {})", set, arg.to_smt()) + } + self.operands.iter().fold("Set_empty".to_string(), recurse) + } + ContainerOpKind::SeqConstructor => { + if self.operands.len() == 1 { + return format!("(Seq_singleton {})", self.operands[0].to_smt()); + } + + fn recurse(seq: String, arg: &Expression) -> String { + format!("(Seq_build {} {})", seq, arg.to_smt()) + } + self.operands.iter().fold("Seq_empty".to_string(), recurse) + } + kind => format!( + "({} {})", + kind.to_smt(), + self.operands + .iter() + .map(|a| a.to_smt()) + .collect::>() + .join(" ") + ), + } + } +} + +impl SMTTranslatable for ContainerOpKind { + fn to_smt(&self) -> String { + match self { + ContainerOpKind::SetCardinality => "Set_card", + ContainerOpKind::SetConstructor => "Set_singleton", + ContainerOpKind::SetContains => "Set_in", + ContainerOpKind::SetEmpty => "Set_empty", + ContainerOpKind::SetIntersection => "Set_intersection", + ContainerOpKind::SetMinus => "Set_difference", + ContainerOpKind::SetSubset => "Set_subset", + ContainerOpKind::SetUnion => "Set_union", + + ContainerOpKind::SeqEmpty => "Seq_empty", + ContainerOpKind::SeqConstructor => "Seq_singleton", + ContainerOpKind::SeqIndex => "Seq_index", + ContainerOpKind::SeqConcat => "Seq_concat", + ContainerOpKind::SeqLen => "Seq_length", + + x => unimplemented!("ContainerOpKind::{}::to_smt()", x), + } + .to_string() + } +} + +// TODO: Check if these make sense +impl SMTTranslatable for Type { + fn to_smt(&self) -> String { + match self { + Type::Int => "Int".to_string(), + Type::Bool => "Bool".to_string(), + Type::Perm => "$Perm".to_string(), + Type::Float(float) => match float { + Float::F32 => "Float32".to_string(), + Float::F64 => "Float64".to_string(), + }, + Type::BitVector(bitvec) => match bitvec { + // TODO: Should they map to the same type? + BitVector::Signed(size) => { + format!("(_ BitVec {})", size.to_smt()) + } + BitVector::Unsigned(size) => { + format!("(_ BitVec {})", size.to_smt()) + } + }, + Type::Seq(seq) => format!("Seq<{}>", seq.element_type.to_smt()), + Type::Set(set) => format!("Set<{}>", set.element_type.to_smt()), + Type::MultiSet(_) => unimplemented!("MultiSet"), + Type::Map(_) => unimplemented!("Map"), + Type::Ref => unimplemented!("Ref"), + Type::Domain(domain) => domain.name.to_string(), + } + } +} + +impl SMTTranslatable for BitVectorSize { + fn to_smt(&self) -> String { + match self { + BitVectorSize::BV8 => "8".to_string(), + BitVectorSize::BV16 => "16".to_string(), + BitVectorSize::BV32 => "32".to_string(), + BitVectorSize::BV64 => "64".to_string(), + BitVectorSize::BV128 => "128".to_string(), + } + } +} diff --git a/native-verifier/src/theories/Map.smt2 b/native-verifier/src/theories/Map.smt2 new file mode 100644 index 00000000000..b428f3e2df4 --- /dev/null +++ b/native-verifier/src/theories/Map.smt2 @@ -0,0 +1,2 @@ + +; ===== Map theory for types KEYTYPENAME and VALUETYPENAME ===== \ No newline at end of file diff --git a/native-verifier/src/theories/MultiSet.smt2 b/native-verifier/src/theories/MultiSet.smt2 new file mode 100644 index 00000000000..04ea0b55fb5 --- /dev/null +++ b/native-verifier/src/theories/MultiSet.smt2 @@ -0,0 +1,2 @@ + +; ===== MultiSet theory for type TYPENAME ===== \ No newline at end of file diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 new file mode 100644 index 00000000000..20d53d68638 --- /dev/null +++ b/native-verifier/src/theories/Preamble.smt2 @@ -0,0 +1,82 @@ +; ===== Static preamble ===== + +(set-option :global-decls true) ; Boogie: default +(set-option :auto_config false) ; Usually a good idea +(set-option :smt.restart_strategy 0) +(set-option :smt.restart_factor |1.5|) +(set-option :smt.case_split 3) +(set-option :smt.delay_units true) +(set-option :smt.delay_units_threshold 16) +(set-option :nnf.sk_hack true) +(set-option :type_check true) +(set-option :smt.bv.reflect true) +(set-option :smt.mbqi false) +(set-option :smt.qi.cost "(+ weight generation)") +(set-option :smt.qi.eager_threshold 1000) +(set-option :smt.qi.max_multi_patterns 1000) +(set-option :smt.phase_selection 0) ; default: 3, Boogie: 0 +(set-option :sat.phase caching) +(set-option :sat.random_seed 0) +(set-option :nlsat.randomize true) +(set-option :nlsat.seed 0) +(set-option :nlsat.shuffle_vars false) +(set-option :fp.spacer.order_children 0) ; Not available with Z3 4.5 +(set-option :fp.spacer.random_seed 0) ; Not available with Z3 4.5 +(set-option :smt.arith.random_initial_value true) ; Boogie: true +(set-option :smt.random_seed 0) +(set-option :sls.random_offset true) +(set-option :sls.random_seed 0) +(set-option :sls.restart_init false) +(set-option :sls.walksat_ucb true) +(set-option :model.v2 true) +(set-option :model.partial false) + +(set-option :timeout 1000) + +; --- Snapshots --- + +(declare-datatypes (($Snap 0)) (( + ($Snap.unit) + ($Snap.combine ($Snap.first $Snap) ($Snap.second $Snap))))) + +; --- References --- + +(declare-sort $Ref 0) +(declare-const $Ref.null $Ref) + +; --- Permissions --- + +(declare-sort $FPM 0) +(declare-sort $PPM 0) +(define-sort $Perm () Real) + +(define-const $Perm.Write $Perm 1.0) +(define-const $Perm.No $Perm 0.0) + +(define-fun $Perm.isValidVar ((p $Perm)) Bool + (<= $Perm.No p)) + +(define-fun $Perm.isReadVar ((p $Perm)) Bool + (and ($Perm.isValidVar p) + (not (= p $Perm.No)))) + +; min function for permissions +(define-fun $Perm.min ((p1 $Perm) (p2 $Perm)) Real + (ite (<= p1 p2) p1 p2)) + +; --- Sort wrappers --- + +; Sort wrappers are no longer part of the static preamble. Instead, they are +; emitted as part of the program-specific preamble. + +; --- Math --- + +;function Math#min(a: int, b: int): int; +(define-fun $Math.min ((a Int) (b Int)) Int + (ite (<= a b) a b)) + +;function Math#clip(a: int): int; +(define-fun $Math.clip ((a Int)) Int + (ite (< a 0) 0 a)) + +; ===== End static preamble ===== \ No newline at end of file diff --git a/native-verifier/src/theories/Seq.smt2 b/native-verifier/src/theories/Seq.smt2 new file mode 100644 index 00000000000..7cfb355bd58 --- /dev/null +++ b/native-verifier/src/theories/Seq.smt2 @@ -0,0 +1,239 @@ + +; ===== Sequence theory for type TYPENAME ===== + +(declare-sort Seq 0) + +(declare-fun Seq_length (Seq) Int) +(declare-const Seq_empty Seq) +(declare-fun Seq_singleton (TYPENAME) Seq) +(declare-fun Seq_build (Seq TYPENAME) Seq) +(declare-fun Seq_index (Seq Int) TYPENAME) +(declare-fun Seq_append (Seq Seq) Seq) +(declare-fun Seq_update (Seq Int TYPENAME) Seq) +(declare-fun Seq_contains (Seq TYPENAME) Bool) +(declare-fun Seq_take (Seq Int) Seq) +(declare-fun Seq_drop (Seq Int) Seq) +(declare-fun Seq_equal (Seq Seq) Bool) +(declare-fun Seq_sameuntil (Seq Seq Int) Bool) + +(assert (forall ((s Seq)) (! + (<= 0 (Seq_length s)) + :pattern ((Seq_length s)) + :qid |$Seq[TYPENAME]_prog.seq_length_non_negative|))) +(assert (= (Seq_length (as Seq_empty Seq)) 0)) +(assert (forall ((s Seq)) (! + (=> (= (Seq_length s) 0) (= s (as Seq_empty Seq))) + :pattern ((Seq_length s)) + :qid |$Seq[TYPENAME]_prog.only_empty_seq_length_zero|))) +(assert (forall ((e TYPENAME)) (! + (= (Seq_length (Seq_singleton e)) 1) + :pattern ((Seq_length (Seq_singleton e))) + :qid |$Seq[TYPENAME]_prog.length_singleton_seq|))) +(assert (forall ((s Seq) (e TYPENAME)) (! + (= (Seq_length (Seq_build s e)) (+ 1 (Seq_length s))) + :pattern ((Seq_length (Seq_build s e))) + :qid |$Seq[TYPENAME]_prog.length_seq_build_inc_by_one|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME)) (! + (ite + (= i (Seq_length s)) + (= (Seq_index (Seq_build s e) i) e) + (= (Seq_index (Seq_build s e) i) (Seq_index s i))) + :pattern ((Seq_index (Seq_build s e) i)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_build|))) +(assert (forall ((s1 Seq) (s2 Seq)) (! + (=> + (and + (not (= s1 (as Seq_empty Seq))) + (not (= s2 (as Seq_empty Seq)))) + (= (Seq_length (Seq_append s1 s2)) (+ (Seq_length s1) (Seq_length s2)))) + :pattern ((Seq_length (Seq_append s1 s2))) + :qid |$Seq[TYPENAME]_prog.seq_length_over_append|))) +(assert (forall ((e TYPENAME)) (! + (= (Seq_index (Seq_singleton e) 0) e) + :pattern ((Seq_singleton e)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_singleton|))) +(assert (forall ((e1 TYPENAME) (e2 TYPENAME)) (! + (= (Seq_contains (Seq_singleton e1) e2) (= e1 e2)) + :pattern ((Seq_contains (Seq_singleton e1) e2)) + :qid |$Seq[TYPENAME]_prog.seq_contains_over_singleton|))) +(assert (forall ((s Seq)) (! + (= (Seq_append (as Seq_empty Seq) s) s) + :pattern ((Seq_append (as Seq_empty Seq) s)) + :qid |$Seq[TYPENAME]_prog.seq_append_empty_left|))) +(assert (forall ((s Seq)) (! + (= (Seq_append s (as Seq_empty Seq)) s) + :pattern ((Seq_append s (as Seq_empty Seq))) + :qid |$Seq[TYPENAME]_prog.seq_append_empty_right|))) +(assert (forall ((s1 Seq) (s2 Seq) (i Int)) (! + (=> + (and + (not (= s1 (as Seq_empty Seq))) + (not (= s2 (as Seq_empty Seq)))) + (ite + (< i (Seq_length s1)) + (= (Seq_index (Seq_append s1 s2) i) (Seq_index s1 i)) + (= (Seq_index (Seq_append s1 s2) i) (Seq_index s2 (- i (Seq_length s1)))))) + :pattern ((Seq_index (Seq_append s1 s2) i)) + :pattern ((Seq_index s1 i) (Seq_append s1 s2)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_append|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME)) (! + (=> + (and (<= 0 i) (< i (Seq_length s))) + (= (Seq_length (Seq_update s i e)) (Seq_length s))) + :pattern ((Seq_length (Seq_update s i e))) + :qid |$Seq[TYPENAME]_prog.seq_length_invariant_over_update|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME) (j Int)) (! + (ite + (=> (and (<= 0 i) (< i (Seq_length s))) (= i j)) + (= (Seq_index (Seq_update s i e) j) e) + (= (Seq_index (Seq_update s i e) j) (Seq_index s j))) + :pattern ((Seq_index (Seq_update s i e) j)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_update|))) +(assert (forall ((s Seq) (e TYPENAME)) (! + (= + (Seq_contains s e) + (exists ((i Int)) (! + (and (<= 0 i) (and (< i (Seq_length s)) (= (Seq_index s i) e))) + :pattern ((Seq_index s i)) + ))) + :pattern ((Seq_contains s e)) + :qid |$Seq[TYPENAME]_prog.seq_element_contains_index_exists|))) +(assert (forall ((e TYPENAME)) (! + (not (Seq_contains (as Seq_empty Seq) e)) + :pattern ((Seq_contains (as Seq_empty Seq) e)) + :qid |$Seq[TYPENAME]_prog.empty_seq_contains_nothing|))) +(assert (forall ((s1 Seq) (s2 Seq) (e TYPENAME)) (! + (= + (Seq_contains (Seq_append s1 s2) e) + (or (Seq_contains s1 e) (Seq_contains s2 e))) + :pattern ((Seq_contains (Seq_append s1 s2) e)) + :qid |$Seq[TYPENAME]_prog.seq_contains_over_append|))) +(assert (forall ((s Seq) (e1 TYPENAME) (e2 TYPENAME)) (! + (= (Seq_contains (Seq_build s e1) e2) (or (= e1 e2) (Seq_contains s e2))) + :pattern ((Seq_contains (Seq_build s e1) e2)) + :qid |$Seq[TYPENAME]_prog.seq_contains_over_build|))) +(assert (forall ((s Seq) (n Int)) (! + (=> (<= n 0) (= (Seq_take s n) (as Seq_empty Seq))) + :pattern ((Seq_take s n)) + :qid |$Seq[TYPENAME]_prog.seq_take_negative_length|))) +(assert (forall ((s Seq) (n Int) (e TYPENAME)) (! + (= + (Seq_contains (Seq_take s n) e) + (exists ((i Int)) (! + (and + (<= 0 i) + (and (< i n) (and (< i (Seq_length s)) (= (Seq_index s i) e)))) + :pattern ((Seq_index s i)) + ))) + :pattern ((Seq_contains (Seq_take s n) e)) + :qid |$Seq[TYPENAME]_prog.seq_contains_over_take_index_exists|))) +(assert (forall ((s Seq) (n Int)) (! + (=> (<= n 0) (= (Seq_drop s n) s)) + :pattern ((Seq_drop s n)) + :qid |$Seq[TYPENAME]_prog.seq_drop_negative_length|))) +(assert (forall ((s Seq) (n Int) (e TYPENAME)) (! + (= + (Seq_contains (Seq_drop s n) e) + (exists ((i Int)) (! + (and + (<= 0 i) + (and (<= n i) (and (< i (Seq_length s)) (= (Seq_index s i) e)))) + :pattern ((Seq_index s i)) + ))) + :pattern ((Seq_contains (Seq_drop s n) e)) + :qid |$Seq[TYPENAME]_prog.seq_contains_over_drop_index_exists|))) +(assert (forall ((s1 Seq) (s2 Seq)) (! + (= + (Seq_equal s1 s2) + (and + (= (Seq_length s1) (Seq_length s2)) + (forall ((i Int)) (! + (=> + (and (<= 0 i) (< i (Seq_length s1))) + (= (Seq_index s1 i) (Seq_index s2 i))) + :pattern ((Seq_index s1 i)) + :pattern ((Seq_index s2 i)) + )))) + :pattern ((Seq_equal s1 s2)) + :qid |$Seq[TYPENAME]_prog.extensional_seq_equality|))) +(assert (forall ((s1 Seq) (s2 Seq)) (! + (=> (Seq_equal s1 s2) (= s1 s2)) + :pattern ((Seq_equal s1 s2)) + :qid |$Seq[TYPENAME]_prog.seq_equality_identity|))) +(assert (forall ((s1 Seq) (s2 Seq) (n Int)) (! + (= + (Seq_sameuntil s1 s2 n) + (forall ((i Int)) (! + (=> (and (<= 0 i) (< i n)) (= (Seq_index s1 i) (Seq_index s2 i))) + :pattern ((Seq_index s1 i)) + :pattern ((Seq_index s2 i)) + ))) + :pattern ((Seq_sameuntil s1 s2 n)) + :qid |$Seq[TYPENAME]_prog.extensional_seq_equality_prefix|))) +(assert (forall ((s Seq) (n Int)) (! + (=> + (<= 0 n) + (ite + (<= n (Seq_length s)) + (= (Seq_length (Seq_take s n)) n) + (= (Seq_length (Seq_take s n)) (Seq_length s)))) + :pattern ((Seq_length (Seq_take s n))) + :qid |$Seq[TYPENAME]_prog.seq_length_over_take|))) +(assert (forall ((s Seq) (n Int) (i Int)) (! + (=> + (and (<= 0 i) (and (< i n) (< i (Seq_length s)))) + (= (Seq_index (Seq_take s n) i) (Seq_index s i))) + :pattern ((Seq_index (Seq_take s n) i)) + :pattern ((Seq_index s i) (Seq_take s n)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_take|))) +(assert (forall ((s Seq) (n Int)) (! + (=> + (<= 0 n) + (ite + (<= n (Seq_length s)) + (= (Seq_length (Seq_drop s n)) (- (Seq_length s) n)) + (= (Seq_length (Seq_drop s n)) 0))) + :pattern ((Seq_length (Seq_drop s n))) + :qid |$Seq[TYPENAME]_prog.seq_length_over_drop|))) +(assert (forall ((s Seq) (n Int) (i Int)) (! + (=> + (and (<= 0 n) (and (<= 0 i) (< i (- (Seq_length s) n)))) + (= (Seq_index (Seq_drop s n) i) (Seq_index s (+ i n)))) + :pattern ((Seq_index (Seq_drop s n) i)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_drop_1|))) +(assert (forall ((s Seq) (n Int) (i Int)) (! + (=> + (and (<= 0 n) (and (<= n i) (< i (Seq_length s)))) + (= (Seq_index (Seq_drop s n) (- i n)) (Seq_index s i))) + :pattern ((Seq_index s i) (Seq_drop s n)) + :qid |$Seq[TYPENAME]_prog.seq_index_over_drop_2|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME) (n Int)) (! + (=> + (and (<= 0 i) (and (< i n) (< n (Seq_length s)))) + (= (Seq_take (Seq_update s i e) n) (Seq_update (Seq_take s n) i e))) + :pattern ((Seq_take (Seq_update s i e) n)) + :qid |$Seq[TYPENAME]_prog.seq_take_over_update_1|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME) (n Int)) (! + (=> + (and (<= n i) (< i (Seq_length s))) + (= (Seq_take (Seq_update s i e) n) (Seq_take s n))) + :pattern ((Seq_take (Seq_update s i e) n)) + :qid |$Seq[TYPENAME]_prog.seq_take_over_update_2|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME) (n Int)) (! + (=> + (and (<= 0 n) (and (<= n i) (< i (Seq_length s)))) + (= (Seq_drop (Seq_update s i e) n) (Seq_update (Seq_drop s n) (- i n) e))) + :pattern ((Seq_drop (Seq_update s i e) n)) + :qid |$Seq[TYPENAME]_prog.seq_drop_over_update_1|))) +(assert (forall ((s Seq) (i Int) (e TYPENAME) (n Int)) (! + (=> + (and (<= 0 i) (and (< i n) (< n (Seq_length s)))) + (= (Seq_drop (Seq_update s i e) n) (Seq_drop s n))) + :pattern ((Seq_drop (Seq_update s i e) n)) + :qid |$Seq[TYPENAME]_prog.seq_drop_over_update_2|))) +(assert (forall ((s Seq) (e TYPENAME) (n Int)) (! + (=> + (and (<= 0 n) (<= n (Seq_length s))) + (= (Seq_drop (Seq_build s e) n) (Seq_build (Seq_drop s n) e))) + :pattern ((Seq_drop (Seq_build s e) n)) + :qid |$Seq[TYPENAME]_prog.seq_drop_over_build|))) \ No newline at end of file diff --git a/native-verifier/src/theories/Set.smt2 b/native-verifier/src/theories/Set.smt2 new file mode 100644 index 00000000000..58dc901da2a --- /dev/null +++ b/native-verifier/src/theories/Set.smt2 @@ -0,0 +1,163 @@ + +; ===== Set theory for type TYPENAME ===== + +(declare-sort Set 0) + +(declare-fun Set_in (TYPENAME Set) Bool) +(declare-fun Set_card (Set) Int) +(declare-const Set_empty Set) +(declare-fun Set_singleton (TYPENAME) Set) +(declare-fun Set_unionone (Set TYPENAME) Set) +(declare-fun Set_union (Set Set) Set) +(declare-fun Set_disjoint (Set Set) Bool) +(declare-fun Set_difference (Set Set) Set) +(declare-fun Set_intersection (Set Set) Set) +(declare-fun Set_subset (Set Set) Bool) +(declare-fun Set_equal (Set Set) Bool) + +(assert (forall ((s Set)) (! + (<= 0 (Set_card s)) + :pattern ((Set_card s)) + :qid |$Set[TYPENAME]_prog.card_non_negative|))) +(assert (forall ((e TYPENAME)) (! + (not (Set_in e (as Set_empty Set))) + :pattern ((Set_in e (as Set_empty Set))) + :qid |$Set[TYPENAME]_prog.in_empty_set|))) +(assert (forall ((s Set)) (! + (and + (= (= (Set_card s) 0) (= s (as Set_empty Set))) + (=> + (not (= (Set_card s) 0)) + (exists ((e TYPENAME)) (! + (Set_in e s) + :pattern ((Set_in e s)) + )))) + :pattern ((Set_card s)) + :qid |$Set[TYPENAME]_prog.empty_set_cardinality|))) +(assert (forall ((e TYPENAME)) (! + (Set_in e (Set_singleton e)) + :pattern ((Set_singleton e)) + :qid |$Set[TYPENAME]_prog.in_singleton_set|))) +(assert (forall ((e1 TYPENAME) (e2 TYPENAME)) (! + (= (Set_in e1 (Set_singleton e2)) (= e1 e2)) + :pattern ((Set_in e1 (Set_singleton e2))) + :qid |$Set[TYPENAME]_prog.in_singleton_set_equality|))) +(assert (forall ((e TYPENAME)) (! + (= (Set_card (Set_singleton e)) 1) + :pattern ((Set_card (Set_singleton e))) + :qid |$Set[TYPENAME]_prog.singleton_set_cardinality|))) +(assert (forall ((s Set) (e TYPENAME)) (! + (Set_in e (Set_unionone s e)) + :pattern ((Set_unionone s e)) + :qid |$Set[TYPENAME]_prog.in_unionone_same|))) +(assert (forall ((s Set) (e1 TYPENAME) (e2 TYPENAME)) (! + (= (Set_in e1 (Set_unionone s e2)) (or (= e1 e2) (Set_in e1 s))) + :pattern ((Set_in e1 (Set_unionone s e2))) + :qid |$Set[TYPENAME]_prog.in_unionone_other|))) +(assert (forall ((s Set) (e1 TYPENAME) (e2 TYPENAME)) (! + (=> (Set_in e1 s) (Set_in e1 (Set_unionone s e2))) + :pattern ((Set_in e1 s) (Set_unionone s e2)) + :qid |$Set[TYPENAME]_prog.invariance_in_unionone|))) +(assert (forall ((s Set) (e TYPENAME)) (! + (=> (Set_in e s) (= (Set_card (Set_unionone s e)) (Set_card s))) + :pattern ((Set_card (Set_unionone s e))) + :qid |$Set[TYPENAME]_prog.unionone_cardinality_invariant|))) +(assert (forall ((s Set) (e TYPENAME)) (! + (=> (not (Set_in e s)) (= (Set_card (Set_unionone s e)) (+ (Set_card s) 1))) + :pattern ((Set_card (Set_unionone s e))) + :qid |$Set[TYPENAME]_prog.unionone_cardinality_changed|))) +(assert (forall ((s1 Set) (s2 Set) (e TYPENAME)) (! + (= (Set_in e (Set_union s1 s2)) (or (Set_in e s1) (Set_in e s2))) + :pattern ((Set_in e (Set_union s1 s2))) + :qid |$Set[TYPENAME]_prog.in_union_in_one|))) +(assert (forall ((s1 Set) (s2 Set) (e TYPENAME)) (! + (=> (Set_in e s1) (Set_in e (Set_union s1 s2))) + :pattern ((Set_in e s1) (Set_union s1 s2)) + :qid |$Set[TYPENAME]_prog.in_left_in_union|))) +(assert (forall ((s1 Set) (s2 Set) (e TYPENAME)) (! + (=> (Set_in e s2) (Set_in e (Set_union s1 s2))) + :pattern ((Set_in e s2) (Set_union s1 s2)) + :qid |$Set[TYPENAME]_prog.in_right_in_union|))) +(assert (forall ((s1 Set) (s2 Set) (e TYPENAME)) (! + (= (Set_in e (Set_intersection s1 s2)) (and (Set_in e s1) (Set_in e s2))) + :pattern ((Set_in e (Set_intersection s1 s2))) + :pattern ((Set_intersection s1 s2) (Set_in e s1)) + :pattern ((Set_intersection s1 s2) (Set_in e s2)) + :qid |$Set[TYPENAME]_prog.in_intersection_in_both|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= (Set_union s1 (Set_union s1 s2)) (Set_union s1 s2)) + :pattern ((Set_union s1 (Set_union s1 s2))) + :qid |$Set[TYPENAME]_prog.union_left_idempotency|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= (Set_union (Set_union s1 s2) s2) (Set_union s1 s2)) + :pattern ((Set_union (Set_union s1 s2) s2)) + :qid |$Set[TYPENAME]_prog.union_right_idempotency|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= (Set_intersection s1 (Set_intersection s1 s2)) (Set_intersection s1 s2)) + :pattern ((Set_intersection s1 (Set_intersection s1 s2))) + :qid |$Set[TYPENAME]_prog.intersection_left_idempotency|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= (Set_intersection (Set_intersection s1 s2) s2) (Set_intersection s1 s2)) + :pattern ((Set_intersection (Set_intersection s1 s2) s2)) + :qid |$Set[TYPENAME]_prog.intersection_right_idempotency|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= + (+ (Set_card (Set_union s1 s2)) (Set_card (Set_intersection s1 s2))) + (+ (Set_card s1) (Set_card s2))) + :pattern ((Set_card (Set_union s1 s2))) + :pattern ((Set_card (Set_intersection s1 s2))) + :qid |$Set[TYPENAME]_prog.cardinality_sums|))) +(assert (forall ((s1 Set) (s2 Set) (e TYPENAME)) (! + (= (Set_in e (Set_difference s1 s2)) (and (Set_in e s1) (not (Set_in e s2)))) + :pattern ((Set_in e (Set_difference s1 s2))) + :qid |$Set[TYPENAME]_prog.in_difference|))) +(assert (forall ((s1 Set) (s2 Set) (e TYPENAME)) (! + (=> (Set_in e s2) (not (Set_in e (Set_difference s1 s2)))) + :pattern ((Set_difference s1 s2) (Set_in e s2)) + :qid |$Set[TYPENAME]_prog.not_in_difference|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= + (Set_subset s1 s2) + (forall ((e TYPENAME)) (! + (=> (Set_in e s1) (Set_in e s2)) + :pattern ((Set_in e s1)) + :pattern ((Set_in e s2)) + ))) + :pattern ((Set_subset s1 s2)) + :qid |$Set[TYPENAME]_prog.subset_definition|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= + (Set_equal s1 s2) + (forall ((e TYPENAME)) (! + (= (Set_in e s1) (Set_in e s2)) + :pattern ((Set_in e s1)) + :pattern ((Set_in e s2)) + ))) + :pattern ((Set_equal s1 s2)) + :qid |$Set[TYPENAME]_prog.equality_definition|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (=> (Set_equal s1 s2) (= s1 s2)) + :pattern ((Set_equal s1 s2)) + :qid |$Set[TYPENAME]_prog.native_equality|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (= + (Set_disjoint s1 s2) + (forall ((e TYPENAME)) (! + (or (not (Set_in e s1)) (not (Set_in e s2))) + :pattern ((Set_in e s1)) + :pattern ((Set_in e s2)) + ))) + :pattern ((Set_disjoint s1 s2)) + :qid |$Set[TYPENAME]_prog.disjointness_definition|))) +(assert (forall ((s1 Set) (s2 Set)) (! + (and + (= + (+ + (+ (Set_card (Set_difference s1 s2)) (Set_card (Set_difference s2 s1))) + (Set_card (Set_intersection s1 s2))) + (Set_card (Set_union s1 s2))) + (= + (Set_card (Set_difference s1 s2)) + (- (Set_card s1) (Set_card (Set_intersection s1 s2))))) + :pattern ((Set_card (Set_difference s1 s2))) + :qid |$Set[TYPENAME]_prog.cardinality_difference|))) diff --git a/native-verifier/src/theory_provider.rs b/native-verifier/src/theory_provider.rs new file mode 100644 index 00000000000..2423d8818a5 --- /dev/null +++ b/native-verifier/src/theory_provider.rs @@ -0,0 +1,36 @@ +pub struct ContainerTheoryProvider<'a> { + template: &'a str, +} + +impl ContainerTheoryProvider<'_> { + pub fn new(theory: &str) -> Self { + let template = match theory { + "Set" => include_str!("theories/Set.smt2"), + "MultiSet" => include_str!("theories/MultiSet.smt2"), + "Seq" => include_str!("theories/Seq.smt2"), + _ => panic!("Theory {} not supported", theory), + }; + Self { template } + } + + pub fn get_theory(&self, element_type: &str) -> String { + self.template.replace("TYPENAME", element_type) + } +} + +pub struct MapTheoryProvider<'a> { + template: &'a str, +} + +impl MapTheoryProvider<'_> { + pub fn new() -> Self { + let template = include_str!("theories/Map.smt2"); + Self { template } + } + + pub fn get_theory(&self, key_type: &str, value_type: &str) -> String { + self.template + .replace("KEYTYPE", key_type) + .replace("VALUETYPE", value_type) + } +} diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs new file mode 100644 index 00000000000..60d83dfe09f --- /dev/null +++ b/native-verifier/src/verifier.rs @@ -0,0 +1,112 @@ +// © 2022, Jakub Janaszkiewicz +// +// 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 crate::smt_lib::*; +use backend_common::{VerificationError, VerificationResult}; +use core::panic; +use log::{self, debug, info, warn}; +use prusti_common::vir::program::Program; +use prusti_utils::{report::log::report_with_writer, run_timed}; +use std::{ + error::Error, + io::Write, + process::{Command, Stdio}, +}; + +pub struct Verifier { + z3_exe: String, +} + +impl Verifier { + pub fn new(z3_exe: String) -> Self { + Self { z3_exe } + } + + pub fn verify(&mut self, program: &Program) -> VerificationResult { + let Program::Low(program) = program else { + panic!("Lithium backend only supports low programs"); + }; + + run_timed!("Translation to SMT-LIB", debug, + let mut smt = SMTLib::new(); + program.build_smt(&mut smt); + let smt_program = smt.to_string(); + + report_with_writer( + "smt", + format!("lithium_{}.smt2", program.name), + |writer| { + writer.write_all(smt_program.as_bytes()).unwrap(); + }, + ); + ); + + run_timed!("SMT verification", debug, + let result: Result> = try { + let mut child = Command::new(self.z3_exe.clone()) + .args(["-smt2", "-in"]) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + + child.stdin + .as_mut() + .ok_or("Child process stdin has not been captured!")? + .write_all(smt_program.as_bytes())?; + + let output = child.wait_with_output()?; + + if output.status.success() { + let raw_output = String::from_utf8(output.stdout)?; + if raw_output.lines().any(|line| line == "sat" || line == "unknown") { + Err(raw_output)? + } else { + raw_output + } + } else { + let err = String::from_utf8(output.stdout)?; + Err(err)? + } + }; + + let is_failure = match result { + Ok(output) => { + info!("SMT verification output:\n{}", output); + !output.contains("unsat") + } + Err(err) => { + warn!("SMT verification errors:\n{}", err.to_string()); + true + } + }; + ); + + if is_failure { + let mut errors: Vec = vec![]; + + let error_full_id = "verification.error".to_string(); + let pos_id = None; + let offending_pos_id = None; + let reason_pos_id = None; + let message = "At least one assertion was 'sat' or 'unknown'".to_string(); + let counterexample = None; + + errors.push(VerificationError::new( + error_full_id, + pos_id, + offending_pos_id, + reason_pos_id, + message, + counterexample, + )); + + VerificationResult::Failure(errors) + } else { + VerificationResult::Success + } + } +} diff --git a/prusti-common/Cargo.toml b/prusti-common/Cargo.toml index a27a0f9dc2e..6d5f99e7dca 100644 --- a/prusti-common/Cargo.toml +++ b/prusti-common/Cargo.toml @@ -10,6 +10,7 @@ doctest = false # we have no doc tests [dependencies] prusti-utils = { path = "../prusti-utils" } viper = { path = "../viper" } +backend-common = { path = "../backend-common" } vir = { path = "../vir" } log = { version = "0.4", features = ["release_max_level_info"] } config = "0.13" diff --git a/prusti-common/src/vir/program_normalization.rs b/prusti-common/src/vir/program_normalization.rs index 5420e9c3635..d5425cb40b7 100644 --- a/prusti-common/src/vir/program_normalization.rs +++ b/prusti-common/src/vir/program_normalization.rs @@ -5,9 +5,9 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::vir::{program::Program, Position}; +use backend_common::VerificationResult; use log::{debug, trace}; use rustc_hash::{FxHashMap, FxHashSet}; -use viper::VerificationResult; pub enum NormalizationInfo { LegacyProgram { original_position_ids: Vec }, diff --git a/prusti-server/Cargo.toml b/prusti-server/Cargo.toml index 6fb906a0a10..851f728d0fb 100644 --- a/prusti-server/Cargo.toml +++ b/prusti-server/Cargo.toml @@ -21,6 +21,8 @@ log = { version = "0.4", features = ["release_max_level_info"] } viper = { path = "../viper" } prusti-common = { path = "../prusti-common" } prusti-utils = { path = "../prusti-utils" } +native-verifier = { path = "../native-verifier" } +backend-common = { path = "../backend-common" } tracing = { path = "../tracing" } env_logger = "0.10" clap = { version = "4.0", features = ["derive"] } diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 5a36ad6c399..ecb3b81297e 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -8,6 +8,7 @@ use viper::{VerificationContext, VerificationResult}; pub enum Backend<'a> { Viper(viper::Verifier<'a>, &'a VerificationContext<'a>), + Lithium(native_verifier::Verifier), } impl<'a> Backend<'a> { @@ -36,6 +37,7 @@ impl<'a> Backend<'a> { viper.verify(viper_program) }) } + Backend::Lithium(lithium) => lithium.verify(program), } } } diff --git a/prusti-server/src/client.rs b/prusti-server/src/client.rs index d1b53ea63bf..5654f299bed 100644 --- a/prusti-server/src/client.rs +++ b/prusti-server/src/client.rs @@ -8,7 +8,7 @@ use crate::VerificationRequest; use prusti_common::config; use reqwest::Client; use url::{ParseError, Url}; -use viper::VerificationResult; +use backend_common::VerificationResult; pub struct PrustiClient { client: Client, diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 446407c5087..3ced74483b3 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -5,6 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{Backend, VerificationRequest, ViperBackendConfig}; +use backend_common::VerificationResult; use log::info; use once_cell::sync::Lazy; use prusti_common::{ @@ -14,16 +15,14 @@ use prusti_common::{ Stopwatch, }; use std::{fs::create_dir_all, path::PathBuf}; -use viper::{ - smt_manager::SmtManager, Cache, VerificationBackend, VerificationContext, VerificationResult, -}; +use viper::{smt_manager::SmtManager, Cache, VerificationBackend, VerificationContext}; #[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>>, mut request: VerificationRequest, cache: impl Cache, -) -> viper::VerificationResult { +) -> VerificationResult { let ast_utils = verification_context.new_ast_utils(); // Only for testing: Check that the normalization is reversible. @@ -78,7 +77,7 @@ pub fn process_verification_request<'v, 't: 'v>( let _ = build_or_dump_viper_program(); }); } - return viper::VerificationResult::Success; + return VerificationResult::Success; } // Early return in case of cache hit diff --git a/prusti-utils/src/utils/mod.rs b/prusti-utils/src/utils/mod.rs index d64afd98f06..19174783b01 100644 --- a/prusti-utils/src/utils/mod.rs +++ b/prusti-utils/src/utils/mod.rs @@ -6,3 +6,4 @@ pub mod identifiers; pub mod to_string; +pub mod run_timed; diff --git a/prusti-utils/src/utils/run_timed.rs b/prusti-utils/src/utils/run_timed.rs new file mode 100644 index 00000000000..08ab869de60 --- /dev/null +++ b/prusti-utils/src/utils/run_timed.rs @@ -0,0 +1,20 @@ +// copied from prusti-common to avoid illogical dependency. +/// Runs statements on the same level as the macro call, timing and logging (info-level by default) how long it took. +#[macro_export] +macro_rules! run_timed { + ($desc:expr, $($s:stmt;)*) => { + run_timed!($desc, info, $($s;)*); + }; + ($desc:expr, $log_level:ident, $($s:stmt;)*) => { + $log_level!("Starting: {}", $desc); + let start = ::std::time::Instant::now(); + $($s)* + let duration = start.elapsed(); + $log_level!( + "Completed: {} ({}.{} seconds)", + $desc, + duration.as_secs(), + duration.subsec_millis() / 10 + ); + }; +} diff --git a/prusti-viper/Cargo.toml b/prusti-viper/Cargo.toml index 9606064ad01..0f176dd2101 100644 --- a/prusti-viper/Cargo.toml +++ b/prusti-viper/Cargo.toml @@ -15,6 +15,7 @@ log = { version = "0.4", features = ["release_max_level_info"] } viper = { path = "../viper" } prusti-interface = { path = "../prusti-interface" } prusti-common = { path = "../prusti-common" } +backend-common = { path = "../backend-common" } prusti-server = { path = "../prusti-server" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } vir-crate = { package = "vir", path = "../vir" } diff --git a/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs b/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs index 66fff62fcf0..85517bab984 100644 --- a/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs +++ b/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs @@ -4,6 +4,7 @@ use crate::encoder::{ places::{Local, LocalVariableManager}, Encoder, }; +use backend_common::{DomainEntry, ModelEntry, SiliconCounterexample}; use prusti_interface::{ data::ProcedureDefId, environment::{body::MirBody, EnvQuery}, @@ -17,7 +18,6 @@ use prusti_rustc_interface::{ }; use rustc_hash::FxHashMap; use std::iter; -use viper::silicon_counterexample::*; use DiscriminantsStateInterface; pub fn backtranslate( diff --git a/prusti-viper/src/encoder/counterexamples/counterexample_translation_refactored.rs b/prusti-viper/src/encoder/counterexamples/counterexample_translation_refactored.rs index 159cf1a39af..4f74b496265 100644 --- a/prusti-viper/src/encoder/counterexamples/counterexample_translation_refactored.rs +++ b/prusti-viper/src/encoder/counterexamples/counterexample_translation_refactored.rs @@ -6,6 +6,7 @@ use crate::encoder::{ places::{Local, LocalVariableManager}, Encoder, }; +use backend_common::{DomainEntry, ModelEntry, SiliconCounterexample}; use prusti_common::config; use prusti_interface::data::ProcedureDefId; use prusti_rustc_interface::{ @@ -20,7 +21,6 @@ use prusti_rustc_interface::{ }; use rustc_hash::FxHashMap; use std::{iter, vec}; -use viper::silicon_counterexample::*; pub fn backtranslate( encoder: &Encoder, diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 4f350f644c9..722b5747571 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -4,14 +4,12 @@ // 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 std::fmt::Debug; - use super::PositionManager; +use backend_common::VerificationError; use log::debug; use prusti_interface::{data::ProcedureDefId, PrustiError}; use prusti_rustc_interface::{errors::MultiSpan, span::source_map::SourceMap}; use rustc_hash::FxHashMap; -use viper::VerificationError; use vir_crate::polymorphic::Position; const ASSERTION_TIMEOUT_HELP_MESSAGE: &str = diff --git a/prusti-viper/src/verifier.rs b/prusti-viper/src/verifier.rs index 019abb0ed40..7360b6bfa1a 100644 --- a/prusti-viper/src/verifier.rs +++ b/prusti-viper/src/verifier.rs @@ -103,18 +103,18 @@ impl<'v, 'tcx> Verifier<'v, 'tcx> { let mut java_exceptions: Vec<_> = vec![]; for (method_name, result) in verification_results.into_iter() { match result { - viper::VerificationResult::Success => {} - viper::VerificationResult::ConsistencyErrors(errors) => { + backend_common::VerificationResult::Success => {} + backend_common::VerificationResult::ConsistencyErrors(errors) => { for error in errors.into_iter() { consistency_errors.push((method_name.clone(), error)); } } - viper::VerificationResult::Failure(errors) => { + backend_common::VerificationResult::Failure(errors) => { for error in errors.into_iter() { verification_errors.push((method_name.clone(), error)); } } - viper::VerificationResult::JavaException(exception) => { + backend_common::VerificationResult::JavaException(exception) => { java_exceptions.push((method_name, exception)); } } @@ -211,10 +211,9 @@ impl<'v, 'tcx> Verifier<'v, 'tcx> { /// Verify a list of programs. /// Returns a list of (program_name, verification_result) tuples. -fn verify_programs( - env: &Environment, - programs: Vec, -) -> Vec<(String, viper::VerificationResult)> { +fn verify_programs(env: &Environment, programs: Vec) + -> Vec<(String, backend_common::VerificationResult)> +{ let source_path = env.name.source_path(); let rust_program_name = source_path .file_name() diff --git a/viper/Cargo.toml b/viper/Cargo.toml index 0715e82be61..0e6a3fd1cdd 100644 --- a/viper/Cargo.toml +++ b/viper/Cargo.toml @@ -11,6 +11,7 @@ license = "MPL-2.0" log = { version = "0.4", features = ["release_max_level_debug"] } error-chain = "0.12" viper-sys = { path = "../viper-sys" } +backend-common = { path = "../backend-common" } jni = { version = "0.20", features = ["invocation"] } uuid = { version = "1.0", features = ["v4"] } serde = { version = "1.0", features = ["derive"] } @@ -19,6 +20,7 @@ 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"} +prusti-utils = { path = "../prusti-utils"} tracing = { path = "../tracing" } [dev-dependencies] diff --git a/viper/src/ast_utils.rs b/viper/src/ast_utils.rs index 55120e17539..6bfa8b33ebb 100644 --- a/viper/src/ast_utils.rs +++ b/viper/src/ast_utils.rs @@ -4,7 +4,8 @@ // 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::{ast_factory::Program, jni_utils::JniUtils, JavaException}; +use crate::{ast_factory::Program, jni_utils::JniUtils}; +use backend_common::JavaException; use jni::{objects::JObject, JNIEnv}; use viper_sys::wrappers::viper::*; diff --git a/viper/src/cache.rs b/viper/src/cache.rs index 4b78dc3d1fc..3a02145e932 100644 --- a/viper/src/cache.rs +++ b/viper/src/cache.rs @@ -6,7 +6,7 @@ use log::{error, info, warn}; -use crate::verification_result::VerificationResult; +use backend_common::VerificationResult; use rustc_hash::FxHashMap; use std::{ fs, io, diff --git a/viper/src/jni_utils.rs b/viper/src/jni_utils.rs index f99623611b1..752c436fb28 100644 --- a/viper/src/jni_utils.rs +++ b/viper/src/jni_utils.rs @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::java_exception::JavaException; +use backend_common::JavaException; use jni::{ errors::{Error, Result as JniResult}, objects::{JObject, JString}, diff --git a/viper/src/lib.rs b/viper/src/lib.rs index 747f7fc1261..ebc70f58049 100644 --- a/viper/src/lib.rs +++ b/viper/src/lib.rs @@ -14,17 +14,14 @@ mod jni_utils; #[macro_use] pub mod utils; mod cache; -mod java_exception; -pub mod silicon_counterexample; pub mod smt_manager; mod verification_backend; +pub mod silicon_counterexample; mod verification_context; -mod verification_result; mod verifier; mod viper; pub use crate::{ - ast_factory::*, ast_utils::*, cache::*, java_exception::*, silicon_counterexample::*, - verification_backend::*, verification_context::*, verification_result::*, verifier::*, - viper::*, + ast_factory::*, ast_utils::*, cache::*, silicon_counterexample::*, verification_backend::*, + verification_context::*, verifier::*, viper::*, }; diff --git a/viper/src/silicon_counterexample.rs b/viper/src/silicon_counterexample.rs index 0ad438a963e..5e1708bc33f 100644 --- a/viper/src/silicon_counterexample.rs +++ b/viper/src/silicon_counterexample.rs @@ -1,112 +1,18 @@ use rustc_hash::FxHashMap; use crate::jni_utils::JniUtils; +use backend_common::{ + DomainEntry, Domains, FunctionEntry, Functions, Model, ModelEntry, SiliconCounterexample, +}; use jni::{objects::JObject, JNIEnv}; use viper_sys::wrappers::{scala, viper::silicon}; -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct SiliconCounterexample { - //pub heap: Heap, - //pub old_heaps: FxHashMap, - pub model: Model, - pub functions: Functions, - pub domains: Domains, - pub old_models: FxHashMap, - // label_order because HashMaps do not guarantee order of elements - // whereas the Map used in scala does guarantee it - pub label_order: Vec, -} - -impl SiliconCounterexample { - pub fn new<'a>( - env: &'a JNIEnv<'a>, - jni: JniUtils<'a>, - counterexample: JObject<'a>, - ) -> SiliconCounterexample { - unwrap_counterexample(env, jni, counterexample) - } -} - -// Heap Definitions -/* -this stuff might be useful at a later stage, when we can actually -trigger unfolding of certain predicates, but for now there is -nothing to be used stored in the heap -*/ -/* -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Heap { - pub entries: Vec, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum HeapEntry { - FieldEntry { - recv: ModelEntry, - //perm & sort omitted - field: String, - entry: ModelEntry, - }, - PredicateEntry { - name: String, - args: Vec, - }, -} -*/ - -// Model Definitions -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Model { - pub entries: FxHashMap, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum ModelEntry { - LitInt(String), - LitFloat(String), - LitBool(bool), - LitPerm(String), - Ref(String, FxHashMap), - NullRef(String), - RecursiveRef(String), - Var(String), - Seq(String, Vec), - Other(String, String), - DomainValue(String, String), - UnprocessedModel, //used for Silicon's Snap type -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Functions { - pub entries: FxHashMap, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct FunctionEntry { - pub options: Vec<(Vec>, Option)>, - pub default: Option, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Domains { - pub entries: FxHashMap, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct DomainEntry { - pub functions: Functions, -} - -impl FunctionEntry { - /// Given a vec of params it finds the correct entry in a function. - pub fn get_function_value(&self, params: &Vec>) -> &Option { - for option in &self.options { - if &option.0 == params { - return &option.1; - } - } - &None - } +pub fn silicon_counterexample<'a>( + env: &'a JNIEnv<'a>, + jni: JniUtils<'a>, + counterexample: JObject<'a>, +) -> SiliconCounterexample { + unwrap_counterexample(env, jni, counterexample) } // methods unwrapping scala converter to newly defined structures diff --git a/viper/src/utils.rs b/viper/src/utils.rs index e532573f9e8..71891b52654 100644 --- a/viper/src/utils.rs +++ b/viper/src/utils.rs @@ -30,24 +30,3 @@ where self.fold(init, |acc, conjunct| ast.and(acc, conjunct)) } } - -// copied from prusti-common to avoid illogical dependency. -/// Runs statements on the same level as the macro call, timing and logging (info-level by default) how long it took. -#[macro_export] -macro_rules! run_timed { - ($desc:expr, $($s:stmt;)*) => { - run_timed!($desc, info, $($s;)*); - }; - ($desc:expr, $log_level:ident, $($s:stmt;)*) => { - $log_level!("Starting: {}", $desc); - let start = ::std::time::Instant::now(); - $($s)* - let duration = start.elapsed(); - $log_level!( - "Completed: {} ({}.{} seconds)", - $desc, - duration.as_secs(), - duration.subsec_millis() / 10 - ); - }; -} diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index 93f7b587d50..3fa7c18cf95 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -5,16 +5,13 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{ - ast_factory::*, - ast_utils::AstUtils, - jni_utils::JniUtils, - silicon_counterexample::SiliconCounterexample, - smt_manager::SmtManager, - verification_backend::VerificationBackend, - verification_result::{VerificationError, VerificationResult}, + ast_factory::*, ast_utils::AstUtils, jni_utils::JniUtils, silicon_counterexample, + smt_manager::SmtManager, verification_backend::VerificationBackend, }; -use jni::{objects::JObject, JNIEnv}; +use backend_common::{SiliconCounterexample, VerificationError, VerificationResult}; +use jni::{errors::Result, objects::JObject, JNIEnv}; use log::{debug, error, info}; +use prusti_utils::run_timed; use std::path::PathBuf; use viper_sys::wrappers::{scala, viper::*}; @@ -222,7 +219,7 @@ impl<'a> Verifier<'a> { "viper/silicon/interfaces/SiliconMappedCounterexample", ) { // only mapped counterexamples are processed - Some(SiliconCounterexample::new( + Some(silicon_counterexample( self.env, self.jni, original_counterexample, From 6821cffd0280efaa95b8dec04c5875f2f1d0274f Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 21 Feb 2023 18:57:56 +0100 Subject: [PATCH 02/54] Add Lithium to VerificationBackend enum --- prusti-server/src/process_verification.rs | 7 ++++++- prusti-server/src/verification_request.rs | 3 +++ prusti-server/tests/basic_requests.rs | 2 +- viper/src/verification_backend.rs | 3 +++ viper/tests/invalid_programs.rs | 1 + viper/tests/simple_programs.rs | 1 + 6 files changed, 15 insertions(+), 2 deletions(-) diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 3ced74483b3..11ead7501a1 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -111,7 +111,11 @@ pub fn process_verification_request<'v, 't: 'v>( ), verification_context, ), - }; + VerificationBackend::Lithium => { + Backend::Lithium(native_verifier::Verifier::new(config::smt_solver_path())) + // TODO: Wrapper support + } + }; stopwatch.start_next("backend verification"); let mut result = backend.verify(&request.program); @@ -169,6 +173,7 @@ fn new_viper_verifier<'v, 't: 'v>( format!("/logPrefix {log_dir_str}"), //"--print".to_string(), "./log/boogie_program/program.bpl".to_string(), ]), + VerificationBackend::Lithium => unreachable!("Lithium is not a Viper backend"), } } else { report_path = None; diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index f5b0b75919c..4dda17c32c2 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -77,6 +77,9 @@ impl ViperBackendConfig { VerificationBackend::Carbon => { verifier_args.extend(vec!["--disableAllocEncoding".to_string()]); } + VerificationBackend::Lithium => { + // TODO: add lithium-specific arguments + } } Self { backend, diff --git a/prusti-server/tests/basic_requests.rs b/prusti-server/tests/basic_requests.rs index edb7aca6768..f63a87e5d2b 100644 --- a/prusti-server/tests/basic_requests.rs +++ b/prusti-server/tests/basic_requests.rs @@ -1,10 +1,10 @@ +use backend_common::VerificationResult; use lazy_static::lazy_static; use prusti_common::vir::*; use prusti_server::{ spawn_server_thread, tokio::runtime::Builder, PrustiClient, VerificationRequest, ViperBackendConfig, }; -use viper::VerificationResult; lazy_static! { // only start the jvm & server once diff --git a/viper/src/verification_backend.rs b/viper/src/verification_backend.rs index 0746a73b29d..086c7311a88 100644 --- a/viper/src/verification_backend.rs +++ b/viper/src/verification_backend.rs @@ -10,6 +10,7 @@ use std::fmt; pub enum VerificationBackend { Silicon, Carbon, + Lithium, } #[derive(Clone, Debug)] @@ -31,6 +32,7 @@ impl std::str::FromStr for VerificationBackend { match backend.to_lowercase().as_str() { "silicon" => Ok(VerificationBackend::Silicon), "carbon" => Ok(VerificationBackend::Carbon), + "lithium" => Ok(VerificationBackend::Lithium), _ => Err(UknownBackendError(backend.to_string())), } } @@ -41,6 +43,7 @@ impl fmt::Display for VerificationBackend { match self { VerificationBackend::Silicon => write!(f, "Silicon"), VerificationBackend::Carbon => write!(f, "Carbon"), + VerificationBackend::Lithium => write!(f, "Lithium"), } } } diff --git a/viper/tests/invalid_programs.rs b/viper/tests/invalid_programs.rs index 13fb3caa2ad..707df652b08 100644 --- a/viper/tests/invalid_programs.rs +++ b/viper/tests/invalid_programs.rs @@ -1,3 +1,4 @@ +use backend_common::VerificationResult; use std::sync::Once; use viper::*; diff --git a/viper/tests/simple_programs.rs b/viper/tests/simple_programs.rs index 798f284c63a..7514453a408 100644 --- a/viper/tests/simple_programs.rs +++ b/viper/tests/simple_programs.rs @@ -1,3 +1,4 @@ +use backend_common::VerificationResult; use std::sync::Once; use viper::*; From eb4c416255ec17186ac14237746fc33d0e4f5c9d Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 22 Feb 2023 00:30:34 +0100 Subject: [PATCH 03/54] Fix simple branching (if-statements in low vir) --- native-verifier/src/smt_lib.rs | 124 +++++++++++++----- .../tests/verify/fail/native/trivial.rs | 5 + .../tests/verify/pass/native/trivial.rs | 5 + 3 files changed, 103 insertions(+), 31 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/native/trivial.rs create mode 100644 prusti-tests/tests/verify/pass/native/trivial.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 939da8cc71c..2731dfe2549 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -52,6 +52,47 @@ impl SMTLib { fn add_code(&mut self, text: String) { self.code.push(text); } + fn emit(&mut self, predicate: FolStatement) { + match predicate { + FolStatement::Comment(comment) => self.add_code(format!("; {}", comment)), + FolStatement::Assume(expression) => { + // assert predicate + self.add_code(format!("(assert {})", expression.to_smt())); + } + FolStatement::Assert(expression) => { + // check if just asserting true + // TODO: Optimization module + if let Expression::Constant(Constant { + ty: Type::Bool, + value: ConstantValue::Bool(true), + .. + }) = expression + { + return; + } + + // negate predicate + let position = expression.position(); + let negated = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(expression.clone()), + position, + }); + + // assert negated predicate + self.add_code("(push)".to_string()); + self.add_code(format!("(assert {})", negated.to_smt())); + self.add_code("(check-sat)".to_string()); + self.add_code("(pop)".to_string()); + + // assume predicate afterwards + self.add_code(format!( + "(assert {}) ; assumed after assert", + expression.to_smt() + )); + } + } + } fn follow(&mut self, block_label: &String, precond: Option<&Expression>) { let block = self .blocks @@ -63,8 +104,12 @@ impl SMTLib { return; } + let is_branch = precond.is_some(); + self.add_code(format!("; Basic block: {}", block.label)); - self.add_code("(push)".to_string()); + if is_branch { + self.add_code("(push)".to_string()); + } // assume precond if any if let Some(precond) = precond { @@ -75,42 +120,60 @@ impl SMTLib { // verify body let predicates = vir_to_fol(&block.statements, &self.methods); - for predicate in predicates.into_iter() { - match predicate { - FolStatement::Comment(comment) => self.add_code(format!("; {}", comment)), - FolStatement::Assume(expression) => { - // assert predicate - self.add_code(format!("(assert {})", expression.to_smt())); - } - FolStatement::Assert(expression) => { - // negate predicate - let position = expression.position(); - let negated = Expression::UnaryOp(UnaryOp { - op_kind: UnaryOpKind::Not, - argument: Box::new(expression), - position, - }); - // assert negated predicate - self.add_code("(push)".to_string()); - self.add_code(format!("(assert {})", negated.to_smt())); - self.add_code("(check-sat)".to_string()); - self.add_code("(pop)".to_string()); - } - } - } + predicates.into_iter().for_each(|predicate| { + self.emit(predicate); + }); // process successor match &block.successor { Successor::Goto(label) => { self.follow(&label.name, None); } - Successor::GotoSwitch(mapping) => mapping.iter().for_each(|(expr, label)| { - self.follow(&label.name, Some(expr)); - }), + Successor::GotoSwitch(mapping) => { + if mapping.len() == 2 { + // TODO: What is wrong with if-statements generating a "true" branch instead of condition and negation? + let ((e1, l1), (e2, l2)) = (&mapping[0], &mapping[1]); + + let neg1 = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(e1.clone()), + position: e1.position(), + }); + + let neg2 = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(e2.clone()), + position: e2.position(), + }); + + let e1_and_neg2 = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::And, + left: Box::new(e1.clone()), + right: Box::new(neg2.clone()), + position: e1.position(), + }); + + let neg1_and_e2 = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::And, + left: Box::new(neg1.clone()), + right: Box::new(e2.clone()), + position: e1.position(), + }); + + self.follow(&l1.name, Some(&e1_and_neg2)); + self.follow(&l2.name, Some(&neg1_and_e2)); + } else { + mapping.iter().for_each(|(expr, label)| { + self.follow(&label.name, Some(expr)); + }) + } + } Successor::Return => {} } - self.add_code(format!("(pop); End basic block: {}", block.label)); + if is_branch { + self.add_code(format!("(pop); End basic block: {}", block.label)); + } } } @@ -179,11 +242,10 @@ impl ToString for SMTLib { result.push_str(&main); - // strip all lines containing "$marker" - // TODO: SSO form for marker variables? result .lines() - .filter(|line| !line.contains("$marker")) + .filter(|&line| !line.contains("$marker")) // TODO: SSO form for marker variables? + .filter(|&line| line != "(assert true)") // TODO: Optimization module .collect::>() .join("\n") } diff --git a/prusti-tests/tests/verify/fail/native/trivial.rs b/prusti-tests/tests/verify/fail/native/trivial.rs new file mode 100644 index 00000000000..a99e53bd767 --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/trivial.rs @@ -0,0 +1,5 @@ +// compile-flags: -Pviper_backend=Lithium + +fn main() { + assert!(false); +} diff --git a/prusti-tests/tests/verify/pass/native/trivial.rs b/prusti-tests/tests/verify/pass/native/trivial.rs new file mode 100644 index 00000000000..77947467bbd --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/trivial.rs @@ -0,0 +1,5 @@ +// compile-flags: -Pviper_backend=Lithium + +fn main() { + assert!(true); +} From daceb781ccf78b187dede7ab3d7859e338d095d4 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 22 Feb 2023 02:05:07 +0100 Subject: [PATCH 04/54] Wonky way of reporting offending positions --- native-verifier/src/smt_lib.rs | 1 + native-verifier/src/verifier.rs | 69 +++++++++---------- .../tests/verify/fail/native/trivial.rs | 2 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 2731dfe2549..c5c5ac5c704 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -82,6 +82,7 @@ impl SMTLib { // assert negated predicate self.add_code("(push)".to_string()); self.add_code(format!("(assert {})", negated.to_smt())); + self.add_code(format!("(echo \"position: {}\")", expression.position().id)); self.add_code("(check-sat)".to_string()); self.add_code("(pop)".to_string()); diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 60d83dfe09f..f5e490a6353 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -7,7 +7,7 @@ use crate::smt_lib::*; use backend_common::{VerificationError, VerificationResult}; use core::panic; -use log::{self, debug, info, warn}; +use log::{self, debug}; use prusti_common::vir::program::Program; use prusti_utils::{report::log::report_with_writer, run_timed}; use std::{ @@ -61,52 +61,51 @@ impl Verifier { let output = child.wait_with_output()?; if output.status.success() { - let raw_output = String::from_utf8(output.stdout)?; - if raw_output.lines().any(|line| line == "sat" || line == "unknown") { - Err(raw_output)? - } else { - raw_output - } + String::from_utf8(output.stdout)? } else { let err = String::from_utf8(output.stdout)?; Err(err)? } }; - - let is_failure = match result { - Ok(output) => { - info!("SMT verification output:\n{}", output); - !output.contains("unsat") - } - Err(err) => { - warn!("SMT verification errors:\n{}", err.to_string()); - true - } - }; ); - if is_failure { - let mut errors: Vec = vec![]; - - let error_full_id = "verification.error".to_string(); - let pos_id = None; - let offending_pos_id = None; - let reason_pos_id = None; - let message = "At least one assertion was 'sat' or 'unknown'".to_string(); - let counterexample = None; + let mut errors = vec![]; + // TODO: Actual unexpected error handling + if let Err(err) = result { errors.push(VerificationError::new( - error_full_id, - pos_id, - offending_pos_id, - reason_pos_id, - message, - counterexample, + "assert.failed:assertion.false".to_string(), + Some("0".to_string()), + Some("0".to_string()), + Some("0".to_string()), + format!("Z3 failed with error: {}", err), + None, )); - VerificationResult::Failure(errors) - } else { + return VerificationResult::Failure(errors); + } + + let mut last_pos: i32 = 0; + for line in result.unwrap().lines() { + if line.starts_with("position: ") { + let position_id = line.split("position: ").nth(1).unwrap(); + last_pos = position_id.parse().unwrap(); + } else if line == "sat" || line == "unknown" { + errors.push(VerificationError::new( + "assert.failed:assertion.false".to_string(), + Some("0".to_string()), + Some(last_pos.to_string()), + Some("0".to_string()), + format!("Assert might fail. Assertion might not hold."), + None, + )); + } + } + + if errors.is_empty() { VerificationResult::Success + } else { + VerificationResult::Failure(errors) } } } diff --git a/prusti-tests/tests/verify/fail/native/trivial.rs b/prusti-tests/tests/verify/fail/native/trivial.rs index a99e53bd767..7b590083b85 100644 --- a/prusti-tests/tests/verify/fail/native/trivial.rs +++ b/prusti-tests/tests/verify/fail/native/trivial.rs @@ -1,5 +1,5 @@ // compile-flags: -Pviper_backend=Lithium fn main() { - assert!(false); + assert!(false); //~ ERROR might not hold } From 35843ff01e3f3138549c48878a1488178b3aec44 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 22 Feb 2023 03:16:44 +0100 Subject: [PATCH 05/54] Fix branching assumptions for switchInt --- native-verifier/src/smt_lib.rs | 59 ++++++++++--------- .../tests/verify/fail/native/assert-seq.rs | 6 ++ .../tests/verify/fail/native/if-else-1.rs | 10 ++++ .../tests/verify/fail/native/if-else-2.rs | 10 ++++ 4 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/native/assert-seq.rs create mode 100644 prusti-tests/tests/verify/fail/native/if-else-1.rs create mode 100644 prusti-tests/tests/verify/fail/native/if-else-2.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index c5c5ac5c704..f82889070cb 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -135,34 +135,39 @@ impl SMTLib { // TODO: What is wrong with if-statements generating a "true" branch instead of condition and negation? let ((e1, l1), (e2, l2)) = (&mapping[0], &mapping[1]); - let neg1 = Expression::UnaryOp(UnaryOp { - op_kind: UnaryOpKind::Not, - argument: Box::new(e1.clone()), - position: e1.position(), - }); - - let neg2 = Expression::UnaryOp(UnaryOp { - op_kind: UnaryOpKind::Not, - argument: Box::new(e2.clone()), - position: e2.position(), - }); - - let e1_and_neg2 = Expression::BinaryOp(BinaryOp { - op_kind: BinaryOpKind::And, - left: Box::new(e1.clone()), - right: Box::new(neg2.clone()), - position: e1.position(), - }); - - let neg1_and_e2 = Expression::BinaryOp(BinaryOp { - op_kind: BinaryOpKind::And, - left: Box::new(neg1.clone()), - right: Box::new(e2.clone()), - position: e1.position(), - }); + if let Expression::Constant(Constant { + ty: Type::Bool, + value: ConstantValue::Bool(true), + .. + }) = &e1 + { + let neg2 = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(e2.clone()), + position: e2.position(), + }); + + self.follow(&l1.name, Some(&neg2)); + } else { + self.follow(&l1.name, Some(e1)); + } - self.follow(&l1.name, Some(&e1_and_neg2)); - self.follow(&l2.name, Some(&neg1_and_e2)); + if let Expression::Constant(Constant { + ty: Type::Bool, + value: ConstantValue::Bool(true), + .. + }) = &e2 + { + let neg1 = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(e1.clone()), + position: e1.position(), + }); + + self.follow(&l2.name, Some(&neg1)); + } else { + self.follow(&l2.name, Some(e2)); + } } else { mapping.iter().for_each(|(expr, label)| { self.follow(&label.name, Some(expr)); diff --git a/prusti-tests/tests/verify/fail/native/assert-seq.rs b/prusti-tests/tests/verify/fail/native/assert-seq.rs new file mode 100644 index 00000000000..6bb24a51743 --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/assert-seq.rs @@ -0,0 +1,6 @@ +// compile-flags: -Pviper_backend=Lithium + +fn main() { + assert!(true); + assert!(false); //~ ERROR might not hold +} diff --git a/prusti-tests/tests/verify/fail/native/if-else-1.rs b/prusti-tests/tests/verify/fail/native/if-else-1.rs new file mode 100644 index 00000000000..78da16316dd --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/if-else-1.rs @@ -0,0 +1,10 @@ +// compile-flags: -Pviper_backend=Lithium + +fn main() { + let x: i32 = 37; + if x == 17 { + assert!(x == 3); + } else { + assert!(x == 3); //~ ERROR might not hold + } +} diff --git a/prusti-tests/tests/verify/fail/native/if-else-2.rs b/prusti-tests/tests/verify/fail/native/if-else-2.rs new file mode 100644 index 00000000000..2b2a0ab2cad --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/if-else-2.rs @@ -0,0 +1,10 @@ +// compile-flags: -Pviper_backend=Lithium + +fn main() { + let x: i32 = 37; + if x == 37 { + assert!(x == 3); //~ ERROR might not hold + } else { + assert!(x == 3); + } +} From 74b090f68b167d371d7dad344115b8ec640e84bb Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 22 Feb 2023 13:24:05 +0100 Subject: [PATCH 06/54] Fix "otherwise" branch of switchInt statements --- native-verifier/src/smt_lib.rs | 77 ++++++++++--------- .../tests/verify/fail/native/switchInt-2.rs | 20 +++++ .../tests/verify/fail/native/switchInt.rs | 20 +++++ .../tests/verify/pass/native/switchInt.rs | 18 +++++ 4 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/native/switchInt-2.rs create mode 100644 prusti-tests/tests/verify/fail/native/switchInt.rs create mode 100644 prusti-tests/tests/verify/pass/native/switchInt.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index f82889070cb..15bc7099c31 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -131,48 +131,53 @@ impl SMTLib { self.follow(&label.name, None); } Successor::GotoSwitch(mapping) => { - if mapping.len() == 2 { - // TODO: What is wrong with if-statements generating a "true" branch instead of condition and negation? - let ((e1, l1), (e2, l2)) = (&mapping[0], &mapping[1]); - - if let Expression::Constant(Constant { - ty: Type::Bool, - value: ConstantValue::Bool(true), - .. - }) = &e1 - { - let neg2 = Expression::UnaryOp(UnaryOp { - op_kind: UnaryOpKind::Not, - argument: Box::new(e2.clone()), - position: e2.position(), - }); - - self.follow(&l1.name, Some(&neg2)); - } else { - self.follow(&l1.name, Some(e1)); - } + // if last branch is "true", it is the "otherwise" branch + // see: prusti-viper/src/encoder/mir/procedures/encoder/mod.rs + let (last_expr, last_label) = mapping.iter().last().unwrap(); - if let Expression::Constant(Constant { - ty: Type::Bool, - value: ConstantValue::Bool(true), - .. - }) = &e2 - { - let neg1 = Expression::UnaryOp(UnaryOp { - op_kind: UnaryOpKind::Not, - argument: Box::new(e1.clone()), - position: e1.position(), + if let Expression::Constant(Constant { + ty: Type::Bool, + value: ConstantValue::Bool(true), + .. + }) = last_expr + { + // create an "and" of all negations of previous expressions + let and = mapping + .iter() + .take(mapping.len() - 1) + .map(|(expr, _)| expr) + .fold(None, |acc, expr| { + if let Some(acc) = acc { + Some(Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::And, + left: Box::new(acc), + right: Box::new(Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(expr.clone()), + position: expr.position(), + })), + position: expr.position(), + })) + } else { + Some(Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(expr.clone()), + position: expr.position(), + })) + } }); - self.follow(&l2.name, Some(&neg1)); - } else { - self.follow(&l2.name, Some(e2)); - } + self.follow(&last_label.name, and.as_ref()); } else { - mapping.iter().for_each(|(expr, label)| { + self.follow(&last_label.name, Some(last_expr)); + } + + mapping + .iter() + .take(mapping.len() - 1) + .for_each(|(expr, label)| { self.follow(&label.name, Some(expr)); }) - } } Successor::Return => {} } diff --git a/prusti-tests/tests/verify/fail/native/switchInt-2.rs b/prusti-tests/tests/verify/fail/native/switchInt-2.rs new file mode 100644 index 00000000000..0abe86c2e30 --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/switchInt-2.rs @@ -0,0 +1,20 @@ +// compile-flags: -Pviper_backend=Lithium + +fn test(x: i32) { + match x { + 0..=100 => { + assert!(x >= 0); + assert!(x <= 100); + assert!(x == 7); //~ ERROR might not hold + } + 50 => { + unreachable!(); + } + _ => { + assert!(x != 0); + assert!(x < 0); //~ ERROR might not hold + } + } +} + +fn main() {} diff --git a/prusti-tests/tests/verify/fail/native/switchInt.rs b/prusti-tests/tests/verify/fail/native/switchInt.rs new file mode 100644 index 00000000000..993a0d3475c --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/switchInt.rs @@ -0,0 +1,20 @@ +// compile-flags: -Pviper_backend=Lithium + +fn test(x: i32) { + match x { + 0 => { + assert!(false); //~ ERROR might not hold + } + 1 => { + assert!(false); //~ ERROR might not hold + } + 2 => { + assert!(true); + } + _ => { + assert!(false); //~ ERROR might not hold + } + } +} + +fn main() {} diff --git a/prusti-tests/tests/verify/pass/native/switchInt.rs b/prusti-tests/tests/verify/pass/native/switchInt.rs new file mode 100644 index 00000000000..aaea4b4c491 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/switchInt.rs @@ -0,0 +1,18 @@ +// compile-flags: -Pviper_backend=Lithium + +fn test(x: i32) { + match x { + 0..=5 => { + assert!(x >= 0); + assert!(x <= 5); + } + 1 => { + assert!(x == 1); + } + _ => { + assert!(x < 0 || x >= 0); + } + } +} + +fn main() {} From f6e7e111c6402ef828511249e69975ffe5406d0b Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Thu, 23 Feb 2023 21:06:46 +0100 Subject: [PATCH 07/54] Fix after rebase --- prusti-server/src/backend.rs | 7 +++++-- prusti-server/src/process_verification.rs | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index ecb3b81297e..8bb63a744d1 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -4,7 +4,7 @@ use prusti_common::{ vir::{LoweringContext, ToViper}, Stopwatch, }; -use viper::{VerificationContext, VerificationResult}; +use viper::VerificationContext; pub enum Backend<'a> { Viper(viper::Verifier<'a>, &'a VerificationContext<'a>), @@ -37,7 +37,10 @@ impl<'a> Backend<'a> { viper.verify(viper_program) }) } - Backend::Lithium(lithium) => lithium.verify(program), + Backend::Lithium(lithium) => { + Stopwatch::start("prusti-server", "verifierication"); + lithium.verify(program) + } } } } diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 11ead7501a1..dab28e2f6ce 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -111,11 +111,11 @@ pub fn process_verification_request<'v, 't: 'v>( ), verification_context, ), - VerificationBackend::Lithium => { - Backend::Lithium(native_verifier::Verifier::new(config::smt_solver_path())) - // TODO: Wrapper support - } - }; + VerificationBackend::Lithium => { + Backend::Lithium(native_verifier::Verifier::new(config::smt_solver_path())) + // TODO: SMT Wrapper support + } + }; stopwatch.start_next("backend verification"); let mut result = backend.verify(&request.program); From 33fe4804ab8702e1c49f0f5ce7d146812000f063 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 28 Feb 2023 00:48:25 +0100 Subject: [PATCH 08/54] Add tests for loops, remove empty Conditional statements --- native-verifier/src/fol.rs | 9 ++++++ native-verifier/src/smt_lib.rs | 8 ++--- prusti-server/src/backend.rs | 3 +- .../tests/verify/fail/native/while.rs | 29 +++++++++++++++++++ .../tests/verify/pass/native/while.rs | 20 +++++++++++++ 5 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/native/while.rs create mode 100644 prusti-tests/tests/verify/pass/native/while.rs diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 153e514fb52..302858d544d 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -32,6 +32,15 @@ fn vir_statement_to_fol_statements( vec![FolStatement::Assume(eq)] } + Statement::Conditional(cond) => { + if !(cond.then_branch.is_empty() && cond.else_branch.is_empty()) { + log::warn!( + "Conditional statement with non-empty branches, guard: {:?}", + cond.guard + ); + } + vec![] + } Statement::MethodCall(method_call) => { let method_decl = known_methods .get(&method_call.method_name) diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 15bc7099c31..c1731588ff5 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -357,6 +357,8 @@ impl SMTTranslatable for MethodDecl { // we assume these to be correct by default and collect their signatures if self.body.is_none() { smt.methods.insert(self.name.clone(), self.clone()); + } else { + unimplemented!("Method bodies are not yet supported"); } } } @@ -430,11 +432,7 @@ impl SMTTranslatable for Expression { ConstantValue::Int(i64) => i64.to_string(), ConstantValue::BigInt(s) => s.clone(), }, - Expression::MagicWand(magic_wand) => format!( - "(=> {} {})", // TODO: is this correct? - magic_wand.left.to_smt(), - magic_wand.right.to_smt() - ), + Expression::MagicWand(magic_wand) => unimplemented!("Magic wands"), Expression::PredicateAccessPredicate(_access) => { // TODO: access predicates for predicates warn!("PredicateAccessPredicate not supported"); diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 8bb63a744d1..9765bd3567e 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -1,4 +1,5 @@ use crate::dump_viper_program; +use backend_common::VerificationResult; use prusti_common::{ config, vir::{LoweringContext, ToViper}, @@ -38,7 +39,7 @@ impl<'a> Backend<'a> { }) } Backend::Lithium(lithium) => { - Stopwatch::start("prusti-server", "verifierication"); + Stopwatch::start("prusti-server", "vir verification"); lithium.verify(program) } } diff --git a/prusti-tests/tests/verify/fail/native/while.rs b/prusti-tests/tests/verify/fail/native/while.rs new file mode 100644 index 00000000000..710e2d521d6 --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/while.rs @@ -0,0 +1,29 @@ +// compile-flags: -Pviper_backend=Lithium + +use prusti_contracts::*; + +const N: i32 = 10; + +#[requires(i <= N)] +#[ensures(result == N)] +fn wrong_invariant(i: i32) -> i32 { + let mut ret = i; + while ret < N { + body_invariant!(ret == i); //~ ERROR loop invariant might not hold + ret += 1; + } + ret +} + +#[requires(i <= N)] +#[ensures(result == N)] //~ ERROR might not hold +fn weak_invariant(i: i32) -> i32 { + let mut ret = i; + while ret < N { + body_invariant!(ret <= N); + ret += 1; + } + ret +} + +fn main() {} diff --git a/prusti-tests/tests/verify/pass/native/while.rs b/prusti-tests/tests/verify/pass/native/while.rs new file mode 100644 index 00000000000..90d1de2f199 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/while.rs @@ -0,0 +1,20 @@ +// compile-flags: -Pviper_backend=Lithium + +use prusti_contracts::*; + +const N: i32 = 10; + +#[requires(i <= N)] +#[ensures(result == N)] +fn test(i: i32) -> i32 { + let mut ret = i; + while ret < N { + body_invariant!(ret < N); + ret += 1; + } + ret +} + +fn main() { + assert!(test(3) == N); +} From e3cb30101a12fff9e26d169844fe18a0c6e95065 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 28 Feb 2023 02:35:37 +0100 Subject: [PATCH 09/54] Fix mod operator --- .gitignore | 1 + native-verifier/src/smt_lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 55f21d7cb51..514fb3305de 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ callgrind.* core report.csv *.profraw +.DS_Store *.lock !/Cargo.lock diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index c1731588ff5..50ff82bacb1 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -432,7 +432,7 @@ impl SMTTranslatable for Expression { ConstantValue::Int(i64) => i64.to_string(), ConstantValue::BigInt(s) => s.clone(), }, - Expression::MagicWand(magic_wand) => unimplemented!("Magic wands"), + Expression::MagicWand(_) => unimplemented!("Magic wands are not supported"), Expression::PredicateAccessPredicate(_access) => { // TODO: access predicates for predicates warn!("PredicateAccessPredicate not supported"); @@ -534,7 +534,7 @@ impl SMTTranslatable for BinaryOpKind { BinaryOpKind::Sub => "-", BinaryOpKind::Mul => "*", BinaryOpKind::Div => "/", - BinaryOpKind::Mod => "%", + BinaryOpKind::Mod => "mod", BinaryOpKind::And => "and", BinaryOpKind::Or => "or", BinaryOpKind::Implies => "=>", From c68d48992353bf22fb8ac5c925593f91b2be210a Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 1 Mar 2023 16:11:57 +0100 Subject: [PATCH 10/54] Add initial F32 and F64 support --- native-verifier/src/fol.rs | 3 +- native-verifier/src/smt_lib.rs | 66 +++++++++++++++++-- prusti-common/src/vir/low_to_viper/ast.rs | 6 +- .../tests/verify/pass/native/ackermann.rs | 52 +++++++++++++++ prusti-viper/src/encoder/builtin_encoder.rs | 14 +++- .../snapshots/into_snapshot/common/mod.rs | 15 ++++- .../core_proof/snapshots/values/interface.rs | 12 ++++ .../middle/core_proof/types/interface.rs | 26 ++++++++ .../src/encoder/mir/constants/interface.rs | 20 ++++++ vir/defs/low/ast/expression.rs | 2 + vir/src/low/macros.rs | 2 + 11 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 prusti-tests/tests/verify/pass/native/ackermann.rs diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 302858d544d..95e8b7cf83d 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -132,8 +132,7 @@ fn vir_statement_to_fol_statements( Statement::Comment(comment) => vec![FolStatement::Comment(comment.comment.clone())], Statement::LogEvent(_) => vec![], // TODO: Embed in SMT-LIB code _ => { - log::warn!("Statement {:?} not yet supported", statement); - vec![] + unimplemented!("Statement {:?} not yet supported", statement); } } } diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 50ff82bacb1..fb1e0f48dcf 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -343,7 +343,7 @@ impl SMTTranslatable for DomainAxiomDecl { smt.add_assert(format!("; {}", comment)); } - smt.add_assert(self.body.to_smt()); + smt.add_code(format!("(assert {}) ; {}", self.body.to_smt(), self.name)); } } @@ -430,13 +430,54 @@ impl SMTTranslatable for Expression { Expression::Constant(constant) => match &constant.value { ConstantValue::Bool(bool) => bool.to_string(), ConstantValue::Int(i64) => i64.to_string(), + ConstantValue::Float32(u32) => { + let bits = u32.to_le_bytes(); + let bits = bits + .iter() + .rev() + .map(|b| format!("{:08b}", b)) + .collect::>() + .join(""); + format!( + "(fp #b{} #b{} #b{})", + &bits.chars().next().unwrap(), + &bits[1..=8], + &bits[9..=31] + ) + } + ConstantValue::Float64(u64) => { + let bits = u64.to_le_bytes(); + let bits = bits + .iter() + .rev() + .map(|b| format!("{:08b}", b)) + .collect::>() + .join(""); + format!( + "(fp #b{} #b{} #b{})", + &bits.chars().next().unwrap(), + &bits[1..=11], + &bits[12..=63] + ) + } ConstantValue::BigInt(s) => s.clone(), }, - Expression::MagicWand(_) => unimplemented!("Magic wands are not supported"), + Expression::MagicWand(wand) => { + warn!("MagicWand not supported: {}", wand); + format!("(=> {} {})", wand.left.to_smt(), wand.right.to_smt()) + } Expression::PredicateAccessPredicate(_access) => { - // TODO: access predicates for predicates - warn!("PredicateAccessPredicate not supported"); - "".to_string() + warn!("PredicateAccessPredicate not supported: {}", _access); + format!( + "({} {})", + _access.name, + _access + .arguments + .iter() + .map(|x| x.to_smt()) + .collect::>() + .join(" ") + ) } Expression::FieldAccessPredicate(_) => unimplemented!(), Expression::Unfolding(_) => unimplemented!(), @@ -481,7 +522,18 @@ impl SMTTranslatable for Expression { ); quant.push_str(") "); - if quantifier.triggers.is_empty() { + let triggers: Vec<_> = quantifier + .triggers + .iter() + .filter(|t| { + !t.terms + .iter() + .any(|t| matches!(t, Expression::PredicateAccessPredicate(_))) + // TODO: Support triggers with predicate access predicates? + }) + .collect(); + + if triggers.is_empty() { quant.push_str(&quantifier.body.to_smt()); quant.push_str(")"); } else { @@ -489,7 +541,7 @@ impl SMTTranslatable for Expression { quant.push_str("(!"); quant.push_str(&quantifier.body.to_smt()); - for trigger in &quantifier.triggers { + for trigger in &triggers { quant.push_str(" :pattern ("); quant.push_str( diff --git a/prusti-common/src/vir/low_to_viper/ast.rs b/prusti-common/src/vir/low_to_viper/ast.rs index 6e56e887b01..933a6330bbe 100644 --- a/prusti-common/src/vir/low_to_viper/ast.rs +++ b/prusti-common/src/vir/low_to_viper/ast.rs @@ -287,6 +287,8 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Constant { expression::ConstantValue::Int(value) => { ast.int_lit_with_pos(*value, self.position.to_viper(context, ast)) } + expression::ConstantValue::Float32(_) => unimplemented!("Float32 to Viper"), + expression::ConstantValue::Float64(_) => unimplemented!("Float64 to Viper"), expression::ConstantValue::BigInt(value) => { ast.int_lit_from_ref_with_pos(value, self.position.to_viper(context, ast)) } @@ -308,7 +310,9 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Constant { Type::Map(_) => unimplemented!(), Type::Ref => unimplemented!(), Type::Perm => match &self.value { - expression::ConstantValue::Bool(_) => { + expression::ConstantValue::Float32(_) + | expression::ConstantValue::Float64(_) + | expression::ConstantValue::Bool(_) => { unreachable!() } expression::ConstantValue::Int(value) => match value { diff --git a/prusti-tests/tests/verify/pass/native/ackermann.rs b/prusti-tests/tests/verify/pass/native/ackermann.rs new file mode 100644 index 00000000000..09677dda590 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/ackermann.rs @@ -0,0 +1,52 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +#[pure] +#[terminates(trusted)] +#[requires(0 <= m && 0 <= n)] +#[ensures(result >= 0)] +fn ack_pure(m: i64, n: i64) -> i64 { + if m == 0 { + n + 1 + } else if n == 0 { + ack_pure(m - 1, 1) + } else { + ack_pure(m - 1, ack_pure(m, n - 1)) + } +} + +#[requires(0 <= m && 0 <= n)] +#[ensures(result == ack_pure(m, n))] +#[ensures(result >= 0)] +fn ack1(m: i64, n: i64) -> i64 { + if m == 0 { + n + 1 + } else if n == 0 { + ack1(m - 1, 1) + } else { + ack1(m - 1, ack1(m, n - 1)) + } +} + +#[requires(0 <= m && 0 <= n)] +#[ensures(result == ack_pure(m, n))] +#[ensures(result >= 0)] +fn ack2(m: i64, n: i64) -> i64 { + match (m, n) { + (0, n) => n + 1, + (m, 0) => ack2(m - 1, 1), + (m, n) => ack2(m - 1, ack2(m, n - 1)), + } +} + +#[trusted] +fn print_i64(a: i64) { + println!("{}", a); // 125 +} + +fn main() { + let a1 = ack1(3, 4); + let a2 = ack2(3, 4); + assert!(a1 == a2); + print_i64(a1); +} diff --git a/prusti-viper/src/encoder/builtin_encoder.rs b/prusti-viper/src/encoder/builtin_encoder.rs index 4892355cd95..7631e4ce845 100644 --- a/prusti-viper/src/encoder/builtin_encoder.rs +++ b/prusti-viper/src/encoder/builtin_encoder.rs @@ -277,12 +277,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinEncoder<'p, 'v, 'tcx> { let mut functions = vec![]; let mut axioms = vec![]; - for t in &[vir::Type::Bool, vir::Type::Int] { + for t in &[ + vir::Type::Bool, + vir::Type::Int, + vir::Type::Float(vir::Float::F32), + vir::Type::Float(vir::Float::F64), + ] { //let f = snapshot::valid_func_for_type(t); let f = { let domain_name: String = match t { // vir::Type::Domain(name) => name.clone(), - vir::Type::Bool | vir::Type::Int => domain_name.to_string(), + vir::Type::Bool + | vir::Type::Int + | vir::Type::Float(vir::Float::F32) + | vir::Type::Float(vir::Float::F64) => domain_name.to_string(), // vir::Type::TypedRef(_) => unreachable!(), // vir::Type::TypeVar(_) => unreachable!(), // vir::Type::Snapshot(_) => unreachable!(), @@ -293,6 +301,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinEncoder<'p, 'v, 'tcx> { // vir::Type::Domain(vir::DomainType{label, ..}) => vir::Type::domain(label.clone()), vir::Type::Bool => vir::Type::Bool, vir::Type::Int => vir::Type::Int, + vir::Type::Float(vir::Float::F32) => vir::Type::Float(vir::Float::F32), + vir::Type::Float(vir::Float::F64) => vir::Type::Float(vir::Float::F64), // vir::Type::TypedRef(_) => unreachable!(), // vir::Type::TypeVar(_) => unreachable!(), // vir::Type::Snapshot(_) => unreachable!(), diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index f88ff2b7ac6..dd6bc554443 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -236,6 +236,12 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { vir_mid::Type::MFloat64 => unimplemented!(), vir_mid::Type::Bool => vir_low::Type::Bool, vir_mid::Type::Int(_) => vir_low::Type::Int, + vir_mid::Type::Float(vir_mid::ty::Float::F32) => { + vir_low::Type::Float(vir_low::ty::Float::F32) + } + vir_mid::Type::Float(vir_mid::ty::Float::F64) => { + vir_low::Type::Float(vir_low::ty::Float::F64) + } vir_mid::Type::MPerm => vir_low::Type::Perm, _ => unimplemented!("constant: {:?}", constant), }; @@ -274,9 +280,12 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { vir_mid::expression::ConstantValue::BigInt(value) => { vir_low::expression::ConstantValue::BigInt(value.clone()) } - vir_mid::expression::ConstantValue::Float(_value) => { - unimplemented!(); - } + vir_mid::expression::ConstantValue::Float(vir_crate::polymorphic::FloatConst::F32( + value, + )) => vir_low::expression::ConstantValue::Float32(*value), + vir_mid::expression::ConstantValue::Float(vir_crate::polymorphic::FloatConst::F64( + value, + )) => vir_low::expression::ConstantValue::Float64(*value), vir_mid::expression::ConstantValue::FnPtr => { unimplemented!(); } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs index 98af319413c..b09a20813d1 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs @@ -105,6 +105,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { let return_type = match &ty { vir_mid::Type::Bool => vir_low::Type::Bool, vir_mid::Type::Int(_) => vir_low::Type::Int, + vir_mid::Type::Float(vir_mid::ty::Float::F32) => { + vir_low::Type::Float(vir_low::ty::Float::F32) + } + vir_mid::Type::Float(vir_mid::ty::Float::F64) => { + vir_low::Type::Float(vir_low::ty::Float::F64) + } vir_mid::Type::Pointer(_) => self.address_type()?, x => unimplemented!("{:?}", x), }; @@ -205,6 +211,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { let low_type = match &ty { vir_mid::Type::Bool => vir_low::Type::Bool, vir_mid::Type::Int(_) => vir_low::Type::Int, + vir_mid::Type::Float(vir_mid::ty::Float::F32) => { + vir_low::Type::Float(vir_low::ty::Float::F32) + } + vir_mid::Type::Float(vir_mid::ty::Float::F64) => { + vir_low::Type::Float(vir_low::ty::Float::F64) + } vir_mid::Type::Pointer(_) => self.address_type()?, vir_mid::Type::Reference(_) => self.address_type()?, x => unimplemented!("{:?}", x), diff --git a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs index aa2b046f5dd..31b133f11e5 100644 --- a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs @@ -98,6 +98,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { let validity = conjuncts.into_iter().conjoin(); self.encode_validity_axioms_primitive(&domain_name, vir_low::Type::Int, validity)?; } + vir_mid::TypeDecl::Float(decl) => { + self.ensure_type_definition(&vir_mid::Type::Bool)?; + self.register_constant_constructor( + &domain_name, + vir_low::Type::Float(vir_low::ty::Float::F32), // TODO: What about f64? + )?; + var_decls! { value: Float64 }; + + let mut conjuncts = Vec::new(); + if let Some(lower_bound) = &decl.lower_bound { + conjuncts + .push(expr! { [lower_bound.clone().to_pure_snapshot(self)? ] <= value }); + } + if let Some(upper_bound) = &decl.upper_bound { + conjuncts + .push(expr! { value <= [upper_bound.clone().to_pure_snapshot(self)? ] }); + } + let validity = conjuncts.into_iter().conjoin(); + self.encode_validity_axioms_primitive( + &domain_name, + vir_low::Type::Float(vir_low::ty::Float::F32), + validity, + )?; + } vir_mid::TypeDecl::Trusted(_) => { // FIXME: ensure type definition for trusted } @@ -422,6 +446,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { let constant_type = match argument_type { vir_mid::Type::Bool => Some(ty! { Bool }), vir_mid::Type::Int(_) => Some(ty! {Int}), + vir_mid::Type::Float(vir_mid::ty::Float::F32) => Some(ty! {Float32}), + vir_mid::Type::Float(vir_mid::ty::Float::F64) => Some(ty! {Float64}), vir_mid::Type::Pointer(_) => Some(ty!(Address)), _ => None, }; diff --git a/prusti-viper/src/encoder/mir/constants/interface.rs b/prusti-viper/src/encoder/mir/constants/interface.rs index 41b3dd1e814..f78861a0fcc 100644 --- a/prusti-viper/src/encoder/mir/constants/interface.rs +++ b/prusti-viper/src/encoder/mir/constants/interface.rs @@ -55,6 +55,26 @@ impl<'v, 'tcx: 'v> ConstantsEncoderInterface<'tcx> for super::super::super::Enco .unwrap(); number.into() } + ty::TyKind::Float(ty::FloatTy::F32) => { + let ty = self.encode_type_high(mir_type)?; + let bits = scalar_value()?.to_u32().unwrap(); + vir_high::Expression::constant_no_pos( + vir_high::expression::ConstantValue::Float( + vir_high::expression::FloatConst::F32(bits), + ), + ty, + ) + } + ty::TyKind::Float(ty::FloatTy::F64) => { + let ty = self.encode_type_high(mir_type)?; + let bits = scalar_value()?.to_u64().unwrap(); + vir_high::Expression::constant_no_pos( + vir_high::expression::ConstantValue::Float( + vir_high::expression::FloatConst::F64(bits), + ), + ty, + ) + } ty::TyKind::FnDef(..) => { let ty = self.encode_type_high(mir_type)?; vir_high::Expression::constant_no_pos( diff --git a/vir/defs/low/ast/expression.rs b/vir/defs/low/ast/expression.rs index 90cefcd028c..88837d28ede 100644 --- a/vir/defs/low/ast/expression.rs +++ b/vir/defs/low/ast/expression.rs @@ -65,6 +65,8 @@ pub struct Constant { pub enum ConstantValue { Bool(bool), Int(i64), + Float32(f32), + Float64(f64), BigInt(String), } diff --git a/vir/src/low/macros.rs b/vir/src/low/macros.rs index 302071ab05c..ad2e2cb49f5 100644 --- a/vir/src/low/macros.rs +++ b/vir/src/low/macros.rs @@ -1,5 +1,7 @@ pub macro ty { (Int) => {$crate::low::ast::ty::Type::Int}, + (Float32) => {$crate::low::ast::ty::Type::Float(crate::low::ast::ty::Float::F32)}, + (Float64) => {$crate::low::ast::ty::Type::Float(crate::low::ast::ty::Float::F64)}, (Bool) => {$crate::low::ast::ty::Type::Bool}, (Perm) => {$crate::low::ast::ty::Type::Perm}, (Place) => {$crate::low::ast::ty::Type::domain("Place".to_string())}, From 9caacd37dc250db8feb4a528179e70c0041419c7 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 1 Mar 2023 18:11:46 +0100 Subject: [PATCH 11/54] Use FP operators, choose correct type declaration --- native-verifier/src/smt_lib.rs | 75 +++++++++++++++---- .../middle/core_proof/types/interface.rs | 33 +++++--- vir/defs/low/ast/expression.rs | 5 +- 3 files changed, 88 insertions(+), 25 deletions(-) diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index fb1e0f48dcf..1202224721d 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -10,6 +10,7 @@ use vir::{ common::position::Positioned, low::{ ast::{expression::*, ty::*}, + operations::ty::Typed, *, }, }; @@ -482,18 +483,28 @@ impl SMTTranslatable for Expression { Expression::FieldAccessPredicate(_) => unimplemented!(), Expression::Unfolding(_) => unimplemented!(), Expression::UnaryOp(unary_op) => { + let op_smt = if unary_op.argument.get_type().is_float() { + FloatUnaryOpKind(unary_op.op_kind).to_smt() + } else { + IntUnaryOpKind(unary_op.op_kind).to_smt() + }; + + format!("({} {})", op_smt, unary_op.argument.to_smt()) + } + Expression::BinaryOp(binary_op) => { + let op_smt = if binary_op.left.get_type().is_float() { + FloatBinaryOpKind(binary_op.op_kind).to_smt() + } else { + IntBinaryOpKind(binary_op.op_kind).to_smt() + }; + format!( - "({} {})", - unary_op.op_kind.to_smt(), - unary_op.argument.to_smt() + "({} {} {})", + op_smt, + binary_op.left.to_smt(), + binary_op.right.to_smt() ) } - Expression::BinaryOp(binary_op) => format!( - "({} {} {})", - binary_op.op_kind.to_smt(), - binary_op.left.to_smt(), - binary_op.right.to_smt() - ), Expression::PermBinaryOp(perm_binary_op) => format!( "({} {} {})", perm_binary_op.op_kind.to_smt(), @@ -573,9 +584,12 @@ impl SMTTranslatable for Expression { } } -impl SMTTranslatable for BinaryOpKind { +struct IntBinaryOpKind(BinaryOpKind); +struct FloatBinaryOpKind(BinaryOpKind); + +impl SMTTranslatable for IntBinaryOpKind { fn to_smt(&self) -> String { - match self { + match self.0 { BinaryOpKind::EqCmp => "=", BinaryOpKind::NeCmp => "distinct", BinaryOpKind::GtCmp => ">", @@ -595,6 +609,28 @@ impl SMTTranslatable for BinaryOpKind { } } +impl SMTTranslatable for FloatBinaryOpKind { + fn to_smt(&self) -> String { + match self.0 { + BinaryOpKind::EqCmp => "fp.eq", + BinaryOpKind::NeCmp => unimplemented!("FP !="), + BinaryOpKind::GtCmp => "fp.gt", + BinaryOpKind::GeCmp => "fp.geq", + BinaryOpKind::LtCmp => "fp.lt", + BinaryOpKind::LeCmp => "fp.leq", + BinaryOpKind::Add => "fp.add roundNearestTiesToAway", + BinaryOpKind::Sub => "fp.sub roundNearestTiesToAway", + BinaryOpKind::Mul => "fp.mul roundNearestTiesToAway", + BinaryOpKind::Div => "fp.div roundNearestTiesToAway", + BinaryOpKind::Mod => "fp.rem", + BinaryOpKind::And => unreachable!("FP and"), + BinaryOpKind::Or => unreachable!("FP or"), + BinaryOpKind::Implies => unreachable!("FP implication"), + } + .to_string() + } +} + impl SMTTranslatable for PermBinaryOpKind { fn to_smt(&self) -> String { match self { @@ -607,9 +643,12 @@ impl SMTTranslatable for PermBinaryOpKind { } } -impl SMTTranslatable for UnaryOpKind { +struct IntUnaryOpKind(UnaryOpKind); +struct FloatUnaryOpKind(UnaryOpKind); + +impl SMTTranslatable for IntUnaryOpKind { fn to_smt(&self) -> String { - match self { + match self.0 { UnaryOpKind::Not => "not", UnaryOpKind::Minus => "-", } @@ -617,6 +656,16 @@ impl SMTTranslatable for UnaryOpKind { } } +impl SMTTranslatable for FloatUnaryOpKind { + fn to_smt(&self) -> String { + match self.0 { + UnaryOpKind::Not => unreachable!("FP not"), + UnaryOpKind::Minus => "fp.neg", + } + .to_string() + } +} + impl SMTTranslatable for ContainerOp { fn to_smt(&self) -> String { match &self.kind { diff --git a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs index 31b133f11e5..850231d9a17 100644 --- a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs @@ -99,12 +99,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { self.encode_validity_axioms_primitive(&domain_name, vir_low::Type::Int, validity)?; } vir_mid::TypeDecl::Float(decl) => { + let is_f64 = domain_name.contains("F64"); + let float_type = || { + if is_f64 { + vir_low::Type::Float(vir_low::ty::Float::F64) + } else { + vir_low::Type::Float(vir_low::ty::Float::F32) + } + }; + self.ensure_type_definition(&vir_mid::Type::Bool)?; - self.register_constant_constructor( - &domain_name, - vir_low::Type::Float(vir_low::ty::Float::F32), // TODO: What about f64? - )?; - var_decls! { value: Float64 }; + self.register_constant_constructor(&domain_name, float_type())?; + + let value = vir_low::ast::variable::VariableDecl::new("value", float_type()); let mut conjuncts = Vec::new(); if let Some(lower_bound) = &decl.lower_bound { @@ -116,11 +123,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { .push(expr! { value <= [upper_bound.clone().to_pure_snapshot(self)? ] }); } let validity = conjuncts.into_iter().conjoin(); - self.encode_validity_axioms_primitive( - &domain_name, - vir_low::Type::Float(vir_low::ty::Float::F32), - validity, - )?; + self.encode_validity_axioms_primitive(&domain_name, float_type(), validity)?; } vir_mid::TypeDecl::Trusted(_) => { // FIXME: ensure type definition for trusted @@ -391,6 +394,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { var_decls! { constant: Int }; Some((expr! { -constant }, constant)) } + vir_mid::Type::Float(vir_mid::ty::Float::F32) => { + assert_eq!(op, vir_low::UnaryOpKind::Minus); + var_decls! { constant: Float32 }; + Some((expr! { -constant }, constant)) + } + vir_mid::Type::Float(vir_mid::ty::Float::F64) => { + assert_eq!(op, vir_low::UnaryOpKind::Minus); + var_decls! { constant: Float64 }; + Some((expr! { -constant }, constant)) + } _ => None, }; if let Some((simplification_result, parameter)) = simplification { diff --git a/vir/defs/low/ast/expression.rs b/vir/defs/low/ast/expression.rs index 88837d28ede..62076a9b5b6 100644 --- a/vir/defs/low/ast/expression.rs +++ b/vir/defs/low/ast/expression.rs @@ -65,8 +65,8 @@ pub struct Constant { pub enum ConstantValue { Bool(bool), Int(i64), - Float32(f32), - Float64(f64), + Float32(u32), + Float64(u64), BigInt(String), } @@ -107,6 +107,7 @@ pub struct Unfolding { pub position: Position, } +#[derive(Copy)] pub enum UnaryOpKind { Not, Minus, From 412772fea08b3e3ba1d46d55548218453229a77f Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 1 Mar 2023 22:18:11 +0100 Subject: [PATCH 12/54] Tests for floating-point support --- native-verifier/src/smt_lib.rs | 5 +- native-verifier/src/theories/Preamble.smt2 | 5 + .../verify/fail/native/floating-point.rs | 140 ++++++++++++++++++ .../core_proof/builtin_methods/interface.rs | 11 +- 4 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/native/floating-point.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 1202224721d..16a2d687128 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -612,8 +612,9 @@ impl SMTTranslatable for IntBinaryOpKind { impl SMTTranslatable for FloatBinaryOpKind { fn to_smt(&self) -> String { match self.0 { - BinaryOpKind::EqCmp => "fp.eq", - BinaryOpKind::NeCmp => unimplemented!("FP !="), + // BinaryOpKind::EqCmp => "fp.eq", // TODO: = in axioms, fp.eq in arithmetic? + BinaryOpKind::EqCmp => "=", + BinaryOpKind::NeCmp => "fp.neq", // Not in SMT-LIB 2.6 standard, part of static preamble BinaryOpKind::GtCmp => "fp.gt", BinaryOpKind::GeCmp => "fp.geq", BinaryOpKind::LtCmp => "fp.lt", diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index 20d53d68638..ba8e9c2c473 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -33,6 +33,11 @@ (set-option :timeout 1000) +; --- Floating-point arithmetic --- + +(define-fun fp.neq ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) +(define-fun fp.neq ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) + ; --- Snapshots --- (declare-datatypes (($Snap 0)) (( diff --git a/prusti-tests/tests/verify/fail/native/floating-point.rs b/prusti-tests/tests/verify/fail/native/floating-point.rs new file mode 100644 index 00000000000..3a06fe9f66f --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/floating-point.rs @@ -0,0 +1,140 @@ +// compile-flags: -Pviper_backend=Lithium + +use prusti_contracts::*; + +#[pure] +#[terminates] +fn is_nan_32(f: f32) -> bool { + f != f +} + +#[pure] +#[terminates] +fn is_nan_64(f: f64) -> bool { + f != f +} + +#[pure] +#[terminates] +fn is_infinite_32(f: f32) -> bool { + f == f32::INFINITY || f == f32::NEG_INFINITY +} + +#[pure] +#[terminates] +fn is_infinite_64(f: f64) -> bool { + f == f64::INFINITY || f == f64::NEG_INFINITY +} + +fn test_nan_32_is_nan() { + let a = 0.0f32 / 0.0f32; + assert!(is_nan_32(a)); +} + +fn test_nan_32_is_inf() { + let a = 0.0f32 / 0.0f32; + assert!(is_infinite_32(a)); //~ ERROR might not hold +} + +fn test_nan_64_is_nan() { + let a = 0.0f64 / 0.0f64; + assert!(is_nan_64(a)); +} + +fn test_nan_64_is_inf() { + let a = 0.0f64 / 0.0f64; + assert!(is_infinite_64(a)); //~ ERROR might not hold +} + +fn test_inf_32_is_inf() { + let a = 1.0f32 / 0.0f32; + assert!(is_infinite_32(a)); +} + +fn test_inf_32_is_nan() { + let a = 1.0f32 / 0.0f32; + assert!(is_nan_32(a)); //~ ERROR might not hold +} + +fn test_inf_64_is_inf() { + let a = 1.0f64 / 0.0f64; + assert!(is_infinite_64(a)); +} + +fn test_inf_64_is_nan() { + let a = 1.0f64 / 0.0f64; + assert!(is_nan_64(a)); //~ ERROR might not hold +} + +fn test_neg_inf_32_is_inf() { + let a = -1.0f32 / 0.0f32; + assert!(is_infinite_32(a)); +} + +fn test_neg_inf_32_is_nan() { + let a = -1.0f32 / 0.0f32; + assert!(is_nan_32(a)); //~ ERROR might not hold +} + +fn test_neg_inf_64_is_inf() { + let a = -1.0f64 / 0.0f64; + assert!(is_infinite_64(a)); +} + +fn test_neg_inf_64_is_nan() { + let a = -1.0f64 / 0.0f64; + assert!(is_nan_64(a)); //~ ERROR might not hold +} + +// THEORY OF INIFINITY + +#[requires(is_nan_32(f))] +#[ensures(!is_infinite_32(f))] +fn axiom1(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(!is_nan_32(f))] +fn axiom2(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f + 1_f32))] +fn axiom3(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f - 1_f32))] +fn axiom4(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f * 2_f32))] +fn axiom5(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f / 2_f32))] +fn axiom6(f: f32) {} + +// THEORY OF NAN + +#[requires(is_infinite_32(f))] +#[ensures(!is_nan_32(f))] +fn axiom7(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f + 1_f32))] +fn axiom8(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f - 1_f32))] +fn axiom9(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f * 2_f32))] +fn axiom10(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f / 2_f32))] +fn axiom11(f: f32) {} + +#[ensures(is_nan_32(f32::NAN))] +fn axiom12() {} + +fn main() {} diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs index 9828f510e12..8ce8c4fffb6 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs @@ -690,8 +690,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { // For some reason, division is not CheckedBinaryOp, but the // regular one. Therefore, we need to put the checks for // overflows into the precondition. - let type_decl = self.encoder.get_type_decl_mid(result_type)?.unwrap_int(); - if let Some(lower) = type_decl.lower_bound { + let type_decl = self.encoder.get_type_decl_mid(result_type)?; + + let lower_bound = if result_type.is_float() { + type_decl.unwrap_float().lower_bound + } else { + type_decl.unwrap_int().lower_bound + }; + + if let Some(lower) = lower_bound { let zero = self.construct_constant_snapshot(result_type, 0.into(), position)?; pres.push(expr! {operand_right != [zero]}); From 9f3b96239f1b0f1c115537302ab9dba20aef26a3 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Sat, 18 Mar 2023 23:26:39 +0100 Subject: [PATCH 13/54] Add preamble for CVC5, split fp.neq into 32 and 64-bit variants --- native-verifier/src/smt_lib.rs | 48 ++++++----- native-verifier/src/theories/Preamble.smt2 | 37 +-------- native-verifier/src/theories/PreambleZ3.smt2 | 87 ++++++++++++++++++++ native-verifier/src/verifier.rs | 36 +++++--- vir/defs/low/ast/ty.rs | 1 + 5 files changed, 144 insertions(+), 65 deletions(-) create mode 100644 native-verifier/src/theories/PreambleZ3.smt2 diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 16a2d687128..a2a038e222c 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -20,7 +20,8 @@ lazy_static! { static ref TWO_PARAM_RE: Regex = Regex::new(r"Map<([^>]+)\s*,\s*([^>]+)>").unwrap(); } -const SMT_PREAMBLE: &str = include_str!("theories/Preamble.smt2"); +const NO_Z3_PREAMBLE: &str = include_str!("theories/Preamble.smt2"); +const Z3_PREAMBLE: &str = include_str!("theories/PreambleZ3.smt2"); pub struct SMTLib { sort_decls: Vec, @@ -28,16 +29,18 @@ pub struct SMTLib { code: Vec, methods: HashMap, blocks: HashMap, + using_z3: bool, } impl SMTLib { - pub fn new() -> Self { + pub fn new(using_z3: bool) -> Self { Self { sort_decls: Vec::new(), func_decls: Vec::new(), code: Vec::new(), methods: HashMap::new(), blocks: Default::default(), + using_z3, } } fn add_sort_decl(&mut self, sort_decl: String) { @@ -192,7 +195,11 @@ impl SMTLib { impl ToString for SMTLib { fn to_string(&self) -> String { let mut result = String::new(); - result.push_str(SMT_PREAMBLE); + result.push_str(if self.using_z3 { + Z3_PREAMBLE + } else { + NO_Z3_PREAMBLE + }); result.push_str("\n\n"); result.push_str(&self.sort_decls.join("\n")); result.push_str("\n\n"); @@ -492,8 +499,8 @@ impl SMTTranslatable for Expression { format!("({} {})", op_smt, unary_op.argument.to_smt()) } Expression::BinaryOp(binary_op) => { - let op_smt = if binary_op.left.get_type().is_float() { - FloatBinaryOpKind(binary_op.op_kind).to_smt() + let op_smt = if let Type::Float(fsize) = binary_op.left.get_type() { + FloatBinaryOpKind(binary_op.op_kind, *fsize).to_smt() } else { IntBinaryOpKind(binary_op.op_kind).to_smt() }; @@ -585,7 +592,7 @@ impl SMTTranslatable for Expression { } struct IntBinaryOpKind(BinaryOpKind); -struct FloatBinaryOpKind(BinaryOpKind); +struct FloatBinaryOpKind(BinaryOpKind, Float); impl SMTTranslatable for IntBinaryOpKind { fn to_smt(&self) -> String { @@ -611,22 +618,21 @@ impl SMTTranslatable for IntBinaryOpKind { impl SMTTranslatable for FloatBinaryOpKind { fn to_smt(&self) -> String { - match self.0 { + match (self.0, self.1) { // BinaryOpKind::EqCmp => "fp.eq", // TODO: = in axioms, fp.eq in arithmetic? - BinaryOpKind::EqCmp => "=", - BinaryOpKind::NeCmp => "fp.neq", // Not in SMT-LIB 2.6 standard, part of static preamble - BinaryOpKind::GtCmp => "fp.gt", - BinaryOpKind::GeCmp => "fp.geq", - BinaryOpKind::LtCmp => "fp.lt", - BinaryOpKind::LeCmp => "fp.leq", - BinaryOpKind::Add => "fp.add roundNearestTiesToAway", - BinaryOpKind::Sub => "fp.sub roundNearestTiesToAway", - BinaryOpKind::Mul => "fp.mul roundNearestTiesToAway", - BinaryOpKind::Div => "fp.div roundNearestTiesToAway", - BinaryOpKind::Mod => "fp.rem", - BinaryOpKind::And => unreachable!("FP and"), - BinaryOpKind::Or => unreachable!("FP or"), - BinaryOpKind::Implies => unreachable!("FP implication"), + (BinaryOpKind::EqCmp, _) => "=", + (BinaryOpKind::NeCmp, Float::F32) => "fp.neq32", // Not in SMT-LIB 2.6 standard, part of static preamble + (BinaryOpKind::NeCmp, Float::F64) => "fp.neq64", // Not in SMT-LIB 2.6 standard, part of static preamble + (BinaryOpKind::GtCmp, _) => "fp.gt", + (BinaryOpKind::GeCmp, _) => "fp.geq", + (BinaryOpKind::LtCmp, _) => "fp.lt", + (BinaryOpKind::LeCmp, _) => "fp.leq", + (BinaryOpKind::Add, _) => "fp.add roundNearestTiesToAway", + (BinaryOpKind::Sub, _) => "fp.sub roundNearestTiesToAway", + (BinaryOpKind::Mul, _) => "fp.mul roundNearestTiesToAway", + (BinaryOpKind::Div, _) => "fp.div roundNearestTiesToAway", + (BinaryOpKind::Mod, _) => "fp.rem", + (other, _) => unreachable!("FP {}", other), } .to_string() } diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index ba8e9c2c473..d346606cd68 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -1,42 +1,11 @@ ; ===== Static preamble ===== -(set-option :global-decls true) ; Boogie: default -(set-option :auto_config false) ; Usually a good idea -(set-option :smt.restart_strategy 0) -(set-option :smt.restart_factor |1.5|) -(set-option :smt.case_split 3) -(set-option :smt.delay_units true) -(set-option :smt.delay_units_threshold 16) -(set-option :nnf.sk_hack true) -(set-option :type_check true) -(set-option :smt.bv.reflect true) -(set-option :smt.mbqi false) -(set-option :smt.qi.cost "(+ weight generation)") -(set-option :smt.qi.eager_threshold 1000) -(set-option :smt.qi.max_multi_patterns 1000) -(set-option :smt.phase_selection 0) ; default: 3, Boogie: 0 -(set-option :sat.phase caching) -(set-option :sat.random_seed 0) -(set-option :nlsat.randomize true) -(set-option :nlsat.seed 0) -(set-option :nlsat.shuffle_vars false) -(set-option :fp.spacer.order_children 0) ; Not available with Z3 4.5 -(set-option :fp.spacer.random_seed 0) ; Not available with Z3 4.5 -(set-option :smt.arith.random_initial_value true) ; Boogie: true -(set-option :smt.random_seed 0) -(set-option :sls.random_offset true) -(set-option :sls.random_seed 0) -(set-option :sls.restart_init false) -(set-option :sls.walksat_ucb true) -(set-option :model.v2 true) -(set-option :model.partial false) - -(set-option :timeout 1000) +(set-logic ALL) ; --- Floating-point arithmetic --- -(define-fun fp.neq ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) -(define-fun fp.neq ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) +(define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) +(define-fun fp.neq64 ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) ; --- Snapshots --- diff --git a/native-verifier/src/theories/PreambleZ3.smt2 b/native-verifier/src/theories/PreambleZ3.smt2 new file mode 100644 index 00000000000..c63baf61d6d --- /dev/null +++ b/native-verifier/src/theories/PreambleZ3.smt2 @@ -0,0 +1,87 @@ +; ===== Static preamble ===== + +(set-option :global-decls true) ; Boogie: default +(set-option :auto_config false) ; Usually a good idea +(set-option :smt.restart_strategy 0) +(set-option :smt.restart_factor |1.5|) +(set-option :smt.case_split 3) +(set-option :smt.delay_units true) +(set-option :smt.delay_units_threshold 16) +(set-option :nnf.sk_hack true) +(set-option :type_check true) +(set-option :smt.bv.reflect true) +(set-option :smt.mbqi false) +(set-option :smt.qi.cost "(+ weight generation)") +(set-option :smt.qi.eager_threshold 1000) +(set-option :smt.qi.max_multi_patterns 1000) +(set-option :smt.phase_selection 0) ; default: 3, Boogie: 0 +(set-option :sat.phase caching) +(set-option :sat.random_seed 0) +(set-option :nlsat.randomize true) +(set-option :nlsat.seed 0) +(set-option :nlsat.shuffle_vars false) +(set-option :fp.spacer.order_children 0) ; Not available with Z3 4.5 +(set-option :fp.spacer.random_seed 0) ; Not available with Z3 4.5 +(set-option :smt.arith.random_initial_value true) ; Boogie: true +(set-option :smt.random_seed 0) +(set-option :sls.random_offset true) +(set-option :sls.random_seed 0) +(set-option :sls.restart_init false) +(set-option :sls.walksat_ucb true) +(set-option :model.v2 true) +(set-option :model.partial false) + +(set-option :timeout 1000) + +; --- Floating-point arithmetic --- + +(define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) +(define-fun fp.neq64 ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) + +; --- Snapshots --- + +(declare-datatypes (($Snap 0)) (( + ($Snap.unit) + ($Snap.combine ($Snap.first $Snap) ($Snap.second $Snap))))) + +; --- References --- + +(declare-sort $Ref 0) +(declare-const $Ref.null $Ref) + +; --- Permissions --- + +(declare-sort $FPM 0) +(declare-sort $PPM 0) +(define-sort $Perm () Real) + +(define-const $Perm.Write $Perm 1.0) +(define-const $Perm.No $Perm 0.0) + +(define-fun $Perm.isValidVar ((p $Perm)) Bool + (<= $Perm.No p)) + +(define-fun $Perm.isReadVar ((p $Perm)) Bool + (and ($Perm.isValidVar p) + (not (= p $Perm.No)))) + +; min function for permissions +(define-fun $Perm.min ((p1 $Perm) (p2 $Perm)) Real + (ite (<= p1 p2) p1 p2)) + +; --- Sort wrappers --- + +; Sort wrappers are no longer part of the static preamble. Instead, they are +; emitted as part of the program-specific preamble. + +; --- Math --- + +;function Math#min(a: int, b: int): int; +(define-fun $Math.min ((a Int) (b Int)) Int + (ite (<= a b) a b)) + +;function Math#clip(a: int): int; +(define-fun $Math.clip ((a Int)) Int + (ite (< a 0) 0 a)) + +; ===== End static preamble ===== \ No newline at end of file diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index f5e490a6353..071d1c71b44 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -7,22 +7,31 @@ use crate::smt_lib::*; use backend_common::{VerificationError, VerificationResult}; use core::panic; +use lazy_static::lazy_static; use log::{self, debug}; use prusti_common::vir::program::Program; use prusti_utils::{report::log::report_with_writer, run_timed}; +use regex::Regex; use std::{ error::Error, io::Write, process::{Command, Stdio}, }; +// lazy regex for parsing z3 output +lazy_static! { + static ref POSITION_REGEX: Regex = Regex::new(r#"^"?position: (\d+)"?"#).unwrap(); +} + pub struct Verifier { - z3_exe: String, + smt_solver_exe: String, } impl Verifier { pub fn new(z3_exe: String) -> Self { - Self { z3_exe } + Self { + smt_solver_exe: z3_exe, + } } pub fn verify(&mut self, program: &Program) -> VerificationResult { @@ -30,8 +39,10 @@ impl Verifier { panic!("Lithium backend only supports low programs"); }; + let is_z3 = self.smt_solver_exe.ends_with("z3"); + run_timed!("Translation to SMT-LIB", debug, - let mut smt = SMTLib::new(); + let mut smt = SMTLib::new(is_z3); program.build_smt(&mut smt); let smt_program = smt.to_string(); @@ -44,11 +55,17 @@ impl Verifier { ); ); - run_timed!("SMT verification", debug, + run_timed!(format!("SMT verification with {}", if is_z3 {"Z3"} else {"CVC5"}), debug, let result: Result> = try { - let mut child = Command::new(self.z3_exe.clone()) - .args(["-smt2", "-in"]) - .stdin(Stdio::piped()) + let mut command = Command::new(self.smt_solver_exe.clone()); + + if is_z3 { + command.args(&["-smt2", "-in"]); + } else { + command.args(&["--incremental"]); + } + + let mut child = command.stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; @@ -87,9 +104,8 @@ impl Verifier { let mut last_pos: i32 = 0; for line in result.unwrap().lines() { - if line.starts_with("position: ") { - let position_id = line.split("position: ").nth(1).unwrap(); - last_pos = position_id.parse().unwrap(); + if let Some(caps) = POSITION_REGEX.captures(line) { + last_pos = caps[1].parse().unwrap(); } else if line == "sat" || line == "unknown" { errors.push(VerificationError::new( "assert.failed:assertion.false".to_string(), diff --git a/vir/defs/low/ast/ty.rs b/vir/defs/low/ast/ty.rs index c8952156e63..a824f0768a3 100644 --- a/vir/defs/low/ast/ty.rs +++ b/vir/defs/low/ast/ty.rs @@ -17,6 +17,7 @@ pub enum Type { Domain(Domain), } +#[derive(Copy)] pub enum Float { F32, F64, From f69cb0b0d9b981b56100d7bc6e498e204ce730e1 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Mon, 20 Mar 2023 22:38:48 +0100 Subject: [PATCH 14/54] Fixes after merge --- prusti-viper/src/encoder/errors/error_manager.rs | 1 + viper/src/verifier.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 722b5747571..4391daa9edc 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -6,6 +6,7 @@ use super::PositionManager; use backend_common::VerificationError; +use core::fmt::Debug; use log::debug; use prusti_interface::{data::ProcedureDefId, PrustiError}; use prusti_rustc_interface::{errors::MultiSpan, span::source_map::SourceMap}; diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index 3fa7c18cf95..37d6a1b2326 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -70,6 +70,22 @@ impl<'a> Verifier<'a> { } } + #[tracing::instrument(level = "debug", skip_all)] + fn instantiate_verifier( + backend: VerificationBackend, + env: &'a JNIEnv, + reporter: JObject, + debug_info: JObject, + ) -> Result> { + match backend { + VerificationBackend::Silicon => silicon::Silicon::with(env).new(reporter, debug_info), + VerificationBackend::Carbon => { + carbon::CarbonVerifier::with(env).new(reporter, debug_info) + } + other => unreachable!("{:?} is not a Viper backend", other), + } + } + #[must_use] #[tracing::instrument(level = "debug", skip_all)] pub fn initialize(self, args: &[String]) -> Self { From 62f4a9bf24de3fc533e5d5252eeb5b73afe0e4cc Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 22 Mar 2023 18:14:12 +0100 Subject: [PATCH 15/54] Quantifiers to snapshots, fix unsized type bound checking bug --- native-verifier/src/fol.rs | 39 ++++++++++++- native-verifier/src/smt_lib.rs | 18 ++++-- .../snapshots/into_snapshot/common/mod.rs | 56 ++++++++++++++++++- .../mir/pure/specifications/encoder_high.rs | 13 ++++- 4 files changed, 114 insertions(+), 12 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 95e8b7cf83d..1d77b81c5cb 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -33,10 +33,43 @@ fn vir_statement_to_fol_statements( vec![FolStatement::Assume(eq)] } Statement::Conditional(cond) => { + // handle trivial cases where the guard is constant true or false, emit the branches instead + if let Expression::Constant(Constant { + value: ConstantValue::Bool(true), + .. + }) = cond.guard + { + return cond + .then_branch + .iter() + .flat_map(|s| vir_statement_to_fol_statements(s, known_methods)) + .collect(); + } else if let Expression::Constant(Constant { + value: ConstantValue::Bool(false), + .. + }) = cond.guard + { + return cond + .else_branch + .iter() + .flat_map(|s| vir_statement_to_fol_statements(s, known_methods)) + .collect(); + } + if !(cond.then_branch.is_empty() && cond.else_branch.is_empty()) { - log::warn!( - "Conditional statement with non-empty branches, guard: {:?}", - cond.guard + unimplemented!( + "Non-trivial conditional statements not supported!!\nGuard: {}\n\nThen-branch:\n{}\n\nElse-branch:\n{}", + cond.guard, + cond.then_branch + .iter() + .map(|s| format!("{}", s)) + .collect::>() + .join(";\n"), + cond.else_branch + .iter() + .map(|s| format!("{}", s)) + .collect::>() + .join(";\n") ); } vec![] diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index a2a038e222c..6093eca47b4 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -18,6 +18,7 @@ use vir::{ lazy_static! { static ref ONE_PARAM_RE: Regex = Regex::new(r"(Set|Seq|MultiSet)<([^>]+)>").unwrap(); static ref TWO_PARAM_RE: Regex = Regex::new(r"Map<([^>]+)\s*,\s*([^>]+)>").unwrap(); + static ref MARKER_RE: Regex = Regex::new(r"label.*\$marker\b\s").unwrap(); } const NO_Z3_PREAMBLE: &str = include_str!("theories/Preamble.smt2"); @@ -67,7 +68,6 @@ impl SMTLib { // check if just asserting true // TODO: Optimization module if let Expression::Constant(Constant { - ty: Type::Bool, value: ConstantValue::Bool(true), .. }) = expression @@ -118,6 +118,7 @@ impl SMTLib { // assume precond if any if let Some(precond) = precond { + // TODO: Location of the call site self.add_code("; Branch precond:".to_string()); self.add_code(format!("(assert {})", precond.to_smt())); } @@ -140,7 +141,6 @@ impl SMTLib { let (last_expr, last_label) = mapping.iter().last().unwrap(); if let Expression::Constant(Constant { - ty: Type::Bool, value: ConstantValue::Bool(true), .. }) = last_expr @@ -263,7 +263,7 @@ impl ToString for SMTLib { result .lines() - .filter(|&line| !line.contains("$marker")) // TODO: SSO form for marker variables? + .filter(|&line| !MARKER_RE.is_match(line)) // TODO: SSO form for marker variables? .filter(|&line| line != "(assert true)") // TODO: Optimization module .collect::>() .join("\n") @@ -471,8 +471,16 @@ impl SMTTranslatable for Expression { ConstantValue::BigInt(s) => s.clone(), }, Expression::MagicWand(wand) => { - warn!("MagicWand not supported: {}", wand); - format!("(=> {} {})", wand.left.to_smt(), wand.right.to_smt()) + // if left is just constant true, we can ignore it + if let Expression::Constant(Constant { + value: ConstantValue::Bool(true), + .. + }) = *wand.left + { + format!("(=> {} {})", wand.left.to_smt(), wand.right.to_smt()) + } else { + unimplemented!("Non-trivial MagicWand not supported: {}", wand); + } } Expression::PredicateAccessPredicate(_access) => { warn!("PredicateAccessPredicate not supported: {}", _access); diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index dd6bc554443..19e72a22b98 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -75,7 +75,9 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { vir_mid::Expression::Conditional(expression) => { self.conditional_to_snapshot(lowerer, expression, expect_math_bool) } - // vir_mid::Expression::Quantifier(expression) => self.quantifier_to_snapshot(lowerer, expression, expect_math_bool), + vir_mid::Expression::Quantifier(expression) => { + self.quantifier_to_snapshot(lowerer, expression, expect_math_bool) + } // vir_mid::Expression::LetExpr(expression) => self.letexpr_to_snapshot(lowerer, expression, expect_math_bool), vir_mid::Expression::FuncApp(expression) => { self.func_app_to_snapshot(lowerer, expression, expect_math_bool) @@ -103,6 +105,58 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { } } + fn quantifier_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + quantifier: &vir_mid::Quantifier, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let vir_mid::Quantifier { + kind, + variables, + triggers, + body, + position, + } = quantifier; + + let vars = variables + .iter() + .map(|variable| self.variable_to_snapshot(lowerer, variable)) + .collect::>>()?; + + let trigs = triggers + .iter() + .map(|trigger| { + let trig = self + .expression_vec_to_snapshot(lowerer, &trigger.terms, expect_math_bool) + .unwrap(); + vir_low::Trigger { terms: trig } + }) + .collect(); + + let body = self.expression_to_snapshot(lowerer, body, expect_math_bool)?; + + let kind = match kind { + vir_mid::expression::QuantifierKind::ForAll => { + vir_low::expression::QuantifierKind::ForAll + } + vir_mid::expression::QuantifierKind::Exists => { + vir_low::expression::QuantifierKind::Exists + } + }; + + // no call to ensure_bool_expression since quantifiers are always math bool and forall is built-in to SMT solvers + Ok(vir_low::Expression::Quantifier( + vir_low::expression::Quantifier { + kind, + variables: vars, + triggers: trigs, + body: Box::new(body), + position: *position, + }, + )) + } + fn variable_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, diff --git a/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs b/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs index 71409b307ac..6ee6fc6dd0e 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs @@ -147,9 +147,16 @@ pub(super) fn encode_quantifier_high<'tcx>( if config::check_overflows() { bounds.extend(encoder.encode_type_bounds_high(&encoded_qvar.clone().into(), arg_ty)); } else if config::encode_unsigned_num_constraint() { - if let ty::TyKind::Uint(_) = arg_ty.kind() { - let expr = - vir_high::Expression::less_equals(0u32.into(), encoded_qvar.clone().into()); + if let ty::TyKind::Uint(utype) = arg_ty.kind() { + let zero = match utype { + ty::UintTy::U8 => 0u8.into(), + ty::UintTy::U16 => 0u16.into(), + ty::UintTy::U32 => 0u32.into(), + ty::UintTy::U64 => 0u64.into(), + ty::UintTy::U128 => 0u128.into(), + ty::UintTy::Usize => 0usize.into(), + }; + let expr = vir_high::Expression::less_equals(zero, encoded_qvar.clone().into()); bounds.push(expr); } } From 08c1e9ecfc1176174d65393b726b38fd08160f64 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 29 Mar 2023 00:50:05 +0200 Subject: [PATCH 16/54] Hack quantifier generation for complex types --- .../fail/native/structure_quantifiers.rs | 30 +++++++++++ .../pass/native/structure_quantifiers.rs | 27 ++++++++++ .../snapshots/into_snapshot/common/mod.rs | 51 ++++++++++++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 prusti-tests/tests/verify/fail/native/structure_quantifiers.rs create mode 100644 prusti-tests/tests/verify/pass/native/structure_quantifiers.rs diff --git a/prusti-tests/tests/verify/fail/native/structure_quantifiers.rs b/prusti-tests/tests/verify/fail/native/structure_quantifiers.rs new file mode 100644 index 00000000000..456ce4c0732 --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/structure_quantifiers.rs @@ -0,0 +1,30 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +pub struct MyStructure {} + +impl MyStructure { + #[pure] + #[terminates] + #[ensures (0 <= result)] + pub fn len(&self) -> i32 { + 17 + } +} + +#[pure] +#[terminates] +#[ensures (0 <= result)] +#[ensures(result == s.len())] +fn len_of(s: &MyStructure) -> i32 { + 17 +} + +#[ensures(forall(|s: MyStructure| s.len() == 3))] //~ ERROR postcondition might not hold +fn test1(min: i32) {} + +#[ensures(forall(|s: MyStructure| len_of(&s) >= 17))] +#[ensures(forall(|s: MyStructure| len_of(&s) >= 18))] //~ ERROR postcondition might not hold +fn test2(min: i32) {} + +fn main() {} diff --git a/prusti-tests/tests/verify/pass/native/structure_quantifiers.rs b/prusti-tests/tests/verify/pass/native/structure_quantifiers.rs new file mode 100644 index 00000000000..18109da9fa6 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/structure_quantifiers.rs @@ -0,0 +1,27 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +pub struct MyStructure {} + +impl MyStructure { + #[pure] + #[terminates] + #[ensures (0 <= result)] + pub fn len(&self) -> i32 { + 17 + } +} + +#[pure] +#[terminates] +#[ensures (0 <= result)] +#[ensures(result == s.len())] +fn len_of(s: &MyStructure) -> i32 { + 17 +} + +#[ensures(forall(|s: MyStructure| len_of(&s) >= 0))] +#[ensures(forall(|s: MyStructure| s.len() >= 10))] +fn test(min: i32) {} + +fn main() {} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index 19e72a22b98..d0605bdd387 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -6,7 +6,10 @@ use crate::encoder::{ lifetimes::*, lowerer::DomainsLowererInterface, references::ReferencesInterface, - snapshots::{IntoSnapshot, SnapshotDomainsInterface, SnapshotValuesInterface}, + snapshots::{ + IntoSnapshot, SnapshotDomainsInterface, SnapshotValidityInterface, + SnapshotValuesInterface, + }, types::TypesInterface, }, }; @@ -136,6 +139,50 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { let body = self.expression_to_snapshot(lowerer, body, expect_math_bool)?; + // generate validity calls for all variables we quantify over + // this is needed to ensure that the quantifier is well-formed + // because most axioms assume valid arguments + // for some reason they are added in the outer scope as assertions, as if the program thought that these are global variables + // TODO: Find a way to make this less cursed + // TODO: do not generate the validity calls for variables outside the quantifier + let mut validity_calls = Vec::new(); + for variable in vars.iter() { + let call = lowerer.encode_snapshot_valid_call( + &variable.ty.to_string(), + vir_low::Expression::Local(vir_low::expression::Local { + variable: variable.clone(), + position: quantifier.position(), + }), + )?; + validity_calls.push(call); + } + + let recursive_apply_and = |mut exprs: Vec| { + let mut result = exprs.pop().unwrap(); + for expr in exprs.into_iter().rev() { + result = vir_low::Expression::BinaryOp( + vir_low::expression::BinaryOp { + left: Box::new(expr), + right: Box::new(result), + position: *position, + op_kind: vir_low::expression::BinaryOpKind::And, + } + .into(), + ); + } + result + }; + + let validity_call_imply_body = vir_low::Expression::BinaryOp( + vir_low::expression::BinaryOp { + left: Box::new(recursive_apply_and(validity_calls)), + right: Box::new(body), + position: *position, + op_kind: vir_low::expression::BinaryOpKind::Implies, + } + .into(), + ); + let kind = match kind { vir_mid::expression::QuantifierKind::ForAll => { vir_low::expression::QuantifierKind::ForAll @@ -151,7 +198,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { kind, variables: vars, triggers: trigs, - body: Box::new(body), + body: Box::new(validity_call_imply_body), position: *position, }, )) From d195597dd4992a118bcc11fa3b8c582c9cc732c8 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 11 Apr 2023 11:56:00 +0200 Subject: [PATCH 17/54] Fix after rebase --- native-verifier/src/fol.rs | 6 +++--- native-verifier/src/smt_lib.rs | 3 ++- viper/src/verifier.rs | 3 +++ vir/defs/low/ast/expression.rs | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 1d77b81c5cb..970bf206aa3 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -114,12 +114,12 @@ fn vir_statement_to_fol_statements( position: op.position, }), Expression::UnaryOp(op) => Expression::UnaryOp(UnaryOp { - op_kind: op.op_kind.clone(), // TODO: Copy trait derivation + op_kind: op.op_kind, argument: Box::new(substitute(&op.argument, mapping)), position: op.position, }), Expression::ContainerOp(op) => Expression::ContainerOp(ContainerOp { - kind: op.kind.clone(), // TODO: Copy trait derivation + kind: op.kind, container_type: op.container_type.clone(), operands: op .operands @@ -163,7 +163,7 @@ fn vir_statement_to_fol_statements( preconds.chain(postconds).collect::>() } Statement::Comment(comment) => vec![FolStatement::Comment(comment.comment.clone())], - Statement::LogEvent(_) => vec![], // TODO: Embed in SMT-LIB code + Statement::LogEvent(_) => vec![], // ignored _ => { unimplemented!("Statement {:?} not yet supported", statement); } diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 6093eca47b4..b8771419c2e 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -303,7 +303,8 @@ impl SMTTranslatable for Program { self.domains.iter().for_each(|d| d.build_smt(smt)); self.methods.iter().for_each(|d| d.build_smt(smt)); self.procedures.iter().for_each(|d| d.build_smt(smt)); - // TODO: Everything else + debug_assert!(self.functions.len() == 0); // TODO: Implement + debug_assert!(self.predicates.len() == 0); } } diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index 37d6a1b2326..041b6a5b7bd 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -53,6 +53,9 @@ impl<'a> Verifier<'a> { VerificationBackend::Carbon => { carbon::CarbonFrontendAPI::with(env).new(reporter) } + VerificationBackend::Lithium => { + unreachable!("Lithium is not a JVM-based backend") + } } }; let frontend_instance = jni.unwrap_result(unwrapped_frontend_instance); diff --git a/vir/defs/low/ast/expression.rs b/vir/defs/low/ast/expression.rs index 62076a9b5b6..d8148d235ef 100644 --- a/vir/defs/low/ast/expression.rs +++ b/vir/defs/low/ast/expression.rs @@ -175,6 +175,7 @@ pub struct ContainerOp { pub position: Position, } +#[derive(Copy)] pub enum ContainerOpKind { SeqEmpty, SeqConstructor, From ada673619d3424d754a90d5736a8347f3c2c2bc4 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 11 Apr 2023 17:55:07 +0200 Subject: [PATCH 18/54] Fix lowering for quantifiers that use closures over function arguments --- .../verify/fail/native/quantifier_closures.rs | 20 +++++++++++++++++++ .../verify/pass/native/quantifier_closures.rs | 15 ++++++++++++++ .../middle/core_proof/types/interface.rs | 15 ++++++++++++++ .../high/operations_internal/expression.rs | 7 ++++++- 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 prusti-tests/tests/verify/fail/native/quantifier_closures.rs create mode 100644 prusti-tests/tests/verify/pass/native/quantifier_closures.rs diff --git a/prusti-tests/tests/verify/fail/native/quantifier_closures.rs b/prusti-tests/tests/verify/fail/native/quantifier_closures.rs new file mode 100644 index 00000000000..828af368c9c --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/quantifier_closures.rs @@ -0,0 +1,20 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +#[requires(forall(|x: i32| x * x >= foo * bar))] //~ ERROR precondition might not hold +fn test_closure_1(foo: i32, bar: i32) -> i32 { + foo + bar + 1 +} + +#[requires(forall(|x: i32| x * x >= foo * bar))] //TODO: both errors reported +fn test_closure_2(foo: i32, bar: i32) -> i32 { + foo + bar + 1 +} + +fn main() { + test_closure_1(5, 3); //TODO: error reported here + + let arg1 = 1; + let arg2 = 2; + test_closure_2(arg1, arg2); //TODO: error reported here +} diff --git a/prusti-tests/tests/verify/pass/native/quantifier_closures.rs b/prusti-tests/tests/verify/pass/native/quantifier_closures.rs new file mode 100644 index 00000000000..820c3fd91f6 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/quantifier_closures.rs @@ -0,0 +1,15 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +#[requires(forall(|x: i32| x * x >= foo * bar))] +fn test_closure(foo: i32, bar: i32) -> i32 { + foo + bar + 1 +} + +fn main() { + test_closure(0, 0); + + let arg1 = 0; + let arg2 = 0; + test_closure(arg1, arg2); +} diff --git a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs index 850231d9a17..de38f0fa6f2 100644 --- a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs @@ -226,6 +226,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { self.register_struct_constructor(&domain_name, Vec::new())?; self.encode_validity_axioms_struct(&domain_name, Vec::new(), false.into())?; } + vir_mid::TypeDecl::Closure(closure) => { + // closure has a name and array of arguments, which represent the "saved" parameters of the function + // it is not a function, just a struct that remembers the arguments at entry time + let mut parameters = Vec::new(); + for (ix, argument) in closure.arguments.iter().enumerate() { + self.ensure_type_definition(&argument)?; + parameters.push(vir_low::VariableDecl::new( + format!("_{}", ix), + argument.to_snapshot(self)?, + )); + } + + self.register_struct_constructor(&domain_name, parameters.clone())?; + self.encode_validity_axioms_struct(&domain_name, parameters, true.into())?; + } _ => unimplemented!("type: {:?}", type_decl), }; Ok(()) diff --git a/vir/defs/high/operations_internal/expression.rs b/vir/defs/high/operations_internal/expression.rs index 72cdd733cdc..e87b62f0458 100644 --- a/vir/defs/high/operations_internal/expression.rs +++ b/vir/defs/high/operations_internal/expression.rs @@ -354,7 +354,7 @@ impl Expression { // (2) rename with a fresh name the quantified variables that conflict with `dst`. for (src, dst) in self.replacements.iter() { if quantifier.variables.contains(&src.get_base()) - || quantifier.variables.contains(&dst.get_base()) + || (dst.is_place() && quantifier.variables.contains(&dst.get_base())) { unimplemented!( "replace_multiple_places doesn't handle replacements that conflict \ @@ -429,6 +429,11 @@ impl Expression { base: box Expression::AddrOf(AddrOf { base, .. }), .. }) => *base, + Expression::Deref(Deref { + base, + ty: Type::Closure(_), + .. + }) => *base, Expression::Field(Field { field, base: box Expression::Constructor(Constructor { arguments, .. }), From 6370a9313d2765726b274ccacf5454f1f3680430 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 19 Apr 2023 00:33:11 +0200 Subject: [PATCH 19/54] Add sum-of-primes test file --- native-verifier/src/smt_lib.rs | 4 +- native-verifier/src/verifier.rs | 4 + .../tests/verify/pass/native/sum_of_primes.rs | 278 ++++++++++++++++++ 3 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 prusti-tests/tests/verify/pass/native/sum_of_primes.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index b8771419c2e..2b612753ac3 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -303,8 +303,8 @@ impl SMTTranslatable for Program { self.domains.iter().for_each(|d| d.build_smt(smt)); self.methods.iter().for_each(|d| d.build_smt(smt)); self.procedures.iter().for_each(|d| d.build_smt(smt)); - debug_assert!(self.functions.len() == 0); // TODO: Implement - debug_assert!(self.predicates.len() == 0); + assert!(self.functions.len() == 0); // TODO: Implement + assert!(self.predicates.len() == 0); } } diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 071d1c71b44..57c0772d308 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -81,6 +81,10 @@ impl Verifier { String::from_utf8(output.stdout)? } else { let err = String::from_utf8(output.stdout)?; + + // only lines that contain "error" are actual errors + let err = err.lines().filter(|line| line.contains("error")).collect::>().join("\n"); + Err(err)? } }; diff --git a/prusti-tests/tests/verify/pass/native/sum_of_primes.rs b/prusti-tests/tests/verify/pass/native/sum_of_primes.rs new file mode 100644 index 00000000000..53040e7a667 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/sum_of_primes.rs @@ -0,0 +1,278 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +pub struct VecWrapperI32 { + v: Vec, +} + +impl VecWrapperI32 { + #[trusted] + #[pure] + #[terminates] + #[ensures (0 <= result)] + pub fn len(&self) -> isize { + self.v.len() as isize + } + + #[trusted] + #[ensures (result.len() == 0)] + pub fn new() -> Self { + VecWrapperI32 { v: Vec::new() } + } + + #[trusted] + #[pure] + #[terminates] + #[requires (0 <= index && index < self.len())] + pub fn lookup(&self, index: isize) -> isize { + self.v[index as usize] + } + + #[trusted] + #[ensures (self.len() == old(self.len()) + 1)] + #[ensures (self.lookup(old(self.len())) == value)] + #[ensures (forall(|i: isize| (0 <= i && i < old(self.len())) ==> + self.lookup(i) == old(self.lookup(i))))] + pub fn push(&mut self, value: isize) { + self.v.push(value); + } +} + +struct Matrix { + _ghost_y_size: usize, + _ghost_x_size: usize, + vec: Vec>, +} + +impl Matrix { + #[trusted] + #[requires(0 < y_size)] + #[requires(0 < x_size)] + #[ensures(result.y_size() == y_size)] + #[ensures(result.x_size() == x_size)] + #[ensures(forall(|y: isize, x: isize| + (0 <= x && x < result.x_size() && 0 <= y && y < result.y_size()) ==> + result.lookup(y, x) == 0))] + fn new(y_size: isize, x_size: isize) -> Self { + Self { + _ghost_y_size: y_size as usize, + _ghost_x_size: x_size as usize, + vec: vec![vec![0; y_size as usize]; x_size as usize], + } + } + + #[pure] + #[terminates] + #[trusted] + #[ensures(0 < result)] + fn x_size(&self) -> isize { + self._ghost_x_size as isize + } + + #[pure] + #[terminates] + #[trusted] + #[ensures(0 < result)] + fn y_size(&self) -> isize { + self._ghost_y_size as isize + } + + #[trusted] + #[requires(0 <= y && y < self.y_size())] + #[requires(0 <= x && x < self.x_size())] + #[ensures(self.y_size() == old(self.y_size()))] + #[ensures(self.x_size() == old(self.x_size()))] + #[ensures(forall(|i: isize, j: isize| + (i >= 0 && i < y && j >= 0 && j < self.x_size()) ==> (self.lookup(i, j) == old(self.lookup(i, j)))))] + #[ensures(self.lookup(y, x) == value)] + #[ensures(forall(|i: isize, j: isize| + (0 <= i && i < self.y_size() && + 0 <= j && j < self.x_size() && !(j == x && i == y)) ==> + self.lookup(i, j) == old(self.lookup(i, j))))] + fn set(&mut self, y: isize, x: isize, value: isize) -> () { + self.vec[y as usize][x as usize] = value + } + + #[pure] + #[terminates] + #[trusted] + #[requires(0 <= y && y < self.y_size())] + #[requires(0 <= x && x < self.x_size())] + fn lookup(&self, y: isize, x: isize) -> isize { + self.vec[y as usize][x as usize] + } +} + +// Recursive solution + +#[trusted] +#[pure] +#[terminates] +#[ensures(result == a + b)] +fn add(a: isize, b: isize) -> isize { + a.checked_add(b).unwrap() +} + +#[pure] +#[terminates] +#[requires(n <= 1120)] +#[requires(k >= 0 && k <= 14)] +#[requires(primes.len() > 0)] +#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2)))] +#[ensures(result == sum_of_different_primes_rec_helper(primes, n, k, primes.len() - 1))] +fn sum_of_different_primes_rec(primes: &VecWrapperI32, n: isize, k: isize) -> isize { + sum_of_different_primes_rec_helper(primes, n, k, primes.len() - 1) +} + +#[pure] +#[terminates(trusted)] +#[requires(n <= 1120)] +#[requires(k >= 0 && k <= 14)] +#[requires(idx_prime >= -1 && idx_prime < primes.len())] +#[requires(primes.len() > 0)] +#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2)))] +fn sum_of_different_primes_rec_helper( + primes: &VecWrapperI32, + n: isize, + k: isize, + idx_prime: isize, +) -> isize { + if k == 0 && n == 0 { + 1 + } else if k <= 0 || n < 0 { + 0 + } else if idx_prime == -1 { + 0 + } else { + let take = sum_of_different_primes_rec_helper( + primes, + n - primes.lookup(idx_prime), + k - 1, + primes.len() - 1, + ); + let leave = sum_of_different_primes_rec_helper(primes, n, k, idx_prime - 1); + add(leave, take) + } +} + +#[requires(n <= 1120)] +#[requires(k >= 0 && k <= 14)] +#[requires(primes.len() > 0)] +#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2)))] +#[ensures((k > 0 && n >= 0) ==> result == sum_of_different_primes_rec_helper(primes, n, k, primes.len() - 1))] +#[ensures(result == sum_of_different_primes_rec(primes, n, k))] +fn sum_of_different_primes_rec_iter(primes: &VecWrapperI32, n: isize, k: isize) -> isize { + if k == 0 && n == 0 { + 1 + } else if k <= 0 || n < 0 { + 0 + } else { + let mut idx = 0isize; + let mut answer = 0; + while idx < primes.len() { + body_invariant!(idx >= 0 && idx < primes.len()); + body_invariant!(n >= 0); + body_invariant!(answer == sum_of_different_primes_rec_helper(primes, n, k, idx - 1)); + answer = add( + answer, + sum_of_different_primes_rec_iter(primes, n - primes.lookup(idx), k - 1), + ); + idx += 1; + } + answer + } +} + +// DP Solution + +#[requires(n > 0 && n <= 1120)] +#[requires(k > 0 && k <= 14)] +#[requires(primes.len() > 0)] +#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2 && primes.lookup(k) <= n)))] +#[ensures(result == sum_of_different_primes_rec(primes, n, k))] +fn sum_of_different_primes(primes: &VecWrapperI32, n: isize, k: isize) -> isize { + let size_k = k + 1; + let size_n = n + 1; + let mut dp = Matrix::new(size_k, size_n); + let mut idx_k = 1isize; + dp.set(0, 0, 1); + while idx_k < size_k { + body_invariant!(idx_k >= 0 && idx_k < size_k); + body_invariant!(dp.y_size() == size_k && dp.x_size() == size_n); + body_invariant!(idx_k > 0); + body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < idx_k && j >= 0 && j <= n) ==> dp.lookup(i, j) == sum_of_different_primes_rec(primes, j, i))); + let mut idx_n = 0isize; + while idx_n < size_n { + body_invariant!(idx_n >= 0 && idx_n < size_n); + body_invariant!(dp.y_size() == size_k && dp.x_size() == size_n); + body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < idx_k && j >= 0 && j < size_n) ==> dp.lookup(i, j) == sum_of_different_primes_rec(primes, j, i))); + body_invariant!(forall(|i: isize| (i >= 0 && i < idx_n) ==> dp.lookup(idx_k, i) == sum_of_different_primes_rec(primes, i, idx_k))); + let mut idx_prime = 0isize; + let mut idx_prev = 0isize; + let mut sum = 0; + let primes_len = primes.len(); + while idx_prime < primes_len { + body_invariant!(idx_k > 0 && idx_k < size_k); + body_invariant!(idx_prime >= 0 && idx_prime < primes_len); + body_invariant!(dp.y_size() == size_k && dp.x_size() == size_n); + body_invariant!(idx_n >= 0 && idx_n < size_n); + body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < idx_k && j >= 0 && j <= n) ==> dp.lookup(i, j) == sum_of_different_primes_rec(primes, j, i))); + body_invariant!(idx_prev <= n); + body_invariant!( + sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime - 1) + ); + idx_prev = idx_n - primes.lookup(idx_prime); + if idx_prev >= 0 { + assert!( + dp.lookup(idx_k - 1, idx_prev) + == sum_of_different_primes_rec(primes, idx_prev, idx_k - 1) + ); + assert!( + dp.lookup(idx_k - 1, idx_prev) + == sum_of_different_primes_rec_helper( + primes, + idx_prev, + idx_k - 1, + primes_len - 1 + ) + ); + sum = add(sum, dp.lookup(idx_k - 1, idx_prev)); + assert!( + sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime) + ); + } else { + assert!( + sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime) + == sum + sum_of_different_primes_rec(primes, idx_prev, idx_k - 1) + ); + assert!(sum_of_different_primes_rec(primes, idx_prev, idx_k - 1) == 0); + assert!( + sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime) + ); + } + idx_prime += 1; + } + + assert!( + sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, primes_len - 1) + ); + assert!(idx_k > 0 && idx_n >= 0); + assert!(idx_prime == primes_len); + assert!(sum == sum_of_different_primes_rec(primes, idx_n, idx_k)); + dp.set(idx_k, idx_n, sum); + idx_n += 1; + } + idx_k += 1; + } + dp.lookup(k, n) +} + +fn main() { + let mut wrap = VecWrapperI32::new(); + wrap.push(2); + wrap.push(3); + wrap.push(5); + + sum_of_different_primes_rec_iter(&wrap, 2, 5); + sum_of_different_primes(&wrap, 2, 5); +} From 64d8cd20b29b37cae0f0493943ae9f5d44b0b482 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 19 Apr 2023 03:48:20 +0200 Subject: [PATCH 20/54] Add super sale test case --- native-verifier/src/fol.rs | 38 ++- .../tests/verify/pass/native/sum_of_primes.rs | 4 +- .../tests/verify/pass/native/super_sale.rs | 278 ++++++++++++++++++ .../high/procedures/inference/ensurer.rs | 2 + .../high/procedures/inference/visitor/mod.rs | 5 +- 5 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 prusti-tests/tests/verify/pass/native/super_sale.rs diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 970bf206aa3..14a05d1da0a 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -56,9 +56,44 @@ fn vir_statement_to_fol_statements( .collect(); } + if cond.then_branch.is_empty() ^ cond.else_branch.is_empty() { + let branch = if cond.then_branch.is_empty() { + &cond.else_branch + } else { + &cond.then_branch + }; + // if the branch is an assertion, emit it as an implication from the guard + let mut statements = vec![]; + for s in branch { + if let Statement::Assert(assert) = s { + let implication = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::Implies, + left: Box::new(cond.guard.clone()), + right: Box::new(assert.expression.clone()), + position: assert.position, + }); + statements.push(FolStatement::Assert(implication)); + } else if let Statement::Inhale(inhale) = s { + let implication = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::Implies, + left: Box::new(cond.guard.clone()), + right: Box::new(inhale.expression.clone()), + position: inhale.position, + }); + statements.push(FolStatement::Assume(implication)); + } else { + unimplemented!( + "Non-assertion statements in conditional branches not supported: {}", + s + ) + } + } + return statements; + } + if !(cond.then_branch.is_empty() && cond.else_branch.is_empty()) { unimplemented!( - "Non-trivial conditional statements not supported!!\nGuard: {}\n\nThen-branch:\n{}\n\nElse-branch:\n{}", + "Non-trivial conditional statements not supported!!\nGuard: {}\n\nThen-branch:\n{}\n\nElse-branch:\n{}\n", cond.guard, cond.then_branch .iter() @@ -72,6 +107,7 @@ fn vir_statement_to_fol_statements( .join(";\n") ); } + vec![] } Statement::MethodCall(method_call) => { diff --git a/prusti-tests/tests/verify/pass/native/sum_of_primes.rs b/prusti-tests/tests/verify/pass/native/sum_of_primes.rs index 53040e7a667..5cdcbeda0df 100644 --- a/prusti-tests/tests/verify/pass/native/sum_of_primes.rs +++ b/prusti-tests/tests/verify/pass/native/sum_of_primes.rs @@ -273,6 +273,6 @@ fn main() { wrap.push(3); wrap.push(5); - sum_of_different_primes_rec_iter(&wrap, 2, 5); - sum_of_different_primes(&wrap, 2, 5); + sum_of_different_primes_rec_iter(&wrap, 3, 3); + sum_of_different_primes(&wrap, 3, 3); } diff --git a/prusti-tests/tests/verify/pass/native/super_sale.rs b/prusti-tests/tests/verify/pass/native/super_sale.rs new file mode 100644 index 00000000000..249c913a324 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/super_sale.rs @@ -0,0 +1,278 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +pub struct VecWrapperI32 { + v: Vec, +} + +impl VecWrapperI32 { + #[trusted] + #[pure] + #[terminates] + #[ensures (0 <= result)] + pub fn len(&self) -> isize { + self.v.len() as isize + } + + #[trusted] + #[ensures (result.len() == 0)] + pub fn new() -> Self { + VecWrapperI32 { v: Vec::new() } + } + + #[trusted] + #[pure] + #[terminates] + #[requires (0 <= index && index < self.len())] + pub fn lookup(&self, index: isize) -> isize { + self.v[index as usize] + } + + #[trusted] + #[ensures (self.len() == old(self.len()) + 1)] + #[ensures (self.lookup(old(self.len())) == value)] + #[ensures (forall(|i: isize| (0 <= i && i < old(self.len())) ==> + self.lookup(i) == old(self.lookup(i))))] + pub fn push(&mut self, value: isize) { + self.v.push(value); + } +} + +#[pure] +#[terminates] +#[ensures (result >= a && result >= b)] +#[ensures (result == a || result == b)] +fn max(a: isize, b: isize) -> isize { + if a < b { + b + } else { + a + } +} + +struct Matrix { + _ghost_y_size: usize, + _ghost_x_size: usize, + vec: Vec>, +} + +impl Matrix { + #[trusted] + #[requires(0 < y_size)] + #[requires(0 < x_size)] + #[ensures(result.y_size() == y_size)] + #[ensures(result.x_size() == x_size)] + #[ensures(forall(|y: isize, x: isize| + (0 <= x && x < result.x_size() && 0 <= y && y < result.y_size()) ==> + result.lookup(y, x) == 0))] + fn new(y_size: isize, x_size: isize) -> Self { + Self { + _ghost_y_size: y_size as usize, + _ghost_x_size: x_size as usize, + vec: vec![vec![0; y_size as usize]; x_size as usize], + } + } + + #[pure] + #[terminates] + #[trusted] + #[ensures(0 < result)] + fn x_size(&self) -> isize { + self._ghost_x_size as isize + } + + #[pure] + #[terminates] + #[trusted] + #[ensures(0 < result)] + fn y_size(&self) -> isize { + self._ghost_y_size as isize + } + + #[trusted] + #[requires(0 <= y && y < self.y_size())] + #[requires(0 <= x && x < self.x_size())] + #[ensures(self.y_size() == old(self.y_size()))] + #[ensures(self.x_size() == old(self.x_size()))] + #[ensures(forall(|i: isize, j: isize| + (i >= 0 && i < y && j >= 0 && j < self.x_size()) ==> (self.lookup(i, j) == old(self.lookup(i, j)))))] + #[ensures(self.lookup(y, x) == value)] + #[ensures(forall(|i: isize, j: isize| + (0 <= i && i < self.y_size() && + 0 <= j && j < self.x_size() && !(j == x && i == y)) ==> + self.lookup(i, j) == old(self.lookup(i, j))))] + fn set(&mut self, y: isize, x: isize, value: isize) -> () { + self.vec[y as usize][x as usize] = value + } + + #[pure] + #[terminates] + #[trusted] + #[requires(0 <= y && y < self.y_size())] + #[requires(0 <= x && x < self.x_size())] + fn lookup(&self, y: isize, x: isize) -> isize { + self.vec[y as usize][x as usize] + } +} + +// Recursive solution + +#[pure] +#[terminates(trusted)] +#[requires(prices.len() == weights.len())] +#[requires(prices.len() > 0 && weights.len() <= 1000)] +#[requires(index < prices.len() && index >= -1)] +#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] +#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] +#[ensures(result <= (index + 1) * 1000)] +#[ensures(result >= 0)] +fn solve_rec( + prices: &VecWrapperI32, + weights: &VecWrapperI32, + index: isize, + remaining_weight: isize, +) -> isize { + if remaining_weight <= 0 || index < 0 { + 0 + } else { + assert!(index >= 0); + if weights.lookup(index) <= remaining_weight { + max( + solve_rec(prices, weights, index - 1, remaining_weight), + prices.lookup(index) + + solve_rec( + prices, + weights, + index - 1, + remaining_weight - weights.lookup(index), + ), + ) + } else { + solve_rec(prices, weights, index - 1, remaining_weight) + } + } +} + +// // DP solution + +#[requires(prices.len() == weights.len())] +#[requires(prices.len() > 0 && weights.len() <= 1000)] +#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] +#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] +#[requires(dp.y_size() == 31 && dp.x_size() == prices.len())] +#[requires(index_weight >= 1 && index_weight < dp.y_size())] +#[requires(index_object >= 0 && index_object < dp.x_size())] +#[requires(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] +#[requires(forall(|i: isize| (i >= 0 && i < index_object) ==> dp.lookup(index_weight, i) == solve_rec(prices, weights, i, index_weight)))] +#[ensures(result == solve_rec(prices, weights, index_object, index_weight))] +#[ensures(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] +#[ensures(forall(|i: isize| (i >= 0 && i < index_object) ==> dp.lookup(index_weight, i) == solve_rec(prices, weights, i, index_weight)))] +fn helper( + prices: &VecWrapperI32, + weights: &VecWrapperI32, + index_weight: isize, + index_object: isize, + dp: &Matrix, +) -> isize { + let mut answer = 0; + let index_before = index_object - 1; + if index_before == -1 { + if index_weight >= weights.lookup(index_object) { + answer = prices.lookup(index_object); + } else { + answer = 0; + } + } else { + if index_weight >= weights.lookup(index_object) { + let remaining_weight = index_weight - weights.lookup(index_object); + assert!( + dp.lookup(remaining_weight, index_object - 1) + == solve_rec(prices, weights, index_object - 1, remaining_weight) + ); + assert!(dp.lookup(remaining_weight, index_object - 1) <= index_object * 1000); + answer = max( + dp.lookup(index_weight, index_object - 1), + prices.lookup(index_object) + dp.lookup(remaining_weight, index_object - 1), + ); + } else { + answer = dp.lookup(index_weight, index_object - 1); + } + } + answer +} + +#[trusted] // TODO: remove trusted +#[requires(prices.len() == weights.len())] +#[requires(prices.len() > 0 && weights.len() <= 1000)] +#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] +#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] +#[requires(dp.y_size() == 31 && dp.x_size() == prices.len())] +#[requires(index_weight >= 1 && index_weight < dp.y_size())] +#[requires(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] +#[ensures(dp.y_size() == old(dp.y_size()))] +#[ensures(dp.x_size() == old(dp.x_size()))] +#[ensures(forall(|i: isize, j: isize| (i >= 0 && i <= index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] +fn helper_loop( + prices: &VecWrapperI32, + weights: &VecWrapperI32, + index_weight: isize, + dp: &mut Matrix, +) { + let mut index_object = 0isize; + let n = prices.len(); + while index_object < dp.x_size() { + body_invariant!(index_weight >= 1 && index_weight < dp.y_size()); + body_invariant!(index_object >= 0 && index_object < dp.x_size()); + body_invariant!(dp.y_size() == 31 && dp.x_size() == prices.len()); + body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); + body_invariant!(forall(|i: isize| (i >= 0 && i < index_object) ==> dp.lookup(index_weight, i) == solve_rec(prices, weights, i, index_weight))); + let answer = helper(prices, weights, index_weight, index_object, &dp); + dp.set(index_weight, index_object, answer); + index_object += 1; + } +} + +#[requires(prices.len() == weights.len())] +#[requires(prices.len() > 0 && weights.len() <= 1000)] +#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] +#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] +#[requires(forall(|k: isize| (k >= 0 && k < max_weights.len()) ==> max_weights.lookup(k) >= 1 && max_weights.lookup(k) <= 30))] +#[ensures(result.len() == max_weights.len())] +#[ensures(forall(|k: isize| (k >= 0 && k < result.len()) ==> result.lookup(k) == solve_rec(prices, weights, prices.len() - 1, max_weights.lookup(k))))] +fn super_sale( + prices: &VecWrapperI32, + weights: &VecWrapperI32, + max_weights: &VecWrapperI32, +) -> VecWrapperI32 { + let mut answer = VecWrapperI32::new(); + let g = max_weights.len(); + let n = prices.len(); + let max_weight = 31; + let mut dp = Matrix::new(max_weight, n); + let mut index_weight = 1isize; + while index_weight <= 30 { + body_invariant!(dp.y_size() == max_weight && dp.x_size() == n); + body_invariant!(index_weight >= 1 && index_weight < max_weight); + body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); + helper_loop(prices, weights, index_weight, &mut dp); + prusti_assume!(dp.y_size() == max_weight && dp.x_size() == n); // TODO: Postcondition should handle this + index_weight += 1; + prusti_assume!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); + // TODO: Postcondition should handle this + } + + let mut index_person = 0isize; + while index_person < g { + body_invariant!(index_person >= 0 && index_person < g); + body_invariant!(answer.len() == index_person); + body_invariant!(forall(|k: isize| (k >= 0 && k < g) ==> max_weights.lookup(k) >= 1 && max_weights.lookup(k) <= 30)); + body_invariant!(forall(|k: isize| (k >= 0 && k < index_person) ==> answer.lookup(k) == solve_rec(prices, weights, prices.len() - 1, max_weights.lookup(k)))); + let w = max_weights.lookup(index_person); + let cur_answer = dp.lookup(w, n - 1); + answer.push(cur_answer); + index_person += 1; + } + answer +} + +fn main() {} diff --git a/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs b/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs index 5209826b1f7..60888307a01 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs @@ -131,6 +131,7 @@ pub(in super::super) fn ensure_required_permission( required_permission: Permission, actions: &mut Vec, ) -> SpannedEncodingResult<()> { + return Ok(()); // TODO: Remove permission reasoning state.debug_print(); let (place, permission_kind) = match required_permission { @@ -319,6 +320,7 @@ fn ensure_permission_in_state( permission_kind: PermissionKind, actions: &mut Vec, ) -> SpannedEncodingResult { + return Ok(true); // TODO: Remove permission reasoning predicate_state.check_consistency(); let to_drop = if check_contains_place(predicate_state, &place, permission_kind)? { debug!(" satisfied: {:?} {}", permission_kind, place); diff --git a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs index 01938a7dc88..afc024533fb 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs @@ -218,8 +218,9 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { state.check_consistency(); let actions = ensure_required_permissions(self, state, consumed_permissions.clone())?; self.process_actions(actions)?; - state.remove_permissions(&consumed_permissions)?; - state.insert_permissions(produced_permissions)?; + // TODO: Remove permission reasoning + // state.remove_permissions(&consumed_permissions)?; + // state.insert_permissions(produced_permissions)?; match &statement { vir_typed::Statement::ObtainMutRef(_) => { // The requirements already performed the needed changes. From fda0031fbae8daa8845674c892209b92faeaccd9 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 25 Apr 2023 19:35:10 +0200 Subject: [PATCH 21/54] Update test cases --- native-verifier/src/smt_lib.rs | 5 +- .../tests/verify/pass/native/sum_of_primes.rs | 278 ------------------ .../tests/verify/pass/native/super_sale.rs | 8 +- .../tests/verify/pass/native/the_jackpot.rs | 229 +++++++++++++++ 4 files changed, 236 insertions(+), 284 deletions(-) delete mode 100644 prusti-tests/tests/verify/pass/native/sum_of_primes.rs create mode 100644 prusti-tests/tests/verify/pass/native/the_jackpot.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 2b612753ac3..fe9332d9ac4 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -92,7 +92,7 @@ impl SMTLib { // assume predicate afterwards self.add_code(format!( - "(assert {}) ; assumed after assert", + "(assert {}) ; assumed after assert\n", expression.to_smt() )); } @@ -267,6 +267,7 @@ impl ToString for SMTLib { .filter(|&line| line != "(assert true)") // TODO: Optimization module .collect::>() .join("\n") + .replace("(push)\n(echo \"position: 0\")\n(check-sat)\n(pop)", "") } } @@ -435,7 +436,7 @@ impl SMTTranslatable for Expression { match self { Expression::Local(local) => local.variable.name.clone(), Expression::Field(_) => unimplemented!(), - Expression::LabelledOld(_) => unimplemented!(), + Expression::LabelledOld(_) => unimplemented!("old expressions"), Expression::Constant(constant) => match &constant.value { ConstantValue::Bool(bool) => bool.to_string(), ConstantValue::Int(i64) => i64.to_string(), diff --git a/prusti-tests/tests/verify/pass/native/sum_of_primes.rs b/prusti-tests/tests/verify/pass/native/sum_of_primes.rs deleted file mode 100644 index 5cdcbeda0df..00000000000 --- a/prusti-tests/tests/verify/pass/native/sum_of_primes.rs +++ /dev/null @@ -1,278 +0,0 @@ -// compile-flags: -Pviper_backend=Lithium -use prusti_contracts::*; - -pub struct VecWrapperI32 { - v: Vec, -} - -impl VecWrapperI32 { - #[trusted] - #[pure] - #[terminates] - #[ensures (0 <= result)] - pub fn len(&self) -> isize { - self.v.len() as isize - } - - #[trusted] - #[ensures (result.len() == 0)] - pub fn new() -> Self { - VecWrapperI32 { v: Vec::new() } - } - - #[trusted] - #[pure] - #[terminates] - #[requires (0 <= index && index < self.len())] - pub fn lookup(&self, index: isize) -> isize { - self.v[index as usize] - } - - #[trusted] - #[ensures (self.len() == old(self.len()) + 1)] - #[ensures (self.lookup(old(self.len())) == value)] - #[ensures (forall(|i: isize| (0 <= i && i < old(self.len())) ==> - self.lookup(i) == old(self.lookup(i))))] - pub fn push(&mut self, value: isize) { - self.v.push(value); - } -} - -struct Matrix { - _ghost_y_size: usize, - _ghost_x_size: usize, - vec: Vec>, -} - -impl Matrix { - #[trusted] - #[requires(0 < y_size)] - #[requires(0 < x_size)] - #[ensures(result.y_size() == y_size)] - #[ensures(result.x_size() == x_size)] - #[ensures(forall(|y: isize, x: isize| - (0 <= x && x < result.x_size() && 0 <= y && y < result.y_size()) ==> - result.lookup(y, x) == 0))] - fn new(y_size: isize, x_size: isize) -> Self { - Self { - _ghost_y_size: y_size as usize, - _ghost_x_size: x_size as usize, - vec: vec![vec![0; y_size as usize]; x_size as usize], - } - } - - #[pure] - #[terminates] - #[trusted] - #[ensures(0 < result)] - fn x_size(&self) -> isize { - self._ghost_x_size as isize - } - - #[pure] - #[terminates] - #[trusted] - #[ensures(0 < result)] - fn y_size(&self) -> isize { - self._ghost_y_size as isize - } - - #[trusted] - #[requires(0 <= y && y < self.y_size())] - #[requires(0 <= x && x < self.x_size())] - #[ensures(self.y_size() == old(self.y_size()))] - #[ensures(self.x_size() == old(self.x_size()))] - #[ensures(forall(|i: isize, j: isize| - (i >= 0 && i < y && j >= 0 && j < self.x_size()) ==> (self.lookup(i, j) == old(self.lookup(i, j)))))] - #[ensures(self.lookup(y, x) == value)] - #[ensures(forall(|i: isize, j: isize| - (0 <= i && i < self.y_size() && - 0 <= j && j < self.x_size() && !(j == x && i == y)) ==> - self.lookup(i, j) == old(self.lookup(i, j))))] - fn set(&mut self, y: isize, x: isize, value: isize) -> () { - self.vec[y as usize][x as usize] = value - } - - #[pure] - #[terminates] - #[trusted] - #[requires(0 <= y && y < self.y_size())] - #[requires(0 <= x && x < self.x_size())] - fn lookup(&self, y: isize, x: isize) -> isize { - self.vec[y as usize][x as usize] - } -} - -// Recursive solution - -#[trusted] -#[pure] -#[terminates] -#[ensures(result == a + b)] -fn add(a: isize, b: isize) -> isize { - a.checked_add(b).unwrap() -} - -#[pure] -#[terminates] -#[requires(n <= 1120)] -#[requires(k >= 0 && k <= 14)] -#[requires(primes.len() > 0)] -#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2)))] -#[ensures(result == sum_of_different_primes_rec_helper(primes, n, k, primes.len() - 1))] -fn sum_of_different_primes_rec(primes: &VecWrapperI32, n: isize, k: isize) -> isize { - sum_of_different_primes_rec_helper(primes, n, k, primes.len() - 1) -} - -#[pure] -#[terminates(trusted)] -#[requires(n <= 1120)] -#[requires(k >= 0 && k <= 14)] -#[requires(idx_prime >= -1 && idx_prime < primes.len())] -#[requires(primes.len() > 0)] -#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2)))] -fn sum_of_different_primes_rec_helper( - primes: &VecWrapperI32, - n: isize, - k: isize, - idx_prime: isize, -) -> isize { - if k == 0 && n == 0 { - 1 - } else if k <= 0 || n < 0 { - 0 - } else if idx_prime == -1 { - 0 - } else { - let take = sum_of_different_primes_rec_helper( - primes, - n - primes.lookup(idx_prime), - k - 1, - primes.len() - 1, - ); - let leave = sum_of_different_primes_rec_helper(primes, n, k, idx_prime - 1); - add(leave, take) - } -} - -#[requires(n <= 1120)] -#[requires(k >= 0 && k <= 14)] -#[requires(primes.len() > 0)] -#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2)))] -#[ensures((k > 0 && n >= 0) ==> result == sum_of_different_primes_rec_helper(primes, n, k, primes.len() - 1))] -#[ensures(result == sum_of_different_primes_rec(primes, n, k))] -fn sum_of_different_primes_rec_iter(primes: &VecWrapperI32, n: isize, k: isize) -> isize { - if k == 0 && n == 0 { - 1 - } else if k <= 0 || n < 0 { - 0 - } else { - let mut idx = 0isize; - let mut answer = 0; - while idx < primes.len() { - body_invariant!(idx >= 0 && idx < primes.len()); - body_invariant!(n >= 0); - body_invariant!(answer == sum_of_different_primes_rec_helper(primes, n, k, idx - 1)); - answer = add( - answer, - sum_of_different_primes_rec_iter(primes, n - primes.lookup(idx), k - 1), - ); - idx += 1; - } - answer - } -} - -// DP Solution - -#[requires(n > 0 && n <= 1120)] -#[requires(k > 0 && k <= 14)] -#[requires(primes.len() > 0)] -#[requires(forall(|k: isize| (k >= 0 && k < primes.len()) ==> (primes.lookup(k) >= 2 && primes.lookup(k) <= n)))] -#[ensures(result == sum_of_different_primes_rec(primes, n, k))] -fn sum_of_different_primes(primes: &VecWrapperI32, n: isize, k: isize) -> isize { - let size_k = k + 1; - let size_n = n + 1; - let mut dp = Matrix::new(size_k, size_n); - let mut idx_k = 1isize; - dp.set(0, 0, 1); - while idx_k < size_k { - body_invariant!(idx_k >= 0 && idx_k < size_k); - body_invariant!(dp.y_size() == size_k && dp.x_size() == size_n); - body_invariant!(idx_k > 0); - body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < idx_k && j >= 0 && j <= n) ==> dp.lookup(i, j) == sum_of_different_primes_rec(primes, j, i))); - let mut idx_n = 0isize; - while idx_n < size_n { - body_invariant!(idx_n >= 0 && idx_n < size_n); - body_invariant!(dp.y_size() == size_k && dp.x_size() == size_n); - body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < idx_k && j >= 0 && j < size_n) ==> dp.lookup(i, j) == sum_of_different_primes_rec(primes, j, i))); - body_invariant!(forall(|i: isize| (i >= 0 && i < idx_n) ==> dp.lookup(idx_k, i) == sum_of_different_primes_rec(primes, i, idx_k))); - let mut idx_prime = 0isize; - let mut idx_prev = 0isize; - let mut sum = 0; - let primes_len = primes.len(); - while idx_prime < primes_len { - body_invariant!(idx_k > 0 && idx_k < size_k); - body_invariant!(idx_prime >= 0 && idx_prime < primes_len); - body_invariant!(dp.y_size() == size_k && dp.x_size() == size_n); - body_invariant!(idx_n >= 0 && idx_n < size_n); - body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < idx_k && j >= 0 && j <= n) ==> dp.lookup(i, j) == sum_of_different_primes_rec(primes, j, i))); - body_invariant!(idx_prev <= n); - body_invariant!( - sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime - 1) - ); - idx_prev = idx_n - primes.lookup(idx_prime); - if idx_prev >= 0 { - assert!( - dp.lookup(idx_k - 1, idx_prev) - == sum_of_different_primes_rec(primes, idx_prev, idx_k - 1) - ); - assert!( - dp.lookup(idx_k - 1, idx_prev) - == sum_of_different_primes_rec_helper( - primes, - idx_prev, - idx_k - 1, - primes_len - 1 - ) - ); - sum = add(sum, dp.lookup(idx_k - 1, idx_prev)); - assert!( - sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime) - ); - } else { - assert!( - sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime) - == sum + sum_of_different_primes_rec(primes, idx_prev, idx_k - 1) - ); - assert!(sum_of_different_primes_rec(primes, idx_prev, idx_k - 1) == 0); - assert!( - sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, idx_prime) - ); - } - idx_prime += 1; - } - - assert!( - sum == sum_of_different_primes_rec_helper(primes, idx_n, idx_k, primes_len - 1) - ); - assert!(idx_k > 0 && idx_n >= 0); - assert!(idx_prime == primes_len); - assert!(sum == sum_of_different_primes_rec(primes, idx_n, idx_k)); - dp.set(idx_k, idx_n, sum); - idx_n += 1; - } - idx_k += 1; - } - dp.lookup(k, n) -} - -fn main() { - let mut wrap = VecWrapperI32::new(); - wrap.push(2); - wrap.push(3); - wrap.push(5); - - sum_of_different_primes_rec_iter(&wrap, 3, 3); - sum_of_different_primes(&wrap, 3, 3); -} diff --git a/prusti-tests/tests/verify/pass/native/super_sale.rs b/prusti-tests/tests/verify/pass/native/super_sale.rs index 249c913a324..3c141595a6a 100644 --- a/prusti-tests/tests/verify/pass/native/super_sale.rs +++ b/prusti-tests/tests/verify/pass/native/super_sale.rs @@ -201,7 +201,7 @@ fn helper( answer } -#[trusted] // TODO: remove trusted +#[trusted] // TODO: Error by the author? #[requires(prices.len() == weights.len())] #[requires(prices.len() > 0 && weights.len() <= 1000)] #[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] @@ -255,10 +255,10 @@ fn super_sale( body_invariant!(index_weight >= 1 && index_weight < max_weight); body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); helper_loop(prices, weights, index_weight, &mut dp); - prusti_assume!(dp.y_size() == max_weight && dp.x_size() == n); // TODO: Postcondition should handle this + prusti_assume!(dp.y_size() == max_weight && dp.x_size() == n); // TODO: Postconditions do not handle old-expressions + prusti_assume!(forall(|i: isize, j: isize| (i >= 0 && i <= index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); + // TODO: Postconditions do not handle old-expressions index_weight += 1; - prusti_assume!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); - // TODO: Postcondition should handle this } let mut index_person = 0isize; diff --git a/prusti-tests/tests/verify/pass/native/the_jackpot.rs b/prusti-tests/tests/verify/pass/native/the_jackpot.rs new file mode 100644 index 00000000000..531f6ac0baa --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/the_jackpot.rs @@ -0,0 +1,229 @@ +// compile-flags: -Pviper_backend=Lithium +use prusti_contracts::*; + +pub struct VecWrapperI32 { + v: Vec, +} + +impl VecWrapperI32 { + #[trusted] + #[pure] + #[terminates] + #[ensures (0 <= result)] + pub fn len(&self) -> usize { + self.v.len() + } + + #[trusted] + #[ensures (result.len() == 0)] + pub fn new() -> Self { + VecWrapperI32 { v: Vec::new() } + } + + #[pure] + #[terminates] + #[trusted] + #[requires (0 <= index)] + #[requires (index < self.len())] + pub fn lookup(&self, index: usize) -> i32 { + self.v[index] + } + + #[trusted] + #[ensures (self.len() == old(self.len()) + 1)] + #[ensures (self.lookup(old(self.len())) == value)] + #[ensures (forall(|i: usize| (0 <= i && i < old(self.len())) ==> + self.lookup(i) == old(self.lookup(i))))] + pub fn push(&mut self, value: i32) { + self.v.push(value); + } +} + +#[pure] +#[terminates] +#[ensures (result >= a && result >= b)] +#[ensures (result == a || result == b)] +fn max(a: i32, b: i32) -> i32 { + if a < b { + b + } else { + a + } +} + +// Recursive solution + +#[pure] +#[terminates(Int::new_usize(i))] +#[requires(i >= 0)] +#[ensures(result >= 0)] +fn to_i32(i: usize) -> i32 { + if i == 0 { + 0 + } else { + 1 + to_i32(i - 1) + } +} + +#[pure] +#[terminates(Int::new_usize(i))] +#[requires (i >= 0)] +#[requires (i < seq.len())] +#[requires (seq.len() > 0)] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures(result >= -1000)] +#[ensures(result <= (to_i32(i) + 1) * 10000)] +fn max_seq_sum_rec(seq: &VecWrapperI32, i: usize) -> i32 { + if i == 0 { + seq.lookup(0) + } else { + let prev = max_seq_sum_rec(seq, i - 1); + if prev > 0 { + prev + seq.lookup(i) + } else { + seq.lookup(i) + } + } +} + +#[pure] +#[terminates(Int::new_usize(idx))] +#[requires (seq.len() > 0)] +#[requires (idx >= 0)] +#[requires (idx < seq.len())] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +fn solve_rec(seq: &VecWrapperI32, idx: usize) -> i32 { + if idx == 0 { + max_seq_sum_rec(seq, idx) + } else { + max(max_seq_sum_rec(seq, idx), solve_rec(seq, idx - 1)) + } +} + +#[pure] +#[terminates] +#[requires (seq.len() > 0)] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +fn the_jackpot_rec(seq: &VecWrapperI32) -> i32 { + max(0, solve_rec(seq, seq.len() - 1)) +} + +// Naive Solution + +#[pure] +#[terminates(Int::new_usize(end))] +#[requires (seq.len() > 0)] +#[requires (start >= 0)] +#[requires (start <= end)] +#[requires (end < seq.len())] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures (result <= max_seq_sum_rec(seq, end))] +#[ensures(result >= (to_i32(end - start + 1)) * -10000)] +#[ensures(result <= (to_i32(end - start + 1)) * 10000)] +fn seq_sum(seq: &VecWrapperI32, start: usize, end: usize) -> i32 { + if end == start { + seq.lookup(end) + } else { + seq.lookup(end) + seq_sum(seq, start, end - 1) + } +} + +#[pure] +#[terminates] +#[requires (a >= b)] +#[ensures (result == a - b)] +fn sub(a: usize, b: usize) -> usize { + a - b +} + +#[requires (seq.len() > 0)] +#[requires (start >= 0)] +#[requires (start <= end)] +#[requires (end < seq.len())] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures (result <= solve_rec(seq, end))] +fn max_seq_sum(seq: &VecWrapperI32, start: usize, end: usize) -> i32 { + if start == end { + seq_sum(seq, start, end) + } else { + max(max_seq_sum(seq, start + 1, end), seq_sum(seq, start, end)) + } +} + +#[requires (seq.len() > 0)] +#[requires (end >= 0)] +#[requires (end < seq.len())] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures (result <= solve_rec(seq, end))] +fn solve_naive(seq: &VecWrapperI32, end: usize) -> i32 { + if end == 0 { + max_seq_sum(seq, 0, end) + } else { + max(solve_naive(seq, end - 1), max_seq_sum(seq, 0, end)) + } +} + +#[requires (seq.len() > 0)] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures (result <= the_jackpot_rec(seq))] +fn the_jackpot_naive(seq: &VecWrapperI32) -> i32 { + max(0, solve_naive(seq, seq.len() - 1)) +} + +// Solution >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +#[requires (seq.len() > 0)] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures (result.len() == seq.len())] +#[ensures (forall(|k:usize| (k >= 0 && k < seq.len()) ==> (result.lookup(k) == max_seq_sum_rec(seq, k))))] +fn solve(seq: &VecWrapperI32) -> VecWrapperI32 { + let mut dp = VecWrapperI32::new(); + dp.push(seq.lookup(0)); + let mut i = 1usize; + let len = seq.len(); + while i < len { + body_invariant!(i >= 1); + body_invariant!(i < seq.len()); + body_invariant!(dp.len() == i); + body_invariant!(forall(|k: usize| (k < i && k >= 0) ==> dp.lookup(k) == max_seq_sum_rec(seq, k))); + body_invariant!(forall(|k: usize| (k < i && k >= 0) ==> dp.lookup(k) <= ((k + 1) as i32) * 10000)); + let prev = dp.lookup(i - 1); + if prev > 0 { + dp.push(prev + seq.lookup(i)); + } else { + dp.push(seq.lookup(i)); + } + i += 1; + } + dp +} + +#[requires (seq.len() > 0)] +#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] +#[requires (seq.len() <= 10000)] +#[ensures (result == the_jackpot_rec(seq))] +fn the_jackpot(seq: &VecWrapperI32) -> i32 { + let dp = solve(seq); + let mut answer = seq.lookup(0); + let len = seq.len(); + let mut idx = 1; + while idx < len { + body_invariant!(idx >= 1); + body_invariant!(idx < len); + body_invariant!(answer == solve_rec(seq, idx - 1)); + answer = max(answer, dp.lookup(idx)); + idx += 1; + } + assert!(answer == solve_rec(seq, seq.len() - 1)); + max(0, answer) +} + +pub fn main() {} From 15579d149f52540ec151bcedc7383e9b375e456c Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 26 Apr 2023 15:34:39 +0200 Subject: [PATCH 22/54] Add Euclid test case, fix quantifiers type mismatch --- native-verifier/src/theories/Preamble.smt2 | 1 + .../tests/verify/pass/native/euclid.rs | 117 ++++++++++++++++++ .../snapshots/into_snapshot/common/mod.rs | 26 ++-- 3 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 prusti-tests/tests/verify/pass/native/euclid.rs diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index d346606cd68..f6638695784 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -1,6 +1,7 @@ ; ===== Static preamble ===== (set-logic ALL) +(set-option :timeout 1000) ; --- Floating-point arithmetic --- diff --git a/prusti-tests/tests/verify/pass/native/euclid.rs b/prusti-tests/tests/verify/pass/native/euclid.rs new file mode 100644 index 00000000000..d6cb2591262 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/euclid.rs @@ -0,0 +1,117 @@ +use prusti_contracts::*; + +macro_rules! divides_prop { + ($x:expr, $y:expr, $z:expr) => { + $z >= 0 && $x * $z == $y + }; +} + +#[pure] +#[terminates] +#[trusted] +#[requires(x > 0 && y > 0)] +#[ensures(x <= y)] +fn divides(x: i32, y: i32) -> bool { + unimplemented!() +} + +#[pure] +#[terminates] +#[trusted] +#[requires(x > 0 && y > 0)] +#[ensures(result >= 1)] +#[ensures(divides(result, x))] +#[ensures(divides(result, y))] +#[ensures(forall(|z: i32| z >= 1 && divides(z, x) && divides(z, y) ==> z <= result))] +fn gcd(x: i32, y: i32) -> i32 { + if x > y { + gcd(x - y, y) + } else if x < y { + gcd(x, y - x) + } else { + x + } +} + +#[trusted] +#[requires(x > 0 && y > 0)] +#[requires(divides_prop!(x, y, z))] +#[ensures(divides(x, y))] +fn lemma_show_divides(x: i32, y: i32, z: i32) {} + +#[trusted] +#[requires(x > 0 && y > 0)] +#[requires(divides(x, y))] +#[ensures(divides_prop!(x, y, result))] +fn lemma_divides(x: i32, y: i32) -> i32 { + unimplemented!() +} + +#[requires(x > 0 && y > 0)] +#[ensures(gcd(x + y, y) == gcd(x, y))] +fn lemma_gcd(x: i32, y: i32) { + lemma_gcd_lower(x, y); + lemma_gcd_upper(x, y); +} + +#[requires(x > 0 && y > 0)] +#[ensures(gcd(x + y, y) >= gcd(x, y))] +fn lemma_gcd_upper(x: i32, y: i32) { + let z: i32 = x + y; + let m: i32 = gcd(z, y); + let n: i32 = gcd(y, x); + + let c: i32 = lemma_divides(n, y); + let d: i32 = lemma_divides(n, x); + + lemma_show_divides(n, z, c + d); +} + +#[requires(x > 0 && y > 0)] +#[ensures(gcd(x + y, y) <= gcd(x, y))] +fn lemma_gcd_lower(x: i32, y: i32) { + let z: i32 = x + y; + let m: i32 = gcd(z, y); + + let c: i32 = lemma_divides(m, z); + let d: i32 = lemma_divides(m, y); + + lemma_show_divides(m, x, c - d); +} + +#[requires(x > 0)] +#[ensures(gcd(x, x) == x)] +fn lemma_gcd_idempotent(x: i32) { + lemma_show_divides(x, x, 1); +} + +#[requires(n > 0 && m > 0)] +#[ensures(result == gcd(n, m))] +fn euclid(n: i32, m: i32) -> i32 { + let mut a: i32 = n; + let mut b: i32 = m; + + while a != b { + body_invariant!(a > 0 && b > 0); + body_invariant!(gcd(a, b) == gcd(n, m)); + + if a > b { + a -= b; + lemma_gcd(a, b); + } else { + b -= a; + lemma_gcd(b, a); + } + } + + lemma_gcd_idempotent(a); + + a +} + +// this will fail for a wrong Z3 version (4.8.7 is required) +fn completeness_check(m: i32, c: i32, d: i32) { + assert!(m * c - m * d == m * (c - d)); +} + +fn main() {} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index d0605bdd387..07061731a67 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -137,7 +137,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { }) .collect(); - let body = self.expression_to_snapshot(lowerer, body, expect_math_bool)?; + let body = self.expression_to_snapshot(lowerer, body, true)?; // generate validity calls for all variables we quantify over // this is needed to ensure that the quantifier is well-formed @@ -193,15 +193,21 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { }; // no call to ensure_bool_expression since quantifiers are always math bool and forall is built-in to SMT solvers - Ok(vir_low::Expression::Quantifier( - vir_low::expression::Quantifier { - kind, - variables: vars, - triggers: trigs, - body: Box::new(validity_call_imply_body), - position: *position, - }, - )) + let qtfy = vir_low::Expression::Quantifier(vir_low::expression::Quantifier { + kind, + variables: vars, + triggers: trigs, + body: Box::new(validity_call_imply_body), + position: *position, + }); + + // wrap in snapshot bool constructor if not expect_math_bool + if !expect_math_bool { + let pos = qtfy.position(); + lowerer.construct_struct_snapshot(&quantifier.get_type(), vec![qtfy], pos) + } else { + Ok(qtfy) + } } fn variable_to_snapshot( From fcfeda2e0c9e8c5d930d86f950a23d1a1f81c45f Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 26 Apr 2023 21:21:32 +0200 Subject: [PATCH 23/54] Optimization module --- native-verifier/src/lib.rs | 1 + native-verifier/src/optimization.rs | 185 ++++++++++++++++++ native-verifier/src/smt_lib.rs | 4 +- native-verifier/src/theories/Preamble.smt2 | 3 +- native-verifier/src/theories/PreambleZ3.smt2 | 2 +- .../tests/verify/fail/native/arithmetic.rs | 7 + .../tests/verify/pass/native/arithmetic.rs | 7 + .../tests/verify/pass/native/euclid.rs | 5 - 8 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 native-verifier/src/optimization.rs create mode 100644 prusti-tests/tests/verify/fail/native/arithmetic.rs create mode 100644 prusti-tests/tests/verify/pass/native/arithmetic.rs diff --git a/native-verifier/src/lib.rs b/native-verifier/src/lib.rs index bf33a15537e..a396e480131 100644 --- a/native-verifier/src/lib.rs +++ b/native-verifier/src/lib.rs @@ -4,6 +4,7 @@ pub mod verifier; mod smt_lib; mod theory_provider; +mod optimization; mod fol; pub use crate::verifier::*; diff --git a/native-verifier/src/optimization.rs b/native-verifier/src/optimization.rs new file mode 100644 index 00000000000..f55701535a6 --- /dev/null +++ b/native-verifier/src/optimization.rs @@ -0,0 +1,185 @@ +use vir::low::{ + expression::{visitors::ExpressionFolder, BinaryOp, UnaryOp}, + Expression, +}; + +use crate::fol::FolStatement; + +struct Optimizer {} + +impl ExpressionFolder for Optimizer { + fn fold_binary_op_enum(&mut self, binary_op: BinaryOp) -> Expression { + let new_left = self.fold_expression(*binary_op.left); + let new_right = self.fold_expression(*binary_op.right); + + match binary_op.op_kind { + vir::low::BinaryOpKind::EqCmp => { + if new_left == new_right { + true.into() + } else { + vir::low::Expression::BinaryOp(BinaryOp { + op_kind: vir::low::BinaryOpKind::EqCmp, + left: Box::new(new_left), + right: Box::new(new_right), + position: binary_op.position, + }) + } + } + vir::low::BinaryOpKind::NeCmp => { + if new_left == new_right { + false.into() + } else { + vir::low::Expression::BinaryOp(BinaryOp { + op_kind: vir::low::BinaryOpKind::NeCmp, + left: Box::new(new_left), + right: Box::new(new_right), + position: binary_op.position, + }) + } + } + vir::low::BinaryOpKind::And => { + if new_left == true.into() { + new_right + } else if new_right == true.into() { + new_left + } else if new_left == false.into() || new_right == false.into() { + false.into() + } else { + vir::low::Expression::BinaryOp(BinaryOp { + op_kind: vir::low::BinaryOpKind::And, + left: Box::new(new_left), + right: Box::new(new_right), + position: binary_op.position, + }) + } + } + vir::low::BinaryOpKind::Or => { + if new_left == true.into() || new_right == true.into() { + true.into() + } else if new_left == false.into() { + new_right + } else if new_right == false.into() { + new_left + } else { + vir::low::Expression::BinaryOp(BinaryOp { + op_kind: vir::low::BinaryOpKind::Or, + left: Box::new(new_left), + right: Box::new(new_right), + position: binary_op.position, + }) + } + } + vir::low::BinaryOpKind::Implies => { + if new_left == true.into() { + new_right + } else if new_right == false.into() { + vir::low::Expression::UnaryOp(UnaryOp { + op_kind: vir::low::UnaryOpKind::Not, + argument: Box::new(new_left), + position: binary_op.position, + }) + } else if new_left == false.into() { + true.into() + } else if new_right == true.into() { + true.into() + } else { + vir::low::Expression::BinaryOp(BinaryOp { + op_kind: vir::low::BinaryOpKind::Implies, + left: Box::new(new_left), + right: Box::new(new_right), + position: binary_op.position, + }) + } + } + typ => vir::low::Expression::BinaryOp(BinaryOp { + op_kind: typ, + left: Box::new(new_left), + right: Box::new(new_right), + position: binary_op.position, + }), + } + } + + fn fold_unary_op_enum(&mut self, unary_op: UnaryOp) -> Expression { + let new_argument = self.fold_expression(*unary_op.argument); + + match unary_op.op_kind { + vir::low::UnaryOpKind::Not => { + if new_argument == true.into() { + false.into() + } else if new_argument == false.into() { + true.into() + } else if let vir::low::Expression::UnaryOp(UnaryOp { + op_kind: vir::low::UnaryOpKind::Not, + argument, + .. + }) = new_argument + { + *argument + } else { + vir::low::Expression::UnaryOp(UnaryOp { + op_kind: vir::low::UnaryOpKind::Not, + argument: Box::new(new_argument), + position: unary_op.position, + }) + } + } + typ => vir::low::Expression::UnaryOp(UnaryOp { + op_kind: typ, + argument: Box::new(new_argument), + position: unary_op.position, + }), + } + } + + fn fold_conditional_enum( + &mut self, + conditional: vir::low::expression::Conditional, + ) -> Expression { + let new_cond = self.fold_expression(*conditional.guard); + let new_true = self.fold_expression(*conditional.then_expr); + let new_false = self.fold_expression(*conditional.else_expr); + + if new_cond == true.into() { + new_true + } else if new_cond == false.into() { + new_false + } else if new_true == new_false { + new_true + } else { + vir::low::Expression::Conditional(vir::low::expression::Conditional { + guard: Box::new(new_cond), + then_expr: Box::new(new_true), + else_expr: Box::new(new_false), + position: conditional.position, + }) + } + } +} + +pub fn optimize_statements(statements: Vec) -> Vec { + statements + .into_iter() + .filter_map(|statement| match statement { + FolStatement::Assume(expr) => { + let mut optimizer = Optimizer {}; + let expr = optimizer.fold_expression(expr); + if expr == true.into() { + None + } else { + Some(FolStatement::Assume(expr)) + } + } + FolStatement::Assert(expr) => { + let mut optimizer = Optimizer {}; + let expr = optimizer.fold_expression(expr); + if expr == true.into() { + None + } else { + Some(FolStatement::Assert(expr)) + } + } + x => Some(x), + }) + .collect() +} diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index fe9332d9ac4..a7cbcd3e30b 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -1,3 +1,5 @@ +use crate::optimization::optimize_statements; + use super::{ fol::{vir_to_fol, FolStatement}, theory_provider::*, @@ -125,6 +127,7 @@ impl SMTLib { // verify body let predicates = vir_to_fol(&block.statements, &self.methods); + let predicates = optimize_statements(predicates); predicates.into_iter().for_each(|predicate| { self.emit(predicate); @@ -267,7 +270,6 @@ impl ToString for SMTLib { .filter(|&line| line != "(assert true)") // TODO: Optimization module .collect::>() .join("\n") - .replace("(push)\n(echo \"position: 0\")\n(check-sat)\n(pop)", "") } } diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index f6638695784..9c6ae57cdc7 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -1,7 +1,8 @@ ; ===== Static preamble ===== (set-logic ALL) -(set-option :timeout 1000) + +(set-option :tlimit-per 5000) ; --- Floating-point arithmetic --- diff --git a/native-verifier/src/theories/PreambleZ3.smt2 b/native-verifier/src/theories/PreambleZ3.smt2 index c63baf61d6d..887ae70f8ea 100644 --- a/native-verifier/src/theories/PreambleZ3.smt2 +++ b/native-verifier/src/theories/PreambleZ3.smt2 @@ -31,7 +31,7 @@ (set-option :model.v2 true) (set-option :model.partial false) -(set-option :timeout 1000) +(set-option :timeout 5000) ; --- Floating-point arithmetic --- diff --git a/prusti-tests/tests/verify/fail/native/arithmetic.rs b/prusti-tests/tests/verify/fail/native/arithmetic.rs new file mode 100644 index 00000000000..68e62a77586 --- /dev/null +++ b/prusti-tests/tests/verify/fail/native/arithmetic.rs @@ -0,0 +1,7 @@ +// compile-flags: -Pviper_backend=Lithium + +fn test(m: i32, c: i32, d: i32) { + assert!(m * c - m * d == m * (c - d + 1)); //~ ERROR might not hold +} + +fn main() {} diff --git a/prusti-tests/tests/verify/pass/native/arithmetic.rs b/prusti-tests/tests/verify/pass/native/arithmetic.rs new file mode 100644 index 00000000000..a029a191594 --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/arithmetic.rs @@ -0,0 +1,7 @@ +// compile-flags: -Pviper_backend=Lithium + +fn test(m: i32, c: i32, d: i32) { + assert!(m * c - m * d == m * (c - d)); +} + +fn main() {} diff --git a/prusti-tests/tests/verify/pass/native/euclid.rs b/prusti-tests/tests/verify/pass/native/euclid.rs index d6cb2591262..d61c8ed0119 100644 --- a/prusti-tests/tests/verify/pass/native/euclid.rs +++ b/prusti-tests/tests/verify/pass/native/euclid.rs @@ -109,9 +109,4 @@ fn euclid(n: i32, m: i32) -> i32 { a } -// this will fail for a wrong Z3 version (4.8.7 is required) -fn completeness_check(m: i32, c: i32, d: i32) { - assert!(m * c - m * d == m * (c - d)); -} - fn main() {} From 9a6c479e37251c4a5921f7711bbcc114070ed340 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 3 May 2023 14:35:26 +0200 Subject: [PATCH 24/54] Correct reporting of failure reason positions --- native-verifier/src/fol.rs | 47 +++++++++++++++---- native-verifier/src/optimization.rs | 8 ++-- native-verifier/src/smt_lib.rs | 27 +++++------ native-verifier/src/verifier.rs | 13 +++-- .../tests/verify/pass/native/euclid.rs | 2 + 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 14a05d1da0a..9d926f1983d 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -1,13 +1,32 @@ use std::collections::HashMap; -use vir::low::{ - ast::{expression::*, statement::*}, - *, +use vir::{ + common::position::Positioned, + low::{ + ast::{expression::*, statement::*}, + expression::visitors::ExpressionFolder, + *, + }, }; pub enum FolStatement { Comment(String), Assume(Expression), - Assert(Expression), + Assert { + expression: Expression, + reason: Option, + }, +} + +pub fn set_position(expr: Expression, new_position: Position) -> Expression { + struct PositionReplacer { + new_position: Position, + } + impl ExpressionFolder for PositionReplacer { + fn fold_position(&mut self, _: Position) -> Position { + self.new_position + } + } + PositionReplacer { new_position }.fold_expression(expr) } fn vir_statement_to_fol_statements( @@ -15,10 +34,16 @@ fn vir_statement_to_fol_statements( known_methods: &HashMap, ) -> Vec { match statement { - Statement::Assert(expr) => vec![FolStatement::Assert(expr.expression.clone())], + Statement::Assert(expr) => vec![FolStatement::Assert { + expression: set_position(expr.expression.clone(), expr.position), + reason: Some(expr.expression.position()), + }], Statement::Assume(expr) => vec![FolStatement::Assume(expr.expression.clone())], Statement::Inhale(expr) => vec![FolStatement::Assume(expr.expression.clone())], - Statement::Exhale(expr) => vec![FolStatement::Assert(expr.expression.clone())], + Statement::Exhale(expr) => vec![FolStatement::Assert { + expression: expr.expression.clone(), + reason: None, + }], Statement::Assign(assign) => { let eq = Expression::BinaryOp(BinaryOp { op_kind: BinaryOpKind::EqCmp, @@ -72,7 +97,10 @@ fn vir_statement_to_fol_statements( right: Box::new(assert.expression.clone()), position: assert.position, }); - statements.push(FolStatement::Assert(implication)); + statements.push(FolStatement::Assert { + expression: implication, + reason: None, + }); } else if let Statement::Inhale(inhale) = s { let implication = Expression::BinaryOp(BinaryOp { op_kind: BinaryOpKind::Implies, @@ -188,7 +216,10 @@ fn vir_statement_to_fol_statements( let preconds = method_decl.pres.iter().map(|pre| { // substitute parameters for arguments - FolStatement::Assert(substitute(pre, ¶ms_to_args)) + FolStatement::Assert { + expression: substitute(pre, ¶ms_to_args), + reason: None, + } }); let postconds = method_decl.posts.iter().map(|post| { diff --git a/native-verifier/src/optimization.rs b/native-verifier/src/optimization.rs index f55701535a6..a0a5262c35b 100644 --- a/native-verifier/src/optimization.rs +++ b/native-verifier/src/optimization.rs @@ -170,13 +170,13 @@ pub fn optimize_statements(statements: Vec) -> Vec { Some(FolStatement::Assume(expr)) } } - FolStatement::Assert(expr) => { + FolStatement::Assert { expression, reason } => { let mut optimizer = Optimizer {}; - let expr = optimizer.fold_expression(expr); - if expr == true.into() { + let expression = optimizer.fold_expression(expression); + if expression == true.into() { None } else { - Some(FolStatement::Assert(expr)) + Some(FolStatement::Assert { expression, reason }) } } x => Some(x), diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index a7cbcd3e30b..e2d8149346b 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -66,17 +66,7 @@ impl SMTLib { // assert predicate self.add_code(format!("(assert {})", expression.to_smt())); } - FolStatement::Assert(expression) => { - // check if just asserting true - // TODO: Optimization module - if let Expression::Constant(Constant { - value: ConstantValue::Bool(true), - .. - }) = expression - { - return; - } - + FolStatement::Assert { expression, reason } => { // negate predicate let position = expression.position(); let negated = Expression::UnaryOp(UnaryOp { @@ -88,7 +78,15 @@ impl SMTLib { // assert negated predicate self.add_code("(push)".to_string()); self.add_code(format!("(assert {})", negated.to_smt())); - self.add_code(format!("(echo \"position: {}\")", expression.position().id)); + if let Some(res) = reason { + self.add_code(format!( + "(echo \"position: {}, reason: {}\")", + position.id, res.id + )); + } else { + self.add_code(format!("(echo \"position: {}\")", position.id)); + } + self.add_code("(check-sat)".to_string()); self.add_code("(pop)".to_string()); @@ -122,7 +120,7 @@ impl SMTLib { if let Some(precond) = precond { // TODO: Location of the call site self.add_code("; Branch precond:".to_string()); - self.add_code(format!("(assert {})", precond.to_smt())); + self.add_assert(precond.to_smt()); } // verify body @@ -212,8 +210,6 @@ impl ToString for SMTLib { main.push_str("\n\n"); main.push_str(&self.code.join("\n")); - // TODO: Sort wrappers - // initialize the theory providers let set_provider = ContainerTheoryProvider::new("Set"); let seq_provider = ContainerTheoryProvider::new("Seq"); @@ -267,7 +263,6 @@ impl ToString for SMTLib { result .lines() .filter(|&line| !MARKER_RE.is_match(line)) // TODO: SSO form for marker variables? - .filter(|&line| line != "(assert true)") // TODO: Optimization module .collect::>() .join("\n") } diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 57c0772d308..951b7390e04 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -20,7 +20,8 @@ use std::{ // lazy regex for parsing z3 output lazy_static! { - static ref POSITION_REGEX: Regex = Regex::new(r#"^"?position: (\d+)"?"#).unwrap(); + static ref POSITION_REGEX: Regex = + Regex::new(r#"^"?position: (\d+)(?:, reason: (\d+))?"?"#).unwrap(); } pub struct Verifier { @@ -106,16 +107,18 @@ impl Verifier { return VerificationResult::Failure(errors); } - let mut last_pos: i32 = 0; + let mut last_pos: (i32, Option) = (0, None); for line in result.unwrap().lines() { if let Some(caps) = POSITION_REGEX.captures(line) { - last_pos = caps[1].parse().unwrap(); + let pos = caps[1].parse().unwrap(); + let res = caps.get(2).map(|r| r.as_str().parse().unwrap()); + last_pos = (pos, res); } else if line == "sat" || line == "unknown" { errors.push(VerificationError::new( "assert.failed:assertion.false".to_string(), Some("0".to_string()), - Some(last_pos.to_string()), - Some("0".to_string()), + Some(last_pos.0.to_string()), + last_pos.1.map(|r| r.to_string()), format!("Assert might fail. Assertion might not hold."), None, )); diff --git a/prusti-tests/tests/verify/pass/native/euclid.rs b/prusti-tests/tests/verify/pass/native/euclid.rs index d61c8ed0119..1e59182e6b5 100644 --- a/prusti-tests/tests/verify/pass/native/euclid.rs +++ b/prusti-tests/tests/verify/pass/native/euclid.rs @@ -1,3 +1,5 @@ +// compile-flags: -Pviper_backend=Lithium + use prusti_contracts::*; macro_rules! divides_prop { From 458566588511d4f6414dfbfe391194a27c488ec8 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 3 May 2023 17:03:28 +0200 Subject: [PATCH 25/54] Remove redundant reasons for failure if the same as failing place --- native-verifier/src/fol.rs | 23 +++- native-verifier/src/optimization.rs | 49 +++++++- native-verifier/src/smt_lib.rs | 117 ++++++++++-------- .../tests/verify/ui/forall_verify.stderr | 6 + .../transformations/inline_functions.rs | 23 ++++ 5 files changed, 162 insertions(+), 56 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 9d926f1983d..44824cea145 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -34,10 +34,22 @@ fn vir_statement_to_fol_statements( known_methods: &HashMap, ) -> Vec { match statement { - Statement::Assert(expr) => vec![FolStatement::Assert { - expression: set_position(expr.expression.clone(), expr.position), - reason: Some(expr.expression.position()), - }], + Statement::Assert(expr) => { + let reason_pos = expr.expression.position(); + let failure_pos = expr.position; + + if reason_pos != failure_pos { + vec![FolStatement::Assert { + expression: set_position(expr.expression.clone(), failure_pos), + reason: Some(reason_pos), + }] + } else { + vec![FolStatement::Assert { + expression: expr.expression.clone(), + reason: None, + }] + } + } Statement::Assume(expr) => vec![FolStatement::Assume(expr.expression.clone())], Statement::Inhale(expr) => vec![FolStatement::Assume(expr.expression.clone())], Statement::Exhale(expr) => vec![FolStatement::Assert { @@ -99,7 +111,7 @@ fn vir_statement_to_fol_statements( }); statements.push(FolStatement::Assert { expression: implication, - reason: None, + reason: None, // TODO: Reason? }); } else if let Statement::Inhale(inhale) = s { let implication = Expression::BinaryOp(BinaryOp { @@ -241,7 +253,6 @@ pub fn vir_to_fol( statements: &Vec, known_methods: &HashMap, ) -> Vec { - // reduce so that the order in the vector is now the Sequence operator statements .iter() .flat_map(|s| vir_statement_to_fol_statements(s, known_methods)) diff --git a/native-verifier/src/optimization.rs b/native-verifier/src/optimization.rs index a0a5262c35b..f0a42cfe324 100644 --- a/native-verifier/src/optimization.rs +++ b/native-verifier/src/optimization.rs @@ -1,5 +1,5 @@ use vir::low::{ - expression::{visitors::ExpressionFolder, BinaryOp, UnaryOp}, + expression::{visitors::ExpressionFolder, BinaryOp, MagicWand, UnaryOp}, Expression, }; @@ -132,6 +132,53 @@ impl ExpressionFolder for Optimizer { } } + fn fold_quantifier_enum( + &mut self, + quantifier: vir::low::expression::Quantifier, + ) -> vir::low::Expression { + if quantifier.triggers.is_empty() { + let new_body = self.fold_expression(*quantifier.body); + if new_body == true.into() { + true.into() + } else if new_body == false.into() { + false.into() + } else { + vir::low::Expression::Quantifier(vir::low::expression::Quantifier { + body: Box::new(new_body), + ..quantifier + }) + } + } else { + quantifier.into() // cannot optimize because we might remove something that is in a trigger + } + } + + fn fold_magic_wand_enum(&mut self, magic_wand: vir::low::expression::MagicWand) -> Expression { + // magic wand (--*) is basically implication, so we can simplify accordingly + let new_left = self.fold_expression(*magic_wand.left); + let new_right = self.fold_expression(*magic_wand.right); + + if new_left == true.into() { + new_right + } else if new_right == false.into() { + vir::low::Expression::UnaryOp(UnaryOp { + op_kind: vir::low::UnaryOpKind::Not, + argument: Box::new(new_left), + position: magic_wand.position, + }) + } else if new_left == false.into() { + true.into() + } else if new_right == true.into() { + true.into() + } else { + vir::low::Expression::MagicWand(MagicWand { + left: Box::new(new_left), + right: Box::new(new_right), + position: magic_wand.position, + }) + } + } + fn fold_conditional_enum( &mut self, conditional: vir::low::expression::Conditional, diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index e2d8149346b..aaf2598ab8f 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -533,63 +533,82 @@ impl SMTTranslatable for Expression { conditional.else_expr.to_smt() ), // TODO: Quantifier triggers - Expression::Quantifier(quantifier) => match quantifier.kind { - QuantifierKind::ForAll => { - let mut quant = String::new(); - quant.push_str("(forall ("); - quant.push_str( - &quantifier - .variables - .iter() - .map(|v| format!("({} {})", v.name, v.ty.to_smt())) - .collect::>() - .join(" "), - ); - quant.push_str(") "); - - let triggers: Vec<_> = quantifier - .triggers + Expression::Quantifier(quantifier) => { + let mut quant = String::new(); + + match quantifier.kind { + QuantifierKind::ForAll => quant.push_str("(forall ("), + QuantifierKind::Exists => quant.push_str("(exists ("), + } + + quant.push_str( + &quantifier + .variables .iter() - .filter(|t| { - !t.terms + .map(|v| format!("({} {})", v.name, v.ty.to_smt())) + .collect::>() + .join(" "), + ); + quant.push_str(") "); + + let triggers: Vec<_> = quantifier + .triggers + .iter() + .filter(|t| { + !t.terms + .iter() + .any(|t| matches!(t, Expression::PredicateAccessPredicate(_))) + // TODO: Support triggers with predicate access predicates? + }) + .collect(); + + if triggers.is_empty() { + quant.push_str(&quantifier.body.to_smt()); + quant.push_str(")"); + } else { + // triggers are :pattern + quant.push_str("(! "); + quant.push_str(&quantifier.body.to_smt()); + + for trigger in &triggers { + quant.push_str(" :pattern ("); + + quant.push_str( + &trigger + .terms .iter() - .any(|t| matches!(t, Expression::PredicateAccessPredicate(_))) - // TODO: Support triggers with predicate access predicates? - }) - .collect(); + .map(|expr| expr.to_smt()) + .collect::>() + .join(" "), + ); - if triggers.is_empty() { - quant.push_str(&quantifier.body.to_smt()); quant.push_str(")"); - } else { - // triggers are :pattern - quant.push_str("(!"); - quant.push_str(&quantifier.body.to_smt()); - - for trigger in &triggers { - quant.push_str(" :pattern ("); - - quant.push_str( - &trigger - .terms - .iter() - .map(|expr| expr.to_smt()) - .collect::>() - .join(" "), - ); - - quant.push_str(")"); - } - - quant.push_str("))"); } - quant + quant.push_str("))"); } - QuantifierKind::Exists => unimplemented!(), - }, + + quant + } Expression::LetExpr(_) => unimplemented!(), - Expression::FuncApp(_) => unimplemented!(), + Expression::FuncApp(func) => { + let mut app = "(".to_string(); + + app.push_str(&func.function_name); + app.push_str(" "); + + app.push_str( + &func + .arguments + .iter() + .map(|arg| arg.to_smt()) + .collect::>() + .join(" "), + ); + + app.push_str(")"); + app + } Expression::DomainFuncApp(domain_func_app) => { mk_app(&domain_func_app.function_name, &domain_func_app.arguments) } diff --git a/prusti-tests/tests/verify/ui/forall_verify.stderr b/prusti-tests/tests/verify/ui/forall_verify.stderr index 63d52476fb4..1876f837af0 100644 --- a/prusti-tests/tests/verify/ui/forall_verify.stderr +++ b/prusti-tests/tests/verify/ui/forall_verify.stderr @@ -11,6 +11,12 @@ note: the error originates here | ^^^^^^^^^^^^^ error: [Prusti: verification error] postcondition might not hold. + --> $DIR/forall_verify.rs:31:11 + | +31 | #[ensures(exists(|x: i32| identity(x) == x + 1))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the error originates here --> $DIR/forall_verify.rs:32:1 | 32 | fn test6() {} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs index c89e1fcd606..eb329f73ca5 100644 --- a/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs @@ -86,6 +86,29 @@ impl<'a> ExpressionFolder for Inliner<'a> { binary_op } + fn fold_quantifier( + &mut self, + mut quantifier: vir_low::expression::Quantifier, + ) -> vir_low::expression::Quantifier { + quantifier.triggers = quantifier + .triggers + .iter() + .map(|trigger| { + let mut trigger = trigger.clone(); + trigger.terms = trigger + .terms + .into_iter() + .map(|term| *self.fold_expression_boxed(box term)) + .collect(); + trigger + }) + .collect(); + + quantifier.body = self.fold_expression_boxed(quantifier.body); + + quantifier + } + fn fold_conditional( &mut self, mut conditional: vir_low::expression::Conditional, From 4b5e02656181252c6dde84348b724e0ad18ea3f1 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 5 May 2023 21:00:24 +0200 Subject: [PATCH 26/54] FP != for Viper --- prusti-common/src/vir/to_viper.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index 568d04489ad..505749fb41c 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -455,6 +455,19 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { Float::F32 => viper::FloatSizeViper::F32, Float::F64 => viper::FloatSizeViper::F64, }; + + if *op == BinaryOpKind::NeCmp { + return ast.not_with_pos( + ast.float_binop( + viper::BinOpFloat::Eq, + size, + left.to_viper(context, ast), + right.to_viper(context, ast), + ), + pos.to_viper(context, ast), + ); + } + let float_op_kind = match op { BinaryOpKind::Add => viper::BinOpFloat::Add, BinaryOpKind::Sub => viper::BinOpFloat::Sub, From ddddb3a9406748da8e32906188ce8d277cb5e541 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 5 May 2023 21:00:32 +0200 Subject: [PATCH 27/54] Passing FP test case --- .../tests/verify/pass/native/floating.rs | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 prusti-tests/tests/verify/pass/native/floating.rs diff --git a/prusti-tests/tests/verify/pass/native/floating.rs b/prusti-tests/tests/verify/pass/native/floating.rs new file mode 100644 index 00000000000..06ef17ce21e --- /dev/null +++ b/prusti-tests/tests/verify/pass/native/floating.rs @@ -0,0 +1,153 @@ +// compile-flags: -Pviper_backend=Lithium + +use prusti_contracts::*; + +#[pure] +#[terminates] +fn is_nan_32(f: f32) -> bool { + f != f +} + +#[pure] +#[terminates] +fn is_nan_64(f: f64) -> bool { + f != f +} + +#[pure] +#[terminates] +fn is_infinite_32(f: f32) -> bool { + f == f32::INFINITY || f == f32::NEG_INFINITY +} + +#[pure] +#[terminates] +fn is_infinite_64(f: f64) -> bool { + f == f64::INFINITY || f == f64::NEG_INFINITY +} + +fn test_nan_32_is_nan() { + let a = 0.0f32 / 0.0f32; + assert!(is_nan_32(a)); +} + +fn test_nan_32_is_inf() { + let a = 0.0f32 / 0.0f32; + assert!(!is_infinite_32(a)); +} + +fn test_nan_64_is_nan() { + let a = 0.0f64 / 0.0f64; + assert!(is_nan_64(a)); +} + +fn test_nan_64_is_inf() { + let a = 0.0f64 / 0.0f64; + assert!(!is_infinite_64(a)); +} + +fn test_inf_32_is_inf() { + let a = 1.0f32 / 0.0f32; + assert!(is_infinite_32(a)); +} + +fn test_inf_32_is_nan() { + let a = 1.0f32 / 0.0f32; + assert!(!is_nan_32(a)); +} + +fn test_inf_64_is_inf() { + let a = 1.0f64 / 0.0f64; + assert!(is_infinite_64(a)); +} + +fn test_inf_64_is_nan() { + let a = 1.0f64 / 0.0f64; + assert!(!is_nan_64(a)); +} + +fn test_neg_inf_32_is_inf() { + let a = -1.0f32 / 0.0f32; + assert!(is_infinite_32(a)); +} + +fn test_neg_inf_32_is_nan() { + let a = -1.0f32 / 0.0f32; + assert!(!is_nan_32(a)); +} + +fn test_neg_inf_64_is_inf() { + let a = -1.0f64 / 0.0f64; + assert!(is_infinite_64(a)); +} + +fn test_neg_inf_64_is_nan() { + let a = -1.0f64 / 0.0f64; + assert!(!is_nan_64(a)); +} + +// THEORY OF INIFINITY + +#[requires(is_nan_32(f))] +#[ensures(!is_infinite_32(f))] +fn axiom1(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(!is_nan_32(f))] +fn axiom2(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f + 1_f32))] +fn axiom3(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f - 1_f32))] +fn axiom4(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f * 2_f32))] +fn axiom5(f: f32) {} + +#[requires(is_infinite_32(f))] +#[ensures(is_infinite_32(f / 2_f32))] +fn axiom6(f: f32) {} + +// THEORY OF NAN + +#[requires(is_infinite_32(f))] +#[ensures(!is_nan_32(f))] +fn axiom7(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f + 1_f32))] +fn axiom8(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f - 1_f32))] +fn axiom9(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f * 2_f32))] +fn axiom10(f: f32) {} + +#[requires(is_nan_32(f))] +#[ensures(is_nan_32(f / 2_f32))] +fn axiom11(f: f32) {} + +#[ensures(is_nan_32(f32::NAN))] +fn axiom12() {} + +fn main() { + test_nan_32_is_nan(); + test_nan_32_is_inf(); + test_nan_64_is_nan(); + test_nan_64_is_inf(); + test_inf_32_is_inf(); + test_inf_32_is_nan(); + test_inf_64_is_inf(); + test_inf_64_is_nan(); + test_neg_inf_32_is_nan(); + test_neg_inf_32_is_inf(); + test_neg_inf_64_is_inf(); + test_neg_inf_64_is_nan(); +} From c36a25dd502b6086a1eee519130b5dfd6d2a8276 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 31 May 2023 16:36:04 +0200 Subject: [PATCH 28/54] Remove main.rs from Lithium --- native-verifier/src/main.rs | 3 --- .../middle/core_proof/transformations/inline_functions.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 native-verifier/src/main.rs diff --git a/native-verifier/src/main.rs b/native-verifier/src/main.rs deleted file mode 100644 index adf706984a3..00000000000 --- a/native-verifier/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - unimplemented!("This is a dummy main function."); -} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs index eb329f73ca5..8b62e843127 100644 --- a/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs @@ -98,7 +98,7 @@ impl<'a> ExpressionFolder for Inliner<'a> { trigger.terms = trigger .terms .into_iter() - .map(|term| *self.fold_expression_boxed(box term)) + .map(|term| *self.fold_expression_boxed(Box::new(term))) .collect(); trigger }) From 9b0840039a2be55d62e04943997273c22693274c Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Sun, 4 Jun 2023 16:41:20 +0200 Subject: [PATCH 29/54] Commit the config --- native-verifier/src/theories/Preamble.smt2 | 2 ++ native-verifier/src/theories/PreambleZ3.smt2 | 2 ++ native-verifier/src/verifier.rs | 6 +++++- prusti-utils/src/config.rs | 16 ++++++++-------- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index 9c6ae57cdc7..621ff1c61d6 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -1,5 +1,7 @@ ; ===== Static preamble ===== +(set-info :smt-lib-version 2.6) + (set-logic ALL) (set-option :tlimit-per 5000) diff --git a/native-verifier/src/theories/PreambleZ3.smt2 b/native-verifier/src/theories/PreambleZ3.smt2 index 887ae70f8ea..a64a6c439ab 100644 --- a/native-verifier/src/theories/PreambleZ3.smt2 +++ b/native-verifier/src/theories/PreambleZ3.smt2 @@ -1,5 +1,7 @@ ; ===== Static preamble ===== +(set-info :smt-lib-version 2.6) + (set-option :global-decls true) ; Boogie: default (set-option :auto_config false) ; Usually a good idea (set-option :smt.restart_strategy 0) diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 951b7390e04..d43d0541965 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -10,7 +10,7 @@ use core::panic; use lazy_static::lazy_static; use log::{self, debug}; use prusti_common::vir::program::Program; -use prusti_utils::{report::log::report_with_writer, run_timed}; +use prusti_utils::{config, report::log::report_with_writer, run_timed}; use regex::Regex; use std::{ error::Error, @@ -40,6 +40,10 @@ impl Verifier { panic!("Lithium backend only supports low programs"); }; + if !config::unsafe_core_proof() { + panic!("Lithium backend only supports unsafe_core_proof=true"); + } + let is_z3 = self.smt_solver_exe.ends_with("z3"); run_timed!("Translation to SMT-LIB", debug, diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index b137f929c6e..2b0eea4b01f 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -69,7 +69,7 @@ lazy_static::lazy_static! { // 0. Default values settings.set_default("be_rustc", false).unwrap(); - settings.set_default("viper_backend", "Silicon").unwrap(); + settings.set_default("viper_backend", "Lithium").unwrap(); settings.set_default::>("smt_solver_path", env::var("Z3_EXE").ok()).unwrap(); settings.set_default::>("smt_solver_wrapper_path", None).unwrap(); settings.set_default::>("boogie_path", env::var("BOOGIE_EXE").ok()).unwrap(); @@ -81,7 +81,7 @@ lazy_static::lazy_static! { settings.set_default("check_overflows", true).unwrap(); settings.set_default("check_panics", true).unwrap(); settings.set_default("encode_unsigned_num_constraint", true).unwrap(); - settings.set_default("encode_bitvectors", false).unwrap(); + settings.set_default("encode_bitvectors", true).unwrap(); settings.set_default("simplify_encoding", true).unwrap(); settings.set_default("log", "").unwrap(); settings.set_default("log_style", "auto").unwrap(); @@ -104,7 +104,7 @@ lazy_static::lazy_static! { settings.set_default("assert_timeout", 10_000).unwrap(); settings.set_default("smt_qi_eager_threshold", 1000).unwrap(); settings.set_default("use_more_complete_exhale", true).unwrap(); - settings.set_default("skip_unsupported_features", false).unwrap(); + settings.set_default("skip_unsupported_features", true).unwrap(); settings.set_default("internal_errors_as_warnings", false).unwrap(); settings.set_default("allow_unreachable_unsupported_code", false).unwrap(); settings.set_default("no_verify", false).unwrap(); @@ -114,16 +114,16 @@ lazy_static::lazy_static! { settings.set_default("json_communication", false).unwrap(); settings.set_default("optimizations", "all").unwrap(); settings.set_default("intern_names", true).unwrap(); - settings.set_default("enable_purification_optimization", false).unwrap(); + settings.set_default("enable_purification_optimization", true).unwrap(); // settings.set_default("enable_manual_axiomatization", false).unwrap(); - settings.set_default("unsafe_core_proof", false).unwrap(); - settings.set_default("verify_core_proof", true).unwrap(); + settings.set_default("unsafe_core_proof", true).unwrap(); + settings.set_default("verify_core_proof", false).unwrap(); settings.set_default("verify_specifications", true).unwrap(); settings.set_default("verify_types", false).unwrap(); settings.set_default("verify_specifications_with_core_proof", false).unwrap(); - settings.set_default("verify_specifications_backend", "Silicon").unwrap(); + settings.set_default("verify_specifications_backend", "Lithium").unwrap(); settings.set_default("use_eval_axioms", true).unwrap(); - settings.set_default("inline_caller_for", false).unwrap(); + settings.set_default("inline_caller_for", true).unwrap(); settings.set_default("check_no_drops", false).unwrap(); settings.set_default("enable_type_invariants", false).unwrap(); settings.set_default("use_new_encoder", true).unwrap(); From c8a4d510afa9f6e05d52a54c192a7fcc2b7ee71f Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 13 Jun 2023 13:52:49 +0200 Subject: [PATCH 30/54] Use files, not stdio, fix negative int encoding --- native-verifier/src/smt_lib.rs | 19 +++++++++--- native-verifier/src/theories/Preamble.smt2 | 26 +++++++++++++++++ native-verifier/src/verifier.rs | 29 ++++++++++++------- .../tests/verify/pass/native/ackermann.rs | 1 + 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index aaf2598ab8f..ac2d9602ab3 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -383,7 +383,7 @@ impl SMTTranslatable for ProcedureDecl { smt.blocks.clear(); self.basic_blocks.iter().for_each(|b| { - smt.blocks.insert(b.label.name.clone(), b.clone()); // TODO: Inefficient copy + smt.blocks.insert(b.label.name.clone(), b.clone()); }); // find a starting block @@ -436,7 +436,13 @@ impl SMTTranslatable for Expression { Expression::LabelledOld(_) => unimplemented!("old expressions"), Expression::Constant(constant) => match &constant.value { ConstantValue::Bool(bool) => bool.to_string(), - ConstantValue::Int(i64) => i64.to_string(), + ConstantValue::Int(i) => { + if *i < 0 { + format!("(- {})", i.abs()) + } else { + i.to_string() + } + } ConstantValue::Float32(u32) => { let bits = u32.to_le_bytes(); let bits = bits @@ -467,7 +473,13 @@ impl SMTTranslatable for Expression { &bits[12..=63] ) } - ConstantValue::BigInt(s) => s.clone(), + ConstantValue::BigInt(s) => { + if s.starts_with("-") { + format!("(- {})", &s[1..]) + } else { + s.clone() + } + } }, Expression::MagicWand(wand) => { // if left is just constant true, we can ignore it @@ -759,7 +771,6 @@ impl SMTTranslatable for ContainerOpKind { } } -// TODO: Check if these make sense impl SMTTranslatable for Type { fn to_smt(&self) -> String { match self { diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index 621ff1c61d6..d6770dc44f8 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -6,6 +6,32 @@ (set-option :tlimit-per 5000) +(set-option :auto-extend-integers true) ; similar to global-decls in Z3 +(set-option :incrementality false) ; similar to auto_config in Z3 + +; Note: restart_strategy and restart_factor do not have direct equivalents in CVC5. +; Similar functionality might be achieved through tuning the various options for decision strategies, but there is no one-to-one mapping. + +(set-option :bitvector-div-zero-const false) ; similar to smt.bv.reflect in Z3 +(set-option :mbqi-mode none) ; similar to smt.mbqi in Z3 + +; The options smt.qi.cost, smt.qi.eager_threshold, and smt.qi.max_multi_patterns might correspond to +; configuring quantifier instantiation strategies in CVC5, but there are no direct equivalents. + +(set-option :decision-random-frequency 0) ; similar to sat.phase caching in Z3 + +; There are no direct equivalents in CVC5 for sat.random_seed, nlsat.* options. + +; Note: fp.spacer.* options are related to Z3's Spacer, a software model checker. CVC5 doesn't have Spacer. + +(set-option :random-seed 0) ; similar to smt.random_seed in Z3 + +; The sls.* options in Z3 are related to stochastic local search. +; CVC5 has options for tuning its decision strategy, but these are quite different from SLS and do not directly correspond to these options. + +(set-option :produce-models true) ; equivalent to model.v2 in Z3 +(set-option :produce-assignments false) ; similar to model.partial in Z3 + ; --- Floating-point arithmetic --- (define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index d43d0541965..2f7da32b7b4 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -45,6 +45,7 @@ impl Verifier { } let is_z3 = self.smt_solver_exe.ends_with("z3"); + let solver_name = if is_z3 { "Z3" } else { "CVC5" }; run_timed!("Translation to SMT-LIB", debug, let mut smt = SMTLib::new(is_z3); @@ -60,25 +61,33 @@ impl Verifier { ); ); - run_timed!(format!("SMT verification with {}", if is_z3 {"Z3"} else {"CVC5"}), debug, + run_timed!(format!("SMT verification with {}", solver_name), debug, let result: Result> = try { let mut command = Command::new(self.smt_solver_exe.clone()); + let path = config::log_dir().join("smt").join(format!("lithium_{}.smt2", program.name)); + let path = path.to_str().unwrap(); + if is_z3 { - command.args(&["-smt2", "-in"]); + command.args(&["-smt2", path]); } else { - command.args(&["--incremental"]); + command.args(&["--incremental", path]); } - let mut child = command.stdin(Stdio::piped()) - .stderr(Stdio::piped()) + let mut child = command.stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; - child.stdin - .as_mut() - .ok_or("Child process stdin has not been captured!")? - .write_all(smt_program.as_bytes())?; + // Check if child process is running + let child_status = child.try_wait()?; + if let Some(exit_status) = child_status { + if exit_status.success() { + Err::("Child process terminated prematurely.".into())?; + } else { + let err = format!("Child process failed to start with exit status: {}", exit_status); + Err::(err.into())?; + } + } let output = child.wait_with_output()?; @@ -104,7 +113,7 @@ impl Verifier { Some("0".to_string()), Some("0".to_string()), Some("0".to_string()), - format!("Z3 failed with error: {}", err), + format!("{} failed with error: {}", solver_name, err), None, )); diff --git a/prusti-tests/tests/verify/pass/native/ackermann.rs b/prusti-tests/tests/verify/pass/native/ackermann.rs index 09677dda590..9acb7a7bd56 100644 --- a/prusti-tests/tests/verify/pass/native/ackermann.rs +++ b/prusti-tests/tests/verify/pass/native/ackermann.rs @@ -1,4 +1,5 @@ // compile-flags: -Pviper_backend=Lithium + use prusti_contracts::*; #[pure] From 4e368acc429486ff714f33c8a19d96fe81db140d Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 13 Jun 2023 14:04:08 +0200 Subject: [PATCH 31/54] Make clippy happy --- backend-common/src/silicon_counterexample.rs | 1 - native-verifier/src/fol.rs | 2 +- native-verifier/src/optimization.rs | 8 ++----- native-verifier/src/smt_lib.rs | 22 ++++++++++--------- native-verifier/src/verifier.rs | 8 +++---- .../high/procedures/inference/ensurer.rs | 2 -- .../high/procedures/inference/visitor/mod.rs | 4 ++-- .../snapshots/into_snapshot/common/mod.rs | 8 +++---- .../middle/core_proof/types/interface.rs | 2 +- 9 files changed, 25 insertions(+), 32 deletions(-) diff --git a/backend-common/src/silicon_counterexample.rs b/backend-common/src/silicon_counterexample.rs index 34159e973b4..8eba6d8f3ff 100644 --- a/backend-common/src/silicon_counterexample.rs +++ b/backend-common/src/silicon_counterexample.rs @@ -1,5 +1,4 @@ use rustc_hash::FxHashMap; -use serde; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct SiliconCounterexample { diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 44824cea145..993ba1a69ca 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -250,7 +250,7 @@ fn vir_statement_to_fol_statements( } pub fn vir_to_fol( - statements: &Vec, + statements: &[Statement], known_methods: &HashMap, ) -> Vec { statements diff --git a/native-verifier/src/optimization.rs b/native-verifier/src/optimization.rs index f0a42cfe324..9f9dc5a176a 100644 --- a/native-verifier/src/optimization.rs +++ b/native-verifier/src/optimization.rs @@ -78,9 +78,7 @@ impl ExpressionFolder for Optimizer { argument: Box::new(new_left), position: binary_op.position, }) - } else if new_left == false.into() { - true.into() - } else if new_right == true.into() { + } else if new_left == false.into() || new_right == true.into() { true.into() } else { vir::low::Expression::BinaryOp(BinaryOp { @@ -166,9 +164,7 @@ impl ExpressionFolder for Optimizer { argument: Box::new(new_left), position: magic_wand.position, }) - } else if new_left == false.into() { - true.into() - } else if new_right == true.into() { + } else if new_left == false.into() || new_right == true.into() { true.into() } else { vir::low::Expression::MagicWand(MagicWand { diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index ac2d9602ab3..12cd6b9e522 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -282,7 +282,7 @@ fn mk_app(name: &String, args: &Vec) -> String where T: SMTTranslatable, { - if args.len() == 0 { + if args.is_empty() { name.clone() } else { format!( @@ -301,8 +301,10 @@ impl SMTTranslatable for Program { self.domains.iter().for_each(|d| d.build_smt(smt)); self.methods.iter().for_each(|d| d.build_smt(smt)); self.procedures.iter().for_each(|d| d.build_smt(smt)); - assert!(self.functions.len() == 0); // TODO: Implement - assert!(self.predicates.len() == 0); + + // the following are empty in vir::low + snapshot-based encoding + assert!(self.functions.is_empty()); + assert!(self.predicates.is_empty()); } } @@ -389,7 +391,7 @@ impl SMTTranslatable for ProcedureDecl { // find a starting block let mut start_blocks = smt.blocks.keys().collect::>(); - for (_, block) in &smt.blocks { + for block in smt.blocks.values() { match &block.successor { Successor::Goto(label) => { start_blocks.remove(&label.name); @@ -474,8 +476,8 @@ impl SMTTranslatable for Expression { ) } ConstantValue::BigInt(s) => { - if s.starts_with("-") { - format!("(- {})", &s[1..]) + if let Some(abs_val) = s.strip_prefix('-') { + format!("(- {})", abs_val) } else { s.clone() } @@ -576,7 +578,7 @@ impl SMTTranslatable for Expression { if triggers.is_empty() { quant.push_str(&quantifier.body.to_smt()); - quant.push_str(")"); + quant.push(')'); } else { // triggers are :pattern quant.push_str("(! "); @@ -594,7 +596,7 @@ impl SMTTranslatable for Expression { .join(" "), ); - quant.push_str(")"); + quant.push(')'); } quant.push_str("))"); @@ -607,7 +609,7 @@ impl SMTTranslatable for Expression { let mut app = "(".to_string(); app.push_str(&func.function_name); - app.push_str(" "); + app.push(' '); app.push_str( &func @@ -618,7 +620,7 @@ impl SMTTranslatable for Expression { .join(" "), ); - app.push_str(")"); + app.push(')'); app } Expression::DomainFuncApp(domain_func_app) => { diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 2f7da32b7b4..4b82fbd9031 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -69,9 +69,9 @@ impl Verifier { let path = path.to_str().unwrap(); if is_z3 { - command.args(&["-smt2", path]); + command.args(["-smt2", path]); } else { - command.args(&["--incremental", path]); + command.args(["--incremental", path]); } let mut child = command.stderr(Stdio::piped()) @@ -85,7 +85,7 @@ impl Verifier { Err::("Child process terminated prematurely.".into())?; } else { let err = format!("Child process failed to start with exit status: {}", exit_status); - Err::(err.into())?; + Err::(err)?; } } @@ -132,7 +132,7 @@ impl Verifier { Some("0".to_string()), Some(last_pos.0.to_string()), last_pos.1.map(|r| r.to_string()), - format!("Assert might fail. Assertion might not hold."), + "Assert might fail. Assertion might not hold.".to_string(), None, )); } diff --git a/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs b/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs index 60888307a01..5209826b1f7 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs @@ -131,7 +131,6 @@ pub(in super::super) fn ensure_required_permission( required_permission: Permission, actions: &mut Vec, ) -> SpannedEncodingResult<()> { - return Ok(()); // TODO: Remove permission reasoning state.debug_print(); let (place, permission_kind) = match required_permission { @@ -320,7 +319,6 @@ fn ensure_permission_in_state( permission_kind: PermissionKind, actions: &mut Vec, ) -> SpannedEncodingResult { - return Ok(true); // TODO: Remove permission reasoning predicate_state.check_consistency(); let to_drop = if check_contains_place(predicate_state, &place, permission_kind)? { debug!(" satisfied: {:?} {}", permission_kind, place); diff --git a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs index afc024533fb..e12dadbae49 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs @@ -219,8 +219,8 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { let actions = ensure_required_permissions(self, state, consumed_permissions.clone())?; self.process_actions(actions)?; // TODO: Remove permission reasoning - // state.remove_permissions(&consumed_permissions)?; - // state.insert_permissions(produced_permissions)?; + state.remove_permissions(&consumed_permissions)?; + state.insert_permissions(produced_permissions)?; match &statement { vir_typed::Statement::ObtainMutRef(_) => { // The requirements already performed the needed changes. diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index 07061731a67..817e46ca26a 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -166,8 +166,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { right: Box::new(result), position: *position, op_kind: vir_low::expression::BinaryOpKind::And, - } - .into(), + }, ); } result @@ -179,8 +178,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { right: Box::new(body), position: *position, op_kind: vir_low::expression::BinaryOpKind::Implies, - } - .into(), + }, ); let kind = match kind { @@ -204,7 +202,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { // wrap in snapshot bool constructor if not expect_math_bool if !expect_math_bool { let pos = qtfy.position(); - lowerer.construct_struct_snapshot(&quantifier.get_type(), vec![qtfy], pos) + lowerer.construct_struct_snapshot(quantifier.get_type(), vec![qtfy], pos) } else { Ok(qtfy) } diff --git a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs index de38f0fa6f2..899619b46e3 100644 --- a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs @@ -231,7 +231,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { // it is not a function, just a struct that remembers the arguments at entry time let mut parameters = Vec::new(); for (ix, argument) in closure.arguments.iter().enumerate() { - self.ensure_type_definition(&argument)?; + self.ensure_type_definition(argument)?; parameters.push(vir_low::VariableDecl::new( format!("_{}", ix), argument.to_snapshot(self)?, From 9ad4f34c5e832de4e2367898cae346f15a504bfd Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 13 Jun 2023 14:07:01 +0200 Subject: [PATCH 32/54] cargo fmt --- backend-common/src/lib.rs | 4 +--- backend-common/src/verification_result.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend-common/src/lib.rs b/backend-common/src/lib.rs index 14a739948a4..6fa91868a14 100644 --- a/backend-common/src/lib.rs +++ b/backend-common/src/lib.rs @@ -2,6 +2,4 @@ mod verification_result; mod java_exception; mod silicon_counterexample; -pub use crate::{ - java_exception::*, silicon_counterexample::*, verification_result::*, -}; +pub use crate::{java_exception::*, silicon_counterexample::*, verification_result::*}; diff --git a/backend-common/src/verification_result.rs b/backend-common/src/verification_result.rs index 65d380813a9..ac3786a62a3 100644 --- a/backend-common/src/verification_result.rs +++ b/backend-common/src/verification_result.rs @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{SiliconCounterexample, JavaException}; +use crate::{JavaException, SiliconCounterexample}; /// The result of a verification request on a Viper program. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] From 614ebc1ef870e0399f39d37b73f319165046153e Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 13 Jun 2023 14:12:25 +0200 Subject: [PATCH 33/54] More cargo fmt --- prusti-server/src/client.rs | 2 +- .../snapshots/into_snapshot/common/mod.rs | 21 ++++++++----------- prusti-viper/src/encoder/mir_encoder/mod.rs | 2 +- prusti-viper/src/encoder/places.rs | 2 +- prusti-viper/src/encoder/procedure_encoder.rs | 11 +++------- prusti-viper/src/verifier.rs | 7 ++++--- 6 files changed, 19 insertions(+), 26 deletions(-) diff --git a/prusti-server/src/client.rs b/prusti-server/src/client.rs index 5654f299bed..c5bd69b7ddb 100644 --- a/prusti-server/src/client.rs +++ b/prusti-server/src/client.rs @@ -5,10 +5,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::VerificationRequest; +use backend_common::VerificationResult; use prusti_common::config; use reqwest::Client; use url::{ParseError, Url}; -use backend_common::VerificationResult; pub struct PrustiClient { client: Client, diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index 817e46ca26a..09214142d5a 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -160,26 +160,23 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { let recursive_apply_and = |mut exprs: Vec| { let mut result = exprs.pop().unwrap(); for expr in exprs.into_iter().rev() { - result = vir_low::Expression::BinaryOp( - vir_low::expression::BinaryOp { - left: Box::new(expr), - right: Box::new(result), - position: *position, - op_kind: vir_low::expression::BinaryOpKind::And, - }, - ); + result = vir_low::Expression::BinaryOp(vir_low::expression::BinaryOp { + left: Box::new(expr), + right: Box::new(result), + position: *position, + op_kind: vir_low::expression::BinaryOpKind::And, + }); } result }; - let validity_call_imply_body = vir_low::Expression::BinaryOp( - vir_low::expression::BinaryOp { + let validity_call_imply_body = + vir_low::Expression::BinaryOp(vir_low::expression::BinaryOp { left: Box::new(recursive_apply_and(validity_calls)), right: Box::new(body), position: *position, op_kind: vir_low::expression::BinaryOpKind::Implies, - }, - ); + }); let kind = match kind { vir_mid::expression::QuantifierKind::ForAll => { diff --git a/prusti-viper/src/encoder/mir_encoder/mod.rs b/prusti-viper/src/encoder/mir_encoder/mod.rs index 8712faff6e1..b8707d86f29 100644 --- a/prusti-viper/src/encoder/mir_encoder/mod.rs +++ b/prusti-viper/src/encoder/mir_encoder/mod.rs @@ -25,7 +25,7 @@ use prusti_interface::environment::mir_utils::MirPlace; use prusti_rustc_interface::{ errors::MultiSpan, hir::def_id::DefId, - index::IndexVec, + index::vec::IndexVec, middle::{mir, ty}, span::{Span, DUMMY_SP}, target::abi, diff --git a/prusti-viper/src/encoder/places.rs b/prusti-viper/src/encoder/places.rs index 508f4b324ae..75833c3f456 100644 --- a/prusti-viper/src/encoder/places.rs +++ b/prusti-viper/src/encoder/places.rs @@ -5,7 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use prusti_rustc_interface::{ - index::{Idx, IndexVec}, + index::vec::{Idx, IndexVec}, middle::{mir, ty::Ty}, }; use std::iter; diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index d76a13f40cc..abb0d4f07c7 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -66,21 +66,16 @@ use prusti_interface::{ }; use prusti_rustc_interface::{ errors::MultiSpan, - index::IndexSlice, middle::{ mir, mir::{Mutability, TerminatorKind}, - ty::{self, GenericArgsRef}, + ty::{self, subst::SubstsRef}, }, span::Span, - target::abi::{FieldIdx, Integer}, + target::abi::Integer, }; use rustc_hash::{FxHashMap, FxHashSet}; -use std::{ - collections::BTreeMap, - convert::TryInto, - fmt::{Debug, Write}, -}; +use std::{collections::BTreeMap, convert::TryInto, fmt::Debug}; use vir_crate::polymorphic::{ self as vir, borrows::Borrow, collect_assigned_vars, compute_identifier, CfgBlockIndex, ExprIterator, Float, Successor, Type, diff --git a/prusti-viper/src/verifier.rs b/prusti-viper/src/verifier.rs index 7360b6bfa1a..0ae50ba4e68 100644 --- a/prusti-viper/src/verifier.rs +++ b/prusti-viper/src/verifier.rs @@ -211,9 +211,10 @@ impl<'v, 'tcx> Verifier<'v, 'tcx> { /// Verify a list of programs. /// Returns a list of (program_name, verification_result) tuples. -fn verify_programs(env: &Environment, programs: Vec) - -> Vec<(String, backend_common::VerificationResult)> -{ +fn verify_programs( + env: &Environment, + programs: Vec, +) -> Vec<(String, backend_common::VerificationResult)> { let source_path = env.name.source_path(); let rust_program_name = source_path .file_name() From fc2e0564a8f5d65d1fcd8dbc57c4d77bd5cb322b Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Tue, 13 Jun 2023 14:55:02 +0200 Subject: [PATCH 34/54] Increase SMT timeout, comment out problematic code in error_manager --- native-verifier/src/theories/Preamble.smt2 | 2 +- native-verifier/src/theories/PreambleZ3.smt2 | 2 +- .../src/encoder/errors/error_manager.rs | 26 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index d6770dc44f8..29e29254444 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -4,7 +4,7 @@ (set-logic ALL) -(set-option :tlimit-per 5000) +(set-option :tlimit-per 10000) (set-option :auto-extend-integers true) ; similar to global-decls in Z3 (set-option :incrementality false) ; similar to auto_config in Z3 diff --git a/native-verifier/src/theories/PreambleZ3.smt2 b/native-verifier/src/theories/PreambleZ3.smt2 index a64a6c439ab..63dc472e447 100644 --- a/native-verifier/src/theories/PreambleZ3.smt2 +++ b/native-verifier/src/theories/PreambleZ3.smt2 @@ -33,7 +33,7 @@ (set-option :model.v2 true) (set-option :model.partial false) -(set-option :timeout 5000) +(set-option :timeout 10000) ; --- Floating-point arithmetic --- diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 4391daa9edc..f216f1a5a22 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -230,19 +230,19 @@ impl<'tcx> ErrorManager<'tcx> { Position::default(), "Trying to register an error on a default position" ); - if let Some(existing_error_ctxt) = self.error_contexts.get(&pos.id()) { - debug_assert_eq!( - existing_error_ctxt, - &error_ctxt, - "An existing error context would be overwritten.\n\ - Position id: {}\n\ - Existing error context: {:?}\n\ - New error context: {:?}", - pos.id(), - existing_error_ctxt, - error_ctxt - ); - } + // if let Some(existing_error_ctxt) = self.error_contexts.get(&pos.id()) { + // debug_assert_eq!( + // existing_error_ctxt, + // &error_ctxt, + // "An existing error context would be overwritten.\n\ + // Position id: {}\n\ + // Existing error context: {:?}\n\ + // New error context: {:?}", + // pos.id(), + // existing_error_ctxt, + // error_ctxt + // ); + // } self.error_contexts.insert(pos.id(), error_ctxt); } From 41bbaaa832f7a5f4182582004cfe08ed7debdf67 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 14 Jun 2023 14:27:58 +0200 Subject: [PATCH 35/54] Fix abs(i32::MIN) overflowing --- native-verifier/src/fol.rs | 24 ++++++++++++++++++------ native-verifier/src/smt_lib.rs | 4 ++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 993ba1a69ca..031fd65b971 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -34,6 +34,8 @@ fn vir_statement_to_fol_statements( known_methods: &HashMap, ) -> Vec { match statement { + Statement::Assume(expr) => vec![FolStatement::Assume(expr.expression.clone())], + Statement::Inhale(expr) => vec![FolStatement::Assume(expr.expression.clone())], Statement::Assert(expr) => { let reason_pos = expr.expression.position(); let failure_pos = expr.position; @@ -50,12 +52,22 @@ fn vir_statement_to_fol_statements( }] } } - Statement::Assume(expr) => vec![FolStatement::Assume(expr.expression.clone())], - Statement::Inhale(expr) => vec![FolStatement::Assume(expr.expression.clone())], - Statement::Exhale(expr) => vec![FolStatement::Assert { - expression: expr.expression.clone(), - reason: None, - }], + Statement::Exhale(expr) => { + let reason_pos = expr.expression.position(); + let failure_pos = expr.position; + + if reason_pos != failure_pos { + vec![FolStatement::Assert { + expression: set_position(expr.expression.clone(), failure_pos), + reason: Some(reason_pos), + }] + } else { + vec![FolStatement::Assert { + expression: expr.expression.clone(), + reason: None, + }] + } + } Statement::Assign(assign) => { let eq = Expression::BinaryOp(BinaryOp { op_kind: BinaryOpKind::EqCmp, diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 12cd6b9e522..b632a8af21a 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -439,8 +439,8 @@ impl SMTTranslatable for Expression { Expression::Constant(constant) => match &constant.value { ConstantValue::Bool(bool) => bool.to_string(), ConstantValue::Int(i) => { - if *i < 0 { - format!("(- {})", i.abs()) + if let Some(abs_val) = i.to_string().strip_prefix('-') { + format!("(- {})", abs_val) } else { i.to_string() } From 6f3ffccc6e1fd21697c51b3995033497295ae93a Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 14 Jun 2023 16:17:53 +0200 Subject: [PATCH 36/54] Refactor encoding of Statement::Conditional --- native-verifier/src/fol.rs | 67 ++++++++------------------------------ 1 file changed, 14 insertions(+), 53 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 031fd65b971..4c21b90e7f5 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -82,42 +82,22 @@ fn vir_statement_to_fol_statements( vec![FolStatement::Assume(eq)] } Statement::Conditional(cond) => { - // handle trivial cases where the guard is constant true or false, emit the branches instead - if let Expression::Constant(Constant { - value: ConstantValue::Bool(true), - .. - }) = cond.guard - { - return cond - .then_branch - .iter() - .flat_map(|s| vir_statement_to_fol_statements(s, known_methods)) - .collect(); - } else if let Expression::Constant(Constant { - value: ConstantValue::Bool(false), - .. - }) = cond.guard - { - return cond - .else_branch - .iter() - .flat_map(|s| vir_statement_to_fol_statements(s, known_methods)) - .collect(); - } + let negate_guard = Expression::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument: Box::new(cond.guard.clone()), + position: cond.guard.position(), + }); - if cond.then_branch.is_empty() ^ cond.else_branch.is_empty() { - let branch = if cond.then_branch.is_empty() { - &cond.else_branch - } else { - &cond.then_branch - }; - // if the branch is an assertion, emit it as an implication from the guard - let mut statements = vec![]; + let mut statements = vec![]; + for (guard, branch) in [ + (cond.guard.clone(), &cond.then_branch), + (negate_guard, &cond.else_branch), + ] { for s in branch { if let Statement::Assert(assert) = s { let implication = Expression::BinaryOp(BinaryOp { op_kind: BinaryOpKind::Implies, - left: Box::new(cond.guard.clone()), + left: Box::new(guard.clone()), right: Box::new(assert.expression.clone()), position: assert.position, }); @@ -128,39 +108,20 @@ fn vir_statement_to_fol_statements( } else if let Statement::Inhale(inhale) = s { let implication = Expression::BinaryOp(BinaryOp { op_kind: BinaryOpKind::Implies, - left: Box::new(cond.guard.clone()), + left: Box::new(guard.clone()), right: Box::new(inhale.expression.clone()), position: inhale.position, }); statements.push(FolStatement::Assume(implication)); } else { unimplemented!( - "Non-assertion statements in conditional branches not supported: {}", + "Non-assertion statements in conditionals not supported: {}", s ) } } - return statements; } - - if !(cond.then_branch.is_empty() && cond.else_branch.is_empty()) { - unimplemented!( - "Non-trivial conditional statements not supported!!\nGuard: {}\n\nThen-branch:\n{}\n\nElse-branch:\n{}\n", - cond.guard, - cond.then_branch - .iter() - .map(|s| format!("{}", s)) - .collect::>() - .join(";\n"), - cond.else_branch - .iter() - .map(|s| format!("{}", s)) - .collect::>() - .join(";\n") - ); - } - - vec![] + return statements; } Statement::MethodCall(method_call) => { let method_decl = known_methods From 25ee57b345ab722c1e81d08ff5261c1e013bce19 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Wed, 14 Jun 2023 16:18:13 +0200 Subject: [PATCH 37/54] Fix invalid file paths when emitting SMT files --- native-verifier/src/verifier.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 4b82fbd9031..fc78fd66c8a 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -47,6 +47,9 @@ impl Verifier { let is_z3 = self.smt_solver_exe.ends_with("z3"); let solver_name = if is_z3 { "Z3" } else { "CVC5" }; + let program_name = prusti_common::report::log::to_legal_file_name(&program.name); + let program_name = format!("lithium_{}.smt2", program_name); + run_timed!("Translation to SMT-LIB", debug, let mut smt = SMTLib::new(is_z3); program.build_smt(&mut smt); @@ -54,7 +57,7 @@ impl Verifier { report_with_writer( "smt", - format!("lithium_{}.smt2", program.name), + program_name.clone(), |writer| { writer.write_all(smt_program.as_bytes()).unwrap(); }, @@ -65,7 +68,7 @@ impl Verifier { let result: Result> = try { let mut command = Command::new(self.smt_solver_exe.clone()); - let path = config::log_dir().join("smt").join(format!("lithium_{}.smt2", program.name)); + let path = config::log_dir().join("smt").join(program_name); let path = path.to_str().unwrap(); if is_z3 { From 6fedb1912b7424fd1d99659629588ca73e1bd5be Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 16 Jun 2023 16:37:38 +0200 Subject: [PATCH 38/54] Fix quantifier optimization rules --- native-verifier/src/optimization.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/native-verifier/src/optimization.rs b/native-verifier/src/optimization.rs index 9f9dc5a176a..2e92f227bb3 100644 --- a/native-verifier/src/optimization.rs +++ b/native-verifier/src/optimization.rs @@ -136,9 +136,13 @@ impl ExpressionFolder for Optimizer { ) -> vir::low::Expression { if quantifier.triggers.is_empty() { let new_body = self.fold_expression(*quantifier.body); - if new_body == true.into() { + if new_body == true.into() + && quantifier.kind == vir::low::expression::QuantifierKind::ForAll + { true.into() - } else if new_body == false.into() { + } else if new_body == false.into() + && quantifier.kind == vir::low::expression::QuantifierKind::Exists + { false.into() } else { vir::low::Expression::Quantifier(vir::low::expression::Quantifier { From 56c06783dfacc7b2a42d14fcec027a9c9b0ffdb6 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Sun, 18 Jun 2023 22:14:22 +0200 Subject: [PATCH 39/54] Various bugfixes --- native-verifier/src/fol.rs | 25 +++++++++++++++++---- native-verifier/src/theories/Preamble.smt2 | 26 ---------------------- native-verifier/src/verifier.rs | 2 +- prusti-utils/src/config.rs | 2 +- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 4c21b90e7f5..076cb361fd2 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -105,6 +105,17 @@ fn vir_statement_to_fol_statements( expression: implication, reason: None, // TODO: Reason? }); + } else if let Statement::Exhale(exhale) = s { + let exhaling = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::Implies, + left: Box::new(guard.clone()), + right: Box::new(exhale.expression.clone()), + position: exhale.position, + }); + statements.push(FolStatement::Assert { + expression: exhaling, + reason: None, // TODO: Reason? + }); } else if let Statement::Inhale(inhale) = s { let implication = Expression::BinaryOp(BinaryOp { op_kind: BinaryOpKind::Implies, @@ -113,11 +124,17 @@ fn vir_statement_to_fol_statements( position: inhale.position, }); statements.push(FolStatement::Assume(implication)); + } else if let Statement::Assume(assume) = s { + let assumption = Expression::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::Implies, + left: Box::new(guard.clone()), + right: Box::new(assume.expression.clone()), + position: assume.position, + }); + statements.push(FolStatement::Assume(assumption)); } else { - unimplemented!( - "Non-assertion statements in conditionals not supported: {}", - s - ) + statements.push(FolStatement::Comment("Generated within a conditional:".to_string())); + statements.extend(vir_statement_to_fol_statements(s, known_methods)); } } } diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index 29e29254444..eed60ab4c11 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -1,34 +1,8 @@ ; ===== Static preamble ===== (set-info :smt-lib-version 2.6) - (set-logic ALL) - (set-option :tlimit-per 10000) - -(set-option :auto-extend-integers true) ; similar to global-decls in Z3 -(set-option :incrementality false) ; similar to auto_config in Z3 - -; Note: restart_strategy and restart_factor do not have direct equivalents in CVC5. -; Similar functionality might be achieved through tuning the various options for decision strategies, but there is no one-to-one mapping. - -(set-option :bitvector-div-zero-const false) ; similar to smt.bv.reflect in Z3 -(set-option :mbqi-mode none) ; similar to smt.mbqi in Z3 - -; The options smt.qi.cost, smt.qi.eager_threshold, and smt.qi.max_multi_patterns might correspond to -; configuring quantifier instantiation strategies in CVC5, but there are no direct equivalents. - -(set-option :decision-random-frequency 0) ; similar to sat.phase caching in Z3 - -; There are no direct equivalents in CVC5 for sat.random_seed, nlsat.* options. - -; Note: fp.spacer.* options are related to Z3's Spacer, a software model checker. CVC5 doesn't have Spacer. - -(set-option :random-seed 0) ; similar to smt.random_seed in Z3 - -; The sls.* options in Z3 are related to stochastic local search. -; CVC5 has options for tuning its decision strategy, but these are quite different from SLS and do not directly correspond to these options. - (set-option :produce-models true) ; equivalent to model.v2 in Z3 (set-option :produce-assignments false) ; similar to model.partial in Z3 diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index fc78fd66c8a..3fb91836d82 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -48,7 +48,7 @@ impl Verifier { let solver_name = if is_z3 { "Z3" } else { "CVC5" }; let program_name = prusti_common::report::log::to_legal_file_name(&program.name); - let program_name = format!("lithium_{}.smt2", program_name); + let program_name = format!("{}.smt2", program_name); // TODO: path mismatch when over config length run_timed!("Translation to SMT-LIB", debug, let mut smt = SMTLib::new(is_z3); diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 2b0eea4b01f..b5dfcbcc194 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -92,7 +92,7 @@ lazy_static::lazy_static! { settings.set_default("dump_debug_info_during_fold", false).unwrap(); settings.set_default("dump_nll_facts", false).unwrap(); settings.set_default("ignore_regions", false).unwrap(); - settings.set_default("max_log_file_name_length", 60).unwrap(); + settings.set_default("max_log_file_name_length", 240).unwrap(); settings.set_default("dump_path_ctxt_in_debug_info", false).unwrap(); settings.set_default("dump_reborrowing_dag_in_debug_info", false).unwrap(); settings.set_default("dump_borrowck_info", false).unwrap(); From f7bec364a80531caf6b5dba3f9063eb40b3580c9 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Mon, 19 Jun 2023 04:35:29 +0200 Subject: [PATCH 40/54] Make sure positions are preserved by the optimizer --- native-verifier/src/fol.rs | 4 +- native-verifier/src/optimization.rs | 142 +++++++++++++++++----------- native-verifier/src/verifier.rs | 2 +- 3 files changed, 91 insertions(+), 57 deletions(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 076cb361fd2..297db072dbf 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -133,7 +133,9 @@ fn vir_statement_to_fol_statements( }); statements.push(FolStatement::Assume(assumption)); } else { - statements.push(FolStatement::Comment("Generated within a conditional:".to_string())); + statements.push(FolStatement::Comment( + "Generated within a conditional:".to_string(), + )); statements.extend(vir_statement_to_fol_statements(s, known_methods)); } } diff --git a/native-verifier/src/optimization.rs b/native-verifier/src/optimization.rs index 2e92f227bb3..c454985a776 100644 --- a/native-verifier/src/optimization.rs +++ b/native-verifier/src/optimization.rs @@ -1,12 +1,36 @@ -use vir::low::{ - expression::{visitors::ExpressionFolder, BinaryOp, MagicWand, UnaryOp}, - Expression, +use vir::{ + common::position::Positioned, + low::{ + expression::{visitors::ExpressionFolder, BinaryOp, Constant, MagicWand, UnaryOp}, + Expression, Position, + }, }; use crate::fol::FolStatement; struct Optimizer {} +fn constant_at(value: bool, position: Position) -> Expression { + vir::low::Expression::Constant(Constant { + value: value.into(), + ty: vir::low::Type::Bool, + position, + }) +} + +fn equal_to_const(value: &Expression, constant: bool) -> bool { + if let vir::low::Expression::Constant(Constant { + value: vir::low::ConstantValue::Bool(b), + ty: vir::low::Type::Bool, + .. + }) = value + { + *b == constant + } else { + false + } +} + impl ExpressionFolder for Optimizer { fn fold_binary_op_enum(&mut self, binary_op: BinaryOp) -> Expression { let new_left = self.fold_expression(*binary_op.left); @@ -15,85 +39,85 @@ impl ExpressionFolder for Optimizer { match binary_op.op_kind { vir::low::BinaryOpKind::EqCmp => { if new_left == new_right { - true.into() + constant_at(true, binary_op.position) } else { vir::low::Expression::BinaryOp(BinaryOp { - op_kind: vir::low::BinaryOpKind::EqCmp, left: Box::new(new_left), right: Box::new(new_right), - position: binary_op.position, + ..binary_op }) } } vir::low::BinaryOpKind::NeCmp => { if new_left == new_right { - false.into() + constant_at(false, binary_op.position) } else { vir::low::Expression::BinaryOp(BinaryOp { - op_kind: vir::low::BinaryOpKind::NeCmp, left: Box::new(new_left), right: Box::new(new_right), - position: binary_op.position, + ..binary_op }) } } vir::low::BinaryOpKind::And => { - if new_left == true.into() { + if equal_to_const(&new_left, true) { new_right - } else if new_right == true.into() { + } else if equal_to_const(&new_right, true) { new_left - } else if new_left == false.into() || new_right == false.into() { - false.into() + } else if equal_to_const(&new_left, false) { + constant_at(false, new_right.position()) + } else if equal_to_const(&new_right, false) { + constant_at(false, new_left.position()) } else { vir::low::Expression::BinaryOp(BinaryOp { - op_kind: vir::low::BinaryOpKind::And, left: Box::new(new_left), right: Box::new(new_right), - position: binary_op.position, + ..binary_op }) } } vir::low::BinaryOpKind::Or => { - if new_left == true.into() || new_right == true.into() { - true.into() - } else if new_left == false.into() { + if equal_to_const(&new_left, true) { + constant_at(true, new_right.position()) + } else if equal_to_const(&new_right, true) { + constant_at(true, new_left.position()) + } else if equal_to_const(&new_left, false) { new_right - } else if new_right == false.into() { + } else if equal_to_const(&new_right, false) { new_left } else { vir::low::Expression::BinaryOp(BinaryOp { - op_kind: vir::low::BinaryOpKind::Or, left: Box::new(new_left), right: Box::new(new_right), - position: binary_op.position, + ..binary_op }) } } vir::low::BinaryOpKind::Implies => { - if new_left == true.into() { + if equal_to_const(&new_left, true) { new_right - } else if new_right == false.into() { - vir::low::Expression::UnaryOp(UnaryOp { + } else if equal_to_const(&new_right, false) { + self.fold_unary_op_enum(UnaryOp { op_kind: vir::low::UnaryOpKind::Not, + position: new_left.position(), argument: Box::new(new_left), - position: binary_op.position, }) - } else if new_left == false.into() || new_right == true.into() { - true.into() + } else if equal_to_const(&new_left, false) { + constant_at(true, new_right.position()) + } else if equal_to_const(&new_right, true) { + constant_at(true, new_left.position()) } else { vir::low::Expression::BinaryOp(BinaryOp { - op_kind: vir::low::BinaryOpKind::Implies, left: Box::new(new_left), right: Box::new(new_right), - position: binary_op.position, + ..binary_op }) } } - typ => vir::low::Expression::BinaryOp(BinaryOp { - op_kind: typ, + _ => vir::low::Expression::BinaryOp(BinaryOp { left: Box::new(new_left), right: Box::new(new_right), - position: binary_op.position, + ..binary_op }), } } @@ -103,10 +127,10 @@ impl ExpressionFolder for Optimizer { match unary_op.op_kind { vir::low::UnaryOpKind::Not => { - if new_argument == true.into() { - false.into() - } else if new_argument == false.into() { - true.into() + if equal_to_const(&new_argument, true) { + constant_at(false, new_argument.position()) + } else if equal_to_const(&new_argument, false) { + constant_at(true, new_argument.position()) } else if let vir::low::Expression::UnaryOp(UnaryOp { op_kind: vir::low::UnaryOpKind::Not, argument, @@ -116,16 +140,14 @@ impl ExpressionFolder for Optimizer { *argument } else { vir::low::Expression::UnaryOp(UnaryOp { - op_kind: vir::low::UnaryOpKind::Not, argument: Box::new(new_argument), - position: unary_op.position, + ..unary_op }) } } - typ => vir::low::Expression::UnaryOp(UnaryOp { - op_kind: typ, + _ => vir::low::Expression::UnaryOp(UnaryOp { argument: Box::new(new_argument), - position: unary_op.position, + ..unary_op }), } } @@ -136,14 +158,14 @@ impl ExpressionFolder for Optimizer { ) -> vir::low::Expression { if quantifier.triggers.is_empty() { let new_body = self.fold_expression(*quantifier.body); - if new_body == true.into() + if equal_to_const(&new_body, true) && quantifier.kind == vir::low::expression::QuantifierKind::ForAll { - true.into() - } else if new_body == false.into() + constant_at(true, new_body.position()) + } else if equal_to_const(&new_body, false) && quantifier.kind == vir::low::expression::QuantifierKind::Exists { - false.into() + constant_at(false, new_body.position()) } else { vir::low::Expression::Quantifier(vir::low::expression::Quantifier { body: Box::new(new_body), @@ -160,16 +182,18 @@ impl ExpressionFolder for Optimizer { let new_left = self.fold_expression(*magic_wand.left); let new_right = self.fold_expression(*magic_wand.right); - if new_left == true.into() { + if equal_to_const(&new_left, true) { new_right - } else if new_right == false.into() { - vir::low::Expression::UnaryOp(UnaryOp { + } else if equal_to_const(&new_right, false) { + self.fold_unary_op_enum(UnaryOp { op_kind: vir::low::UnaryOpKind::Not, + position: new_left.position(), argument: Box::new(new_left), - position: magic_wand.position, }) - } else if new_left == false.into() || new_right == true.into() { - true.into() + } else if equal_to_const(&new_left, false) { + constant_at(true, new_right.position()) + } else if equal_to_const(&new_right, true) { + constant_at(true, new_left.position()) } else { vir::low::Expression::MagicWand(MagicWand { left: Box::new(new_left), @@ -187,12 +211,20 @@ impl ExpressionFolder for Optimizer { let new_true = self.fold_expression(*conditional.then_expr); let new_false = self.fold_expression(*conditional.else_expr); - if new_cond == true.into() { + if equal_to_const(&new_cond, true) { new_true - } else if new_cond == false.into() { + } else if equal_to_const(&new_cond, false) { new_false } else if new_true == new_false { new_true + } else if equal_to_const(&new_true, true) && equal_to_const(&new_false, false) { + new_cond + } else if equal_to_const(&new_true, false) && equal_to_const(&new_false, true) { + self.fold_unary_op_enum(UnaryOp { + op_kind: vir::low::UnaryOpKind::Not, + position: new_cond.position(), + argument: Box::new(new_cond), + }) } else { vir::low::Expression::Conditional(vir::low::expression::Conditional { guard: Box::new(new_cond), @@ -211,7 +243,7 @@ pub fn optimize_statements(statements: Vec) -> Vec { FolStatement::Assume(expr) => { let mut optimizer = Optimizer {}; let expr = optimizer.fold_expression(expr); - if expr == true.into() { + if equal_to_const(&expr, true) { None } else { Some(FolStatement::Assume(expr)) @@ -220,7 +252,7 @@ pub fn optimize_statements(statements: Vec) -> Vec { FolStatement::Assert { expression, reason } => { let mut optimizer = Optimizer {}; let expression = optimizer.fold_expression(expression); - if expression == true.into() { + if equal_to_const(&expression, true) { None } else { Some(FolStatement::Assert { expression, reason }) diff --git a/native-verifier/src/verifier.rs b/native-verifier/src/verifier.rs index 3fb91836d82..074428a7923 100644 --- a/native-verifier/src/verifier.rs +++ b/native-verifier/src/verifier.rs @@ -116,7 +116,7 @@ impl Verifier { Some("0".to_string()), Some("0".to_string()), Some("0".to_string()), - format!("{} failed with error: {}", solver_name, err), + format!("{} failed with unexpected error: {}", solver_name, err), None, )); From bc085e4a4f99a70452024218f85a6293cf2516fc Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Mon, 19 Jun 2023 11:18:36 +0200 Subject: [PATCH 41/54] Add bisect test case --- native-verifier/src/smt_lib.rs | 2 +- .../tests/verify/pass/native/floating.rs | 2 +- .../verify_overflow/fail/native/bisect.rs | 42 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 prusti-tests/tests/verify_overflow/fail/native/bisect.rs diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index b632a8af21a..205cbc23d32 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -646,7 +646,7 @@ impl SMTTranslatable for IntBinaryOpKind { BinaryOpKind::Add => "+", BinaryOpKind::Sub => "-", BinaryOpKind::Mul => "*", - BinaryOpKind::Div => "/", + BinaryOpKind::Div => "div", BinaryOpKind::Mod => "mod", BinaryOpKind::And => "and", BinaryOpKind::Or => "or", diff --git a/prusti-tests/tests/verify/pass/native/floating.rs b/prusti-tests/tests/verify/pass/native/floating.rs index 06ef17ce21e..e5815ff492d 100644 --- a/prusti-tests/tests/verify/pass/native/floating.rs +++ b/prusti-tests/tests/verify/pass/native/floating.rs @@ -86,7 +86,7 @@ fn test_neg_inf_64_is_nan() { assert!(!is_nan_64(a)); } -// THEORY OF INIFINITY +// THEORY OF INFINITY #[requires(is_nan_32(f))] #[ensures(!is_infinite_32(f))] diff --git a/prusti-tests/tests/verify_overflow/fail/native/bisect.rs b/prusti-tests/tests/verify_overflow/fail/native/bisect.rs new file mode 100644 index 00000000000..e836c6bab3a --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/native/bisect.rs @@ -0,0 +1,42 @@ +use prusti_contracts::*; + +/// A monotonically increasing discrete function, with domain [0, domain_size) +struct Function; + +impl Function { + #[pure] + #[trusted] // abstract, actual implementation doesn't matter + fn domain_size(&self) -> usize { + 42 + } + + #[pure] + #[trusted] + #[requires(x < self.domain_size())] + fn eval(&self, x: usize) -> i32 { + x as i32 + } +} + +/// Find the `x` s.t. `f(x) == target` +#[ensures(if let Some(x) = result { f.eval(x) == target } else { true })] +fn bisect(f: &Function, target: i32) -> Option { + let mut low = 0; + let mut high = f.domain_size(); + assert!(high == f.domain_size()); + while low < high { + body_invariant!(low < high && high <= f.domain_size()); + let mid = (low + high) / 2; + let mid_val = f.eval(mid); + if mid_val < target { + low = mid + 1; + } else if mid_val > target { + high = mid; + } else { + return Some(mid); + } + } + None +} + +fn main() {} From 1ad0fbe16daa91d71c1b8ec09d0cf52533e65803 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Mon, 19 Jun 2023 15:15:35 +0200 Subject: [PATCH 42/54] Remove overcomplicated tests --- .../tests/verify/pass/native/super_sale.rs | 278 ------------------ .../tests/verify/pass/native/the_jackpot.rs | 229 --------------- 2 files changed, 507 deletions(-) delete mode 100644 prusti-tests/tests/verify/pass/native/super_sale.rs delete mode 100644 prusti-tests/tests/verify/pass/native/the_jackpot.rs diff --git a/prusti-tests/tests/verify/pass/native/super_sale.rs b/prusti-tests/tests/verify/pass/native/super_sale.rs deleted file mode 100644 index 3c141595a6a..00000000000 --- a/prusti-tests/tests/verify/pass/native/super_sale.rs +++ /dev/null @@ -1,278 +0,0 @@ -// compile-flags: -Pviper_backend=Lithium -use prusti_contracts::*; - -pub struct VecWrapperI32 { - v: Vec, -} - -impl VecWrapperI32 { - #[trusted] - #[pure] - #[terminates] - #[ensures (0 <= result)] - pub fn len(&self) -> isize { - self.v.len() as isize - } - - #[trusted] - #[ensures (result.len() == 0)] - pub fn new() -> Self { - VecWrapperI32 { v: Vec::new() } - } - - #[trusted] - #[pure] - #[terminates] - #[requires (0 <= index && index < self.len())] - pub fn lookup(&self, index: isize) -> isize { - self.v[index as usize] - } - - #[trusted] - #[ensures (self.len() == old(self.len()) + 1)] - #[ensures (self.lookup(old(self.len())) == value)] - #[ensures (forall(|i: isize| (0 <= i && i < old(self.len())) ==> - self.lookup(i) == old(self.lookup(i))))] - pub fn push(&mut self, value: isize) { - self.v.push(value); - } -} - -#[pure] -#[terminates] -#[ensures (result >= a && result >= b)] -#[ensures (result == a || result == b)] -fn max(a: isize, b: isize) -> isize { - if a < b { - b - } else { - a - } -} - -struct Matrix { - _ghost_y_size: usize, - _ghost_x_size: usize, - vec: Vec>, -} - -impl Matrix { - #[trusted] - #[requires(0 < y_size)] - #[requires(0 < x_size)] - #[ensures(result.y_size() == y_size)] - #[ensures(result.x_size() == x_size)] - #[ensures(forall(|y: isize, x: isize| - (0 <= x && x < result.x_size() && 0 <= y && y < result.y_size()) ==> - result.lookup(y, x) == 0))] - fn new(y_size: isize, x_size: isize) -> Self { - Self { - _ghost_y_size: y_size as usize, - _ghost_x_size: x_size as usize, - vec: vec![vec![0; y_size as usize]; x_size as usize], - } - } - - #[pure] - #[terminates] - #[trusted] - #[ensures(0 < result)] - fn x_size(&self) -> isize { - self._ghost_x_size as isize - } - - #[pure] - #[terminates] - #[trusted] - #[ensures(0 < result)] - fn y_size(&self) -> isize { - self._ghost_y_size as isize - } - - #[trusted] - #[requires(0 <= y && y < self.y_size())] - #[requires(0 <= x && x < self.x_size())] - #[ensures(self.y_size() == old(self.y_size()))] - #[ensures(self.x_size() == old(self.x_size()))] - #[ensures(forall(|i: isize, j: isize| - (i >= 0 && i < y && j >= 0 && j < self.x_size()) ==> (self.lookup(i, j) == old(self.lookup(i, j)))))] - #[ensures(self.lookup(y, x) == value)] - #[ensures(forall(|i: isize, j: isize| - (0 <= i && i < self.y_size() && - 0 <= j && j < self.x_size() && !(j == x && i == y)) ==> - self.lookup(i, j) == old(self.lookup(i, j))))] - fn set(&mut self, y: isize, x: isize, value: isize) -> () { - self.vec[y as usize][x as usize] = value - } - - #[pure] - #[terminates] - #[trusted] - #[requires(0 <= y && y < self.y_size())] - #[requires(0 <= x && x < self.x_size())] - fn lookup(&self, y: isize, x: isize) -> isize { - self.vec[y as usize][x as usize] - } -} - -// Recursive solution - -#[pure] -#[terminates(trusted)] -#[requires(prices.len() == weights.len())] -#[requires(prices.len() > 0 && weights.len() <= 1000)] -#[requires(index < prices.len() && index >= -1)] -#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] -#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] -#[ensures(result <= (index + 1) * 1000)] -#[ensures(result >= 0)] -fn solve_rec( - prices: &VecWrapperI32, - weights: &VecWrapperI32, - index: isize, - remaining_weight: isize, -) -> isize { - if remaining_weight <= 0 || index < 0 { - 0 - } else { - assert!(index >= 0); - if weights.lookup(index) <= remaining_weight { - max( - solve_rec(prices, weights, index - 1, remaining_weight), - prices.lookup(index) - + solve_rec( - prices, - weights, - index - 1, - remaining_weight - weights.lookup(index), - ), - ) - } else { - solve_rec(prices, weights, index - 1, remaining_weight) - } - } -} - -// // DP solution - -#[requires(prices.len() == weights.len())] -#[requires(prices.len() > 0 && weights.len() <= 1000)] -#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] -#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] -#[requires(dp.y_size() == 31 && dp.x_size() == prices.len())] -#[requires(index_weight >= 1 && index_weight < dp.y_size())] -#[requires(index_object >= 0 && index_object < dp.x_size())] -#[requires(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] -#[requires(forall(|i: isize| (i >= 0 && i < index_object) ==> dp.lookup(index_weight, i) == solve_rec(prices, weights, i, index_weight)))] -#[ensures(result == solve_rec(prices, weights, index_object, index_weight))] -#[ensures(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] -#[ensures(forall(|i: isize| (i >= 0 && i < index_object) ==> dp.lookup(index_weight, i) == solve_rec(prices, weights, i, index_weight)))] -fn helper( - prices: &VecWrapperI32, - weights: &VecWrapperI32, - index_weight: isize, - index_object: isize, - dp: &Matrix, -) -> isize { - let mut answer = 0; - let index_before = index_object - 1; - if index_before == -1 { - if index_weight >= weights.lookup(index_object) { - answer = prices.lookup(index_object); - } else { - answer = 0; - } - } else { - if index_weight >= weights.lookup(index_object) { - let remaining_weight = index_weight - weights.lookup(index_object); - assert!( - dp.lookup(remaining_weight, index_object - 1) - == solve_rec(prices, weights, index_object - 1, remaining_weight) - ); - assert!(dp.lookup(remaining_weight, index_object - 1) <= index_object * 1000); - answer = max( - dp.lookup(index_weight, index_object - 1), - prices.lookup(index_object) + dp.lookup(remaining_weight, index_object - 1), - ); - } else { - answer = dp.lookup(index_weight, index_object - 1); - } - } - answer -} - -#[trusted] // TODO: Error by the author? -#[requires(prices.len() == weights.len())] -#[requires(prices.len() > 0 && weights.len() <= 1000)] -#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] -#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] -#[requires(dp.y_size() == 31 && dp.x_size() == prices.len())] -#[requires(index_weight >= 1 && index_weight < dp.y_size())] -#[requires(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] -#[ensures(dp.y_size() == old(dp.y_size()))] -#[ensures(dp.x_size() == old(dp.x_size()))] -#[ensures(forall(|i: isize, j: isize| (i >= 0 && i <= index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i)))] -fn helper_loop( - prices: &VecWrapperI32, - weights: &VecWrapperI32, - index_weight: isize, - dp: &mut Matrix, -) { - let mut index_object = 0isize; - let n = prices.len(); - while index_object < dp.x_size() { - body_invariant!(index_weight >= 1 && index_weight < dp.y_size()); - body_invariant!(index_object >= 0 && index_object < dp.x_size()); - body_invariant!(dp.y_size() == 31 && dp.x_size() == prices.len()); - body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); - body_invariant!(forall(|i: isize| (i >= 0 && i < index_object) ==> dp.lookup(index_weight, i) == solve_rec(prices, weights, i, index_weight))); - let answer = helper(prices, weights, index_weight, index_object, &dp); - dp.set(index_weight, index_object, answer); - index_object += 1; - } -} - -#[requires(prices.len() == weights.len())] -#[requires(prices.len() > 0 && weights.len() <= 1000)] -#[requires(forall(|k: isize| (k >= 0 && k < prices.len()) ==> prices.lookup(k) >= 1 && prices.lookup(k) <= 1000))] -#[requires(forall(|k: isize| (k >= 0 && k < weights.len()) ==> weights.lookup(k) >= 1 && weights.lookup(k) <= 30))] -#[requires(forall(|k: isize| (k >= 0 && k < max_weights.len()) ==> max_weights.lookup(k) >= 1 && max_weights.lookup(k) <= 30))] -#[ensures(result.len() == max_weights.len())] -#[ensures(forall(|k: isize| (k >= 0 && k < result.len()) ==> result.lookup(k) == solve_rec(prices, weights, prices.len() - 1, max_weights.lookup(k))))] -fn super_sale( - prices: &VecWrapperI32, - weights: &VecWrapperI32, - max_weights: &VecWrapperI32, -) -> VecWrapperI32 { - let mut answer = VecWrapperI32::new(); - let g = max_weights.len(); - let n = prices.len(); - let max_weight = 31; - let mut dp = Matrix::new(max_weight, n); - let mut index_weight = 1isize; - while index_weight <= 30 { - body_invariant!(dp.y_size() == max_weight && dp.x_size() == n); - body_invariant!(index_weight >= 1 && index_weight < max_weight); - body_invariant!(forall(|i: isize, j: isize| (i >= 0 && i < index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); - helper_loop(prices, weights, index_weight, &mut dp); - prusti_assume!(dp.y_size() == max_weight && dp.x_size() == n); // TODO: Postconditions do not handle old-expressions - prusti_assume!(forall(|i: isize, j: isize| (i >= 0 && i <= index_weight && j >= 0 && j < dp.x_size()) ==> dp.lookup(i, j) == solve_rec(prices, weights, j, i))); - // TODO: Postconditions do not handle old-expressions - index_weight += 1; - } - - let mut index_person = 0isize; - while index_person < g { - body_invariant!(index_person >= 0 && index_person < g); - body_invariant!(answer.len() == index_person); - body_invariant!(forall(|k: isize| (k >= 0 && k < g) ==> max_weights.lookup(k) >= 1 && max_weights.lookup(k) <= 30)); - body_invariant!(forall(|k: isize| (k >= 0 && k < index_person) ==> answer.lookup(k) == solve_rec(prices, weights, prices.len() - 1, max_weights.lookup(k)))); - let w = max_weights.lookup(index_person); - let cur_answer = dp.lookup(w, n - 1); - answer.push(cur_answer); - index_person += 1; - } - answer -} - -fn main() {} diff --git a/prusti-tests/tests/verify/pass/native/the_jackpot.rs b/prusti-tests/tests/verify/pass/native/the_jackpot.rs deleted file mode 100644 index 531f6ac0baa..00000000000 --- a/prusti-tests/tests/verify/pass/native/the_jackpot.rs +++ /dev/null @@ -1,229 +0,0 @@ -// compile-flags: -Pviper_backend=Lithium -use prusti_contracts::*; - -pub struct VecWrapperI32 { - v: Vec, -} - -impl VecWrapperI32 { - #[trusted] - #[pure] - #[terminates] - #[ensures (0 <= result)] - pub fn len(&self) -> usize { - self.v.len() - } - - #[trusted] - #[ensures (result.len() == 0)] - pub fn new() -> Self { - VecWrapperI32 { v: Vec::new() } - } - - #[pure] - #[terminates] - #[trusted] - #[requires (0 <= index)] - #[requires (index < self.len())] - pub fn lookup(&self, index: usize) -> i32 { - self.v[index] - } - - #[trusted] - #[ensures (self.len() == old(self.len()) + 1)] - #[ensures (self.lookup(old(self.len())) == value)] - #[ensures (forall(|i: usize| (0 <= i && i < old(self.len())) ==> - self.lookup(i) == old(self.lookup(i))))] - pub fn push(&mut self, value: i32) { - self.v.push(value); - } -} - -#[pure] -#[terminates] -#[ensures (result >= a && result >= b)] -#[ensures (result == a || result == b)] -fn max(a: i32, b: i32) -> i32 { - if a < b { - b - } else { - a - } -} - -// Recursive solution - -#[pure] -#[terminates(Int::new_usize(i))] -#[requires(i >= 0)] -#[ensures(result >= 0)] -fn to_i32(i: usize) -> i32 { - if i == 0 { - 0 - } else { - 1 + to_i32(i - 1) - } -} - -#[pure] -#[terminates(Int::new_usize(i))] -#[requires (i >= 0)] -#[requires (i < seq.len())] -#[requires (seq.len() > 0)] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures(result >= -1000)] -#[ensures(result <= (to_i32(i) + 1) * 10000)] -fn max_seq_sum_rec(seq: &VecWrapperI32, i: usize) -> i32 { - if i == 0 { - seq.lookup(0) - } else { - let prev = max_seq_sum_rec(seq, i - 1); - if prev > 0 { - prev + seq.lookup(i) - } else { - seq.lookup(i) - } - } -} - -#[pure] -#[terminates(Int::new_usize(idx))] -#[requires (seq.len() > 0)] -#[requires (idx >= 0)] -#[requires (idx < seq.len())] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -fn solve_rec(seq: &VecWrapperI32, idx: usize) -> i32 { - if idx == 0 { - max_seq_sum_rec(seq, idx) - } else { - max(max_seq_sum_rec(seq, idx), solve_rec(seq, idx - 1)) - } -} - -#[pure] -#[terminates] -#[requires (seq.len() > 0)] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -fn the_jackpot_rec(seq: &VecWrapperI32) -> i32 { - max(0, solve_rec(seq, seq.len() - 1)) -} - -// Naive Solution - -#[pure] -#[terminates(Int::new_usize(end))] -#[requires (seq.len() > 0)] -#[requires (start >= 0)] -#[requires (start <= end)] -#[requires (end < seq.len())] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures (result <= max_seq_sum_rec(seq, end))] -#[ensures(result >= (to_i32(end - start + 1)) * -10000)] -#[ensures(result <= (to_i32(end - start + 1)) * 10000)] -fn seq_sum(seq: &VecWrapperI32, start: usize, end: usize) -> i32 { - if end == start { - seq.lookup(end) - } else { - seq.lookup(end) + seq_sum(seq, start, end - 1) - } -} - -#[pure] -#[terminates] -#[requires (a >= b)] -#[ensures (result == a - b)] -fn sub(a: usize, b: usize) -> usize { - a - b -} - -#[requires (seq.len() > 0)] -#[requires (start >= 0)] -#[requires (start <= end)] -#[requires (end < seq.len())] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures (result <= solve_rec(seq, end))] -fn max_seq_sum(seq: &VecWrapperI32, start: usize, end: usize) -> i32 { - if start == end { - seq_sum(seq, start, end) - } else { - max(max_seq_sum(seq, start + 1, end), seq_sum(seq, start, end)) - } -} - -#[requires (seq.len() > 0)] -#[requires (end >= 0)] -#[requires (end < seq.len())] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures (result <= solve_rec(seq, end))] -fn solve_naive(seq: &VecWrapperI32, end: usize) -> i32 { - if end == 0 { - max_seq_sum(seq, 0, end) - } else { - max(solve_naive(seq, end - 1), max_seq_sum(seq, 0, end)) - } -} - -#[requires (seq.len() > 0)] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures (result <= the_jackpot_rec(seq))] -fn the_jackpot_naive(seq: &VecWrapperI32) -> i32 { - max(0, solve_naive(seq, seq.len() - 1)) -} - -// Solution >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - -#[requires (seq.len() > 0)] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures (result.len() == seq.len())] -#[ensures (forall(|k:usize| (k >= 0 && k < seq.len()) ==> (result.lookup(k) == max_seq_sum_rec(seq, k))))] -fn solve(seq: &VecWrapperI32) -> VecWrapperI32 { - let mut dp = VecWrapperI32::new(); - dp.push(seq.lookup(0)); - let mut i = 1usize; - let len = seq.len(); - while i < len { - body_invariant!(i >= 1); - body_invariant!(i < seq.len()); - body_invariant!(dp.len() == i); - body_invariant!(forall(|k: usize| (k < i && k >= 0) ==> dp.lookup(k) == max_seq_sum_rec(seq, k))); - body_invariant!(forall(|k: usize| (k < i && k >= 0) ==> dp.lookup(k) <= ((k + 1) as i32) * 10000)); - let prev = dp.lookup(i - 1); - if prev > 0 { - dp.push(prev + seq.lookup(i)); - } else { - dp.push(seq.lookup(i)); - } - i += 1; - } - dp -} - -#[requires (seq.len() > 0)] -#[requires (forall(|k: usize| (k >= 0 && k < seq.len()) ==> (seq.lookup(k) >= -1000 && seq.lookup(k) <= 1000)))] -#[requires (seq.len() <= 10000)] -#[ensures (result == the_jackpot_rec(seq))] -fn the_jackpot(seq: &VecWrapperI32) -> i32 { - let dp = solve(seq); - let mut answer = seq.lookup(0); - let len = seq.len(); - let mut idx = 1; - while idx < len { - body_invariant!(idx >= 1); - body_invariant!(idx < len); - body_invariant!(answer == solve_rec(seq, idx - 1)); - answer = max(answer, dp.lookup(idx)); - idx += 1; - } - assert!(answer == solve_rec(seq, seq.len() - 1)); - max(0, answer) -} - -pub fn main() {} From d97b6647e98b1ff1de9f8e4425735c24ef0d3930 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Thu, 22 Jun 2023 05:15:26 +0200 Subject: [PATCH 43/54] Fix test error placement --- .../tests/verify/fail/native/quantifier_closures.rs | 6 +++--- prusti-tests/tests/verify_overflow/fail/native/bisect.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prusti-tests/tests/verify/fail/native/quantifier_closures.rs b/prusti-tests/tests/verify/fail/native/quantifier_closures.rs index 828af368c9c..3a0045f3592 100644 --- a/prusti-tests/tests/verify/fail/native/quantifier_closures.rs +++ b/prusti-tests/tests/verify/fail/native/quantifier_closures.rs @@ -1,18 +1,18 @@ // compile-flags: -Pviper_backend=Lithium use prusti_contracts::*; -#[requires(forall(|x: i32| x * x >= foo * bar))] //~ ERROR precondition might not hold +#[requires(forall(|x: i32| x * x >= foo * bar))] fn test_closure_1(foo: i32, bar: i32) -> i32 { foo + bar + 1 } -#[requires(forall(|x: i32| x * x >= foo * bar))] //TODO: both errors reported +#[requires(forall(|x: i32| x * x >= foo * bar))] fn test_closure_2(foo: i32, bar: i32) -> i32 { foo + bar + 1 } fn main() { - test_closure_1(5, 3); //TODO: error reported here + test_closure_1(5, 3); //~ ERROR precondition might not hold let arg1 = 1; let arg2 = 2; diff --git a/prusti-tests/tests/verify_overflow/fail/native/bisect.rs b/prusti-tests/tests/verify_overflow/fail/native/bisect.rs index e836c6bab3a..4f4f3570f85 100644 --- a/prusti-tests/tests/verify_overflow/fail/native/bisect.rs +++ b/prusti-tests/tests/verify_overflow/fail/native/bisect.rs @@ -26,7 +26,7 @@ fn bisect(f: &Function, target: i32) -> Option { assert!(high == f.domain_size()); while low < high { body_invariant!(low < high && high <= f.domain_size()); - let mid = (low + high) / 2; + let mid = (low + high) / 2; //~ ERROR attempt to add with overflow let mid_val = f.eval(mid); if mid_val < target { low = mid + 1; From 8e18928cfa04383d2d10369a51639ab60436265d Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 23 Jun 2023 19:53:02 +0200 Subject: [PATCH 44/54] Clean up SMT preambles, fix modulo operator encoding --- native-verifier/src/smt_lib.rs | 9 +++- native-verifier/src/theories/Preamble.smt2 | 55 ++++---------------- native-verifier/src/theories/PreambleZ3.smt2 | 55 ++++---------------- 3 files changed, 29 insertions(+), 90 deletions(-) diff --git a/native-verifier/src/smt_lib.rs b/native-verifier/src/smt_lib.rs index 205cbc23d32..1bee320efb0 100644 --- a/native-verifier/src/smt_lib.rs +++ b/native-verifier/src/smt_lib.rs @@ -67,8 +67,13 @@ impl SMTLib { self.add_code(format!("(assert {})", expression.to_smt())); } FolStatement::Assert { expression, reason } => { - // negate predicate let position = expression.position(); + + if position.id == 0 { + return; + } + + // negate predicate let negated = Expression::UnaryOp(UnaryOp { op_kind: UnaryOpKind::Not, argument: Box::new(expression.clone()), @@ -647,7 +652,7 @@ impl SMTTranslatable for IntBinaryOpKind { BinaryOpKind::Sub => "-", BinaryOpKind::Mul => "*", BinaryOpKind::Div => "div", - BinaryOpKind::Mod => "mod", + BinaryOpKind::Mod => "rust_mod", BinaryOpKind::And => "and", BinaryOpKind::Or => "or", BinaryOpKind::Implies => "=>", diff --git a/native-verifier/src/theories/Preamble.smt2 b/native-verifier/src/theories/Preamble.smt2 index eed60ab4c11..6dd18112f79 100644 --- a/native-verifier/src/theories/Preamble.smt2 +++ b/native-verifier/src/theories/Preamble.smt2 @@ -6,55 +6,22 @@ (set-option :produce-models true) ; equivalent to model.v2 in Z3 (set-option :produce-assignments false) ; similar to model.partial in Z3 -; --- Floating-point arithmetic --- +; --- Modelling Rust's implementation of % --- -(define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) -(define-fun fp.neq64 ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) - -; --- Snapshots --- - -(declare-datatypes (($Snap 0)) (( - ($Snap.unit) - ($Snap.combine ($Snap.first $Snap) ($Snap.second $Snap))))) +(define-fun rust_mod ((dividend Int) (divisor Int)) Int + (ite (>= dividend 0) + (mod dividend (abs divisor)) + (- (mod (- dividend) (abs divisor))) + ) +) -; --- References --- +; --- Inequality operator for FloatingPoint bit-vectors --- -(declare-sort $Ref 0) -(declare-const $Ref.null $Ref) +(define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) +(define-fun fp.neq64 ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) -; --- Permissions --- +; --- Legacy permission sort --- -(declare-sort $FPM 0) -(declare-sort $PPM 0) (define-sort $Perm () Real) -(define-const $Perm.Write $Perm 1.0) -(define-const $Perm.No $Perm 0.0) - -(define-fun $Perm.isValidVar ((p $Perm)) Bool - (<= $Perm.No p)) - -(define-fun $Perm.isReadVar ((p $Perm)) Bool - (and ($Perm.isValidVar p) - (not (= p $Perm.No)))) - -; min function for permissions -(define-fun $Perm.min ((p1 $Perm) (p2 $Perm)) Real - (ite (<= p1 p2) p1 p2)) - -; --- Sort wrappers --- - -; Sort wrappers are no longer part of the static preamble. Instead, they are -; emitted as part of the program-specific preamble. - -; --- Math --- - -;function Math#min(a: int, b: int): int; -(define-fun $Math.min ((a Int) (b Int)) Int - (ite (<= a b) a b)) - -;function Math#clip(a: int): int; -(define-fun $Math.clip ((a Int)) Int - (ite (< a 0) 0 a)) - ; ===== End static preamble ===== \ No newline at end of file diff --git a/native-verifier/src/theories/PreambleZ3.smt2 b/native-verifier/src/theories/PreambleZ3.smt2 index 63dc472e447..19dc0d32fdc 100644 --- a/native-verifier/src/theories/PreambleZ3.smt2 +++ b/native-verifier/src/theories/PreambleZ3.smt2 @@ -35,55 +35,22 @@ (set-option :timeout 10000) -; --- Floating-point arithmetic --- +; --- Modelling Rust's implementation of % --- -(define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) -(define-fun fp.neq64 ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) - -; --- Snapshots --- - -(declare-datatypes (($Snap 0)) (( - ($Snap.unit) - ($Snap.combine ($Snap.first $Snap) ($Snap.second $Snap))))) +(define-fun rust_mod ((dividend Int) (divisor Int)) Int + (ite (>= dividend 0) + (mod dividend (abs divisor)) + (- (mod (- dividend) (abs divisor))) + ) +) -; --- References --- +; --- Inequality operator for FloatingPoint bit-vectors --- -(declare-sort $Ref 0) -(declare-const $Ref.null $Ref) +(define-fun fp.neq32 ((x (_ FloatingPoint 8 24)) (y (_ FloatingPoint 8 24))) Bool (not (fp.eq x y))) +(define-fun fp.neq64 ((x (_ FloatingPoint 11 53)) (y (_ FloatingPoint 11 53))) Bool (not (fp.eq x y))) -; --- Permissions --- +; --- Legacy permission sort --- -(declare-sort $FPM 0) -(declare-sort $PPM 0) (define-sort $Perm () Real) -(define-const $Perm.Write $Perm 1.0) -(define-const $Perm.No $Perm 0.0) - -(define-fun $Perm.isValidVar ((p $Perm)) Bool - (<= $Perm.No p)) - -(define-fun $Perm.isReadVar ((p $Perm)) Bool - (and ($Perm.isValidVar p) - (not (= p $Perm.No)))) - -; min function for permissions -(define-fun $Perm.min ((p1 $Perm) (p2 $Perm)) Real - (ite (<= p1 p2) p1 p2)) - -; --- Sort wrappers --- - -; Sort wrappers are no longer part of the static preamble. Instead, they are -; emitted as part of the program-specific preamble. - -; --- Math --- - -;function Math#min(a: int, b: int): int; -(define-fun $Math.min ((a Int) (b Int)) Int - (ite (<= a b) a b)) - -;function Math#clip(a: int): int; -(define-fun $Math.clip ((a Int)) Int - (ite (< a 0) 0 a)) - ; ===== End static preamble ===== \ No newline at end of file From 6a216fea7eb1c53f859eeaaceb40008c4d8a58a2 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 23 Jun 2023 21:12:20 +0200 Subject: [PATCH 45/54] Add Lithium test coverage script To be removed when we want to merge this into Prusti. Committing for now since it's relevant to my thesis. --- test.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 test.sh diff --git a/test.sh b/test.sh new file mode 100755 index 00000000000..c1b264d5785 --- /dev/null +++ b/test.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# ANSI escape codes for colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Initialize or clear the files +> passing.txt +> failing.txt +> panicking.txt + +# Initialize counters +passed=0 +failed=0 +panicked=0 + +for i in prusti-tests/tests/verify/pass/**/*.rs; do + echo -n "Testing file: $i" + + # Run the test + output=$(PRUSTI_FULL_COMPILATION=false target/release/prusti-rustc --edition=2021 $i -Pcheck_overflows=false 2>&1) + + # Check the exit status of the test + if [ $? -eq 0 ]; then + # Test passed, append to passing.txt + echo "$i" >> passing.txt + echo -e " ...${GREEN}ok${NC}" + ((passed++)) + else + # Test failed + if echo "$output" | grep -q "^thread 'rustc' panicked at"; then + # There was a panic, append output to panicking.txt + echo -e "\n---- $i ----\n$output" >> panicking.txt + echo -e " ...${YELLOW}PANICKED${NC}" + ((panicked++)) + else + # Other error, append output to failing.txt + echo -e "\n---- $i ----\n$output" >> failing.txt + echo -e " ...${RED}FAILED${NC}" + ((failed++)) + fi + fi +done + +# Print test statistics +echo -e "${GREEN}$passed tests passed${NC}" +echo -e "${RED}$failed tests failed${NC}" +echo -e "${YELLOW}$panicked tests panicked${NC}" From 18db0072b0178533fb379ff5da73397d4feeb3d8 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 21 Jul 2023 00:54:46 +0200 Subject: [PATCH 46/54] Fixes after merging master --- prusti-viper/src/encoder/mir_encoder/mod.rs | 2 +- prusti-viper/src/encoder/procedure_encoder.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/prusti-viper/src/encoder/mir_encoder/mod.rs b/prusti-viper/src/encoder/mir_encoder/mod.rs index b8707d86f29..8712faff6e1 100644 --- a/prusti-viper/src/encoder/mir_encoder/mod.rs +++ b/prusti-viper/src/encoder/mir_encoder/mod.rs @@ -25,7 +25,7 @@ use prusti_interface::environment::mir_utils::MirPlace; use prusti_rustc_interface::{ errors::MultiSpan, hir::def_id::DefId, - index::vec::IndexVec, + index::IndexVec, middle::{mir, ty}, span::{Span, DUMMY_SP}, target::abi, diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index abb0d4f07c7..bb0598ad61c 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -66,13 +66,14 @@ use prusti_interface::{ }; use prusti_rustc_interface::{ errors::MultiSpan, + index::IndexSlice, middle::{ mir, mir::{Mutability, TerminatorKind}, ty::{self, subst::SubstsRef}, }, span::Span, - target::abi::Integer, + target::abi::{FieldIdx, Integer}, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::{collections::BTreeMap, convert::TryInto, fmt::Debug}; From dce49bb9d1f794bfe89e9c5318e8e36d253e48ae Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 21 Jul 2023 01:21:04 +0200 Subject: [PATCH 47/54] Make clippy happy --- native-verifier/src/fol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native-verifier/src/fol.rs b/native-verifier/src/fol.rs index 297db072dbf..93b177d0485 100644 --- a/native-verifier/src/fol.rs +++ b/native-verifier/src/fol.rs @@ -140,7 +140,7 @@ fn vir_statement_to_fol_statements( } } } - return statements; + statements } Statement::MethodCall(method_call) => { let method_decl = known_methods From 9ba703d6ccce5d37b9fc6888abfc371410c20239 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 21 Jul 2023 01:30:08 +0200 Subject: [PATCH 48/54] Fix test compilation error --- viper/tests/multiple_errors.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/viper/tests/multiple_errors.rs b/viper/tests/multiple_errors.rs index e7391c3b1bf..ec1472e207c 100644 --- a/viper/tests/multiple_errors.rs +++ b/viper/tests/multiple_errors.rs @@ -1,3 +1,4 @@ +use backend_common::VerificationResult; use std::{sync::Once, vec}; use viper::*; From 18bc02f92788349a6c9242f7c85544cb9186c125 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 18 Aug 2023 11:57:37 +0200 Subject: [PATCH 49/54] Restore config --- prusti-utils/src/config.rs | 18 +++++++++--------- .../high/procedures/inference/visitor/mod.rs | 1 - prusti-viper/src/encoder/places.rs | 2 +- prusti-viper/src/encoder/procedure_encoder.rs | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index b5dfcbcc194..b137f929c6e 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -69,7 +69,7 @@ lazy_static::lazy_static! { // 0. Default values settings.set_default("be_rustc", false).unwrap(); - settings.set_default("viper_backend", "Lithium").unwrap(); + settings.set_default("viper_backend", "Silicon").unwrap(); settings.set_default::>("smt_solver_path", env::var("Z3_EXE").ok()).unwrap(); settings.set_default::>("smt_solver_wrapper_path", None).unwrap(); settings.set_default::>("boogie_path", env::var("BOOGIE_EXE").ok()).unwrap(); @@ -81,7 +81,7 @@ lazy_static::lazy_static! { settings.set_default("check_overflows", true).unwrap(); settings.set_default("check_panics", true).unwrap(); settings.set_default("encode_unsigned_num_constraint", true).unwrap(); - settings.set_default("encode_bitvectors", true).unwrap(); + settings.set_default("encode_bitvectors", false).unwrap(); settings.set_default("simplify_encoding", true).unwrap(); settings.set_default("log", "").unwrap(); settings.set_default("log_style", "auto").unwrap(); @@ -92,7 +92,7 @@ lazy_static::lazy_static! { settings.set_default("dump_debug_info_during_fold", false).unwrap(); settings.set_default("dump_nll_facts", false).unwrap(); settings.set_default("ignore_regions", false).unwrap(); - settings.set_default("max_log_file_name_length", 240).unwrap(); + settings.set_default("max_log_file_name_length", 60).unwrap(); settings.set_default("dump_path_ctxt_in_debug_info", false).unwrap(); settings.set_default("dump_reborrowing_dag_in_debug_info", false).unwrap(); settings.set_default("dump_borrowck_info", false).unwrap(); @@ -104,7 +104,7 @@ lazy_static::lazy_static! { settings.set_default("assert_timeout", 10_000).unwrap(); settings.set_default("smt_qi_eager_threshold", 1000).unwrap(); settings.set_default("use_more_complete_exhale", true).unwrap(); - settings.set_default("skip_unsupported_features", true).unwrap(); + settings.set_default("skip_unsupported_features", false).unwrap(); settings.set_default("internal_errors_as_warnings", false).unwrap(); settings.set_default("allow_unreachable_unsupported_code", false).unwrap(); settings.set_default("no_verify", false).unwrap(); @@ -114,16 +114,16 @@ lazy_static::lazy_static! { settings.set_default("json_communication", false).unwrap(); settings.set_default("optimizations", "all").unwrap(); settings.set_default("intern_names", true).unwrap(); - settings.set_default("enable_purification_optimization", true).unwrap(); + settings.set_default("enable_purification_optimization", false).unwrap(); // settings.set_default("enable_manual_axiomatization", false).unwrap(); - settings.set_default("unsafe_core_proof", true).unwrap(); - settings.set_default("verify_core_proof", false).unwrap(); + settings.set_default("unsafe_core_proof", false).unwrap(); + settings.set_default("verify_core_proof", true).unwrap(); settings.set_default("verify_specifications", true).unwrap(); settings.set_default("verify_types", false).unwrap(); settings.set_default("verify_specifications_with_core_proof", false).unwrap(); - settings.set_default("verify_specifications_backend", "Lithium").unwrap(); + settings.set_default("verify_specifications_backend", "Silicon").unwrap(); settings.set_default("use_eval_axioms", true).unwrap(); - settings.set_default("inline_caller_for", true).unwrap(); + settings.set_default("inline_caller_for", false).unwrap(); settings.set_default("check_no_drops", false).unwrap(); settings.set_default("enable_type_invariants", false).unwrap(); settings.set_default("use_new_encoder", true).unwrap(); diff --git a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs index e12dadbae49..01938a7dc88 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs @@ -218,7 +218,6 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { state.check_consistency(); let actions = ensure_required_permissions(self, state, consumed_permissions.clone())?; self.process_actions(actions)?; - // TODO: Remove permission reasoning state.remove_permissions(&consumed_permissions)?; state.insert_permissions(produced_permissions)?; match &statement { diff --git a/prusti-viper/src/encoder/places.rs b/prusti-viper/src/encoder/places.rs index 75833c3f456..508f4b324ae 100644 --- a/prusti-viper/src/encoder/places.rs +++ b/prusti-viper/src/encoder/places.rs @@ -5,7 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use prusti_rustc_interface::{ - index::vec::{Idx, IndexVec}, + index::{Idx, IndexVec}, middle::{mir, ty::Ty}, }; use std::iter; diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index bb0598ad61c..f3fd99214ff 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -70,7 +70,7 @@ use prusti_rustc_interface::{ middle::{ mir, mir::{Mutability, TerminatorKind}, - ty::{self, subst::SubstsRef}, + ty::{self, GenericArgsRef}, }, span::Span, target::abi::{FieldIdx, Integer}, From 9aed8af8d561a7cd36744729d6bd95d248d55066 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 1 Sep 2023 15:20:29 +0200 Subject: [PATCH 50/54] Fixes after rebase --- prusti-viper/src/encoder/procedure_encoder.rs | 6 +++++- viper/src/verifier.rs | 16 ---------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index f3fd99214ff..d76a13f40cc 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -76,7 +76,11 @@ use prusti_rustc_interface::{ target::abi::{FieldIdx, Integer}, }; use rustc_hash::{FxHashMap, FxHashSet}; -use std::{collections::BTreeMap, convert::TryInto, fmt::Debug}; +use std::{ + collections::BTreeMap, + convert::TryInto, + fmt::{Debug, Write}, +}; use vir_crate::polymorphic::{ self as vir, borrows::Borrow, collect_assigned_vars, compute_identifier, CfgBlockIndex, ExprIterator, Float, Successor, Type, diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index 041b6a5b7bd..c0d3805a3b9 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -73,22 +73,6 @@ impl<'a> Verifier<'a> { } } - #[tracing::instrument(level = "debug", skip_all)] - fn instantiate_verifier( - backend: VerificationBackend, - env: &'a JNIEnv, - reporter: JObject, - debug_info: JObject, - ) -> Result> { - match backend { - VerificationBackend::Silicon => silicon::Silicon::with(env).new(reporter, debug_info), - VerificationBackend::Carbon => { - carbon::CarbonVerifier::with(env).new(reporter, debug_info) - } - other => unreachable!("{:?} is not a Viper backend", other), - } - } - #[must_use] #[tracing::instrument(level = "debug", skip_all)] pub fn initialize(self, args: &[String]) -> Self { From 8372022d2875ebb07c3a0baec180437f18da9d48 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 1 Sep 2023 15:45:30 +0200 Subject: [PATCH 51/54] Clippy --- viper/src/verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/src/verifier.rs b/viper/src/verifier.rs index c0d3805a3b9..3a91512bcd5 100644 --- a/viper/src/verifier.rs +++ b/viper/src/verifier.rs @@ -9,7 +9,7 @@ use crate::{ smt_manager::SmtManager, verification_backend::VerificationBackend, }; use backend_common::{SiliconCounterexample, VerificationError, VerificationResult}; -use jni::{errors::Result, objects::JObject, JNIEnv}; +use jni::{objects::JObject, JNIEnv}; use log::{debug, error, info}; use prusti_utils::run_timed; use std::path::PathBuf; From 000c701ccdc9065f2aff4155d136939a93022b39 Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 27 Oct 2023 11:08:49 +0200 Subject: [PATCH 52/54] Fix stopwatch getting released immediately --- prusti-server/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 9765bd3567e..6841563c2ea 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -39,7 +39,7 @@ impl<'a> Backend<'a> { }) } Backend::Lithium(lithium) => { - Stopwatch::start("prusti-server", "vir verification"); + let _stopwatch = Stopwatch::start("prusti-server", "vir verification"); lithium.verify(program) } } From 09066e136016bd1c13df20df4558184d748a3aaf Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 27 Oct 2023 13:08:45 +0200 Subject: [PATCH 53/54] Add PRUSTI_NATIVE_VERIFIER env variable --- prusti-utils/src/config.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index b137f929c6e..3ecde175aae 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -180,6 +180,7 @@ lazy_static::lazy_static! { allowed_keys.insert("rustc_log_args".to_string()); allowed_keys.insert("rustc_log_env".to_string()); allowed_keys.insert("original_smt_solver_path".to_string()); + allowed_keys.insert("native_verifier".to_string()); // TODO: reduce this to something more sensible: static MAX_CONFIG_LEN: usize = 40; @@ -190,6 +191,17 @@ lazy_static::lazy_static! { allowed_keys.iter().filter(|key| key.len() > MAX_CONFIG_LEN).collect::>() ); + // 0. Apply Lithium overrides if necessary + if let Ok(true) = env::var("PRUSTI_NATIVE_VERIFIER").map(|s| s.parse::().unwrap_or(false)) { + settings.set_default("viper_backend", "Lithium").unwrap(); + settings.set_default("verify_specifications_backend", "Lithium").unwrap(); + settings.set_default("encode_bitvectors", true).unwrap(); + settings.set_default("enable_purification_optimization", true).unwrap(); + settings.set_default("unsafe_core_proof", true).unwrap(); + settings.set_default("verify_core_proof", false).unwrap(); + settings.set_default("inline_caller_for", true).unwrap(); + } + // 1. Override with default env variables (e.g. `DEFAULT_PRUSTI_CACHE_PATH`, ...) settings.merge( Environment::with_prefix("DEFAULT_PRUSTI").ignore_empty(true) From 766462981fcb13dbc1105c3e940dacd91ed9737c Mon Sep 17 00:00:00 2001 From: Jakub Janaszkiewicz Date: Fri, 27 Oct 2023 13:21:18 +0200 Subject: [PATCH 54/54] Restore test case unrelated to Lithium --- prusti-tests/tests/verify/ui/forall_verify.stderr | 6 ------ 1 file changed, 6 deletions(-) diff --git a/prusti-tests/tests/verify/ui/forall_verify.stderr b/prusti-tests/tests/verify/ui/forall_verify.stderr index 1876f837af0..63d52476fb4 100644 --- a/prusti-tests/tests/verify/ui/forall_verify.stderr +++ b/prusti-tests/tests/verify/ui/forall_verify.stderr @@ -11,12 +11,6 @@ note: the error originates here | ^^^^^^^^^^^^^ error: [Prusti: verification error] postcondition might not hold. - --> $DIR/forall_verify.rs:31:11 - | -31 | #[ensures(exists(|x: i32| identity(x) == x + 1))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -note: the error originates here --> $DIR/forall_verify.rs:32:1 | 32 | fn test6() {}